Skip to content

Commit dd666a7

Browse files
libzfs: add key provider machinery and a keylocation=exec:// impl
This acquires the key material by running the file specified by the URI with [path, op, fsname], and reading back data from the pipe located at fd 3 after the child exits (this means, that, i.a., children that write Too Much get an I/O error instead of hanging, exec:// providers can be written in any language that can write(3, [buf]), and they can be as interactive (or non-interactive) and as verbose (or terse) as they want) See zfs-change-key(8) for example statesome providers, or the abomination below for a trivial stateless one #!/bin/sh -x echo "$0" "$@" [ -z "$2" ] && { echo "No dataset name (zfs-create?)" >&2 exit 1 } if command -v sha256 >/dev/null; then sha256 -qs "$2" else echo -n "$2" | sha256sum | awk '{print $1}' fi | tee /dev/stderr >&3 See zfs-change-key(8) for a user-level description of key-providers, or below for state machines load: * [_ _] => error * [_ x] => [x _], unseal(x) * [o x] => + show error + let user choose to try either one or the other state + instruct what to invoke in either case * [o _] => unseal(o) new: into staging area * fresh : [_ _] => [_ x] * regenerating: [o _] => [o x] * dirty: [? x] => shift (on success): mark new state as current, free old state * [_ _] => how? * [_ x] => [x _] * [o x] => [x _], free(o) * [o _] => [_ _], free(o) i.e. [a b] => [b _], free(a) unshift (on deletion): move current state to new * [_ _] => how? * [_ x] => wrong * [o x] => wrong * [o _] => [_ o] i.e. [a b] => [_ a] (technically free(b), i guess, but shouldn't happen) cancel (on error): free new state if present * how? : [_ _] => * from new : [_ x] => [_ _] free(x) * from new : [o x] => [o _] free(x) * from inherit/other executable: [o _] => [o _] i.e. [a, b] => [a, _], free(b) two stable states: [_ _] -> new: [_ n] --ok----> shift : [n _] [_ _] -> new: [_ n] --error-> cancel: [n _] [o _] -> new: [o n] --ok----> shift : [n _], free(o) [o _] -> new: [o n] --error-> cancel: [o _], free(n) [o _] (inheriting) --ok----> shift : [_ _], free(o) [o _] (inheriting) --error-> cancel: [o _] inheriting homomorphic to switching to something else [o _] -> unshift: [_ o] --ok----> cancel: [_ _], free(o) [o _] -> unshift: [_ o] --error-> shift : [o _] if shift or cancel wasn't called: [_ o] -> load -> [o, _], unseal [ó o] -> load -> pick a resolution. should allow loading either to check with zfs load-key -n and instruct what to do in either case [_ o] -> new -> error? try to load first, and pick a resolution [ó o] -> new -> error? try to load first, and pick a resolution i.e. somehow libzfs failed to do the shift after committing Signed-off-by: Ahelenia Ziemiańska <[email protected]>
1 parent 5611bdc commit dd666a7

File tree

9 files changed

+538
-32
lines changed

9 files changed

+538
-32
lines changed

config/user-libexec.m4

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
AC_DEFUN([ZFS_AC_CONFIG_USER_ZFSEXEC], [
2+
AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
3+
24
AC_ARG_WITH(zfsexecdir,
35
AS_HELP_STRING([--with-zfsexecdir=DIR],
46
[install scripts [[@<:@libexecdir@:>@/zfs]]]),
57
[zfsexecdir=$withval],
68
[zfsexecdir="${libexecdir}/zfs"])
79
810
AC_SUBST([zfsexecdir])
11+
AC_LIB_WITH_FINAL_PREFIX([
12+
eval true_zfsexecdir=\"$zfsexecdir\"
13+
AC_DEFINE_UNQUOTED([ZFSEXECDIR], ["$true_zfsexecdir"], [location of non-user-runnable executables])
14+
])
915
])

include/libzfs_impl.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,17 @@ extern int find_shares_object(differ_info_t *di);
258258
extern void libzfs_set_pipe_max(int infd);
259259
extern void zfs_commit_proto(zfs_share_proto_t *);
260260

261+
typedef enum {
262+
BACK_OP_LOAD,
263+
BACK_OP_NEW,
264+
BACK_OP_SHIFT,
265+
BACK_OP_UNSHIFT,
266+
BACK_OP_CANCEL,
267+
} encryption_backend_op_t;
268+
269+
extern int notify_encryption_backend(zfs_handle_t *zhp,
270+
const char *keylocation, encryption_backend_op_t what_of);
271+
261272
#ifdef __cplusplus
262273
}
263274
#endif

lib/libzfs/libzfs_crypto.c

