diff options
| author | Tom Caputi <tcaputi@datto.com> | 2019-06-25 19:39:35 +0000 |
|---|---|---|
| committer | Jerry Jelinek <jerry.jelinek@joyent.com> | 2019-06-25 19:40:06 +0000 |
| commit | eb633035c80613ec93d62f90482837adaaf21a0a (patch) | |
| tree | 67f2e3e15231d06a3525ce3958bbce24aa3de7e8 /usr/src/lib/libzfs | |
| parent | 07eb1aef88b873c5c1036d9cf69820c1ef6a32fb (diff) | |
| download | illumos-joyent-eb633035c80613ec93d62f90482837adaaf21a0a.tar.gz | |
8727 Native data and metadata encryption for zfs
Portions contributed by: Jorgen Lundman <lundman@lundman.net>
Portions contributed by: Jerry Jelinek <jerry.jelinek@joyent.com>
Portions contributed by: Paul Zuchowski <pzuchowski@datto.com>
Portions contributed by: Tim Chase <tim@chase2k.com>
Portions contributed by: Matthew Ahrens <mahrens@delphix.com>
Portions contributed by: ab-oe <arkadiusz.bubala@open-e.com>
Portions contributed by: Brian Behlendorf <behlendorf1@llnl.gov>
Portions contributed by: loli10K <ezomori.nozomu@gmail.com>
Portions contributed by: Igor K <igor@dilos.org>
Portions contributed by: Richard Laager <rlaager@wiktel.com>
Reviewed by: Jason Cohen <jwittlincohen@gmail.com>
Reviewed by: Allan Jude <allanjude@freebsd.org>
Reviewed by: George Melikov <mail@gmelikov.ru>
Reviewed by: Paul Dagnelie <pcd@delphix.com>
Reviewed by: RageLtMan <rageltman@sempervictus>
Reviewed by: Matthew Thode <prometheanfire@gentoo.org>
Reviewed by: Giuseppe Di Natale <dinatale2@llnl.gov>
Reviewed by: Kash Pande <kash@tripleback.net>
Reviewed by: Alek Pinchuk <apinchuk@datto.com>
Reviewed by: Dan Kimmel <dan.kimmel@delphix.com>
Reviewed by: David Quigley <david.quigley@intel.com>
Reviewed by: Jorgen Lundman <lundman@lundman.net>
Reviewed by: Matthew Ahrens <mahrens@delphix.com>
Reviewed by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed by: Toomas Soome <tsoome@me.com>
Reviewed by: C Fraire <cfraire@me.com>
Reviewed by: Jason King <jason.king@joyent.com>
Reviewed by: Andy Stormont <astormont@racktopsystems.com>
Approved by: Garrett D'Amore <garrett@damore.org>
Diffstat (limited to 'usr/src/lib/libzfs')
| -rw-r--r-- | usr/src/lib/libzfs/Makefile.com | 10 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs.h | 29 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_changelist.c | 6 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_crypto.c | 1529 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_dataset.c | 133 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_diff.c | 5 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_iter.c | 1 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_mount.c | 46 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_pool.c | 28 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_sendrecv.c | 622 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_status.c | 20 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/libzfs_util.c | 2 | ||||
| -rw-r--r-- | usr/src/lib/libzfs/common/mapfile-vers | 5 |
13 files changed, 2347 insertions, 89 deletions
diff --git a/usr/src/lib/libzfs/Makefile.com b/usr/src/lib/libzfs/Makefile.com index 996087bc0a..355923acae 100644 --- a/usr/src/lib/libzfs/Makefile.com +++ b/usr/src/lib/libzfs/Makefile.com @@ -41,6 +41,7 @@ OBJS_SHARED= \ OBJS_COMMON= \ libzfs_changelist.o \ libzfs_config.o \ + libzfs_crypto.o \ libzfs_dataset.o \ libzfs_diff.o \ libzfs_fru.o \ @@ -72,7 +73,8 @@ INCS += -I../../libc/inc CSTD= $(CSTD_GNU99) C99LMODE= -Xc99=%all LDLIBS += -lc -lm -ldevid -lgen -lnvpair -luutil -lavl -lefi \ - -ladm -lidmap -ltsol -lmd -lumem -lzfs_core -lcmdutils + -ladm -lidmap -ltsol -lcryptoutil -lpkcs11 -lmd -lumem -lzfs_core \ + -lcmdutils CPPFLAGS += $(INCS) -D_LARGEFILE64_SOURCE=1 -D_REENTRANT $(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG @@ -89,6 +91,12 @@ SRCS= $(OBJS_COMMON:%.o=$(SRCDIR)/%.c) \ $(OBJS_SHARED:%.o=$(SRC)/common/zfs/%.c) $(LINTLIB) := SRCS= $(SRCDIR)/$(LINTSRC) +# lint complains about unused inline functions, even though +# they are "inline", not "static inline", with "extern inline" +# implementations and usage in libzpool. +LINTFLAGS += -erroff=E_STATIC_UNUSED +LINTFLAGS64 += -erroff=E_STATIC_UNUSED + .KEEP_STATE: all: $(LIBS) diff --git a/usr/src/lib/libzfs/common/libzfs.h b/usr/src/lib/libzfs/common/libzfs.h index af5e5c35d5..ca3bb76797 100644 --- a/usr/src/lib/libzfs/common/libzfs.h +++ b/usr/src/lib/libzfs/common/libzfs.h @@ -141,6 +141,7 @@ typedef enum zfs_error { EZFS_INITIALIZING, /* currently initializing */ EZFS_NO_INITIALIZE, /* no active initialize */ EZFS_NO_RESILVER_DEFER, /* pool doesn't support resilver_defer */ + EZFS_CRYPTOFAILED, /* failed to setup encryption */ EZFS_UNKNOWN } zfs_error_t; @@ -336,6 +337,7 @@ typedef enum { ZPOOL_STATUS_IO_FAILURE_CONTINUE, /* failed I/O, failmode 'continue' */ ZPOOL_STATUS_IO_FAILURE_MMP, /* failed MMP, failmode not 'panic' */ ZPOOL_STATUS_BAD_LOG, /* cannot read log chain(s) */ + ZPOOL_STATUS_ERRATA, /* informational errata available */ /* * If the pool has unsupported features but can still be opened in @@ -371,8 +373,10 @@ typedef enum { ZPOOL_STATUS_OK } zpool_status_t; -extern zpool_status_t zpool_get_status(zpool_handle_t *, char **); -extern zpool_status_t zpool_import_status(nvlist_t *, char **); +extern zpool_status_t zpool_get_status(zpool_handle_t *, char **, + zpool_errata_t *); +extern zpool_status_t zpool_import_status(nvlist_t *, char **, + zpool_errata_t *); extern void zpool_dump_ddt(const ddt_stat_t *dds, const ddt_histogram_t *ddh); /* @@ -474,8 +478,8 @@ extern uint64_t zfs_prop_default_numeric(zfs_prop_t); extern const char *zfs_prop_column_name(zfs_prop_t); extern boolean_t zfs_prop_align_right(zfs_prop_t); -extern nvlist_t *zfs_valid_proplist(libzfs_handle_t *, zfs_type_t, - nvlist_t *, uint64_t, zfs_handle_t *, zpool_handle_t *, const char *); +extern nvlist_t *zfs_valid_proplist(libzfs_handle_t *, zfs_type_t, nvlist_t *, + uint64_t, zfs_handle_t *, zpool_handle_t *, boolean_t, const char *); extern const char *zfs_prop_to_name(zfs_prop_t); extern int zfs_prop_set(zfs_handle_t *, const char *, const char *); @@ -505,6 +509,19 @@ extern nvlist_t *zfs_get_recvd_props(zfs_handle_t *); extern nvlist_t *zfs_get_clones_nvl(zfs_handle_t *); +/* + * zfs encryption management + */ +extern int zfs_crypto_get_encryption_root(zfs_handle_t *, boolean_t *, char *); +extern int zfs_crypto_create(libzfs_handle_t *, char *, nvlist_t *, nvlist_t *, + uint8_t **, uint_t *); +extern int zfs_crypto_clone_check(libzfs_handle_t *, zfs_handle_t *, char *, + nvlist_t *); +extern int zfs_crypto_attempt_load_keys(libzfs_handle_t *, char *); +extern int zfs_crypto_load_key(zfs_handle_t *, boolean_t, char *); +extern int zfs_crypto_unload_key(zfs_handle_t *); +extern int zfs_crypto_rewrap(zfs_handle_t *, nvlist_t *, boolean_t); + typedef struct zprop_list { int pl_prop; char *pl_user_prop; @@ -653,6 +670,9 @@ typedef struct sendflags { /* compressed WRITE records are permitted */ boolean_t compress; + + /* raw encrypted records are permitted */ + boolean_t raw; } sendflags_t; typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *); @@ -737,6 +757,7 @@ extern const char *zfs_type_to_name(zfs_type_t); extern void zfs_refresh_properties(zfs_handle_t *); extern int zfs_name_valid(const char *, zfs_type_t); extern zfs_handle_t *zfs_path_to_zhandle(libzfs_handle_t *, char *, zfs_type_t); +extern int zfs_parent_name(zfs_handle_t *, char *, size_t); extern boolean_t zfs_dataset_exists(libzfs_handle_t *, const char *, zfs_type_t); extern int zfs_spa_version(zfs_handle_t *, int *); diff --git a/usr/src/lib/libzfs/common/libzfs_changelist.c b/usr/src/lib/libzfs/common/libzfs_changelist.c index af5cb35f9d..99d226019f 100644 --- a/usr/src/lib/libzfs/common/libzfs_changelist.c +++ b/usr/src/lib/libzfs/common/libzfs_changelist.c @@ -225,6 +225,7 @@ changelist_postfix(prop_changelist_t *clp) boolean_t sharenfs; boolean_t sharesmb; boolean_t mounted; + boolean_t needs_key; /* * If we are in the global zone, but this dataset is exported @@ -253,9 +254,12 @@ changelist_postfix(prop_changelist_t *clp) shareopts, sizeof (shareopts), NULL, NULL, 0, B_FALSE) == 0) && (strcmp(shareopts, "off") != 0)); + needs_key = (zfs_prop_get_int(cn->cn_handle, + ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE); + mounted = zfs_is_mounted(cn->cn_handle, NULL); - if (!mounted && (cn->cn_mounted || + if (!mounted && !needs_key && (cn->cn_mounted || ((sharenfs || sharesmb || clp->cl_waslegacy) && (zfs_prop_get_int(cn->cn_handle, ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON)))) { diff --git a/usr/src/lib/libzfs/common/libzfs_crypto.c b/usr/src/lib/libzfs/common/libzfs_crypto.c new file mode 100644 index 0000000000..4533ed8111 --- /dev/null +++ b/usr/src/lib/libzfs/common/libzfs_crypto.c @@ -0,0 +1,1529 @@ +/* + * CDDL HEADER START + * + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2017, Datto, Inc. All rights reserved. + */ + +#include <string.h> +#include <strings.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/zfs_context.h> +#include <sys/fs/zfs.h> +#include <sys/dsl_crypt.h> +#ifdef sun +#include <kmfapi.h> +#include <security/pkcs11.h> +#include <cryptoutil.h> +#else +#include <sys/crypto/icp.h> +#endif +#include <libintl.h> +#include <termios.h> +#include <signal.h> +#include <errno.h> +#include <libzfs.h> +#include "libzfs_impl.h" +#include "zfeature_common.h" + +/* + * User keys are used to decrypt the master encryption keys of a dataset. This + * indirection allows a user to change his / her access key without having to + * re-encrypt the entire dataset. User keys can be provided in one of several + * ways. Raw keys are simply given to the kernel as is. Similarly, hex keys + * are converted to binary and passed into the kernel. Password based keys are + * a bit more complicated. Passwords alone do not provide suitable entropy for + * encryption and may be too short or too long to be used. In order to derive + * a more appropriate key we use a PBKDF2 function. This function is designed + * to take a (relatively) long time to calculate in order to discourage + * attackers from guessing from a list of common passwords. PBKDF2 requires + * 2 additional parameters. The first is the number of iterations to run, which + * will ultimately determine how long it takes to derive the resulting key from + * the password. The second parameter is a salt that is randomly generated for + * each dataset. The salt is used to "tweak" PBKDF2 such that a group of + * attackers cannot reasonably generate a table of commonly known passwords to + * their output keys and expect it work for all past and future PBKDF2 users. + * We store the salt as a hidden property of the dataset (although it is + * technically ok if the salt is known to the attacker). + */ + +typedef enum key_locator { + KEY_LOCATOR_NONE, + KEY_LOCATOR_PROMPT, + KEY_LOCATOR_URI +} key_locator_t; + +#define MIN_PASSPHRASE_LEN 8 +#define MAX_PASSPHRASE_LEN 512 +#define MAX_KEY_PROMPT_ATTEMPTS 3 + +static int caught_interrupt; + +static zfs_keylocation_t +zfs_prop_parse_keylocation(const char *str) +{ + if (strcmp("prompt", str) == 0) + return (ZFS_KEYLOCATION_PROMPT); + else if (strlen(str) > 8 && strncmp("file:///", str, 8) == 0) + return (ZFS_KEYLOCATION_URI); + + return (ZFS_KEYLOCATION_NONE); +} + +static int +hex_key_to_raw(char *hex, int hexlen, uint8_t *out) +{ + int ret, i; + unsigned int c; + + for (i = 0; i < hexlen; i += 2) { + if (!isxdigit(hex[i]) || !isxdigit(hex[i + 1])) { + ret = EINVAL; + goto error; + } + + ret = sscanf(&hex[i], "%02x", &c); + if (ret != 1) { + ret = EINVAL; + goto error; + } + + out[i / 2] = c; + } + + return (0); + +error: + return (ret); +} + + +static void +catch_signal(int sig) +{ + caught_interrupt = sig; +} + +static char * +get_format_prompt_string(zfs_keyformat_t format) +{ + switch (format) { + case ZFS_KEYFORMAT_RAW: + return ("raw key"); + case ZFS_KEYFORMAT_HEX: + return ("hex key"); + case ZFS_KEYFORMAT_PASSPHRASE: + return ("passphrase"); + default: + /* shouldn't happen */ + return (NULL); + } +} + +static int +get_key_material_raw(FILE *fd, const char *fsname, zfs_keyformat_t keyformat, + boolean_t again, boolean_t newkey, uint8_t **buf, size_t *len_out) +{ + int ret = 0, bytes; + size_t buflen = 0; + struct termios old_term, new_term; + struct sigaction act, osigint, osigtstp; + + *len_out = 0; + + if (isatty(fileno(fd))) { + /* + * handle SIGINT and ignore SIGSTP. This is necessary to + * restore the state of the terminal. + */ + caught_interrupt = 0; + act.sa_flags = 0; + (void) sigemptyset(&act.sa_mask); + act.sa_handler = catch_signal; + + (void) sigaction(SIGINT, &act, &osigint); + act.sa_handler = SIG_IGN; + (void) sigaction(SIGTSTP, &act, &osigtstp); + + /* prompt for the key */ + if (fsname != NULL) { + (void) printf("%s %s%s for '%s': ", + (again) ? "Re-enter" : "Enter", + (newkey) ? "new " : "", + get_format_prompt_string( + (zfs_keyformat_t)keyformat), + fsname); + } else { + (void) printf("%s %s%s: ", + (again) ? "Re-enter" : "Enter", + (newkey) ? "new " : "", + get_format_prompt_string( + (zfs_keyformat_t)keyformat)); + + } + (void) fflush(stdout); + + /* disable the terminal echo for key input */ + (void) tcgetattr(fileno(fd), &old_term); + + new_term = old_term; + new_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + + ret = tcsetattr(fileno(fd), TCSAFLUSH, &new_term); + if (ret != 0) { + ret = errno; + errno = 0; + goto out; + } + } + + /* read the key material */ + if (keyformat != ZFS_KEYFORMAT_RAW) { + bytes = getline((char **)buf, &buflen, fd); + if (bytes < 0) { + ret = errno; + errno = 0; + goto out; + } + + /* trim the ending newline if it exists */ + if ((*buf)[bytes - 1] == '\n') { + (*buf)[bytes - 1] = '\0'; + bytes--; + } + } else { + /* + * Raw keys may have newline characters in them and so can't + * use getline(). Here we attempt to read 33 bytes so that we + * can properly check the key length (the file should only have + * 32 bytes). + */ + *buf = malloc((WRAPPING_KEY_LEN + 1) * sizeof (char)); + if (*buf == NULL) { + ret = ENOMEM; + goto out; + } + + bytes = fread(*buf, 1, WRAPPING_KEY_LEN + 1, fd); + if (bytes < 0) { + /* size errors are handled by the calling function */ + free(*buf); + *buf = NULL; + ret = errno; + errno = 0; + goto out; + } + } + + *len_out = bytes; + +out: + if (isatty(fileno(fd))) { + /* reset the teminal */ + (void) tcsetattr(fileno(fd), TCSAFLUSH, &old_term); + (void) sigaction(SIGINT, &osigint, NULL); + (void) sigaction(SIGTSTP, &osigtstp, NULL); + + /* if we caught a signal, re-throw it now */ + if (caught_interrupt != 0) { + (void) kill(getpid(), caught_interrupt); + } + + /* print the newline that was not echo'd */ + (void) printf("\n"); + } + + return (ret); + +} + +/* + * Attempts to fetch key material, no matter where it might live. The key + * material is allocated and returned in km_out. *can_retry_out will be set + * to B_TRUE if the user is providing the key material interactively, allowing + * for re-entry attempts. + */ +static int +get_key_material(libzfs_handle_t *hdl, boolean_t do_verify, boolean_t newkey, + zfs_keyformat_t keyformat, char *keylocation, const char *fsname, + uint8_t **km_out, size_t *kmlen_out, boolean_t *can_retry_out) +{ + int ret, i; + zfs_keylocation_t keyloc = ZFS_KEYLOCATION_NONE; + FILE *fd = NULL; + uint8_t *km = NULL, *km2 = NULL; + size_t kmlen, kmlen2; + boolean_t can_retry = B_FALSE; + + /* verify and parse the keylocation */ + keyloc = zfs_prop_parse_keylocation(keylocation); + + /* open the appropriate file descriptor */ + switch (keyloc) { + case ZFS_KEYLOCATION_PROMPT: + fd = stdin; + if (isatty(fileno(fd))) { + can_retry = B_TRUE; + + /* raw keys cannot be entered on the terminal */ + if (keyformat == ZFS_KEYFORMAT_RAW) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Cannot enter raw keys on the terminal")); + goto error; + } + } + break; + case ZFS_KEYLOCATION_URI: + fd = fopen(&keylocation[7], "r"); + if (!fd) { + ret = errno; + errno = 0; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Failed to open key material file")); + goto error; + } + break; + default: + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Invalid keylocation.")); + goto error; + } + + /* fetch the key material into the buffer */ + ret = get_key_material_raw(fd, fsname, keyformat, B_FALSE, newkey, + &km, &kmlen); + if (ret != 0) + goto error; + + /* do basic validation of the key material */ + switch (keyformat) { + case ZFS_KEYFORMAT_RAW: + /* verify the key length is correct */ + if (kmlen < WRAPPING_KEY_LEN) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Raw key too short (expected %u)."), + WRAPPING_KEY_LEN); + goto error; + } + + if (kmlen > WRAPPING_KEY_LEN) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Raw key too long (expected %u)."), + WRAPPING_KEY_LEN); + goto error; + } + break; + case ZFS_KEYFORMAT_HEX: + /* verify the key length is correct */ + if (kmlen < WRAPPING_KEY_LEN * 2) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Hex key too short (expected %u)."), + WRAPPING_KEY_LEN * 2); + goto error; + } + + if (kmlen > WRAPPING_KEY_LEN * 2) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Hex key too long (expected %u)."), + WRAPPING_KEY_LEN * 2); + goto error; + } + + /* check for invalid hex digits */ + for (i = 0; i < WRAPPING_KEY_LEN * 2; i++) { + if (!isxdigit((char)km[i])) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Invalid hex character detected.")); + goto error; + } + } + break; + case ZFS_KEYFORMAT_PASSPHRASE: + /* verify the length is within bounds */ + if (kmlen > MAX_PASSPHRASE_LEN) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Passphrase too long (max %u)."), + MAX_PASSPHRASE_LEN); + goto error; + } + + if (kmlen < MIN_PASSPHRASE_LEN) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Passphrase too short (min %u)."), + MIN_PASSPHRASE_LEN); + goto error; + } + break; + default: + /* can't happen, checked above */ + break; + } + + if (do_verify && isatty(fileno(fd))) { + ret = get_key_material_raw(fd, fsname, keyformat, B_TRUE, + newkey, &km2, &kmlen2); + if (ret != 0) + goto error; + + if (kmlen2 != kmlen || + (memcmp((char *)km, (char *)km2, kmlen) != 0)) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Provided keys do not match.")); + goto error; + } + } + + if (fd != stdin) + (void) fclose(fd); + + if (km2 != NULL) + free(km2); + + *km_out = km; + *kmlen_out = kmlen; + if (can_retry_out != NULL) + *can_retry_out = can_retry; + + return (0); + +error: + if (km != NULL) + free(km); + + if (km2 != NULL) + free(km2); + + if (fd != NULL && fd != stdin) + (void) fclose(fd); + + *km_out = NULL; + *kmlen_out = 0; + + if (can_retry_out != NULL) + *can_retry_out = can_retry; + + return (ret); +} + +/* This needs to be fixed to be compatible with other platforms */ + +static int +pbkdf2(uint8_t *passphrase, size_t passphraselen, uint8_t *salt, + size_t saltlen, uint64_t iterations, uint8_t *output, + size_t outputlen) +{ + int ret = 0; + CK_SESSION_HANDLE session; + char *tmpkeydata = NULL; + size_t tmpkeydatalen = 0; + CK_OBJECT_HANDLE obj; + + /* initialize output */ + (void) memset(output, 0, outputlen); + + ret = SUNW_C_GetMechSession(CKM_PKCS5_PBKD2, &session); + if (ret) { + (void) fprintf(stderr, "failed to connect to pkcs5: %s\n", + pkcs11_strerror(ret)); + return (ret); + } + + ret = pkcs11_PasswdToPBKD2Object(session, (char *)passphrase, + passphraselen, salt, saltlen, iterations, CKK_AES, outputlen, 0, + &obj); + + if (ret == CKR_OK) + ret = pkcs11_ObjectToKey(session, obj, (void **)&tmpkeydata, + &tmpkeydatalen, B_TRUE); + + (void) C_CloseSession(session); + if (ret) { + (void) fprintf(stderr, "unable to generate key: %s\n", + pkcs11_strerror(ret)); + return (ret); + } + + /* + * Because it allocates an area for the passphrase, we copy it out + * then zero the original + */ + (void) memcpy(output, tmpkeydata, tmpkeydatalen); + (void) memset(tmpkeydata, 0, tmpkeydatalen); + free(tmpkeydata); + + return (ret); +} + +/* ARGSUSED */ +static int +derive_key(libzfs_handle_t *hdl, zfs_keyformat_t format, uint64_t iters, + uint8_t *key_material, size_t key_material_len, uint64_t salt, + uint8_t **key_out) +{ + int ret; + uint8_t *key; + + *key_out = NULL; + + key = zfs_alloc(hdl, WRAPPING_KEY_LEN); + if (!key) + return (ENOMEM); + + switch (format) { + case ZFS_KEYFORMAT_RAW: + bcopy(key_material, key, WRAPPING_KEY_LEN); + break; + case ZFS_KEYFORMAT_HEX: + ret = hex_key_to_raw((char *)key_material, + WRAPPING_KEY_LEN * 2, key); + if (ret != 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Invalid hex key provided.")); + goto error; + } + break; + case ZFS_KEYFORMAT_PASSPHRASE: + salt = LE_64(salt); + ret = pbkdf2(key_material, strlen((char *)key_material), + ((uint8_t *)&salt), sizeof (uint64_t), iters, + key, WRAPPING_KEY_LEN); + if (ret != 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Failed to generate key from passphrase.")); + goto error; + } + break; + default: + ret = EINVAL; + goto error; + } + + *key_out = key; + return (0); + +error: + free(key); + + *key_out = NULL; + return (ret); +} + +static boolean_t +encryption_feature_is_enabled(zpool_handle_t *zph) +{ + nvlist_t *features; + uint64_t feat_refcount; + + /* check that features can be enabled */ + if (zpool_get_prop_int(zph, ZPOOL_PROP_VERSION, NULL) + < SPA_VERSION_FEATURES) + return (B_FALSE); + + /* check for crypto feature */ + features = zpool_get_features(zph); + if (!features || nvlist_lookup_uint64(features, + spa_feature_table[SPA_FEATURE_ENCRYPTION].fi_guid, + &feat_refcount) != 0) + return (B_FALSE); + + return (B_TRUE); +} + +static int +populate_create_encryption_params_nvlists(libzfs_handle_t *hdl, + zfs_handle_t *zhp, boolean_t newkey, zfs_keyformat_t keyformat, + char *keylocation, nvlist_t *props, uint8_t **wkeydata, uint_t *wkeylen) +{ + int ret; + uint64_t iters = 0, salt = 0; + uint8_t *key_material = NULL; + size_t key_material_len = 0; + uint8_t *key_data = NULL; + const char *fsname = (zhp) ? zfs_get_name(zhp) : NULL; + + /* get key material from keyformat and keylocation */ + ret = get_key_material(hdl, B_TRUE, newkey, keyformat, keylocation, + fsname, &key_material, &key_material_len, NULL); + if (ret != 0) + goto error; + + /* passphrase formats require a salt and pbkdf2 iters property */ + if (keyformat == ZFS_KEYFORMAT_PASSPHRASE) { +#ifdef sun + /* always generate a new salt */ + ret = pkcs11_get_random(&salt, sizeof (uint64_t)); + if (ret != 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Failed to generate salt.")); + goto error; + } +#else + random_init(); + + ret = random_get_bytes((uint8_t *)&salt, sizeof (uint64_t)); + if (ret != 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Failed to generate salt.")); + goto error; + } + + random_fini(); +#endif + + ret = nvlist_add_uint64(props, + zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt); + if (ret != 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Failed to add salt to properties.")); + goto error; + } + + /* + * If not otherwise specified, use the default number of + * pbkdf2 iterations. If specified, we have already checked + * that the given value is greater than MIN_PBKDF2_ITERATIONS + * during zfs_valid_proplist(). + */ + ret = nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &iters); + if (ret == ENOENT) { + iters = DEFAULT_PBKDF2_ITERATIONS; + ret = nvlist_add_uint64(props, + zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), iters); + if (ret != 0) + goto error; + } else if (ret != 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Failed to get pbkdf2 iterations.")); + goto error; + } + } else { + /* check that pbkdf2iters was not specified by the user */ + ret = nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &iters); + if (ret == 0) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Cannot specify pbkdf2iters with a non-passphrase " + "keyformat.")); + goto error; + } + } + + /* derive a key from the key material */ + ret = derive_key(hdl, (zfs_keyformat_t)keyformat, iters, key_material, + key_material_len, salt, &key_data); + if (ret != 0) + goto error; + + free(key_material); + + *wkeydata = key_data; + *wkeylen = WRAPPING_KEY_LEN; + return (0); + +error: + if (key_material != NULL) + free(key_material); + if (key_data != NULL) + free(key_data); + + *wkeydata = NULL; + *wkeylen = 0; + return (ret); +} + +static boolean_t +proplist_has_encryption_props(nvlist_t *props) +{ + int ret; + uint64_t intval; + char *strval; + + ret = nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_ENCRYPTION), &intval); + if (ret == 0 && intval != ZIO_CRYPT_OFF) + return (B_TRUE); + + ret = nvlist_lookup_string(props, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &strval); + if (ret == 0 && strcmp(strval, "none") != 0) + return (B_TRUE); + + ret = nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &intval); + if (ret == 0) + return (B_TRUE); + + ret = nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &intval); + if (ret == 0) + return (B_TRUE); + + return (B_FALSE); +} + +int +zfs_crypto_get_encryption_root(zfs_handle_t *zhp, boolean_t *is_encroot, + char *buf) +{ + int ret; + char prop_encroot[MAXNAMELEN]; + + /* if the dataset isn't encrypted, just return */ + if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) == ZIO_CRYPT_OFF) { + *is_encroot = B_FALSE; + if (buf != NULL) + buf[0] = '\0'; + return (0); + } + + ret = zfs_prop_get(zhp, ZFS_PROP_ENCRYPTION_ROOT, prop_encroot, + sizeof (prop_encroot), NULL, NULL, 0, B_TRUE); + if (ret != 0) { + *is_encroot = B_FALSE; + if (buf != NULL) + buf[0] = '\0'; + return (ret); + } + + *is_encroot = strcmp(prop_encroot, zfs_get_name(zhp)) == 0; + if (buf != NULL) + (void) strcpy(buf, prop_encroot); + + return (0); +} + +int +zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props, + nvlist_t *pool_props, uint8_t **wkeydata_out, uint_t *wkeylen_out) +{ + int ret; + uint64_t crypt = ZIO_CRYPT_INHERIT, pcrypt = ZIO_CRYPT_INHERIT; + uint64_t keyformat = ZFS_KEYFORMAT_NONE; + char *keylocation = NULL; + zfs_handle_t *pzhp = NULL; + uint8_t *wkeydata = NULL; + uint_t wkeylen = 0; + boolean_t local_crypt = B_TRUE; + + /* lookup crypt from props */ + ret = nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_ENCRYPTION), &crypt); + if (ret != 0) + local_crypt = B_FALSE; + + /* lookup key location and format from props */ + (void) nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &keyformat); + (void) nvlist_lookup_string(props, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation); + + if (parent_name != NULL) { + /* get a reference to parent dataset */ + pzhp = make_dataset_handle(hdl, parent_name); + if (pzhp == NULL) { + ret = ENOENT; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Failed to lookup parent.")); + goto out; + } + + /* Lookup parent's crypt */ + pcrypt = zfs_prop_get_int(pzhp, ZFS_PROP_ENCRYPTION); + + /* Params require the encryption feature */ + if (!encryption_feature_is_enabled(pzhp->zpool_hdl)) { + if (proplist_has_encryption_props(props)) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Encryption feature not enabled.")); + goto out; + } + + ret = 0; + goto out; + } + } else { + /* + * special case for root dataset where encryption feature + * feature won't be on disk yet + */ + if (!nvlist_exists(pool_props, "feature@encryption")) { + if (proplist_has_encryption_props(props)) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Encryption feature not enabled.")); + goto out; + } + + ret = 0; + goto out; + } + + pcrypt = ZIO_CRYPT_OFF; + } + + /* Check for encryption being explicitly truned off */ + if (crypt == ZIO_CRYPT_OFF && pcrypt != ZIO_CRYPT_OFF) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Invalid encryption value. Dataset must be encrypted.")); + goto out; + } + + /* Get the inherited encryption property if we don't have it locally */ + if (!local_crypt) + crypt = pcrypt; + + /* + * At this point crypt should be the actual encryption value. If + * encryption is off just verify that no encryption properties have + * been specified and return. + */ + if (crypt == ZIO_CRYPT_OFF) { + if (proplist_has_encryption_props(props)) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Encryption must be turned on to set encryption " + "properties.")); + goto out; + } + + ret = 0; + goto out; + } + + /* + * If we have a parent crypt it is valid to specify encryption alone. + * This will result in a child that is encrypted with the chosen + * encryption suite that will also inherit the parent's key. If + * the parent is not encrypted we need an encryption suite provided. + */ + if (pcrypt == ZIO_CRYPT_OFF && keylocation == NULL && + keyformat == ZFS_KEYFORMAT_NONE) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Keyformat required for new encryption root.")); + goto out; + } + + /* + * Specifying a keylocation implies this will be a new encryption root. + * Check that a keyformat is also specified. + */ + if (keylocation != NULL && keyformat == ZFS_KEYFORMAT_NONE) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Keyformat required for new encryption root.")); + goto out; + } + + /* default to prompt if no keylocation is specified */ + if (keyformat != ZFS_KEYFORMAT_NONE && keylocation == NULL) { + keylocation = "prompt"; + ret = nvlist_add_string(props, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION), keylocation); + if (ret != 0) + goto out; + } + + /* + * If a local key is provided, this dataset will be a new + * encryption root. Populate the encryption params. + */ + if (keylocation != NULL) { + ret = populate_create_encryption_params_nvlists(hdl, NULL, + B_FALSE, keyformat, keylocation, props, &wkeydata, + &wkeylen); + if (ret != 0) + goto out; + } + + if (pzhp != NULL) + zfs_close(pzhp); + + *wkeydata_out = wkeydata; + *wkeylen_out = wkeylen; + return (0); + +out: + if (pzhp != NULL) + zfs_close(pzhp); + if (wkeydata != NULL) + free(wkeydata); + + *wkeydata_out = NULL; + *wkeylen_out = 0; + return (ret); +} + +int +zfs_crypto_clone_check(libzfs_handle_t *hdl, zfs_handle_t *origin_zhp, + char *parent_name, nvlist_t *props) +{ + int ret; + zfs_handle_t *pzhp = NULL; + uint64_t pcrypt, ocrypt; + + /* + * No encryption properties should be specified. They will all be + * inherited from the origin dataset. + */ + if (nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_KEYFORMAT)) || + nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_KEYLOCATION)) || + nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_ENCRYPTION)) || + nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS))) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Encryption properties must inherit from origin dataset.")); + goto out; + } + + /* get a reference to parent dataset, should never be NULL */ + pzhp = make_dataset_handle(hdl, parent_name); + if (pzhp == NULL) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Failed to lookup parent.")); + return (ENOENT); + } + + /* Lookup parent's crypt */ + pcrypt = zfs_prop_get_int(pzhp, ZFS_PROP_ENCRYPTION); + ocrypt = zfs_prop_get_int(origin_zhp, ZFS_PROP_ENCRYPTION); + + /* all children of encrypted parents must be encrypted */ + if (pcrypt != ZIO_CRYPT_OFF && ocrypt == ZIO_CRYPT_OFF) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "Cannot create unencrypted clone as a child " + "of encrypted parent.")); + goto out; + } + + zfs_close(pzhp); + return (0); + +out: + if (pzhp != NULL) + zfs_close(pzhp); + return (ret); +} + +typedef struct loadkeys_cbdata { + uint64_t cb_numfailed; + uint64_t cb_numattempted; +} loadkey_cbdata_t; + +static int +load_keys_cb(zfs_handle_t *zhp, void *arg) +{ + int ret; + boolean_t is_encroot; + loadkey_cbdata_t *cb = arg; + uint64_t keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS); + + /* only attempt to load keys for encryption roots */ + ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL); + if (ret != 0 || !is_encroot) + goto out; + + /* don't attempt to load already loaded keys */ + if (keystatus == ZFS_KEYSTATUS_AVAILABLE) + goto out; + + /* Attempt to load the key. Record status in cb. */ + cb->cb_numattempted++; + + ret = zfs_crypto_load_key(zhp, B_FALSE, NULL); + if (ret) + cb->cb_numfailed++; + +out: + (void) zfs_iter_filesystems(zhp, load_keys_cb, cb); + zfs_close(zhp); + + /* always return 0, since this function is best effort */ + return (0); +} + +/* + * This function is best effort. It attempts to load all the keys for the given + * filesystem and all of its children. + */ +int +zfs_crypto_attempt_load_keys(libzfs_handle_t *hdl, char *fsname) +{ + int ret; + zfs_handle_t *zhp = NULL; + loadkey_cbdata_t cb = { 0 }; + + zhp = zfs_open(hdl, fsname, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); + if (zhp == NULL) { + ret = ENOENT; + goto error; + } + + ret = load_keys_cb(zfs_handle_dup(zhp), &cb); + if (ret) + goto error; + + (void) printf(gettext("%llu / %llu keys successfully loaded\n"), + (u_longlong_t)(cb.cb_numattempted - cb.cb_numfailed), + (u_longlong_t)cb.cb_numattempted); + + if (cb.cb_numfailed != 0) { + ret = -1; + goto error; + } + + zfs_close(zhp); + return (0); + +error: + if (zhp != NULL) + zfs_close(zhp); + return (ret); +} + +int +zfs_crypto_load_key(zfs_handle_t *zhp, boolean_t noop, char *alt_keylocation) +{ + int ret, attempts = 0; + char errbuf[1024]; + uint64_t keystatus, iters = 0, salt = 0; + uint64_t keyformat = ZFS_KEYFORMAT_NONE; + char prop_keylocation[MAXNAMELEN]; + char prop_encroot[MAXNAMELEN]; + char *keylocation = NULL; + uint8_t *key_material = NULL, *key_data = NULL; + size_t key_material_len; + boolean_t is_encroot, can_retry = B_FALSE, correctible = B_FALSE; + + (void) snprintf(errbuf, sizeof (errbuf), + dgettext(TEXT_DOMAIN, "Key load error")); + + /* check that encryption is enabled for the pool */ + if (!encryption_feature_is_enabled(zhp->zpool_hdl)) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Encryption feature not enabled.")); + ret = EINVAL; + goto error; + } + + /* Fetch the keyformat. Check that the dataset is encrypted. */ + keyformat = zfs_prop_get_int(zhp, ZFS_PROP_KEYFORMAT); + if (keyformat == ZFS_KEYFORMAT_NONE) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "'%s' is not encrypted."), zfs_get_name(zhp)); + ret = EINVAL; + goto error; + } + + /* + * Fetch the key location. Check that we are working with an + * encryption root. + */ + ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, prop_encroot); + if (ret != 0) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Failed to get encryption root for '%s'."), + zfs_get_name(zhp)); + goto error; + } else if (!is_encroot) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Keys must be loaded for encryption root of '%s' (%s)."), + zfs_get_name(zhp), prop_encroot); + ret = EINVAL; + goto error; + } + + /* + * if the caller has elected to override the keylocation property + * use that instead + */ + if (alt_keylocation != NULL) { + keylocation = alt_keylocation; + } else { + ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION, prop_keylocation, + sizeof (prop_keylocation), NULL, NULL, 0, B_TRUE); + if (ret != 0) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Failed to get keylocation for '%s'."), + zfs_get_name(zhp)); + goto error; + } + + keylocation = prop_keylocation; + } + + /* check that the key is unloaded unless this is a noop */ + if (!noop) { + keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS); + if (keystatus == ZFS_KEYSTATUS_AVAILABLE) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Key already loaded for '%s'."), zfs_get_name(zhp)); + ret = EEXIST; + goto error; + } + } + + /* passphrase formats require a salt and pbkdf2_iters property */ + if (keyformat == ZFS_KEYFORMAT_PASSPHRASE) { + salt = zfs_prop_get_int(zhp, ZFS_PROP_PBKDF2_SALT); + iters = zfs_prop_get_int(zhp, ZFS_PROP_PBKDF2_ITERS); + } + +try_again: + /* fetching and deriving the key are correctible errors. set the flag */ + correctible = B_TRUE; + + /* get key material from key format and location */ + ret = get_key_material(zhp->zfs_hdl, B_FALSE, B_FALSE, keyformat, + keylocation, zfs_get_name(zhp), &key_material, &key_material_len, + &can_retry); + if (ret != 0) + goto error; + + /* derive a key from the key material */ + ret = derive_key(zhp->zfs_hdl, keyformat, iters, key_material, + key_material_len, salt, &key_data); + if (ret != 0) + goto error; + + correctible = B_FALSE; + + /* pass the wrapping key and noop flag to the ioctl */ + ret = lzc_load_key(zhp->zfs_name, noop, key_data, WRAPPING_KEY_LEN); + if (ret != 0) { + switch (ret) { + case EINVAL: + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Invalid parameters provided for %s."), + zfs_get_name(zhp)); + break; + case EEXIST: + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Key already loaded for '%s'."), zfs_get_name(zhp)); + break; + case EBUSY: + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "'%s' is busy."), zfs_get_name(zhp)); + break; + case EACCES: + correctible = B_TRUE; + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Incorrect key provided for '%s'."), + zfs_get_name(zhp)); + break; + } + goto error; + } + + free(key_material); + free(key_data); + + return (0); + +error: + (void) zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf); + if (key_material != NULL) { + free(key_material); + key_material = NULL; + } + if (key_data != NULL) { + free(key_data); + key_data = NULL; + } + + /* + * Here we decide if it is ok to allow the user to retry entering their + * key. The can_retry flag will be set if the user is entering their + * key from an interactive prompt. The correctible flag will only be + * set if an error that occured could be corrected by retrying. Both + * flags are needed to allow the user to attempt key entry again + */ + if (can_retry && correctible && attempts <= MAX_KEY_PROMPT_ATTEMPTS) { + attempts++; + goto try_again; + } + + return (ret); +} + +int +zfs_crypto_unload_key(zfs_handle_t *zhp) +{ + int ret; + char errbuf[1024]; + char prop_encroot[MAXNAMELEN]; + uint64_t keystatus, keyformat; + boolean_t is_encroot; + + (void) snprintf(errbuf, sizeof (errbuf), + dgettext(TEXT_DOMAIN, "Key unload error")); + + /* check that encryption is enabled for the pool */ + if (!encryption_feature_is_enabled(zhp->zpool_hdl)) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Encryption feature not enabled.")); + ret = EINVAL; + goto error; + } + + /* Fetch the keyformat. Check that the dataset is encrypted. */ + keyformat = zfs_prop_get_int(zhp, ZFS_PROP_KEYFORMAT); + if (keyformat == ZFS_KEYFORMAT_NONE) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "'%s' is not encrypted."), zfs_get_name(zhp)); + ret = EINVAL; + goto error; + } + + /* + * Fetch the key location. Check that we are working with an + * encryption root. + */ + ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, prop_encroot); + if (ret != 0) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Failed to get encryption root for '%s'."), + zfs_get_name(zhp)); + goto error; + } else if (!is_encroot) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Keys must be unloaded for encryption root of '%s' (%s)."), + zfs_get_name(zhp), prop_encroot); + ret = EINVAL; + goto error; + } + + /* check that the key is loaded */ + keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS); + if (keystatus == ZFS_KEYSTATUS_UNAVAILABLE) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Key already unloaded for '%s'."), zfs_get_name(zhp)); + ret = EACCES; + goto error; + } + + /* call the ioctl */ + ret = lzc_unload_key(zhp->zfs_name); + + if (ret != 0) { + switch (ret) { + case EACCES: + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Key already unloaded for '%s'."), + zfs_get_name(zhp)); + break; + case EBUSY: + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "'%s' is busy."), zfs_get_name(zhp)); + break; + } + (void) zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf); + } + + return (ret); + +error: + (void) zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf); + return (ret); +} + +static int +zfs_crypto_verify_rewrap_nvlist(zfs_handle_t *zhp, nvlist_t *props, + nvlist_t **props_out, char *errbuf) +{ + int ret; + nvpair_t *elem = NULL; + zfs_prop_t prop; + nvlist_t *new_props = NULL; + + new_props = fnvlist_alloc(); + + /* + * loop through all provided properties, we should only have + * keyformat, keylocation and pbkdf2iters. The actual validation of + * values is done by zfs_valid_proplist(). + */ + while ((elem = nvlist_next_nvpair(props, elem)) != NULL) { + const char *propname = nvpair_name(elem); + prop = zfs_name_to_prop(propname); + + switch (prop) { + case ZFS_PROP_PBKDF2_ITERS: + case ZFS_PROP_KEYFORMAT: + case ZFS_PROP_KEYLOCATION: + break; + default: + ret = EINVAL; + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Only keyformat, keylocation and pbkdf2iters may " + "be set with this command.")); + goto error; + } + } + + new_props = zfs_valid_proplist(zhp->zfs_hdl, zhp->zfs_type, props, + zfs_prop_get_int(zhp, ZFS_PROP_ZONED), NULL, zhp->zpool_hdl, + B_TRUE, errbuf); + if (new_props == NULL) + goto error; + + *props_out = new_props; + return (0); + +error: + nvlist_free(new_props); + *props_out = NULL; + return (ret); +} + +int +zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey) +{ + int ret; + char errbuf[1024]; + boolean_t is_encroot; + nvlist_t *props = NULL; + uint8_t *wkeydata = NULL; + uint_t wkeylen = 0; + dcp_cmd_t cmd = (inheritkey) ? DCP_CMD_INHERIT : DCP_CMD_NEW_KEY; + uint64_t crypt, pcrypt, keystatus, pkeystatus; + uint64_t keyformat = ZFS_KEYFORMAT_NONE; + zfs_handle_t *pzhp = NULL; + char *keylocation = NULL; + char origin_name[MAXNAMELEN]; + char prop_keylocation[MAXNAMELEN]; + char parent_name[ZFS_MAX_DATASET_NAME_LEN]; + + (void) snprintf(errbuf, sizeof (errbuf), + dgettext(TEXT_DOMAIN, "Key change error")); + + /* check that encryption is enabled for the pool */ + if (!encryption_feature_is_enabled(zhp->zpool_hdl)) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Encryption feature not enabled.")); + ret = EINVAL; + goto error; + } + + /* get crypt from dataset */ + crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION); + if (crypt == ZIO_CRYPT_OFF) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Dataset not encrypted.")); + ret = EINVAL; + goto error; + } + + /* get the encryption root of the dataset */ + ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL); + if (ret != 0) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Failed to get encryption root for '%s'."), + zfs_get_name(zhp)); + goto error; + } + + /* Clones use their origin's key and cannot rewrap it */ + ret = zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin_name, + sizeof (origin_name), NULL, NULL, 0, B_TRUE); + if (ret == 0 && strcmp(origin_name, "") != 0) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Keys cannot be changed on clones.")); + ret = EINVAL; + goto error; + } + + /* + * If the user wants to use the inheritkey variant of this function + * we don't need to collect any crypto arguments. + */ + if (!inheritkey) { + /* validate the provided properties */ + ret = zfs_crypto_verify_rewrap_nvlist(zhp, raw_props, &props, + errbuf); + if (ret != 0) + goto error; + + /* + * Load keyformat and keylocation from the nvlist. Fetch from + * the dataset properties if not specified. + */ + (void) nvlist_lookup_uint64(props, + zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &keyformat); + (void) nvlist_lookup_string(props, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation); + + if (is_encroot) { + /* + * If this is already an ecryption root, just keep + * any properties not set by the user. + */ + if (keyformat == ZFS_KEYFORMAT_NONE) { + keyformat = zfs_prop_get_int(zhp, + ZFS_PROP_KEYFORMAT); + ret = nvlist_add_uint64(props, + zfs_prop_to_name(ZFS_PROP_KEYFORMAT), + keyformat); + } + + if (keylocation == NULL) { + ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION, + prop_keylocation, sizeof (prop_keylocation), + NULL, NULL, 0, B_TRUE); + if (ret != 0) { + zfs_error_aux(zhp->zfs_hdl, + dgettext(TEXT_DOMAIN, "Failed to " + "get existing keylocation " + "property.")); + goto error; + } + + keylocation = prop_keylocation; + } + } else { + /* need a new key for non-encryption roots */ + if (keyformat == ZFS_KEYFORMAT_NONE) { + ret = EINVAL; + zfs_error_aux(zhp->zfs_hdl, + dgettext(TEXT_DOMAIN, "Keyformat required " + "for new encryption root.")); + goto error; + } + + /* default to prompt if no keylocation is specified */ + if (keylocation == NULL) { + keylocation = "prompt"; + ret = nvlist_add_string(props, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION), + keylocation); + if (ret != 0) + goto error; + } + } + + /* fetch the new wrapping key and associated properties */ + ret = populate_create_encryption_params_nvlists(zhp->zfs_hdl, + zhp, B_TRUE, keyformat, keylocation, props, &wkeydata, + &wkeylen); + if (ret != 0) + goto error; + } else { + /* check that zhp is an encryption root */ + if (!is_encroot) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Key inheriting can only be performed on " + "encryption roots.")); + ret = EINVAL; + goto error; + } + + /* get the parent's name */ + ret = zfs_parent_name(zhp, parent_name, sizeof (parent_name)); + if (ret != 0) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Root dataset cannot inherit key.")); + ret = EINVAL; + goto error; + } + + /* get a handle to the parent */ + pzhp = make_dataset_handle(zhp->zfs_hdl, parent_name); + if (pzhp == NULL) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Failed to lookup parent.")); + ret = ENOENT; + goto error; + } + + /* parent must be encrypted */ + pcrypt = zfs_prop_get_int(pzhp, ZFS_PROP_ENCRYPTION); + if (pcrypt == ZIO_CRYPT_OFF) { + zfs_error_aux(pzhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Parent must be encrypted.")); + ret = EINVAL; + goto error; + } + + /* check that the parent's key is loaded */ + pkeystatus = zfs_prop_get_int(pzhp, ZFS_PROP_KEYSTATUS); + if (pkeystatus == ZFS_KEYSTATUS_UNAVAILABLE) { + zfs_error_aux(pzhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Parent key must be loaded.")); + ret = EACCES; + goto error; + } + } + + /* check that the key is loaded */ + keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS); + if (keystatus == ZFS_KEYSTATUS_UNAVAILABLE) { + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Key must be loaded.")); + ret = EACCES; + goto error; + } + + /* call the ioctl */ + ret = lzc_change_key(zhp->zfs_name, cmd, props, wkeydata, wkeylen); + if (ret != 0) { + switch (ret) { + case EINVAL: + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Invalid properties for key change.")); + break; + case EACCES: + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "Key is not currently loaded.")); + break; + } + (void) zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf); + } + + if (pzhp != NULL) + zfs_close(pzhp); + if (props != NULL) + nvlist_free(props); + if (wkeydata != NULL) + free(wkeydata); + + return (ret); + +error: + if (pzhp != NULL) + zfs_close(pzhp); + if (props != NULL) + nvlist_free(props); + if (wkeydata != NULL) + free(wkeydata); + + (void) zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf); + return (ret); +} diff --git a/usr/src/lib/libzfs/common/libzfs_dataset.c b/usr/src/lib/libzfs/common/libzfs_dataset.c index 2ca09e51d4..117e414a9a 100644 --- a/usr/src/lib/libzfs/common/libzfs_dataset.c +++ b/usr/src/lib/libzfs/common/libzfs_dataset.c @@ -59,6 +59,7 @@ #include <sys/dnode.h> #include <sys/spa.h> #include <sys/zap.h> +#include <sys/dsl_crypt.h> #include <libzfs.h> #include "zfs_namecheck.h" @@ -951,7 +952,7 @@ zfs_which_resv_prop(zfs_handle_t *zhp, zfs_prop_t *resv_prop) nvlist_t * zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, uint64_t zoned, zfs_handle_t *zhp, zpool_handle_t *zpool_hdl, - const char *errbuf) + boolean_t key_params_ok, const char *errbuf) { nvpair_t *elem; uint64_t intval; @@ -1108,7 +1109,8 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, } if (zfs_prop_readonly(prop) && - (!zfs_prop_setonce(prop) || zhp != NULL)) { + !(zfs_prop_setonce(prop) && zhp == NULL) && + !(zfs_prop_encryption_key_param(prop) && key_params_ok)) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' is readonly"), propname); @@ -1403,6 +1405,48 @@ badlabel: break; + case ZFS_PROP_KEYLOCATION: + if (!zfs_prop_valid_keylocation(strval, B_FALSE)) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "invalid keylocation")); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + if (zhp != NULL) { + uint64_t crypt = + zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION); + + if (crypt == ZIO_CRYPT_OFF && + strcmp(strval, "none") != 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "keylocation must be 'none' " + "for unencrypted datasets")); + (void) zfs_error(hdl, EZFS_BADPROP, + errbuf); + goto error; + } else if (crypt != ZIO_CRYPT_OFF && + strcmp(strval, "none") == 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "keylocation must not be 'none' " + "for encrypted datasets")); + (void) zfs_error(hdl, EZFS_BADPROP, + errbuf); + goto error; + } + } + break; + + case ZFS_PROP_PBKDF2_ITERS: + if (intval < MIN_PBKDF2_ITERATIONS) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "minimum pbkdf2 iterations is %u"), + MIN_PBKDF2_ITERATIONS); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + break; + case ZFS_PROP_UTF8ONLY: chosen_utf = (int)intval; break; @@ -1476,6 +1520,27 @@ badlabel: break; } } + + /* check encryption properties */ + if (zhp != NULL) { + int64_t crypt = zfs_prop_get_int(zhp, + ZFS_PROP_ENCRYPTION); + + switch (prop) { + case ZFS_PROP_COPIES: + if (crypt != ZIO_CRYPT_OFF && intval > 2) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "encrypted datasets cannot have " + "3 copies")); + (void) zfs_error(hdl, EZFS_BADPROP, + errbuf); + goto error; + } + break; + default: + break; + } + } } /* @@ -1688,6 +1753,16 @@ zfs_setprop_error(libzfs_handle_t *hdl, zfs_prop_t prop, int err, } break; + case EACCES: + if (prop == ZFS_PROP_KEYLOCATION) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "keylocation may only be set on encryption roots")); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + } else { + (void) zfs_standard_error(hdl, err, errbuf); + } + break; + case EOVERFLOW: /* * This platform can't address a volume this big. @@ -1757,7 +1832,7 @@ zfs_prop_set_list(zfs_handle_t *zhp, nvlist_t *props) if ((nvl = zfs_valid_proplist(hdl, zhp->zfs_type, props, zfs_prop_get_int(zhp, ZFS_PROP_ZONED), zhp, zhp->zpool_hdl, - errbuf)) == NULL) + B_FALSE, errbuf)) == NULL) goto error; /* @@ -3254,6 +3329,12 @@ parent_name(const char *path, char *buf, size_t buflen) return (0); } +int +zfs_parent_name(zfs_handle_t *zhp, char *buf, size_t buflen) +{ + return (parent_name(zfs_get_name(zhp), buf, buflen)); +} + /* * If accept_ancestor is false, then check to make sure that the given path has * a parent, and that it exists. If accept_ancestor is true, then find the @@ -3486,7 +3567,10 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, int ret; uint64_t size = 0; uint64_t blocksize = zfs_prop_default_numeric(ZFS_PROP_VOLBLOCKSIZE); + uint8_t *wkeydata = NULL; + uint_t wkeylen = 0; char errbuf[1024]; + char parent[MAXNAMELEN]; uint64_t zoned; enum lzc_dataset_type ost; zpool_handle_t *zpool_handle; @@ -3539,7 +3623,7 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, return (-1); if (props && (props = zfs_valid_proplist(hdl, type, props, - zoned, NULL, zpool_handle, errbuf)) == 0) { + zoned, NULL, zpool_handle, B_TRUE, errbuf)) == 0) { zpool_close(zpool_handle); return (-1); } @@ -3591,15 +3675,21 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, } } + (void) parent_name(path, parent, sizeof (parent)); + if (zfs_crypto_create(hdl, parent, props, NULL, &wkeydata, + &wkeylen) != 0) { + nvlist_free(props); + return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); + } + /* create the dataset */ - ret = lzc_create(path, ost, props); + ret = lzc_create(path, ost, props, wkeydata, wkeylen); nvlist_free(props); + if (wkeydata != NULL) + free(wkeydata); /* check for failure */ if (ret != 0) { - char parent[ZFS_MAX_DATASET_NAME_LEN]; - (void) parent_name(path, parent, sizeof (parent)); - switch (errno) { case ENOENT: zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, @@ -3620,6 +3710,12 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid property value(s) specified")); return (zfs_error(hdl, EZFS_BADPROP, errbuf)); + case EACCES: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "encryption root's key is not loaded " + "or provided")); + return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); + #ifdef _ILP32 case EOVERFLOW: /* @@ -3815,7 +3911,7 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props) type = ZFS_TYPE_FILESYSTEM; } if ((props = zfs_valid_proplist(hdl, type, props, zoned, - zhp, zhp->zpool_hdl, errbuf)) == NULL) + zhp, zhp->zpool_hdl, B_TRUE, errbuf)) == NULL) return (-1); if (zfs_fix_auto_resv(zhp, props) == -1) { nvlist_free(props); @@ -3823,6 +3919,11 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props) } } + if (zfs_crypto_clone_check(hdl, zhp, parent, props) != 0) { + nvlist_free(props); + return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); + } + ret = lzc_clone(target, zhp->zfs_name, props); nvlist_free(props); @@ -4001,7 +4102,7 @@ zfs_snapshot_nvl(libzfs_handle_t *hdl, nvlist_t *snaps, nvlist_t *props) if (props != NULL && (props = zfs_valid_proplist(hdl, ZFS_TYPE_SNAPSHOT, - props, B_FALSE, NULL, zpool_hdl, errbuf)) == NULL) { + props, B_FALSE, NULL, zpool_hdl, B_FALSE, errbuf)) == NULL) { zpool_close(zpool_hdl); return (-1); } @@ -4392,6 +4493,18 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive, "a child dataset already has a snapshot " "with the new name")); (void) zfs_error(hdl, EZFS_EXISTS, errbuf); + } else if (errno == EACCES) { + if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) == + ZIO_CRYPT_OFF) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "cannot rename an unencrypted dataset to " + "be a decendent of an encrypted one")); + } else { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "cannot move encryption child outside of " + "its encryption root")); + } + (void) zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf); } else { (void) zfs_standard_error(zhp->zfs_hdl, errno, errbuf); } diff --git a/usr/src/lib/libzfs/common/libzfs_diff.c b/usr/src/lib/libzfs/common/libzfs_diff.c index ad1fa67f1f..4d2bd5156d 100644 --- a/usr/src/lib/libzfs/common/libzfs_diff.c +++ b/usr/src/lib/libzfs/common/libzfs_diff.c @@ -112,6 +112,11 @@ get_stats_for_obj(differ_info_t *di, const char *dsname, uint64_t obj, "The sys_config privilege or diff delegated permission " "is needed\nto discover path names")); return (-1); + } else if (di->zerr == EACCES) { + (void) snprintf(di->errbuf, sizeof (di->errbuf), + dgettext(TEXT_DOMAIN, + "Key must be loaded to discover path names")); + return (-1); } else { (void) snprintf(di->errbuf, sizeof (di->errbuf), dgettext(TEXT_DOMAIN, diff --git a/usr/src/lib/libzfs/common/libzfs_iter.c b/usr/src/lib/libzfs/common/libzfs_iter.c index bdef9757ef..cb5ed6b005 100644 --- a/usr/src/lib/libzfs/common/libzfs_iter.c +++ b/usr/src/lib/libzfs/common/libzfs_iter.c @@ -191,6 +191,7 @@ zfs_iter_bookmarks(zfs_handle_t *zhp, zfs_iter_f func, void *data) fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_GUID)); fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_CREATETXG)); fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_CREATION)); + fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_IVSET_GUID)); if ((err = lzc_get_bookmarks(zhp->zfs_name, props, &bmarks)) != 0) goto out; diff --git a/usr/src/lib/libzfs/common/libzfs_mount.c b/usr/src/lib/libzfs/common/libzfs_mount.c index 9bbf4d2233..44c0ec89e9 100644 --- a/usr/src/lib/libzfs/common/libzfs_mount.c +++ b/usr/src/lib/libzfs/common/libzfs_mount.c @@ -80,6 +80,7 @@ #include <sys/mount.h> #include <sys/stat.h> #include <sys/statvfs.h> +#include <sys/dsl_crypt.h> #include <libzfs.h> @@ -339,6 +340,8 @@ zfs_mount(zfs_handle_t *zhp, const char *options, int flags) char mountpoint[ZFS_MAXPROPLEN]; char mntopts[MNT_LINE_MAX]; libzfs_handle_t *hdl = zhp->zfs_hdl; + uint64_t keystatus; + int rc; if (options == NULL) mntopts[0] = '\0'; @@ -354,6 +357,39 @@ zfs_mount(zfs_handle_t *zhp, const char *options, int flags) if (!zfs_is_mountable(zhp, mountpoint, sizeof (mountpoint), NULL)) return (0); + /* + * If the filesystem is encrypted the key must be loaded in order to + * mount. If the key isn't loaded, the MS_CRYPT flag decides whether + * or not we attempt to load the keys. Note: we must call + * zfs_refresh_properties() here since some callers of this function + * (most notably zpool_enable_datasets()) may implicitly load our key + * by loading the parent's key first. + */ + if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) { + zfs_refresh_properties(zhp); + keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS); + + /* + * If the key is unavailable and MS_CRYPT is set give the + * user a chance to enter the key. Otherwise just fail + * immediately. + */ + if (keystatus == ZFS_KEYSTATUS_UNAVAILABLE) { + if (flags & MS_CRYPT) { + rc = zfs_crypto_load_key(zhp, B_FALSE, NULL); + if (rc != 0) + return (rc); + } else { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "encryption key not loaded")); + return (zfs_error_fmt(hdl, EZFS_MOUNTFAILED, + dgettext(TEXT_DOMAIN, "cannot mount '%s'"), + mountpoint)); + } + } + + } + /* Create the directory if it doesn't already exist */ if (lstat(mountpoint, &buf) != 0) { if (mkdirp(mountpoint, 0755) != 0) { @@ -1121,6 +1157,12 @@ zfs_iter_cb(zfs_handle_t *zhp, void *data) return (0); } + if (zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS) == + ZFS_KEYSTATUS_UNAVAILABLE) { + zfs_close(zhp); + return (0); + } + /* * If this filesystem is inconsistent and has a receive resume * token, we can not mount it. @@ -1313,6 +1355,10 @@ zfs_mount_one(zfs_handle_t *zhp, void *arg) mount_state_t *ms = arg; int ret = 0; + if (zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS) == + ZFS_KEYSTATUS_UNAVAILABLE) + return (0); + if (zfs_mount(zhp, ms->ms_mntopts, ms->ms_mntflags) != 0) ret = ms->ms_mntstatus = -1; return (ret); diff --git a/usr/src/lib/libzfs/common/libzfs_pool.c b/usr/src/lib/libzfs/common/libzfs_pool.c index 1f636dd147..f82518d86b 100644 --- a/usr/src/lib/libzfs/common/libzfs_pool.c +++ b/usr/src/lib/libzfs/common/libzfs_pool.c @@ -1162,6 +1162,9 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot, zfs_cmd_t zc = { 0 }; nvlist_t *zc_fsprops = NULL; nvlist_t *zc_props = NULL; + nvlist_t *hidden_args = NULL; + uint8_t *wkeydata = NULL; + uint_t wkeylen = 0; char msg[1024]; int ret = -1; @@ -1192,7 +1195,7 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot, strcmp(zonestr, "on") == 0); if ((zc_fsprops = zfs_valid_proplist(hdl, ZFS_TYPE_FILESYSTEM, - fsprops, zoned, NULL, NULL, msg)) == NULL) { + fsprops, zoned, NULL, NULL, B_TRUE, msg)) == NULL) { goto create_failed; } @@ -1210,10 +1213,27 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot, (nvlist_alloc(&zc_props, NV_UNIQUE_NAME, 0) != 0)) { goto create_failed; } + if (zfs_crypto_create(hdl, NULL, zc_fsprops, props, + &wkeydata, &wkeylen) != 0) { + (void) zfs_error(hdl, EZFS_CRYPTOFAILED, msg); + goto create_failed; + } if (nvlist_add_nvlist(zc_props, ZPOOL_ROOTFS_PROPS, zc_fsprops) != 0) { goto create_failed; } + if (wkeydata != NULL) { + if (nvlist_alloc(&hidden_args, NV_UNIQUE_NAME, 0) != 0) + goto create_failed; + + if (nvlist_add_uint8_array(hidden_args, "wkeydata", + wkeydata, wkeylen) != 0) + goto create_failed; + + if (nvlist_add_nvlist(zc_props, ZPOOL_HIDDEN_ARGS, + hidden_args) != 0) + goto create_failed; + } } if (zc_props && zcmd_write_src_nvlist(hdl, &zc, zc_props) != 0) @@ -1226,6 +1246,9 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot, zcmd_free_nvlists(&zc); nvlist_free(zc_props); nvlist_free(zc_fsprops); + nvlist_free(hidden_args); + if (wkeydata != NULL) + free(wkeydata); switch (errno) { case EBUSY: @@ -1286,6 +1309,9 @@ create_failed: zcmd_free_nvlists(&zc); nvlist_free(zc_props); nvlist_free(zc_fsprops); + nvlist_free(hidden_args); + if (wkeydata != NULL) + free(wkeydata); return (ret); } diff --git a/usr/src/lib/libzfs/common/libzfs_sendrecv.c b/usr/src/lib/libzfs/common/libzfs_sendrecv.c index 7ed81fd0d1..c933a24e89 100644 --- a/usr/src/lib/libzfs/common/libzfs_sendrecv.c +++ b/usr/src/lib/libzfs/common/libzfs_sendrecv.c @@ -55,6 +55,7 @@ #include <zlib.h> #include <sha2.h> #include <sys/zio_checksum.h> +#include <sys/dsl_crypt.h> #include <sys/ddt.h> /* in libzfs_dataset.c */ @@ -324,11 +325,9 @@ cksummer(void *arg) struct drr_object *drro = &drr->drr_u.drr_object; if (drro->drr_bonuslen > 0) { (void) ssread(buf, - P2ROUNDUP((uint64_t)drro->drr_bonuslen, 8), - ofp); + DRR_OBJECT_PAYLOAD_SIZE(drro), ofp); } - if (dump_record(drr, buf, - P2ROUNDUP((uint64_t)drro->drr_bonuslen, 8), + if (dump_record(drr, buf, DRR_OBJECT_PAYLOAD_SIZE(drro), &stream_cksum, outfd) != 0) goto out; break; @@ -337,8 +336,8 @@ cksummer(void *arg) case DRR_SPILL: { struct drr_spill *drrs = &drr->drr_u.drr_spill; - (void) ssread(buf, drrs->drr_length, ofp); - if (dump_record(drr, buf, drrs->drr_length, + (void) ssread(buf, DRR_SPILL_PAYLOAD_SIZE(drrs), ofp); + if (dump_record(drr, buf, DRR_SPILL_PAYLOAD_SIZE(drrs), &stream_cksum, outfd) != 0) goto out; break; @@ -368,7 +367,7 @@ cksummer(void *arg) if (ZIO_CHECKSUM_EQUAL(drrw->drr_key.ddk_cksum, zero_cksum) || - !DRR_IS_DEDUP_CAPABLE(drrw->drr_checksumflags)) { + !DRR_IS_DEDUP_CAPABLE(drrw->drr_flags)) { SHA256_CTX ctx; zio_cksum_t tmpsha256; @@ -384,7 +383,7 @@ cksummer(void *arg) drrw->drr_key.ddk_cksum.zc_word[3] = BE_64(tmpsha256.zc_word[3]); drrw->drr_checksumtype = ZIO_CHECKSUM_SHA256; - drrw->drr_checksumflags = DRR_CHECKSUM_DEDUP; + drrw->drr_flags |= DRR_CHECKSUM_DEDUP; } dataref.ref_guid = drrw->drr_toguid; @@ -413,8 +412,7 @@ cksummer(void *arg) wbr_drrr->drr_checksumtype = drrw->drr_checksumtype; - wbr_drrr->drr_checksumflags = - drrw->drr_checksumtype; + wbr_drrr->drr_flags = drrw->drr_flags; wbr_drrr->drr_key.ddk_cksum = drrw->drr_key.ddk_cksum; wbr_drrr->drr_key.ddk_prop = @@ -453,6 +451,14 @@ cksummer(void *arg) break; } + case DRR_OBJECT_RANGE: + { + if (dump_record(drr, NULL, 0, &stream_cksum, + outfd) != 0) + goto out; + break; + } + default: (void) fprintf(stderr, "INVALID record type 0x%x\n", drr->drr_type); @@ -601,6 +607,7 @@ typedef struct send_data { const char *fsname; const char *fromsnap; const char *tosnap; + boolean_t raw; boolean_t recursive; boolean_t verbose; @@ -620,6 +627,7 @@ typedef struct send_data { * "snapprops" -> { name (lastname) -> { name -> value } } * * "origin" -> number (guid) (if clone) + * "is_encroot" -> boolean * "sent" -> boolean (not on-disk) * } * } @@ -778,7 +786,7 @@ static int send_iterate_fs(zfs_handle_t *zhp, void *arg) { send_data_t *sd = arg; - nvlist_t *nvfs, *nv; + nvlist_t *nvfs = NULL, *nv = NULL; int rv = 0; uint64_t parent_fromsnap_guid_save = sd->parent_fromsnap_guid; uint64_t fromsnap_txg_save = sd->fromsnap_txg; @@ -842,8 +850,37 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg) /* iterate over props */ VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0)); send_iterate_prop(zhp, nv); + + if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) { + boolean_t encroot; + + /* determine if this dataset is an encryption root */ + if (zfs_crypto_get_encryption_root(zhp, &encroot, NULL) != 0) { + rv = -1; + goto out; + } + + if (encroot) + VERIFY(0 == nvlist_add_boolean(nvfs, "is_encroot")); + + /* + * Encrypted datasets can only be sent with properties if + * the raw flag is specified because the receive side doesn't + * currently have a mechanism for recursively asking the user + * for new encryption parameters. + */ + if (!sd->raw) { + (void) fprintf(stderr, dgettext(TEXT_DOMAIN, + "cannot send %s@%s: encrypted dataset %s may not " + "be sent with properties without the raw flag\n"), + sd->fsname, sd->tosnap, zhp->zfs_name); + rv = -1; + goto out; + } + + } + VERIFY(0 == nvlist_add_nvlist(nvfs, "props", nv)); - nvlist_free(nv); /* iterate over snaps, and set sd->parent_fromsnap_guid */ sd->parent_fromsnap_guid = 0; @@ -859,7 +896,6 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg) (void) snprintf(guidstring, sizeof (guidstring), "0x%llx", (longlong_t)guid); VERIFY(0 == nvlist_add_nvlist(sd->fss, guidstring, nvfs)); - nvlist_free(nvfs); /* iterate over children */ if (sd->recursive) @@ -869,6 +905,8 @@ out: sd->parent_fromsnap_guid = parent_fromsnap_guid_save; sd->fromsnap_txg = fromsnap_txg_save; sd->tosnap_txg = tosnap_txg_save; + nvlist_free(nv); + nvlist_free(nvfs); zfs_close(zhp); return (rv); @@ -876,7 +914,7 @@ out: static int gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap, - const char *tosnap, boolean_t recursive, boolean_t verbose, + const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t verbose, nvlist_t **nvlp, avl_tree_t **avlp) { zfs_handle_t *zhp; @@ -892,6 +930,7 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap, sd.fromsnap = fromsnap; sd.tosnap = tosnap; sd.recursive = recursive; + sd.raw = raw; sd.verbose = verbose; if ((error = send_iterate_fs(zhp, &sd)) != 0) { @@ -923,7 +962,7 @@ typedef struct send_dump_data { uint64_t prevsnap_obj; boolean_t seenfrom, seento, replicate, doall, fromorigin; boolean_t verbose, dryrun, parsable, progress, embed_data, std_out; - boolean_t large_block, compress; + boolean_t large_block, compress, raw; int outfd; boolean_t err; nvlist_t *fss; @@ -965,6 +1004,11 @@ estimate_ioctl(zfs_handle_t *zhp, uint64_t fromsnap_obj, "not an earlier snapshot from the same fs")); return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf)); + case EACCES: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "source key must be loaded")); + return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); + case ENOENT: if (zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_SNAPSHOT)) { @@ -1045,6 +1089,11 @@ dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, uint64_t fromsnap_obj, "not an earlier snapshot from the same fs")); return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf)); + case EACCES: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "source key must be loaded")); + return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); + case ENOENT: if (zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_SNAPSHOT)) { @@ -1226,6 +1275,8 @@ dump_snapshot(zfs_handle_t *zhp, void *arg) flags |= LZC_SEND_FLAG_EMBED_DATA; if (sdd->compress) flags |= LZC_SEND_FLAG_COMPRESS; + if (sdd->raw) + flags |= LZC_SEND_FLAG_RAW; if (!sdd->doall && !isfromsnap && !istosnap) { if (sdd->replicate) { @@ -1610,6 +1661,8 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd, lzc_flags |= LZC_SEND_FLAG_EMBED_DATA; if (flags->compress || nvlist_exists(resume_nvl, "compressok")) lzc_flags |= LZC_SEND_FLAG_COMPRESS; + if (flags->raw || nvlist_exists(resume_nvl, "rawok")) + lzc_flags |= LZC_SEND_FLAG_RAW; if (guid_to_name(hdl, toname, toguid, B_FALSE, name) != 0) { if (zfs_dataset_exists(hdl, toname, ZFS_TYPE_DATASET)) { @@ -1687,6 +1740,11 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd, switch (error) { case 0: return (0); + case EACCES: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "source key must be loaded")); + return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); + case EXDEV: case ENOENT: case EDQUOT: @@ -1765,7 +1823,14 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, } } - if (flags->dedup && !flags->dryrun) { + /* + * Start the dedup thread if this is a dedup stream. We do not bother + * doing this if this a raw send of an encrypted dataset with dedup off + * because normal encrypted blocks won't dedup. + */ + if (flags->dedup && !flags->dryrun && !(flags->raw && + zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF && + zfs_prop_get_int(zhp, ZFS_PROP_DEDUP) == ZIO_CHECKSUM_OFF)) { featureflags |= (DMU_BACKUP_FEATURE_DEDUP | DMU_BACKUP_FEATURE_DEDUPPROPS); if ((err = pipe(pipefd)) != 0) { @@ -1804,10 +1869,13 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, VERIFY(0 == nvlist_add_boolean(hdrnv, "not_recursive")); } + if (flags->raw) { + VERIFY(0 == nvlist_add_boolean(hdrnv, "raw")); + } err = gather_nvlist(zhp->zfs_hdl, zhp->zfs_name, - fromsnap, tosnap, flags->replicate, flags->verbose, - &fss, &fsavl); + fromsnap, tosnap, flags->replicate, flags->raw, + flags->verbose, &fss, &fsavl); if (err) goto err_out; VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss)); @@ -1872,6 +1940,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, sdd.large_block = flags->largeblock; sdd.embed_data = flags->embed_data; sdd.compress = flags->compress; + sdd.raw = flags->raw; sdd.filter_cb = filter_func; sdd.filter_cb_arg = cb_arg; if (debugnvp) @@ -2033,6 +2102,11 @@ zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, } return (zfs_error(hdl, EZFS_NOENT, errbuf)); + case EACCES: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "dataset key must be loaded")); + return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); + case EBUSY: zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "target is busy; if a filesystem, " @@ -2123,26 +2197,86 @@ recv_read_nvlist(libzfs_handle_t *hdl, int fd, int len, nvlist_t **nvp, return (0); } +/* + * Returns the grand origin (origin of origin of origin...) of a given handle. + * If this dataset is not a clone, it simply returns a copy of the original + * handle. + */ +static zfs_handle_t * +recv_open_grand_origin(zfs_handle_t *zhp) +{ + char origin[ZFS_MAX_DATASET_NAME_LEN]; + zprop_source_t src; + zfs_handle_t *ozhp = zfs_handle_dup(zhp); + + while (ozhp != NULL) { + if (zfs_prop_get(ozhp, ZFS_PROP_ORIGIN, origin, + sizeof (origin), &src, NULL, 0, B_FALSE) != 0) + break; + + (void) zfs_close(ozhp); + ozhp = zfs_open(zhp->zfs_hdl, origin, ZFS_TYPE_FILESYSTEM); + } + + return (ozhp); +} + +static int +recv_rename_impl(zfs_handle_t *zhp, const char *source, const char *target) +{ + int err; + zfs_handle_t *ozhp = NULL; + + /* + * Attempt to rename the dataset. If it fails with EACCES we have + * attempted to rename the dataset outside of its encryption root. + * Force the dataset to become an encryption root and try again. + */ + err = lzc_rename(source, target); + if (err == EACCES) { + ozhp = recv_open_grand_origin(zhp); + if (ozhp == NULL) { + err = ENOENT; + goto out; + } + + err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY, + NULL, NULL, 0); + if (err != 0) + goto out; + + err = lzc_rename(source, target); + } + +out: + if (ozhp != NULL) + zfs_close(ozhp); + return (err); +} + static int recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname, int baselen, char *newname, recvflags_t *flags) { static int seq; int err; - prop_changelist_t *clp; - zfs_handle_t *zhp; + prop_changelist_t *clp = NULL; + zfs_handle_t *zhp = NULL; zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET); - if (zhp == NULL) - return (-1); + if (zhp == NULL) { + err = -1; + goto out; + } clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, flags->force ? MS_FORCE : 0); - zfs_close(zhp); - if (clp == NULL) - return (-1); + if (clp == NULL) { + err = -1; + goto out; + } err = changelist_prefix(clp); if (err) - return (err); + goto out; if (tryname) { (void) strcpy(newname, tryname); @@ -2150,7 +2284,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname, (void) printf("attempting rename %s to %s\n", name, newname); } - err = lzc_rename(name, newname); + err = recv_rename_impl(zhp, name, newname); if (err == 0) changelist_rename(clp, name, tryname); } else { @@ -2166,7 +2300,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname, (void) printf("failed - trying rename %s to %s\n", name, newname); } - err = lzc_rename(name, newname); + err = recv_rename_impl(zhp, name, newname); if (err == 0) changelist_rename(clp, name, newname); if (err && flags->verbose) { @@ -2182,7 +2316,62 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname, } (void) changelist_postfix(clp); - changelist_free(clp); + +out: + if (clp != NULL) + changelist_free(clp); + if (zhp != NULL) + zfs_close(zhp); + + return (err); +} + +static int +recv_promote(libzfs_handle_t *hdl, const char *fsname, + const char *origin_fsname, recvflags_t *flags) +{ + int err; + zfs_cmd_t zc = {"\0"}; + zfs_handle_t *zhp = NULL, *ozhp = NULL; + + if (flags->verbose) + (void) printf("promoting %s\n", fsname); + + (void) strlcpy(zc.zc_value, origin_fsname, sizeof (zc.zc_value)); + (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_name)); + + /* + * Attempt to promote the dataset. If it fails with EACCES the + * promotion would cause this dataset to leave its encryption root. + * Force the origin to become an encryption root and try again. + */ + err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc); + if (err == EACCES) { + zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET); + if (zhp == NULL) { + err = -1; + goto out; + } + + ozhp = recv_open_grand_origin(zhp); + if (ozhp == NULL) { + err = -1; + goto out; + } + + err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY, + NULL, NULL, 0); + if (err != 0) + goto out; + + err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc); + } + +out: + if (zhp != NULL) + zfs_close(zhp); + if (ozhp != NULL) + zfs_close(ozhp); return (err); } @@ -2386,6 +2575,150 @@ created_before(libzfs_handle_t *hdl, avl_tree_t *avl, return (rv); } +/* + * This function reestablishes the heirarchy of encryption roots after a + * recursive incremental receive has completed. This must be done after the + * second call to recv_incremental_replication() has renamed and promoted all + * sent datasets to their final locations in the dataset heriarchy. + */ +/* ARGSUSED */ +static int +recv_fix_encryption_hierarchy(libzfs_handle_t *hdl, const char *destname, + nvlist_t *stream_nv, avl_tree_t *stream_avl) +{ + int err; + nvpair_t *fselem = NULL; + nvlist_t *stream_fss; + char *cp; + char top_zfs[ZFS_MAX_DATASET_NAME_LEN]; + + (void) strcpy(top_zfs, destname); + cp = strrchr(top_zfs, '@'); + if (cp != NULL) + *cp = '\0'; + + VERIFY(0 == nvlist_lookup_nvlist(stream_nv, "fss", &stream_fss)); + + while ((fselem = nvlist_next_nvpair(stream_fss, fselem)) != NULL) { + zfs_handle_t *zhp = NULL; + uint64_t crypt; + nvlist_t *snaps, *props, *stream_nvfs = NULL; + nvpair_t *snapel = NULL; + boolean_t is_encroot, is_clone, stream_encroot; + char *cp; + char *stream_keylocation = NULL; + char keylocation[MAXNAMELEN]; + char fsname[ZFS_MAX_DATASET_NAME_LEN]; + + keylocation[0] = '\0'; + VERIFY(0 == nvpair_value_nvlist(fselem, &stream_nvfs)); + VERIFY(0 == nvlist_lookup_nvlist(stream_nvfs, "snaps", &snaps)); + VERIFY(0 == nvlist_lookup_nvlist(stream_nvfs, "props", &props)); + stream_encroot = nvlist_exists(stream_nvfs, "is_encroot"); + + /* find a snapshot from the stream that exists locally */ + err = ENOENT; + while ((snapel = nvlist_next_nvpair(snaps, snapel)) != NULL) { + uint64_t guid; + + VERIFY(0 == nvpair_value_uint64(snapel, &guid)); + err = guid_to_name(hdl, destname, guid, B_FALSE, + fsname); + if (err == 0) + break; + } + + if (err != 0) + continue; + + cp = strchr(fsname, '@'); + if (cp != NULL) + *cp = '\0'; + + zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET); + if (zhp == NULL) { + err = ENOENT; + goto error; + } + + crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION); + is_clone = zhp->zfs_dmustats.dds_origin[0] != '\0'; + (void) zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL); + + /* we don't need to do anything for unencrypted filesystems */ + if (crypt == ZIO_CRYPT_OFF) { + zfs_close(zhp); + continue; + } + + /* + * If the dataset is flagged as an encryption root, was not + * received as a clone and is not currently an encryption root, + * force it to become one. Fixup the keylocation if necessary. + */ + if (stream_encroot) { + if (!is_clone && !is_encroot) { + err = lzc_change_key(fsname, + DCP_CMD_FORCE_NEW_KEY, NULL, NULL, 0); + if (err != 0) { + zfs_close(zhp); + goto error; + } + } + + VERIFY(0 == nvlist_lookup_string(props, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION), + &stream_keylocation)); + + /* + * Refresh the properties in case the call to + * lzc_change_key() changed the value. + */ + zfs_refresh_properties(zhp); + err = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION, + keylocation, sizeof (keylocation), NULL, NULL, + 0, B_TRUE); + if (err != 0) { + zfs_close(zhp); + goto error; + } + + if (strcmp(keylocation, stream_keylocation) != 0) { + err = zfs_prop_set(zhp, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION), + stream_keylocation); + if (err != 0) { + zfs_close(zhp); + goto error; + } + } + } + + /* + * If the dataset is not flagged as an encryption root and is + * currently an encryption root, force it to inherit from its + * parent. The root of a raw send should never be + * force-inherited. + */ + if (!stream_encroot && is_encroot && + strcmp(top_zfs, fsname) != 0) { + err = lzc_change_key(fsname, DCP_CMD_FORCE_INHERIT, + NULL, NULL, 0); + if (err != 0) { + zfs_close(zhp); + goto error; + } + } + + zfs_close(zhp); + } + + return (0); + +error: + return (err); +} + static int recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, recvflags_t *flags, nvlist_t *stream_nv, avl_tree_t *stream_avl, @@ -2412,7 +2745,7 @@ again: needagain = progress = B_FALSE; if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL, - recursive, B_FALSE, &local_nv, &local_avl)) != 0) + recursive, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0) return (error); /* @@ -2461,22 +2794,15 @@ again: stream_originguid, originguid)) { case 1: { /* promote it! */ - zfs_cmd_t zc = { 0 }; nvlist_t *origin_nvfs; char *origin_fsname; - if (flags->verbose) - (void) printf("promoting %s\n", fsname); - origin_nvfs = fsavl_find(local_avl, originguid, NULL); VERIFY(0 == nvlist_lookup_string(origin_nvfs, "name", &origin_fsname)); - (void) strlcpy(zc.zc_value, origin_fsname, - sizeof (zc.zc_value)); - (void) strlcpy(zc.zc_name, fsname, - sizeof (zc.zc_name)); - error = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc); + error = recv_promote(hdl, fsname, origin_fsname, + flags); if (error == 0) progress = B_TRUE; break; @@ -2665,7 +2991,7 @@ again: goto again; } - return (needagain); + return (needagain || error != 0); } static int @@ -2685,7 +3011,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, int error; boolean_t anyerr = B_FALSE; boolean_t softerr = B_FALSE; - boolean_t recursive; + boolean_t recursive, raw; (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot receive")); @@ -2709,6 +3035,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") == ENOENT); + raw = (nvlist_lookup_boolean(stream_nv, "raw") == 0); if (recursive && strchr(destname, '@')) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, @@ -2864,6 +3191,11 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, stream_nv, stream_avl, NULL); } + if (raw && softerr == 0) { + softerr = recv_fix_encryption_hierarchy(hdl, destname, + stream_nv, stream_avl); + } + out: fsavl_destroy(stream_avl); nvlist_free(stream_nv); @@ -3030,14 +3362,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, const char *chopprefix; boolean_t newfs = B_FALSE; boolean_t stream_wantsnewfs; + boolean_t newprops = B_FALSE; uint64_t parent_snapguid = 0; prop_changelist_t *clp = NULL; nvlist_t *snapprops_nvlist = NULL; zprop_errflags_t prop_errflags; boolean_t recursive; char *snapname = NULL; + nvlist_t *props = NULL; + char tmp_keylocation[MAXNAMELEN]; begin_time = time(NULL); + bzero(tmp_keylocation, MAXNAMELEN); (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot receive")); @@ -3046,32 +3382,50 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, ENOENT); if (stream_avl != NULL) { + char *keylocation = NULL; + nvlist_t *lookup = NULL; nvlist_t *fs = fsavl_find(stream_avl, drrb->drr_toguid, &snapname); - nvlist_t *props; int ret; (void) nvlist_lookup_uint64(fs, "parentfromsnap", &parent_snapguid); err = nvlist_lookup_nvlist(fs, "props", &props); - if (err) + if (err) { VERIFY(0 == nvlist_alloc(&props, NV_UNIQUE_NAME, 0)); + newprops = B_TRUE; + } + /* + * The keylocation property may only be set on encryption roots, + * but this dataset might not become an encryption root until + * recv_fix_encryption_hierarchy() is called. That function + * will fixup the keylocation anyway, so we temporarily unset + * the keylocation for now to avoid any errors from the receive + * ioctl. + */ + err = nvlist_lookup_string(props, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation); + if (err == 0) { + (void) strcpy(tmp_keylocation, keylocation); + (void) nvlist_remove_all(props, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION)); + } if (flags->canmountoff) { VERIFY(0 == nvlist_add_uint64(props, zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0)); } ret = zcmd_write_src_nvlist(hdl, &zc, props); - if (err) - nvlist_free(props); - if (0 == nvlist_lookup_nvlist(fs, "snapprops", &props)) { - VERIFY(0 == nvlist_lookup_nvlist(props, + if (0 == nvlist_lookup_nvlist(fs, "snapprops", &lookup)) { + VERIFY(0 == nvlist_lookup_nvlist(lookup, snapname, &snapprops_nvlist)); } - if (ret != 0) - return (-1); + if (ret != 0) { + err = -1; + goto out; + } } cp = NULL; @@ -3092,7 +3446,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (strchr(tosnap, '@')) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " "argument - snapshot not allowed with -e")); - return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); + err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf); + goto out; } chopprefix = strrchr(sendfs, '/'); @@ -3119,7 +3474,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (strchr(tosnap, '@')) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " "argument - snapshot not allowed with -d")); - return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); + err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf); + goto out; } chopprefix = strchr(drrb->drr_toname, '/'); @@ -3137,7 +3493,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "cannot specify snapshot name for multi-snapshot " "stream")); - return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); + err = zfs_error(hdl, EZFS_BADSTREAM, errbuf); + goto out; } chopprefix = drrb->drr_toname + strlen(drrb->drr_toname); } @@ -3156,7 +3513,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, free(cp); if (!zfs_name_valid(zc.zc_value, ZFS_TYPE_SNAPSHOT)) { zcmd_free_nvlists(&zc); - return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); + err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf); + goto out; } /* @@ -3174,7 +3532,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "local origin for clone %s does not exist"), zc.zc_value); - return (zfs_error(hdl, EZFS_NOENT, errbuf)); + err = zfs_error(hdl, EZFS_NOENT, errbuf); + goto out; } if (flags->verbose) (void) printf("found clone origin %s\n", zc.zc_string); @@ -3182,6 +3541,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, boolean_t resuming = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & DMU_BACKUP_FEATURE_RESUMING; + boolean_t raw = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & + DMU_BACKUP_FEATURE_RAW; + boolean_t embedded = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & + DMU_BACKUP_FEATURE_EMBED_DATA; stream_wantsnewfs = (drrb->drr_fromguid == NULL || (drrb->drr_flags & DRR_FLAG_CLONE) || originsnap) && !resuming; @@ -3241,6 +3604,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) { zfs_handle_t *zhp; + boolean_t encrypted; /* * Destination fs exists. It must be one of these cases: @@ -3257,7 +3621,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, "destination '%s' exists\n" "must specify -F to overwrite it"), zc.zc_name); - return (zfs_error(hdl, EZFS_EXISTS, errbuf)); + err = zfs_error(hdl, EZFS_EXISTS, errbuf); + goto out; } if (ioctl(hdl->libzfs_fd, ZFS_IOC_SNAPSHOT_LIST_NEXT, &zc) == 0) { @@ -3266,14 +3631,16 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, "destination has snapshots (eg. %s)\n" "must destroy them to overwrite it"), zc.zc_name); - return (zfs_error(hdl, EZFS_EXISTS, errbuf)); + err = zfs_error(hdl, EZFS_EXISTS, errbuf); + goto out; } } if ((zhp = zfs_open(hdl, zc.zc_name, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) { zcmd_free_nvlists(&zc); - return (-1); + err = -1; + goto out; } if (stream_wantsnewfs && @@ -3284,7 +3651,42 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, "destination '%s' is a clone\n" "must destroy it to overwrite it"), zc.zc_name); - return (zfs_error(hdl, EZFS_EXISTS, errbuf)); + err = zfs_error(hdl, EZFS_EXISTS, errbuf); + goto out; + } + + /* + * Raw sends can not be performed as an incremental on top + * of existing unencrypted datasets. zfs recv -F cant be + * used to blow away an existing encrypted filesystem. This + * is because it would require the dsl dir to point to the + * new key (or lack of a key) and the old key at the same + * time. The -F flag may still be used for deleting + * intermediate snapshots that would otherwise prevent the + * receive from working. + */ + encrypted = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != + ZIO_CRYPT_OFF; + if (!stream_wantsnewfs && !encrypted && raw) { + zfs_close(zhp); + zcmd_free_nvlists(&zc); + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "cannot perform raw receive on top of " + "existing unencrypted dataset")); + err = zfs_error(hdl, EZFS_BADRESTORE, errbuf); + goto out; + } + + if (stream_wantsnewfs && flags->force && + ((raw && !encrypted) || encrypted)) { + zfs_close(zhp); + zcmd_free_nvlists(&zc); + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "zfs receive -F cannot be used to destroy an " + "encrypted filesystem or overwrite an " + "unencrypted one with an encrypted one")); + err = zfs_error(hdl, EZFS_BADRESTORE, errbuf); + goto out; } if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM && @@ -3294,13 +3696,15 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (clp == NULL) { zfs_close(zhp); zcmd_free_nvlists(&zc); - return (-1); + err = -1; + goto out; } if (changelist_prefix(clp) != 0) { changelist_free(clp); zfs_close(zhp); zcmd_free_nvlists(&zc); - return (-1); + err = -1; + goto out; } } @@ -3317,6 +3721,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, zfs_close(zhp); } else { + zfs_handle_t *zhp; + /* * Destination filesystem does not exist. Therefore we better * be creating a new filesystem (either from a full backup, or @@ -3329,7 +3735,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, zcmd_free_nvlists(&zc); zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "destination '%s' does not exist"), zc.zc_name); - return (zfs_error(hdl, EZFS_NOENT, errbuf)); + err = zfs_error(hdl, EZFS_NOENT, errbuf); + goto out; } /* @@ -3341,10 +3748,45 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (flags->isprefix && !flags->istail && !flags->dryrun && create_parents(hdl, zc.zc_value, strlen(tosnap)) != 0) { zcmd_free_nvlists(&zc); - return (zfs_error(hdl, EZFS_BADRESTORE, errbuf)); + err = zfs_error(hdl, EZFS_BADRESTORE, errbuf); + goto out; + } + + /* + * It is invalid to receive a properties stream that was + * unencrypted on the send side as a child of an encrypted + * parent. Technically there is nothing preventing this, but + * it would mean that the encryption=off property which is + * locally set on the send side would not be received correctly. + * We can infer encryption=off if the stream is not raw and + * properties were included since the send side will only ever + * send the encryption property in a raw nvlist header. + */ + if (!raw && props != NULL) { + uint64_t crypt; + + zhp = zfs_open(hdl, zc.zc_name, ZFS_TYPE_DATASET); + if (zhp == NULL) { + err = zfs_error(hdl, EZFS_BADRESTORE, + errbuf); + goto out; + } + + crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION); + zfs_close(zhp); + + if (crypt != ZIO_CRYPT_OFF) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "parent '%s' must not be encrypted to " + "receive unencrypted property"), + zc.zc_name); + err = zfs_error(hdl, EZFS_BADPROP, errbuf); + goto out; + } } newfs = B_TRUE; + *cp = '/'; } zc.zc_begin_record = *drr_noswap; @@ -3361,7 +3803,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (flags->dryrun) { zcmd_free_nvlists(&zc); - return (recv_skip(hdl, infd, flags->byteswap)); + err = recv_skip(hdl, infd, flags->byteswap); + goto out; } zc.zc_nvlist_dst = (uint64_t)(uintptr_t)prop_errbuf; @@ -3448,7 +3891,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, * get a strange "does not exist" error message. */ *cp = '\0'; - if (gather_nvlist(hdl, zc.zc_value, NULL, NULL, B_FALSE, + if (gather_nvlist(hdl, zc.zc_value, NULL, NULL, B_FALSE, B_TRUE, B_FALSE, &local_nv, &local_avl) == 0) { *cp = '@'; fs = fsavl_find(local_avl, drrb->drr_toguid, NULL); @@ -3484,6 +3927,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, "since most recent snapshot"), zc.zc_name); (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf); break; + case EACCES: + if (raw && stream_wantsnewfs) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "failed to create encryption key")); + } else if (raw && !stream_wantsnewfs) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "encryption key does not match " + "existing key")); + } else { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "inherited key must be loaded")); + } + (void) zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf); + break; case EEXIST: cp = strchr(zc.zc_value, '@'); if (newfs) { @@ -3498,6 +3955,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, *cp = '@'; break; case EINVAL: + if (embedded && !raw) + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "incompatible embedded data stream " + "feature with encrypted receive.")); (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); break; case ECKSUM: @@ -3514,6 +3975,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, "destination %s space quota exceeded"), zc.zc_name); (void) zfs_error(hdl, EZFS_NOSPC, errbuf); break; + case ZFS_ERR_FROM_IVSET_GUID_MISSING: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "IV set guid missing. See errata %u at" + "http://zfsonlinux.org/msg/ZFS-8000-ER"), + ZPOOL_ERRATA_ZOL_8308_ENCRYPTION); + (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); + break; + case ZFS_ERR_FROM_IVSET_GUID_MISMATCH: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "IV set guid mismatch. See the 'zfs receive' " + "man page section\n discussing the limitations " + "of raw encrypted send streams.")); + (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); + break; default: (void) zfs_standard_error(hdl, ioctl_errno, errbuf); } @@ -3566,8 +4041,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, (void) fprintf(stderr, "\n"); } - if (err || ioctl_err) - return (-1); + if (err || ioctl_err) { + err = -1; + goto out; + } *action_handlep = zc.zc_action_handle; @@ -3585,7 +4062,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, buf1, delta, buf2); } - return (0); + err = 0; +out: + + if (tmp_keylocation[0] != '\0') { + VERIFY(0 == nvlist_add_string(props, + zfs_prop_to_name(ZFS_PROP_KEYLOCATION), tmp_keylocation)); + } + + if (newprops) + nvlist_free(props); + + return (err); } static int diff --git a/usr/src/lib/libzfs/common/libzfs_status.c b/usr/src/lib/libzfs/common/libzfs_status.c index 975309c423..46ea7f944f 100644 --- a/usr/src/lib/libzfs/common/libzfs_status.c +++ b/usr/src/lib/libzfs/common/libzfs_status.c @@ -198,7 +198,7 @@ find_vdev_problem(nvlist_t *vdev, int (*func)(uint64_t, uint64_t, uint64_t)) * only picks the most damaging of all the current errors to report. */ static zpool_status_t -check_status(nvlist_t *config, boolean_t isimport) +check_status(nvlist_t *config, boolean_t isimport, zpool_errata_t *erratap) { nvlist_t *nvroot; vdev_stat_t *vs; @@ -209,6 +209,7 @@ check_status(nvlist_t *config, boolean_t isimport) uint64_t stateval; uint64_t suspended; uint64_t hostid = 0; + uint64_t errata = 0; unsigned long system_hostid = get_system_hostid(); verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_VERSION, @@ -369,6 +370,15 @@ check_status(nvlist_t *config, boolean_t isimport) return (ZPOOL_STATUS_REMOVED_DEV); /* + * Informational errata available. + */ + (void) nvlist_lookup_uint64(config, ZPOOL_CONFIG_ERRATA, &errata); + if (errata) { + *erratap = errata; + return (ZPOOL_STATUS_ERRATA); + } + + /* * Outdated, but usable, version */ if (SPA_VERSION_IS_SUPPORTED(version) && version != SPA_VERSION) @@ -403,9 +413,9 @@ check_status(nvlist_t *config, boolean_t isimport) } zpool_status_t -zpool_get_status(zpool_handle_t *zhp, char **msgid) +zpool_get_status(zpool_handle_t *zhp, char **msgid, zpool_errata_t *errata) { - zpool_status_t ret = check_status(zhp->zpool_config, B_FALSE); + zpool_status_t ret = check_status(zhp->zpool_config, B_FALSE, errata); if (ret >= NMSGID) *msgid = NULL; @@ -416,9 +426,9 @@ zpool_get_status(zpool_handle_t *zhp, char **msgid) } zpool_status_t -zpool_import_status(nvlist_t *config, char **msgid) +zpool_import_status(nvlist_t *config, char **msgid, zpool_errata_t *errata) { - zpool_status_t ret = check_status(config, B_TRUE); + zpool_status_t ret = check_status(config, B_TRUE, errata); if (ret >= NMSGID) *msgid = NULL; diff --git a/usr/src/lib/libzfs/common/libzfs_util.c b/usr/src/lib/libzfs/common/libzfs_util.c index 246874b9f9..b56c394b59 100644 --- a/usr/src/lib/libzfs/common/libzfs_util.c +++ b/usr/src/lib/libzfs/common/libzfs_util.c @@ -263,6 +263,8 @@ libzfs_error_description(libzfs_handle_t *hdl) case EZFS_NO_RESILVER_DEFER: return (dgettext(TEXT_DOMAIN, "this action requires the " "resilver_defer feature")); + case EZFS_CRYPTOFAILED: + return (dgettext(TEXT_DOMAIN, "encryption failure")); case EZFS_UNKNOWN: return (dgettext(TEXT_DOMAIN, "unknown error")); default: diff --git a/usr/src/lib/libzfs/common/mapfile-vers b/usr/src/lib/libzfs/common/mapfile-vers index 26a68259af..f4412bbd9a 100644 --- a/usr/src/lib/libzfs/common/mapfile-vers +++ b/usr/src/lib/libzfs/common/mapfile-vers @@ -71,6 +71,11 @@ SYMBOL_VERSION SUNWprivate_1.1 { zfs_close; zfs_create; zfs_create_ancestors; + zfs_crypto_attempt_load_keys; + zfs_crypto_get_encryption_root; + zfs_crypto_load_key; + zfs_crypto_rewrap; + zfs_crypto_unload_key; zfs_dataset_exists; zfs_deleg_share_nfs; zfs_destroy; |
