summaryrefslogtreecommitdiff
path: root/usr/src/lib/libzfs
diff options
context:
space:
mode:
authorTom Caputi <tcaputi@datto.com>2019-06-25 19:39:35 +0000
committerJerry Jelinek <jerry.jelinek@joyent.com>2019-06-25 19:40:06 +0000
commiteb633035c80613ec93d62f90482837adaaf21a0a (patch)
tree67f2e3e15231d06a3525ce3958bbce24aa3de7e8 /usr/src/lib/libzfs
parent07eb1aef88b873c5c1036d9cf69820c1ef6a32fb (diff)
downloadillumos-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.com10
-rw-r--r--usr/src/lib/libzfs/common/libzfs.h29
-rw-r--r--usr/src/lib/libzfs/common/libzfs_changelist.c6
-rw-r--r--usr/src/lib/libzfs/common/libzfs_crypto.c1529
-rw-r--r--usr/src/lib/libzfs/common/libzfs_dataset.c133
-rw-r--r--usr/src/lib/libzfs/common/libzfs_diff.c5
-rw-r--r--usr/src/lib/libzfs/common/libzfs_iter.c1
-rw-r--r--usr/src/lib/libzfs/common/libzfs_mount.c46
-rw-r--r--usr/src/lib/libzfs/common/libzfs_pool.c28
-rw-r--r--usr/src/lib/libzfs/common/libzfs_sendrecv.c622
-rw-r--r--usr/src/lib/libzfs/common/libzfs_status.c20
-rw-r--r--usr/src/lib/libzfs/common/libzfs_util.c2
-rw-r--r--usr/src/lib/libzfs/common/mapfile-vers5
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;