Lines changed: 186 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <sys/zfs_context.h>
2222
#include <sys/fs/zfs.h>
2323
#include <sys/dsl_crypt.h>
24+
#include <sys/wait.h>
2425
#include <libintl.h>
2526
#include <termios.h>
2627
#include <signal.h>
@@ -71,14 +72,25 @@ static int get_key_material_file(libzfs_handle_t *, const char *, const char *,
7172
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
7273
static int get_key_material_https(libzfs_handle_t *, const char *, const char *,
7374
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
75+
static int get_key_material_exec(libzfs_handle_t *, const char *, const char *,
76+
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
7477

7578
static zfs_uri_handler_t uri_handlers[] = {
7679
{ "file", get_key_material_file },
7780
{ "https", get_key_material_https },
7881
{ "http", get_key_material_https },
82+
{ "exec", get_key_material_exec },
7983
{ NULL, NULL }
8084
};
8185

86+
static const char *backend_ops_lcase[] = {
87+
[BACK_OP_LOAD] = "load",
88+
[BACK_OP_NEW] = "new",
89+
[BACK_OP_SHIFT] = "shift",
90+
[BACK_OP_UNSHIFT] = "unshift",
91+
[BACK_OP_CANCEL] = "cancel",
92+
};
93+
8294
static int
8395
pkcs11_get_urandom(uint8_t *buf, size_t bytes)
8496
{
@@ -669,6 +681,148 @@ get_key_material_https(libzfs_handle_t *hdl, const char *uri,
669681
return (ret);
670682
}
671683

684+
static int
685+
execute_key_provider_exec(libzfs_handle_t *hdl, const char *name,
686+
encryption_backend_op_t op, const char *fsname, int *outfd)
687+
{
688+
int ret = 0, status, compipe[2];
689+
char *setpath = getenv("ZFS_PROVIDER_DIR");
690+
char * const argv[] =
691+
{(char *)name, (char *)backend_ops_lcase[op], (char *)fsname, NULL};
692+
pid_t child;
693+
694+
if (pipe2(compipe, O_NONBLOCK | O_CLOEXEC) != 0) {
695+
ret = errno;
696+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
697+
"Failed to create key provider pipes: %s"), strerror(ret));
698+
return (ret);
699+
}
700+
701+
switch ((child = fork())) {
702+
case -1:
703+
ret = errno;
704+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
705+
"Failed to start key provider %s: %s"),
706+
name, strerror(ret));
707+
(void) close(compipe[1]);
708+
goto end;
709+
case 0: /* child */
710+
(void) dup2(compipe[1], 3);
711+
712+
char fullpath[PATH_MAX];
713+
if (setpath != NULL) {
714+
strlcpy(fullpath, setpath, sizeof (fullpath));
715+
strlcat(fullpath, "/", sizeof (fullpath));
716+
if (strlcat(fullpath, name, sizeof (fullpath)) <
717+
sizeof (fullpath))
718+
(void) execv(fullpath, argv);
719+
}
720+
721+
strcpy(fullpath, SYSCONFDIR "/zfs/providers.d/");
722+
if (strlcat(fullpath, name, sizeof (fullpath)) <
723+
sizeof (fullpath))
724+
(void) execv(fullpath, argv);
725+
726+
strcpy(fullpath, ZFSEXECDIR "/providers.d/");
727+
if (strlcat(fullpath, name, sizeof (fullpath)) <
728+
sizeof (fullpath))
729+
(void) execv(fullpath, argv);
730+
731+
status = write(3, strerror(errno), strlen(strerror(errno)));
732+
_exit(-1);
733+
}
734+
735+
/* parent */
736+
(void) close(compipe[1]);
737+
738+
while (waitpid(child, &status, 0) == -1)
739+
if (errno != EINTR) {
740+
ret = errno;
741+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
742+
"Failed to wait for key provider %s (%d): %s"),
743+
name, child, strerror(ret));
744+
745+
goto end;
746+
}
747+
748+
if (WIFSIGNALED(status)) {
749+
ret = EZFS_INTR;
750+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
751+
"Key provider %s killed by %s"),
752+
name, strsignal(WTERMSIG(status)));
753+
} else if (WEXITSTATUS(status) == 0xff) {
754+
char errbuf[128] = {0};
755+
status = read(compipe[0], errbuf, sizeof (errbuf) - 1);
756+
757+
ret = ENOEXEC;
758+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
759+
"Failed to start key provider %s: %s"),
760+
name, strlen(errbuf) ? errbuf : "(?)");
761+
} else if (WEXITSTATUS(status) != 0) {
762+
ret = ECANCELED;
763+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
764+
"Key provider %s failed with %d"),
765+
name, WEXITSTATUS(status));
766+
}
767+
768+
end:
769+
if (ret == 0)
770+
*outfd = compipe[0];
771+
else
772+
(void) close(compipe[0]);
773+
errno = 0;
774+
return (ret);
775+
}
776+
777+
static int
778+
get_key_material_exec(libzfs_handle_t *hdl, const char *uri,
779+
const char *fsname, zfs_keyformat_t keyformat, boolean_t newkey,
780+
uint8_t **restrict buf, size_t *restrict len_out)
781+
{
782+
int ret = 0, rdpipe = -1;
783+
FILE *f;
784+
785+
if (strlen(uri) < 7)
786+
return (EINVAL);
787+
788+
if ((ret = execute_key_provider_exec(hdl, uri + 7,
789+
newkey ? BACK_OP_NEW : BACK_OP_LOAD, fsname, &rdpipe)) != 0)
790+
return (ret);
791+
792+
if ((f = fdopen(rdpipe, "r")) == NULL) {
793+
ret = errno;
794+
errno = 0;
795+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
796+
"Failed to fdopen key provider pipe"));
797+
798+
(void) close(rdpipe);
799+
return (ret);
800+
}
801+
802+
ret = get_key_material_raw(f, keyformat, buf, len_out);
803+
(void) fclose(f);
804+
return (ret);
805+
}
806+
807+
__attribute__((visibility("hidden"))) int
808+
notify_encryption_backend(zfs_handle_t *zhp, const char *keylocation,
809+
encryption_backend_op_t what_of)
810+
{
811+
int ret = 0;
812+
int rdpipe = -1;
813+
814+
if (keylocation == NULL)
815+
return (0);
816+
817+
if (strncmp(keylocation, "exec://", 7) == 0) {
818+
if ((ret = execute_key_provider_exec(zhp->zfs_hdl,
819+
keylocation + 7, what_of, zfs_get_name(zhp), &rdpipe)) == 0)
820+
(void) close(rdpipe);
821+
}
822+
823+
return (ret);
824+
}
825+
672826
/*
673827
* Attempts to fetch key material, no matter where it might live. The key
674828
* material is allocated and returned in km_out. *can_retry_out will be set
@@ -694,7 +848,6 @@ get_key_material(libzfs_handle_t *hdl, boolean_t do_verify, boolean_t newkey,
694848
if (ret != 0)
695849
goto error;
696850

697-
/* open the appropriate file descriptor */
698851
switch (keyloc) {
699852
case ZFS_KEYLOCATION_PROMPT:
700853
if (isatty(fileno(stdin))) {
@@ -1574,7 +1727,7 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
15741727
{
15751728
int ret;
15761729
char errbuf[1024];
1577-
boolean_t is_encroot;
1730+
boolean_t is_encroot, requested_new_key = B_FALSE;
15781731
nvlist_t *props = NULL;
15791732
uint8_t *wkeydata = NULL;
15801733
uint_t wkeylen = 0;
@@ -1626,6 +1779,18 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
16261779
goto error;
16271780
}
16281781

1782+
/* Get current location to send the right transition to backend */
1783+
ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
1784+
prop_keylocation, sizeof (prop_keylocation),
1785+
NULL, NULL, 0, B_TRUE);
1786+
if (ret != 0) {
1787+
zfs_error_aux(zhp->zfs_hdl,
1788+
dgettext(TEXT_DOMAIN, "Failed to "
1789+
"get existing keylocation "
1790+
"property."));
1791+
goto error;
1792+
}
1793+
16291794
/*
16301795
* If the user wants to use the inheritkey variant of this function
16311796
* we don't need to collect any crypto arguments.
@@ -1660,26 +1825,14 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
16601825
if (ret != 0) {
16611826
zfs_error_aux(zhp->zfs_hdl,
16621827
dgettext(TEXT_DOMAIN, "Failed to "
1663-
"get existing keyformat "
1828+
"insert existing keyformat "
16641829
"property."));
16651830
goto error;
16661831
}
16671832
}
16681833

1669-
if (keylocation == NULL) {
1670-
ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
1671-
prop_keylocation, sizeof (prop_keylocation),
1672-
NULL, NULL, 0, B_TRUE);
1673-
if (ret != 0) {
1674-
zfs_error_aux(zhp->zfs_hdl,
1675-
dgettext(TEXT_DOMAIN, "Failed to "
1676-
"get existing keylocation "
1677-
"property."));
1678-
goto error;
1679-
}
1680-
1834+
if (keylocation == NULL)
16811835
keylocation = prop_keylocation;
1682-
}
16831836
} else {
16841837
/* need a new key for non-encryption roots */
16851838
if (keyformat == ZFS_KEYFORMAT_NONE) {
@@ -1705,6 +1858,7 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
17051858
ret = populate_create_encryption_params_nvlists(zhp->zfs_hdl,
17061859
zhp, B_TRUE, keyformat, keylocation, props, &wkeydata,
17071860
&wkeylen);
1861+
requested_new_key = B_TRUE;
17081862
if (ret != 0)
17091863
goto error;
17101864
} else {
@@ -1783,6 +1937,18 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
17831937
zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
17841938
}
17851939

1940+
/*
1941+
* we can't roll the key back; depending on the scenario,
1942+
* this will either resolve itself autimatically,
1943+
* or user will have to try old/new key and remove the wrong one;
1944+
* freeing the old key would be nice, but best-effort as well
1945+
*/
1946+
notify_encryption_backend(zhp,
1947+
keylocation, ret == 0 ? BACK_OP_SHIFT : BACK_OP_CANCEL);
1948+
if (ret == 0 && strcmp(prop_keylocation, keylocation ?: "none"))
1949+
notify_encryption_backend(zhp,
1950+
prop_keylocation, BACK_OP_SHIFT);
1951+
17861952
if (pzhp != NULL)
17871953
zfs_close(pzhp);
17881954
if (props != NULL)
@@ -1793,6 +1959,10 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
17931959
return (ret);
17941960

17951961
error:
1962+
if (requested_new_key)
1963+
notify_encryption_backend(zhp,
1964+
keylocation, BACK_OP_CANCEL);
1965+
17961966
if (pzhp != NULL)
17971967
zfs_close(pzhp);
17981968
if (props != NULL)

lib/libzfs/libzfs_dataset.c

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3775,6 +3775,33 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
37753775
return (0);
37763776
}
37773777

