From bff67ccaa6183affe8e4ca47550c4254ec610f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Wed, 14 Oct 2020 20:09:09 +0200 Subject: [PATCH] Explode out the implementation. Add bin/unlock. Trim extraneous NEEDEDs --- Makefile | 1 + configMakefile | 3 + src/bin/getprop.cpp | 139 -------------------------------------------- src/bin/rewrap.cpp | 46 +++++++++++++++ src/bin/unlock.cpp | 41 +++++++++++++ src/common.hpp | 33 +++++++++++ src/fd.cpp | 21 +++++++ src/fd.hpp | 28 +++++++++ src/main.hpp | 62 ++++++++++++++++++++ src/zfs.cpp | 33 +++++++++++ src/zfs.hpp | 15 +++++ 11 files changed, 283 insertions(+), 139 deletions(-) delete mode 100644 src/bin/getprop.cpp create mode 100644 src/bin/rewrap.cpp create mode 100644 src/bin/unlock.cpp create mode 100644 src/common.hpp create mode 100644 src/fd.cpp create mode 100644 src/fd.hpp create mode 100644 src/main.hpp create mode 100644 src/zfs.cpp create mode 100644 src/zfs.hpp diff --git a/Makefile b/Makefile index ca948d3..03c69d7 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,7 @@ $(OUTDIR)%$(EXE) : $(subst $(SRCDIR),$(OBJDIR),$(subst .cpp,$(OBJ),$(SRCDIR)bin/ @mkdir -p $(dir $@) $(CXX) $(CXXAR) -o$@ $^ $(PIC) $(LDAR) $(STRIP) $(STRIPAR) $@ + $(LDD) --unused $@ | $(AWK) -F/ 'BEGIN {args = ""} /^\t/ {args = args " --remove-needed " $$NF} END { if(!args) exit; print "$(PATCHELF)" args " $@"}' | sh -x $(OBJDIR)%$(OBJ) : $(SRCDIR)%.cpp @mkdir -p $(dir $@) diff --git a/configMakefile b/configMakefile index e60a394..be5b7fa 100644 --- a/configMakefile +++ b/configMakefile @@ -52,6 +52,9 @@ TZPFMS_VERSION := "0.0.0-$(shell git rev-list HEAD --count)" INCCMAKEAR := CXXFLAGS="$(INCCXXAR)" LNCMAKEAR := LDFLAGS="$(LNCXXAR)" +LDD ?= ldd +AWK ?= awk +PATCHELF ?= patchelf OBJ := .o CXXAR := -O3 -std=c++17 -fno-exceptions -Wall -Wextra $(CXXSPECIFIC) -pipe $(INCCXXAR) $(PIC) STRIP ?= strip diff --git a/src/bin/getprop.cpp b/src/bin/getprop.cpp deleted file mode 100644 index ec74a21..0000000 --- a/src/bin/getprop.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include -#include -#include -// #include -#define WRAPPING_KEY_LEN 32 - -#include - -#include -#include - -// clang++ -Wall -Wextra -pedantic -Wno-gnu-{statement-expression,include-next} -fno-exceptions -O3 -std=c++17 getprop.cpp -ogetprop $(pkg-config --cflags --libs libzfs{,_core}) -lrt - - -template -struct quickscope_wrapper { - F func; - - ~quickscope_wrapper() { func(); } -}; - -template -quickscope_wrapper(F)->quickscope_wrapper; - - -static const constexpr uint8_t our_test_key[WRAPPING_KEY_LEN] = { - 0xe2, 0xac, 0xf7, 0x89, 0x32, 0x37, 0xcb, 0x94, 0x67, 0xeb, 0x2b, 0xe9, 0xa3, 0x48, 0x83, 0x72, - 0xd5, 0x4c, 0xc5, 0x1c, 0x99, 0x65, 0xb0, 0x8d, 0x05, 0xa6, 0xd5, 0xff, 0x7a, 0xf7, 0xeb, 0xfc, -}; - - -#define RETERR (__COUNTER__ + 1) -#define TRY_GENERIC(what, cond_pre, cond_post, err_src, ...) \ - ({ \ - auto _try_ret = (__VA_ARGS__); \ - if(cond_pre _try_ret cond_post) { \ - if constexpr(what != nullptr) \ - fprintf(stderr, "Couldn't %s: %s\n", static_cast(what), strerror(err_src)); \ - return RETERR; \ - } \ - _try_ret; \ - }) -#define TRY(what, ...) TRY_GENERIC(what, , == -1, errno, __VA_ARGS__) -#define TRY_PTR(what, ...) TRY_GENERIC(what, !, , errno, __VA_ARGS__) -#define TRY_NVL(what, ...) TRY_GENERIC(what, , , _try_ret, __VA_ARGS__) -#define TRY_MAIN(...) \ - do { \ - if(auto _try_ret = (__VA_ARGS__)) \ - return _try_ret; \ - } while(0) - - -template -static int with_stdin_at(int fd, F && what) { - auto stdin_saved = TRY("dup() stdin", dup(0)); - quickscope_wrapper stdin_saved_deleter{[=] { close(stdin_saved); }}; - - TRY("dup2() onto stdin", dup2(fd, 0)); - - if(int ret = what()) { - dup2(stdin_saved, 0); - return ret; - } - - TRY("dup2() stdin back onto stdin", dup2(stdin_saved, 0)); - return 0; -} - -/// with_len may not exceed pipe capacity (64k by default) -static int filled_fd(int & fd, const void * with, size_t with_len) { - int pipes[2]; - TRY("create buffer pipe", pipe(pipes)); - quickscope_wrapper pipes_w_deleter{[=] { close(pipes[1]); }}; - fd = pipes[0]; - - auto ret = write(pipes[1], with, with_len); - if(ret >= 0 && ret < WRAPPING_KEY_LEN) { - ret = -1; - errno = ENODATA; - } - TRY("write to buffer pipe", ret); - - return 0; -} - - -int main(int, char ** argv) { - const auto libz = TRY_PTR("initialise libzfs", libzfs_init()); - quickscope_wrapper libz_deleter{[=] { libzfs_fini(libz); }}; - - libzfs_print_on_error(libz, B_TRUE); - - auto dataset = TRY_PTR(nullptr, zfs_open(libz, argv[1], ZFS_TYPE_FILESYSTEM)); - quickscope_wrapper dataset_deleter{[&] { zfs_close(dataset); }}; - - { - char encryption_root[MAXNAMELEN]; - boolean_t dataset_is_root; - TRY("get encryption root", zfs_crypto_get_encryption_root(dataset, &dataset_is_root, encryption_root)); - - if(!dataset_is_root && !strlen(encryption_root)) { - fprintf(stderr, "Dataset %s not encrypted?\n", zfs_get_name(dataset)); - return RETERR; - } else if(!dataset_is_root) { - printf("Using dataset %s's encryption root %s instead.\n", zfs_get_name(dataset), encryption_root); - // TODO: disallow maybe? or require force option? - zfs_close(dataset); - dataset = TRY_PTR(nullptr, zfs_open(libz, encryption_root, ZFS_TYPE_FILESYSTEM)); - } - } - - - /// zfs_crypto_rewrap() with "prompt" reads from stdin, but not if it's a TTY; - /// this user-proofs the set-up, and means we don't have to touch the filesysten: - /// instead, get an FD, write the raw key data there, dup() it onto stdin, - /// let libzfs read it, then restore stdin - - int key_fd; - TRY_MAIN(filled_fd(key_fd, (void *)our_test_key, WRAPPING_KEY_LEN)); - quickscope_wrapper key_fd_deleter{[=] { close(key_fd); }}; - - TRY_MAIN(with_stdin_at(key_fd, [&] { - nvlist_t * rewrap_args; - TRY_NVL("allocate rewrap nvlist", nvlist_alloc(&rewrap_args, NV_UNIQUE_NAME, 0)); - quickscope_wrapper rewrap_args_deleter{[=] { nvlist_free(rewrap_args); }}; - TRY_NVL("add keyformat to rewrap nvlist", - nvlist_add_string(rewrap_args, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), "raw")); // Why can't this be uint64 and ZFS_KEYFORMAT_RAW? - TRY_NVL("add keylocation to rewrap nvlist", nvlist_add_string(rewrap_args, zfs_prop_to_name(ZFS_PROP_KEYLOCATION), "prompt")); - if(zfs_crypto_rewrap(dataset, rewrap_args, B_FALSE)) - return RETERR; // Error printed by libzfs - else - printf("Key for %s changed\n", zfs_get_name(dataset)); - - return 0; - })); - - const auto props = zfs_get_all_props(dataset); - dump_nvlist(props, 2); -} diff --git a/src/bin/rewrap.cpp b/src/bin/rewrap.cpp new file mode 100644 index 0000000..348b0bd --- /dev/null +++ b/src/bin/rewrap.cpp @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: MIT */ + + +#include +// #include +#define WRAPPING_KEY_LEN 32 + +#include + +#include "../fd.hpp" +#include "../main.hpp" +#include "../zfs.hpp" + + +static const constexpr uint8_t our_test_key[WRAPPING_KEY_LEN] = { + 0xe2, 0xac, 0xf7, 0x89, 0x32, 0x37, 0xcb, 0x94, 0x67, 0xeb, 0x2b, 0xe9, 0xa3, 0x48, 0x83, 0x72, + 0xd5, 0x4c, 0xc5, 0x1c, 0x99, 0x65, 0xb0, 0x8d, 0x05, 0xa6, 0xd5, 0xff, 0x7a, 0xf7, 0xeb, 0xfc, +}; + + +int main(int argc, char ** argv) { + return do_main( + argc, argv, "", [](auto) {}, + [](auto dataset) { + /// zfs_crypto_rewrap() with "prompt" reads from stdin, but not if it's a TTY; + /// this user-proofs the set-up, and means we don't have to touch the filesysten: + /// instead, get an FD, write the raw key data there, dup() it onto stdin, + /// let libzfs read it, then restore stdin + + int key_fd; + TRY_MAIN(filled_fd(key_fd, (void *)our_test_key, WRAPPING_KEY_LEN)); + quickscope_wrapper key_fd_deleter{[=] { close(key_fd); }}; + + + TRY_MAIN(with_stdin_at(key_fd, [&] { + if(zfs_crypto_rewrap(dataset, TRY_PTR("get rewrap args", rewrap_args()), B_FALSE)) + return __LINE__; // Error printed by libzfs + else + printf("Key for %s changed\n", zfs_get_name(dataset)); + + return 0; + })); + + return 0; + }); +} diff --git a/src/bin/unlock.cpp b/src/bin/unlock.cpp new file mode 100644 index 0000000..6dc19a1 --- /dev/null +++ b/src/bin/unlock.cpp @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: MIT */ + + +#include +// #include +#define WRAPPING_KEY_LEN 32 + +#include + +#include "../fd.hpp" +#include "../main.hpp" + + +static const constexpr uint8_t our_test_key[WRAPPING_KEY_LEN] = { + 0xe2, 0xac, 0xf7, 0x89, 0x32, 0x37, 0xcb, 0x94, 0x67, 0xeb, 0x2b, 0xe9, 0xa3, 0x48, 0x83, 0x72, + 0xd5, 0x4c, 0xc5, 0x1c, 0x99, 0x65, 0xb0, 0x8d, 0x05, 0xa6, 0xd5, 0xff, 0x7a, 0xf7, 0xeb, 0xfc, +}; + + +int main(int argc, char ** argv) { + auto noop = B_FALSE; + return do_main( + argc, argv, "n", [&](char) { noop = B_TRUE; }, + [&](auto dataset) { + int key_fd; + TRY_MAIN(filled_fd(key_fd, (void *)our_test_key, WRAPPING_KEY_LEN)); + quickscope_wrapper key_fd_deleter{[=] { close(key_fd); }}; + + + TRY_MAIN(with_stdin_at(key_fd, [&] { + if(zfs_crypto_load_key(dataset, noop, nullptr)) + return __LINE__; // Error printed by libzfs + else + printf("Key for %s loaded\n", zfs_get_name(dataset)); + + return 0; + })); + + return 0; + }); +} diff --git a/src/common.hpp b/src/common.hpp new file mode 100644 index 0000000..7addb8e --- /dev/null +++ b/src/common.hpp @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: MIT */ + + +#pragma once + + +#include +#include +#include + + +#define TRY_GENERIC(what, cond_pre, cond_post, err_src, err_ret, ...) \ + ({ \ + auto _try_ret = (__VA_ARGS__); \ + if(cond_pre _try_ret cond_post) { \ + if constexpr(what != nullptr) \ + fprintf(stderr, "Couldn't %s: %s\n", static_cast(what), strerror(err_src)); \ + return err_ret; \ + } \ + _try_ret; \ + }) +#define TRY(what, ...) TRY_GENERIC(what, , == -1, errno, __LINE__, __VA_ARGS__) + + +template +struct quickscope_wrapper { + F func; + + ~quickscope_wrapper() { func(); } +}; + +template +quickscope_wrapper(F)->quickscope_wrapper; diff --git a/src/fd.cpp b/src/fd.cpp new file mode 100644 index 0000000..297f21f --- /dev/null +++ b/src/fd.cpp @@ -0,0 +1,21 @@ + /* SPDX-License-Identifier: MIT */ + + +#include "fd.hpp" + + +int filled_fd(int & fd, const void * with, size_t with_len) { + int pipes[2]; + TRY("create buffer pipe", pipe(pipes)); + quickscope_wrapper pipes_w_deleter{[=] { close(pipes[1]); }}; + fd = pipes[0]; + + auto ret = write(pipes[1], with, with_len); + if(ret >= 0 && static_cast(ret) < with_len) { + ret = -1; + errno = ENODATA; + } + TRY("write to buffer pipe", ret); + + return 0; +} diff --git a/src/fd.hpp b/src/fd.hpp new file mode 100644 index 0000000..654c8d8 --- /dev/null +++ b/src/fd.hpp @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: MIT */ + + +#pragma once + + +#include "common.hpp" +#include + + +template +int with_stdin_at(int fd, F && what) { + auto stdin_saved = TRY("dup() stdin", dup(0)); + quickscope_wrapper stdin_saved_deleter{[=] { close(stdin_saved); }}; + + TRY("dup2() onto stdin", dup2(fd, 0)); + + if(int ret = what()) { + dup2(stdin_saved, 0); + return ret; + } + + TRY("dup2() stdin back onto stdin", dup2(stdin_saved, 0)); + return 0; +} + +/// with_len may not exceed pipe capacity (64k by default) +extern int filled_fd(int & fd, const void * with, size_t with_len); diff --git a/src/main.hpp b/src/main.hpp new file mode 100644 index 0000000..3720f04 --- /dev/null +++ b/src/main.hpp @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: MIT */ + + +#pragma once + + +#include "common.hpp" +#include +#include + + +#define TRY_PTR(what, ...) TRY_GENERIC(what, !, , errno, __LINE__, __VA_ARGS__) +#define TRY_MAIN(...) \ + do { \ + if(auto _try_ret = (__VA_ARGS__)) \ + return _try_ret; \ + } while(0) + + +template +int do_main(int argc, char ** argv, const char * getoptions, G && getoptfn, M && main) { + const auto libz = TRY_PTR("initialise libzfs", libzfs_init()); + quickscope_wrapper libz_deleter{[=] { libzfs_fini(libz); }}; + + libzfs_print_on_error(libz, B_TRUE); + +#if __GLIBC__ + setenv("POSIXLY_CORRECT", "1", true); +#endif + for(int opt; (opt = getopt(argc, argv, getoptions)) != -1;) + if(opt == '?') + return __LINE__; + else + getoptfn(opt); + + if(optind >= argc) { + fprintf(stderr, "No dataset to act on?\n"); + return __LINE__; + } + auto dataset = TRY_PTR(nullptr, zfs_open(libz, argv[optind], ZFS_TYPE_FILESYSTEM)); + quickscope_wrapper dataset_deleter{[&] { zfs_close(dataset); }}; + + { + char encryption_root[MAXNAMELEN]; + boolean_t dataset_is_root; + TRY("get encryption root", zfs_crypto_get_encryption_root(dataset, &dataset_is_root, encryption_root)); + + if(!dataset_is_root && !strlen(encryption_root)) { + fprintf(stderr, "Dataset %s not encrypted?\n", zfs_get_name(dataset)); + return __LINE__; + } else if(!dataset_is_root) { + printf("Using dataset %s's encryption root %s instead.\n", zfs_get_name(dataset), encryption_root); + // TODO: disallow maybe? or require force option? + strcpy(argv[1], encryption_root); + zfs_close(dataset); + dataset = TRY_PTR(nullptr, zfs_open(libz, encryption_root, ZFS_TYPE_FILESYSTEM)); + } + } + + + return main(dataset); +} diff --git a/src/zfs.cpp b/src/zfs.cpp new file mode 100644 index 0000000..a7c86c2 --- /dev/null +++ b/src/zfs.cpp @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: MIT */ + + +#include "zfs.hpp" +#include "common.hpp" + +#include + + +#define TRY_NVL(what, ...) TRY_GENERIC(what, , , _try_ret, _try_ret, __VA_ARGS__) + + +nvlist_t * rewrap_args() { + static nvlist_t * ret{}; + static quickscope_wrapper ret_deleter{[&] { nvlist_free(ret); }}; + + if(!ret) + if(auto err = + [&] { + TRY_NVL("allocate rewrap nvlist", nvlist_alloc(&ret, NV_UNIQUE_NAME, 0)); + TRY_NVL("add keyformat to rewrap nvlist", + nvlist_add_string(ret, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), "raw")); // Why can't this be uint64 and ZFS_KEYFORMAT_RAW? + TRY_NVL("add keylocation to rewrap nvlist", nvlist_add_string(ret, zfs_prop_to_name(ZFS_PROP_KEYLOCATION), "prompt")); + return 0; + }(); + err && ret) { + nvlist_free(ret); + ret = nullptr; + errno = err; + } + + return ret; +} diff --git a/src/zfs.hpp b/src/zfs.hpp new file mode 100644 index 0000000..ffb38cb --- /dev/null +++ b/src/zfs.hpp @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: MIT */ + + +#pragma once + + +#include +// #include +// #include +//// #include +// #define WRAPPING_KEY_LEN 32 + + +/// Static nvlist with {keyformat=raw, keylocation=prompt} +extern nvlist_t * rewrap_args();