Skip to content

Commit 88fc42c

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 a27ab6d commit 88fc42c

File tree

10 files changed

+1082
-24
lines changed

10 files changed

+1082
-24
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
@@ -256,6 +256,17 @@ extern int find_shares_object(differ_info_t *di);
256256
extern void libzfs_set_pipe_max(int infd);
257257
extern void zfs_commit_proto(zfs_share_proto_t *);
258258

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

lib/libzfs/libzfs_crypto.c

Lines changed: 176 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>
@@ -59,12 +60,23 @@ static int caught_interrupt;
5960

6061
static int get_key_material_file(libzfs_handle_t *, const char *, const char *,
6162
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
63+
static int get_key_material_exec(libzfs_handle_t *, const char *, const char *,
64+
zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
6265

6366
static zfs_uri_handler_t uri_handlers[] = {
6467
{ "file", get_key_material_file },
68+
{ "exec", get_key_material_exec },
6569
{ NULL, NULL }
6670
};
6771

72+
static const char *backend_ops_lcase[] = {
73+
[BACK_OP_LOAD] = "load",
74+
[BACK_OP_NEW] = "new",
75+
[BACK_OP_SHIFT] = "shift",
76+
[BACK_OP_UNSHIFT] = "unshift",
77+
[BACK_OP_CANCEL] = "cancel",
78+
};
79+
6880
static int
6981
pkcs11_get_urandom(uint8_t *buf, size_t bytes)
7082
{
@@ -483,6 +495,138 @@ get_key_material_file(libzfs_handle_t *hdl, const char *uri,
483495
return (ret);
484496
}
485497

498+
static int
499+
execute_key_provider_exec(libzfs_handle_t *hdl, const char *name,
500+
encryption_backend_op_t op, const char *fsname, int *outfd)
501+
{
502+
int ret = 0, status, compipe[2];
503+
char * const argv[] =
504+
{(char *)name, (char *)backend_ops_lcase[op], (char *)fsname, NULL};
505+
pid_t child;
506+
507+
if (pipe2(compipe, O_NONBLOCK | O_CLOEXEC) != 0) {
508+
ret = errno;
509+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
510+
"Failed to create key provider pipes: %s"), strerror(ret));
511+
return (ret);
512+
}
513+
514+
switch ((child = fork())) {
515+
case -1:
516+
ret = errno;
517+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
518+
"Failed to start key provider %s: %s"),
519+
name, strerror(ret));
520+
(void) close(compipe[1]);
521+
goto end;
522+
case 0: /* child */
523+
(void) dup2(compipe[1], 3);
524+
525+
char *fullpath, *setpath = getenv("ZFS_PROVIDER_DIR");
526+
if (setpath != NULL &&
527+
asprintf(&fullpath, "%s/%s", setpath, name) != -1)
528+
(void) execv(fullpath, argv);
529+
if (asprintf(
530+
&fullpath, SYSCONFDIR "/zfs/providers.d/%s", name) != -1)
531+
(void) execv(fullpath, argv);
532+
if (asprintf(
533+
&fullpath, ZFSEXECDIR "/providers.d/%s", name) != -1)
534+
(void) execv(fullpath, argv);
535+
write(3, strerror(errno), strlen(strerror(errno)) + 1);
536+
_exit(-1);
537+
}
538+
539+
/* parent */
540+
(void) close(compipe[1]);
541+
542+
while (waitpid(child, &status, 0) == -1)
543+
if (errno != EINTR) {
544+
ret = errno;
545+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
546+
"Failed to wait for key provider %s (%d): %s"),
547+
name, child, strerror(ret));
548+
549+
goto end;
550+
}
551+
552+
if (WIFSIGNALED(status)) {
553+
ret = EZFS_INTR;
554+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
555+
"Key provider %s killed by %s"),
556+
name, strsignal(WTERMSIG(status)));
557+
} else if (WEXITSTATUS(status) == 0xff) {
558+
char errbuf[128] = {0};
559+
(void) read(compipe[0], errbuf, sizeof (errbuf) - 1);
560+
561+
ret = ENOEXEC;
562+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
563+
"Failed to start key provider %s: %s"),
564+
name, strlen(errbuf) ? errbuf : "(?)");
565+
} else if (WEXITSTATUS(status) != 0) {
566+
ret = ECANCELED;
567+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
568+
"Key provider %s failed with %d"),
569+
name, WEXITSTATUS(status));
570+
}
571+
572+
end:
573+
if (ret == 0)
574+
*outfd = compipe[0];
575+
else
576+
(void) close(compipe[0]);
577+
errno = 0;
578+
return (ret);
579+
}
580+
581+
static int
582+
get_key_material_exec(libzfs_handle_t *hdl, const char *uri,
583+
const char *fsname, zfs_keyformat_t keyformat, boolean_t newkey,
584+
uint8_t **restrict buf, size_t *restrict len_out)
585+
{
586+
int ret = 0, rdpipe = -1;
587+
FILE *f;
588+
589+
if (strlen(uri) < 7)
590+
return (EINVAL);
591+
592+
if ((ret = execute_key_provider_exec(hdl, uri + 7,
593+
newkey ? BACK_OP_NEW : BACK_OP_LOAD, fsname, &rdpipe)) != 0)
594+
return (ret);
595+
596+
if ((f = fdopen(rdpipe, "r")) == NULL) {
597+
ret = errno;
598+
errno = 0;
599+
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
600+
"Failed to fdopen key provider pipe"));
601+
602+
(void) close(rdpipe);
603+
return (ret);
604+
}
605+
606+
ret = get_key_material_raw(f, keyformat, buf, len_out);
607+
(void) fclose(f);
608+
return (ret);
609+
}
610+
611+
__attribute__((visibility("hidden"))) int
612+
notify_encryption_backend(zfs_handle_t *zhp, const char *keylocation,
613+
encryption_backend_op_t what_of)
614+
{
615+
int ret = 0;
616+
int rdpipe = -1;
617+
618+
if (keylocation == NULL)
619+
return (0);
620+
621+
if (strncmp(keylocation, "exec://", 7) == 0) {
622+
if ((ret = execute_key_provider_exec(zhp->zfs_hdl,
623+
keylocation + 7, what_of, zfs_get_name(zhp), &rdpipe)) == 0)
624+
(void) close(rdpipe);
625+
}
626+
627+
return (ret);
628+
}
629+
486630
/*
487631
* Attempts to fetch key material, no matter where it might live. The key
488632
* material is allocated and returned in km_out. *can_retry_out will be set
@@ -508,7 +652,6 @@ get_key_material(libzfs_handle_t *hdl, boolean_t do_verify, boolean_t newkey,
508652
if (ret != 0)
509653
goto error;
510654

511-
/* open the appropriate file descriptor */
512655
switch (keyloc) {
513656
case ZFS_KEYLOCATION_PROMPT:
514657
if (isatty(fileno(stdin))) {
@@ -1388,7 +1531,7 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
13881531
{
13891532
int ret;
13901533
char errbuf[1024];
1391-
boolean_t is_encroot;
1534+
boolean_t is_encroot, requested_new_key = B_FALSE;
13921535
nvlist_t *props = NULL;
13931536
uint8_t *wkeydata = NULL;
13941537
uint_t wkeylen = 0;
@@ -1440,6 +1583,18 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
14401583
goto error;
14411584
}
14421585

1586+
/* Get current location to send the right transition to backend */
1587+
ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
1588+
prop_keylocation, sizeof (prop_keylocation),
1589+
NULL, NULL, 0, B_TRUE);
1590+
if (ret != 0) {
1591+
zfs_error_aux(zhp->zfs_hdl,
1592+
dgettext(TEXT_DOMAIN, "Failed to "
1593+
"get existing keylocation "
1594+
"property."));
1595+
goto error;
1596+
}
1597+
14431598
/*
14441599
* If the user wants to use the inheritkey variant of this function
14451600
* we don't need to collect any crypto arguments.
@@ -1474,26 +1629,14 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
14741629
if (ret != 0) {
14751630
zfs_error_aux(zhp->zfs_hdl,
14761631
dgettext(TEXT_DOMAIN, "Failed to "
1477-
"get existing keyformat "
1632+
"insert existing keyformat "
14781633
"property."));
14791634
goto error;
14801635
}
14811636
}
14821637

1483-
if (keylocation == NULL) {
1484-
ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
1485-
prop_keylocation, sizeof (prop_keylocation),
1486-
NULL, NULL, 0, B_TRUE);
1487-
if (ret != 0) {
1488-
zfs_error_aux(zhp->zfs_hdl,
1489-
dgettext(TEXT_DOMAIN, "Failed to "
1490-
"get existing keylocation "
1491-
"property."));
1492-
goto error;
1493-
}
1494-
1638+
if (keylocation == NULL)
14951639
keylocation = prop_keylocation;
1496-
}
14971640
} else {
14981641
/* need a new key for non-encryption roots */
14991642
if (keyformat == ZFS_KEYFORMAT_NONE) {
@@ -1519,6 +1662,7 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
15191662
ret = populate_create_encryption_params_nvlists(zhp->zfs_hdl,
15201663
zhp, B_TRUE, keyformat, keylocation, props, &wkeydata,
15211664
&wkeylen);
1665+
requested_new_key = B_TRUE;
15221666
if (ret != 0)
15231667
goto error;
15241668
} else {
@@ -1597,6 +1741,18 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
15971741
zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
15981742
}
15991743

1744+
/*
1745+
* we can't roll the key back; depending on the scenario,
1746+
* this will either resolve itself autimatically,
1747+
* or user will have to try old/new key and remove the wrong one;
1748+
* freeing the old key would be nice, but best-effort as well
1749+
*/
1750+
notify_encryption_backend(zhp,
1751+
keylocation, ret == 0 ? BACK_OP_SHIFT : BACK_OP_CANCEL);
1752+
if (ret == 0 && strcmp(prop_keylocation, keylocation ?: "none"))
1753+
notify_encryption_backend(zhp,
1754+
prop_keylocation, BACK_OP_SHIFT);
1755+
16001756
if (pzhp != NULL)
16011757
zfs_close(pzhp);
16021758
if (props != NULL)
@@ -1607,6 +1763,10 @@ zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
16071763
return (ret);
16081764

16091765
error:
1766+
if (requested_new_key)
1767+
notify_encryption_backend(zhp,
1768+
keylocation, BACK_OP_CANCEL);
1769+
16101770
if (pzhp != NULL)
16111771
zfs_close(pzhp);
16121772
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);

man/man8/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
/zed.8
2+
/zfsprops.8
3+
/zfs-load-key.8
24
/zfs-mount-generator.8

0 commit comments

Comments
 (0)