3778+
static int
3779+
zfs_destroy_notification_applicable(zfs_handle_t *zhp, char *keylocation,
3780+
size_t keylocation_len, boolean_t *notify)
3781+
{
3782+
boolean_t is_encroot;
3783+
3784+
*notify = B_FALSE;
3785+
3786+
if ((zhp->zfs_type & ZFS_TYPE_DATASET) == 0 ||
3787+
zfs_prop_get_int(zhp, ZFS_PROP_KEYFORMAT) == ZFS_KEYFORMAT_NONE)
3788+
return (0);
3789+
3790+
zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
3791+
if (!is_encroot)
3792+
return (0);
3793+
3794+
if (zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
3795+
keylocation, keylocation_len,
3796+
NULL, NULL, 0, B_TRUE) != 0)
3797+
return (zfs_standard_error_fmt(zhp->zfs_hdl, EZFS_BADPROP,
3798+
dgettext(TEXT_DOMAIN, "couldn't get keylocation for '%s'"),
3799+
zhp->zfs_name));
3800+
3801+
*notify = B_TRUE;
3802+
return (0);
3803+
}
3804+
37783805
/*
37793806
* Destroys the given dataset. The caller must make sure that the filesystem
37803807
* isn't mounted, and that there are no active dependents. If the file system
@@ -3784,6 +3811,8 @@ int
37843811
zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
37853812
{
37863813
int error;
3814+
char keylocation[MAXNAMELEN];
3815+
boolean_t notify = B_FALSE;
37873816

37883817
if (zhp->zfs_type != ZFS_TYPE_SNAPSHOT && defer)
37893818
return (EINVAL);
@@ -3807,15 +3836,34 @@ zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
38073836
error = lzc_destroy_snaps(nv, defer, NULL);
38083837
fnvlist_free(nv);
38093838
} else {
3839+
if ((error = zfs_destroy_notification_applicable(zhp,
3840+
keylocation, sizeof (keylocation), &notify)) != 0)
3841+
return (error);
3842+
3843+
if (notify) {
3844+
error = notify_encryption_backend(zhp, keylocation,
3845+
BACK_OP_UNSHIFT);
3846+
if (error)
3847+
return (zfs_standard_error_fmt(zhp->zfs_hdl,
3848+
error, dgettext(TEXT_DOMAIN,
3849+
"couldn't notify encryption back-end "
3850+
"about destruction of '%s'"),
3851+
zhp->zfs_name));
3852+
}
3853+
38103854
error = lzc_destroy(zhp->zfs_name);
38113855
}
38123856

38133857
if (error != 0 && error != ENOENT) {
3814-
return (zfs_standard_error_fmt(zhp->zfs_hdl, errno,
3858+
error = errno;
3859+
3860+
notify_encryption_backend(zhp, keylocation, BACK_OP_SHIFT);
3861+
return (zfs_standard_error_fmt(zhp->zfs_hdl, error,
38153862
dgettext(TEXT_DOMAIN, "cannot destroy '%s'"),
38163863
zhp->zfs_name));
38173864
}
38183865

3866+
notify_encryption_backend(zhp, keylocation, BACK_OP_CANCEL);
38193867
remove_mountpoint(zhp);
38203868

38213869
return (0);

0 commit comments

Comments
 (0)