aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--contrib/pyzfs/libzfs_core/_libzfs_core.py16
-rw-r--r--contrib/pyzfs/libzfs_core/bindings/libzfs_core.py6
-rw-r--r--include/libzfs.h2
-rw-r--r--include/libzfs_core.h2
-rw-r--r--include/sys/dmu_send.h6
-rw-r--r--lib/libzfs/libzfs_crypto.c14
-rw-r--r--lib/libzfs/libzfs_dataset.c4
-rw-r--r--lib/libzfs/libzfs_pool.c2
-rw-r--r--lib/libzfs/libzfs_sendrecv.c95
-rw-r--r--lib/libzfs_core/libzfs_core.c53
-rw-r--r--man/man8/zfs.828
-rw-r--r--module/zfs/dmu_send.c93
-rw-r--r--module/zfs/dsl_crypt.c4
-rw-r--r--module/zfs/zfs_ioctl.c70
-rw-r--r--tests/runfiles/linux.run5
-rw-r--r--tests/zfs-tests/tests/functional/rsend/Makefile.am1
-rwxr-xr-xtests/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh199
17 files changed, 507 insertions, 93 deletions
diff --git a/contrib/pyzfs/libzfs_core/_libzfs_core.py b/contrib/pyzfs/libzfs_core/_libzfs_core.py
index 1e38a3f32..ffc930812 100644
--- a/contrib/pyzfs/libzfs_core/_libzfs_core.py
+++ b/contrib/pyzfs/libzfs_core/_libzfs_core.py
@@ -1303,7 +1303,8 @@ def lzc_receive_one(
@_uncommitted()
def lzc_receive_with_cmdprops(
snapname, fd, begin_record, force=False, resumable=False, raw=False,
- origin=None, props=None, cmdprops=None, cleanup_fd=-1, action_handle=0
+ origin=None, props=None, cmdprops=None, key=None, cleanup_fd=-1,
+ action_handle=0
):
'''
Like :func:`lzc_receive_one`, but allows the caller to pass an additional
@@ -1333,6 +1334,8 @@ def lzc_receive_with_cmdprops(
every other value is set locally as if the command "zfs set" was
invoked immediately before the receive.
:type cmdprops: dict of bytes : Any
+ :param key: raw bytes representing user's wrapping key
+ :type key: bytes
:param int cleanup_fd: file descriptor used to set a cleanup-on-exit file
descriptor.
:param int action_handle: variable used to pass the handle for guid/ds
@@ -1400,14 +1403,19 @@ def lzc_receive_with_cmdprops(
props = {}
if cmdprops is None:
cmdprops = {}
+ if key is None:
+ key = bytes("")
+ else:
+ key = bytes(key)
+
nvlist = nvlist_in(props)
cmdnvlist = nvlist_in(cmdprops)
properrs = {}
with nvlist_out(properrs) as c_errors:
ret = _lib.lzc_receive_with_cmdprops(
- snapname, nvlist, cmdnvlist, c_origin, force, resumable, raw, fd,
- begin_record, cleanup_fd, c_read_bytes, c_errflags,
- c_action_handle, c_errors)
+ snapname, nvlist, cmdnvlist, key, len(key), c_origin,
+ force, resumable, raw, fd, begin_record, cleanup_fd, c_read_bytes,
+ c_errflags, c_action_handle, c_errors)
errors.lzc_receive_translate_errors(
ret, snapname, fd, force, raw, False, False, origin, properrs)
return (int(c_read_bytes[0]), action_handle)
diff --git a/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py b/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py
index a67a01ee7..55899b556 100644
--- a/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py
+++ b/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py
@@ -108,9 +108,9 @@ CDEF = """
int lzc_receive_resumable(const char *, nvlist_t *, const char *,
boolean_t, boolean_t, int);
int lzc_receive_with_cmdprops(const char *, nvlist_t *, nvlist_t *,
- const char *, boolean_t, boolean_t, boolean_t, int,
- const dmu_replay_record_t *, int, uint64_t *, uint64_t *, uint64_t *,
- nvlist_t **);
+ uint8_t *, uint_t, const char *, boolean_t, boolean_t,
+ boolean_t, int, const dmu_replay_record_t *, int, uint64_t *,
+ uint64_t *, uint64_t *, nvlist_t **);
int lzc_receive_with_header(const char *, nvlist_t *, const char *,
boolean_t, boolean_t, boolean_t, int, const dmu_replay_record_t *);
int lzc_release(nvlist_t *, nvlist_t **);
diff --git a/include/libzfs.h b/include/libzfs.h
index c0c0f3c3c..5c157bbf3 100644
--- a/include/libzfs.h
+++ b/include/libzfs.h
@@ -527,7 +527,7 @@ extern nvlist_t *zfs_get_clones_nvl(zfs_handle_t *);
*/
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 *);
+ boolean_t stdin_available, 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 *);
diff --git a/include/libzfs_core.h b/include/libzfs_core.h
index 4ca9b254c..c22cbf18e 100644
--- a/include/libzfs_core.h
+++ b/include/libzfs_core.h
@@ -93,7 +93,7 @@ int lzc_receive_one(const char *, nvlist_t *, const char *, boolean_t,
boolean_t, boolean_t, int, const struct dmu_replay_record *, int,
uint64_t *, uint64_t *, uint64_t *, nvlist_t **);
int lzc_receive_with_cmdprops(const char *, nvlist_t *, nvlist_t *,
- const char *, boolean_t, boolean_t, boolean_t, int,
+ uint8_t *, uint_t, const char *, boolean_t, boolean_t, boolean_t, int,
const struct dmu_replay_record *, int, uint64_t *, uint64_t *,
uint64_t *, nvlist_t **);
diff --git a/include/sys/dmu_send.h b/include/sys/dmu_send.h
index c0b2aafdb..396710470 100644
--- a/include/sys/dmu_send.h
+++ b/include/sys/dmu_send.h
@@ -30,6 +30,7 @@
#define _DMU_SEND_H
#include <sys/inttypes.h>
+#include <sys/dsl_crypt.h>
#include <sys/spa.h>
struct vnode;
@@ -72,8 +73,9 @@ typedef struct dmu_recv_cookie {
} dmu_recv_cookie_t;
int dmu_recv_begin(char *tofs, char *tosnap,
- struct dmu_replay_record *drr_begin,
- boolean_t force, boolean_t resumable, char *origin, dmu_recv_cookie_t *drc);
+ struct dmu_replay_record *drr_begin, boolean_t force, boolean_t resumable,
+ nvlist_t *localprops, nvlist_t *hidden_args, char *origin,
+ dmu_recv_cookie_t *drc);
int dmu_recv_stream(dmu_recv_cookie_t *drc, struct vnode *vp, offset_t *voffp,
int cleanup_fd, uint64_t *action_handlep);
int dmu_recv_end(dmu_recv_cookie_t *drc, void *owner);
diff --git a/lib/libzfs/libzfs_crypto.c b/lib/libzfs/libzfs_crypto.c
index 0956466e2..3318a6bd2 100644
--- a/lib/libzfs/libzfs_crypto.c
+++ b/lib/libzfs/libzfs_crypto.c
@@ -667,7 +667,8 @@ zfs_crypto_get_encryption_root(zfs_handle_t *zhp, boolean_t *is_encroot,
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)
+ nvlist_t *pool_props, boolean_t stdin_available, uint8_t **wkeydata_out,
+ uint_t *wkeylen_out)
{
int ret;
char errbuf[1024];
@@ -808,6 +809,17 @@ zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props,
* encryption root. Populate the encryption params.
*/
if (keylocation != NULL) {
+ /*
+ * 'zfs recv -o keylocation=prompt' won't work because stdin
+ * is being used by the send stream, so we disallow it.
+ */
+ if (!stdin_available && strcmp(keylocation, "prompt") == 0) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Cannot use "
+ "'prompt' keylocation because stdin is in use."));
+ goto out;
+ }
+
ret = populate_create_encryption_params_nvlists(hdl, NULL,
B_FALSE, keyformat, keylocation, props, &wkeydata,
&wkeylen);
diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c
index 7f7dd1594..85b4c5531 100644
--- a/lib/libzfs/libzfs_dataset.c
+++ b/lib/libzfs/libzfs_dataset.c
@@ -3731,8 +3731,8 @@ 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) {
+ if (zfs_crypto_create(hdl, parent, props, NULL, B_TRUE,
+ &wkeydata, &wkeylen) != 0) {
nvlist_free(props);
return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
}
diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c
index d19ca7714..0785c3170 100644
--- a/lib/libzfs/libzfs_pool.c
+++ b/lib/libzfs/libzfs_pool.c
@@ -1224,7 +1224,7 @@ 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,
+ if (zfs_crypto_create(hdl, NULL, zc_fsprops, props, B_TRUE,
&wkeydata, &wkeylen) != 0) {
zfs_error(hdl, EZFS_CRYPTOFAILED, msg);
goto create_failed;
diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c
index 9a4f373a1..b5c91ec20 100644
--- a/lib/libzfs/libzfs_sendrecv.c
+++ b/lib/libzfs/libzfs_sendrecv.c
@@ -3461,16 +3461,19 @@ recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap,
* oxprops: valid output override (-o) and excluded (-x) properties
*/
static int
-zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned,
- boolean_t recursive, boolean_t toplevel, nvlist_t *recvprops,
- nvlist_t *cmdprops, nvlist_t *origprops, nvlist_t **oxprops,
- const char *errbuf)
+zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type,
+ char *fsname, boolean_t zoned, boolean_t recursive, boolean_t newfs,
+ boolean_t raw, boolean_t toplevel, nvlist_t *recvprops, nvlist_t *cmdprops,
+ nvlist_t *origprops, nvlist_t **oxprops, uint8_t **wkeydata_out,
+ uint_t *wkeylen_out, const char *errbuf)
{
nvpair_t *nvp;
nvlist_t *oprops, *voprops;
zfs_handle_t *zhp = NULL;
zpool_handle_t *zpool_hdl = NULL;
+ char *cp;
int ret = 0;
+ char namebuf[ZFS_MAX_DATASET_NAME_LEN];
if (nvlist_empty(cmdprops))
return (0); /* No properties to override or exclude */
@@ -3478,6 +3481,33 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned,
*oxprops = fnvlist_alloc();
oprops = fnvlist_alloc();
+ strlcpy(namebuf, fsname, ZFS_MAX_DATASET_NAME_LEN);
+
+ /*
+ * Get our dataset handle. The target dataset may not exist yet.
+ */
+ if (zfs_dataset_exists(hdl, namebuf, ZFS_TYPE_DATASET)) {
+ zhp = zfs_open(hdl, namebuf, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ ret = -1;
+ goto error;
+ }
+ }
+
+ /* open the zpool handle */
+ cp = strchr(namebuf, '/');
+ if (cp != NULL)
+ *cp = '\0';
+ zpool_hdl = zpool_open(hdl, namebuf);
+ if (zpool_hdl == NULL) {
+ ret = -1;
+ goto error;
+ }
+
+ /* restore namebuf to match fsname for later use */
+ if (cp != NULL)
+ *cp = '/';
+
/*
* first iteration: process excluded (-x) properties now and gather
* added (-o) properties to be later processed by zfs_valid_proplist()
@@ -3509,6 +3539,17 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned,
goto error;
}
+ /* raw streams can't override encryption properties */
+ if ((zfs_prop_encryption_key_param(prop) ||
+ prop == ZFS_PROP_ENCRYPTION) && (raw || !newfs)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption property '%s' cannot "
+ "be set or excluded for raw or incremental "
+ "streams."), name);
+ ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
switch (nvpair_type(nvp)) {
case DATA_TYPE_BOOLEAN: /* -x property */
/*
@@ -3559,6 +3600,21 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned,
goto error;
}
+ /*
+ * zfs_crypto_create() requires the parent name. Get it
+ * by truncating the fsname copy stored in namebuf.
+ */
+ cp = strrchr(namebuf, '/');
+ if (cp != NULL)
+ *cp = '\0';
+
+ if (!raw && zfs_crypto_create(hdl, namebuf, voprops, NULL,
+ B_FALSE, wkeydata_out, wkeylen_out) != 0) {
+ fnvlist_free(voprops);
+ ret = zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf);
+ goto error;
+ }
+
/* second pass: process "-o" properties */
fnvlist_merge(*oxprops, voprops);
fnvlist_free(voprops);
@@ -3572,6 +3628,10 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned,
}
error:
+ if (zhp != NULL)
+ zfs_close(zhp);
+ if (zpool_hdl != NULL)
+ zpool_close(zpool_hdl);
fnvlist_free(oprops);
return (ret);
}
@@ -3615,6 +3675,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
boolean_t toplevel = B_FALSE;
boolean_t zoned = B_FALSE;
boolean_t hastoken = B_FALSE;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
begin_time = time(NULL);
bzero(origin, MAXNAMELEN);
@@ -4001,9 +4063,13 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
* 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.
+ * send the encryption property in a raw nvlist header. This
+ * check will be avoided if the user specifically overrides
+ * the encryption property on the command line.
*/
- if (!raw && rcvprops != NULL) {
+ if (!raw && rcvprops != NULL &&
+ !nvlist_exists(cmdprops,
+ zfs_prop_to_name(ZFS_PROP_ENCRYPTION))) {
uint64_t crypt;
zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
@@ -4053,13 +4119,15 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
err = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
goto out;
}
- if ((err = zfs_setup_cmdline_props(hdl, type, zoned, recursive,
- toplevel, rcvprops, cmdprops, origprops, &oxprops, errbuf)) != 0)
+ if ((err = zfs_setup_cmdline_props(hdl, type, name, zoned, recursive,
+ stream_wantsnewfs, raw, toplevel, rcvprops, cmdprops, origprops,
+ &oxprops, &wkeydata, &wkeylen, errbuf)) != 0)
goto out;
- err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops, oxprops,
- origin, flags->force, flags->resumable, raw, infd, drr_noswap,
- cleanup_fd, &read_bytes, &errflags, action_handlep, &prop_errors);
+ err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops,
+ oxprops, wkeydata, wkeylen, origin, flags->force, flags->resumable,
+ raw, infd, drr_noswap, cleanup_fd, &read_bytes, &errflags,
+ action_handlep, &prop_errors);
ioctl_errno = ioctl_err;
prop_errflags = errflags;
@@ -4346,6 +4414,11 @@ zfs_receive_checkprops(libzfs_handle_t *hdl, nvlist_t *props,
if (prop == ZFS_PROP_ORIGIN)
continue;
+ /* encryption params have their own verification later */
+ if (prop == ZFS_PROP_ENCRYPTION ||
+ zfs_prop_encryption_key_param(prop))
+ continue;
+
/*
* cannot override readonly, set-once and other specific
* settable properties
diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c
index ab2b70519..3a0b02690 100644
--- a/lib/libzfs_core/libzfs_core.c
+++ b/lib/libzfs_core/libzfs_core.c
@@ -662,8 +662,9 @@ recv_read(int fd, void *buf, int ilen)
*/
static int
recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
- const char *origin, boolean_t force, boolean_t resumable, boolean_t raw,
- int input_fd, const dmu_replay_record_t *begin_record, int cleanup_fd,
+ uint8_t *wkeydata, uint_t wkeylen, const char *origin, boolean_t force,
+ boolean_t resumable, boolean_t raw, int input_fd,
+ const dmu_replay_record_t *begin_record, int cleanup_fd,
uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
nvlist_t **errors)
{
@@ -703,7 +704,11 @@ recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
drr = *begin_record;
}
- if (resumable || raw) {
+ /*
+ * Raw receives, resumable receives, and receives that include a
+ * wrapping key all use the new interface.
+ */
+ if (resumable || raw || wkeydata != NULL) {
nvlist_t *outnvl = NULL;
nvlist_t *innvl = fnvlist_alloc();
@@ -715,6 +720,20 @@ recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
if (localprops != NULL)
fnvlist_add_nvlist(innvl, "localprops", localprops);
+ if (wkeydata != NULL) {
+ /*
+ * wkeydata must be placed in the special
+ * ZPOOL_HIDDEN_ARGS nvlist so that it
+ * will not be printed to the zpool history.
+ */
+ nvlist_t *hidden_args = fnvlist_alloc();
+ fnvlist_add_uint8_array(hidden_args, "wkeydata",
+ wkeydata, wkeylen);
+ fnvlist_add_nvlist(innvl, ZPOOL_HIDDEN_ARGS,
+ hidden_args);
+ nvlist_free(hidden_args);
+ }
+
if (origin != NULL && strlen(origin))
fnvlist_add_string(innvl, "origin", origin);
@@ -846,8 +865,8 @@ int
lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
boolean_t force, boolean_t raw, int fd)
{
- return (recv_impl(snapname, props, NULL, origin, force, B_FALSE, raw,
- fd, NULL, -1, NULL, NULL, NULL, NULL));
+ return (recv_impl(snapname, props, NULL, NULL, 0, origin, force,
+ B_FALSE, raw, fd, NULL, -1, NULL, NULL, NULL, NULL));
}
/*
@@ -860,8 +879,8 @@ int
lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin,
boolean_t force, boolean_t raw, int fd)
{
- return (recv_impl(snapname, props, NULL, origin, force, B_TRUE, raw,
- fd, NULL, -1, NULL, NULL, NULL, NULL));
+ return (recv_impl(snapname, props, NULL, NULL, 0, origin, force,
+ B_TRUE, raw, fd, NULL, -1, NULL, NULL, NULL, NULL));
}
/*
@@ -883,8 +902,8 @@ lzc_receive_with_header(const char *snapname, nvlist_t *props,
if (begin_record == NULL)
return (EINVAL);
- return (recv_impl(snapname, props, NULL, origin, force, resumable, raw,
- fd, begin_record, -1, NULL, NULL, NULL, NULL));
+ return (recv_impl(snapname, props, NULL, NULL, 0, origin, force,
+ resumable, raw, fd, begin_record, -1, NULL, NULL, NULL, NULL));
}
/*
@@ -913,9 +932,9 @@ int lzc_receive_one(const char *snapname, nvlist_t *props,
uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
nvlist_t **errors)
{
- return (recv_impl(snapname, props, NULL, origin, force, resumable,
- raw, input_fd, begin_record, cleanup_fd, read_bytes, errflags,
- action_handle, errors));
+ return (recv_impl(snapname, props, NULL, NULL, 0, origin, force,
+ resumable, raw, input_fd, begin_record, cleanup_fd, read_bytes,
+ errflags, action_handle, errors));
}
/*
@@ -927,15 +946,15 @@ int lzc_receive_one(const char *snapname, nvlist_t *props,
* this nvlist
*/
int lzc_receive_with_cmdprops(const char *snapname, nvlist_t *props,
- nvlist_t *cmdprops, const char *origin, boolean_t force,
- boolean_t resumable, boolean_t raw, int input_fd,
+ nvlist_t *cmdprops, uint8_t *wkeydata, uint_t wkeylen, const char *origin,
+ boolean_t force, boolean_t resumable, boolean_t raw, int input_fd,
const dmu_replay_record_t *begin_record, int cleanup_fd,
uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
nvlist_t **errors)
{
- return (recv_impl(snapname, props, cmdprops, origin, force, resumable,
- raw, input_fd, begin_record, cleanup_fd, read_bytes, errflags,
- action_handle, errors));
+ return (recv_impl(snapname, props, cmdprops, wkeydata, wkeylen, origin,
+ force, resumable, raw, input_fd, begin_record, cleanup_fd,
+ read_bytes, errflags, action_handle, errors));
}
/*
diff --git a/man/man8/zfs.8 b/man/man8/zfs.8
index f1eed207f..e356fc196 100644
--- a/man/man8/zfs.8
+++ b/man/man8/zfs.8
@@ -3912,6 +3912,34 @@ results if the same property is specified in multiple
or
.Fl x
options.
+.Pp
+The
+.Fl o
+option may also be used to override encryption properties upon initial
+receive. This allows unencrypted streams to be received as encrypted datasets.
+To cause the received dataset (or root dataset of a recursive stream) to be
+received as an encryption root, specify encryption properties in the same
+manner as is required for
+.Nm
+.Cm create .
+For instance:
+.Bd -literal
+# zfs send tank/test@snap1 | zfs recv -o encryption=on -o keyformat=passphrase -o keylocation=file:///path/to/keyfile
+.Ed
+.Pp
+Note that
+.Op Fl o Ar keylocation Ns = Ns Ar prompt
+may not be specified here, since stdin is already being utilized for the send
+stream. Once the receive has completed, you can use
+.Nm
+.Cm set
+to change this setting after the fact. Similarly, you can receive a dataset as
+an encrypted child by specifying
+.Op Fl x Ar encryption
+to force the property to be inherited. Overriding encryption properties (except
+for
+.Sy keylocation Ns )
+is not possible with raw send streams.
.It Fl s
If the receive is interrupted, save the partially received state, rather
than deleting it.
diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c
index ded086087..80a4843b3 100644
--- a/module/zfs/dmu_send.c
+++ b/module/zfs/dmu_send.c
@@ -1526,18 +1526,17 @@ typedef struct dmu_recv_begin_arg {
const char *drba_origin;
dmu_recv_cookie_t *drba_cookie;
cred_t *drba_cred;
+ dsl_crypto_params_t *drba_dcp;
uint64_t drba_snapobj;
} dmu_recv_begin_arg_t;
static int
recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
- uint64_t fromguid)
+ uint64_t fromguid, uint64_t featureflags)
{
uint64_t val;
int error;
dsl_pool_t *dp = ds->ds_dir->dd_pool;
- struct drr_begin *drrb = drba->drba_cookie->drc_drrb;
- uint64_t featureflags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo);
boolean_t encrypted = ds->ds_dir->dd_crypto_obj != 0;
boolean_t raw = (featureflags & DMU_BACKUP_FEATURE_RAW) != 0;
@@ -1624,6 +1623,13 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
if ((!encrypted && raw) || encrypted)
return (SET_ERROR(EINVAL));
+ if ((featureflags & DMU_BACKUP_FEATURE_RAW) == 0) {
+ error = dmu_objset_create_crypt_check(
+ ds->ds_dir->dd_parent, drba->drba_dcp);
+ if (error != 0)
+ return (error);
+ }
+
drba->drba_snapobj = 0;
}
@@ -1690,7 +1696,7 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx)
!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_LARGE_DNODE))
return (SET_ERROR(ENOTSUP));
- if ((featureflags & DMU_BACKUP_FEATURE_RAW)) {
+ if (featureflags & DMU_BACKUP_FEATURE_RAW) {
/* raw receives require the encryption feature */
if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_ENCRYPTION))
return (SET_ERROR(ENOTSUP));
@@ -1708,7 +1714,8 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx)
return (SET_ERROR(EINVAL));
}
- error = recv_begin_check_existing_impl(drba, ds, fromguid);
+ error = recv_begin_check_existing_impl(drba, ds, fromguid,
+ featureflags);
dsl_dataset_rele_flags(ds, dsflags, FTAG);
} else if (error == ENOENT) {
/* target fs does not exist; must be a full backup or clone */
@@ -1738,6 +1745,16 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx)
if (error != 0)
return (error);
+ if ((featureflags & DMU_BACKUP_FEATURE_RAW) == 0 &&
+ drba->drba_origin == NULL) {
+ error = dmu_objset_create_crypt_check(ds->ds_dir,
+ drba->drba_dcp);
+ if (error != 0) {
+ dsl_dataset_rele_flags(ds, dsflags, FTAG);
+ return (error);
+ }
+ }
+
/*
* Check filesystem and snapshot limits before receiving. We'll
* recheck snapshot limits again at the end (we create the
@@ -1801,15 +1818,27 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
ds_hold_flags_t dsflags = 0;
int error;
uint64_t crflags = 0;
- dsl_crypto_params_t *dcpp = NULL;
- dsl_crypto_params_t dcp = { 0 };
+ dsl_crypto_params_t dummy_dcp = { 0 };
+ dsl_crypto_params_t *dcp = drba->drba_dcp;
if (drrb->drr_flags & DRR_FLAG_CI_DATA)
crflags |= DS_FLAG_CI_DATASET;
- if ((featureflags & DMU_BACKUP_FEATURE_RAW) == 0) {
+
+ if ((featureflags & DMU_BACKUP_FEATURE_RAW) == 0)
dsflags |= DS_HOLD_FLAG_DECRYPT;
- } else {
- dcp.cp_cmd = DCP_CMD_RAW_RECV;
+
+ /*
+ * Raw, non-incremental recvs always use a dummy dcp with
+ * the raw cmd set. Raw incremental recvs do not use a dcp
+ * since the encryption parameters are already set in stone.
+ */
+ if (dcp == NULL && drba->drba_snapobj == 0 &&
+ drba->drba_origin == NULL) {
+ ASSERT3P(dcp, ==, NULL);
+ dcp = &dummy_dcp;
+
+ if (featureflags & DMU_BACKUP_FEATURE_RAW)
+ dcp->cp_cmd = DCP_CMD_RAW_RECV;
}
error = dsl_dataset_hold_flags(dp, tofs, dsflags, FTAG, &ds);
@@ -1820,13 +1849,11 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
if (drba->drba_snapobj != 0) {
VERIFY0(dsl_dataset_hold_obj(dp,
drba->drba_snapobj, FTAG, &snap));
- } else {
- /* we use the dcp whenever we are not making a clone */
- dcpp = &dcp;
+ ASSERT3P(dcp, ==, NULL);
}
dsobj = dsl_dataset_create_sync(ds->ds_dir, recv_clone_name,
- snap, crflags, drba->drba_cred, dcpp, tx);
+ snap, crflags, drba->drba_cred, dcp, tx);
if (drba->drba_snapobj != 0)
dsl_dataset_rele(snap, FTAG);
dsl_dataset_rele_flags(ds, dsflags, FTAG);
@@ -1840,19 +1867,18 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
if (drba->drba_origin != NULL) {
VERIFY0(dsl_dataset_hold(dp, drba->drba_origin,
FTAG, &origin));
- } else {
- /* we use the dcp whenever we are not making a clone */
- dcpp = &dcp;
+ ASSERT3P(dcp, ==, NULL);
}
/* Create new dataset. */
dsobj = dsl_dataset_create_sync(dd, strrchr(tofs, '/') + 1,
- origin, crflags, drba->drba_cred, dcpp, tx);
+ origin, crflags, drba->drba_cred, dcp, tx);
if (origin != NULL)
dsl_dataset_rele(origin, FTAG);
dsl_dir_rele(dd, FTAG);
drba->drba_cookie->drc_newfs = B_TRUE;
}
+
VERIFY0(dsl_dataset_own_obj(dp, dsobj, dsflags, dmu_recv_tag, &newds));
VERIFY0(dmu_objset_from_ds(newds, &os));
@@ -2103,7 +2129,8 @@ dmu_recv_resume_begin_sync(void *arg, dmu_tx_t *tx)
*/
int
dmu_recv_begin(char *tofs, char *tosnap, dmu_replay_record_t *drr_begin,
- boolean_t force, boolean_t resumable, char *origin, dmu_recv_cookie_t *drc)
+ boolean_t force, boolean_t resumable, nvlist_t *localprops,
+ nvlist_t *hidden_args, char *origin, dmu_recv_cookie_t *drc)
{
dmu_recv_begin_arg_t drba = { 0 };
@@ -2139,9 +2166,33 @@ dmu_recv_begin(char *tofs, char *tosnap, dmu_replay_record_t *drr_begin,
dmu_recv_resume_begin_check, dmu_recv_resume_begin_sync,
&drba, 5, ZFS_SPACE_CHECK_NORMAL));
} else {
- return (dsl_sync_task(tofs,
+ int err;
+
+ /*
+ * For non-raw, non-incremental, non-resuming receives the
+ * user can specify encryption parameters on the command line
+ * with "zfs recv -o". For these receives we create a dcp and
+ * pass it to the sync task. Creating the dcp will implicitly
+ * remove the encryption params from the localprops nvlist,
+ * which avoids errors when trying to set these normally
+ * read-only properties. Any other kind of receive that
+ * attempts to set these properties will fail as a result.
+ */
+ if ((DMU_GET_FEATUREFLAGS(drc->drc_drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_RAW) == 0 &&
+ origin == NULL && drc->drc_drrb->drr_fromguid == 0) {
+ err = dsl_crypto_params_create_nvlist(DCP_CMD_NONE,
+ localprops, hidden_args, &drba.drba_dcp);
+ if (err != 0)
+ return (err);
+ }
+
+ err = dsl_sync_task(tofs,
dmu_recv_begin_check, dmu_recv_begin_sync,
- &drba, 5, ZFS_SPACE_CHECK_NORMAL));
+ &drba, 5, ZFS_SPACE_CHECK_NORMAL);
+ dsl_crypto_params_free(drba.drba_dcp, !!err);
+
+ return (err);
}
}
diff --git a/module/zfs/dsl_crypt.c b/module/zfs/dsl_crypt.c
index 9bdfc8d91..38e6123d3 100644
--- a/module/zfs/dsl_crypt.c
+++ b/module/zfs/dsl_crypt.c
@@ -1719,6 +1719,10 @@ dmu_objset_create_crypt_check(dsl_dir_t *parentdd, dsl_crypto_params_t *dcp)
{
int ret;
uint64_t pcrypt, crypt;
+ dsl_crypto_params_t dummy_dcp = { 0 };
+
+ if (dcp == NULL)
+ dcp = &dummy_dcp;
if (dcp->cp_cmd != DCP_CMD_NONE)
return (SET_ERROR(EINVAL));
diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c
index 74a58fb89..6a5846bfe 100644
--- a/module/zfs/zfs_ioctl.c
+++ b/module/zfs/zfs_ioctl.c
@@ -4339,15 +4339,17 @@ static boolean_t zfs_ioc_recv_inject_err;
*/
static int
zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
- nvlist_t *localprops, boolean_t force, boolean_t resumable, int input_fd,
- dmu_replay_record_t *begin_record, int cleanup_fd, uint64_t *read_bytes,
- uint64_t *errflags, uint64_t *action_handle, nvlist_t **errors)
+ nvlist_t *localprops, nvlist_t *hidden_args, boolean_t force,
+ boolean_t resumable, int input_fd, dmu_replay_record_t *begin_record,
+ int cleanup_fd, uint64_t *read_bytes, uint64_t *errflags,
+ uint64_t *action_handle, nvlist_t **errors)
{
dmu_recv_cookie_t drc;
int error = 0;
int props_error = 0;
offset_t off;
- nvlist_t *delayprops = NULL; /* sent properties applied post-receive */
+ nvlist_t *local_delayprops = NULL;
+ nvlist_t *recv_delayprops = NULL;
nvlist_t *origprops = NULL; /* existing properties */
nvlist_t *origrecvd = NULL; /* existing received properties */
boolean_t first_recvd_props = B_FALSE;
@@ -4361,8 +4363,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
if (input_fp == NULL)
return (SET_ERROR(EBADF));
- error = dmu_recv_begin(tofs, tosnap,
- begin_record, force, resumable, origin, &drc);
+ error = dmu_recv_begin(tofs, tosnap, begin_record, force,
+ resumable, localprops, hidden_args, origin, &drc);
if (error != 0)
goto out;
@@ -4379,8 +4381,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
/*
* If new received properties are supplied, they are to
- * completely replace the existing received properties, so stash
- * away the existing ones.
+ * completely replace the existing received properties,
+ * so stash away the existing ones.
*/
if (dsl_prop_get_received(tofs, &origrecvd) == 0) {
nvlist_t *errlist = NULL;
@@ -4427,7 +4429,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
props_error = dsl_prop_set_hasrecvd(tofs);
if (props_error == 0) {
- delayprops = extract_delay_props(recvprops);
+ recv_delayprops = extract_delay_props(recvprops);
(void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED,
recvprops, *errors);
}
@@ -4454,6 +4456,8 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
fnvlist_add_nvpair(oprops, nvp);
}
}
+
+ local_delayprops = extract_delay_props(oprops);
(void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL,
oprops, *errors);
(void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_INHERITED,
@@ -4495,26 +4499,33 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops,
}
/* Set delayed properties now, after we're done receiving. */
- if (delayprops != NULL && error == 0) {
+ if (recv_delayprops != NULL && error == 0) {
(void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED,
- delayprops, *errors);
+ recv_delayprops, *errors);
+ }
+ if (local_delayprops != NULL && error == 0) {
+ (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL,
+ local_delayprops, *errors);
}
}
- if (delayprops != NULL) {
- /*
- * Merge delayed props back in with initial props, in case
- * we're DEBUG and zfs_ioc_recv_inject_err is set (which means
- * we have to make sure clear_received_props() includes
- * the delayed properties).
- *
- * Since zfs_ioc_recv_inject_err is only in DEBUG kernels,
- * using ASSERT() will be just like a VERIFY.
- */
- ASSERT(nvlist_merge(recvprops, delayprops, 0) == 0);
- nvlist_free(delayprops);
+ /*
+ * Merge delayed props back in with initial props, in case
+ * we're DEBUG and zfs_ioc_recv_inject_err is set (which means
+ * we have to make sure clear_received_props() includes
+ * the delayed properties).
+ *
+ * Since zfs_ioc_recv_inject_err is only in DEBUG kernels,
+ * using ASSERT() will be just like a VERIFY.
+ */
+ if (recv_delayprops != NULL) {
+ ASSERT(nvlist_merge(recvprops, recv_delayprops, 0) == 0);
+ nvlist_free(recv_delayprops);
+ }
+ if (local_delayprops != NULL) {
+ ASSERT(nvlist_merge(localprops, local_delayprops, 0) == 0);
+ nvlist_free(local_delayprops);
}
-
*read_bytes = off - input_fp->f_offset;
if (VOP_SEEK(input_fp->f_vnode, input_fp->f_offset, &off, NULL) == 0)
@@ -4689,7 +4700,7 @@ zfs_ioc_recv(zfs_cmd_t *zc)
begin_record.drr_u.drr_begin = zc->zc_begin_record;
error = zfs_ioc_recv_impl(tofs, tosnap, origin, recvdprops, localprops,
- zc->zc_guid, B_FALSE, zc->zc_cookie, &begin_record,
+ NULL, zc->zc_guid, B_FALSE, zc->zc_cookie, &begin_record,
zc->zc_cleanup_fd, &zc->zc_cookie, &zc->zc_obj,
&zc->zc_action_handle, &errors);
nvlist_free(recvdprops);
@@ -4743,6 +4754,7 @@ zfs_ioc_recv_new(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl)
nvlist_t *errors = NULL;
nvlist_t *recvprops = NULL;
nvlist_t *localprops = NULL;
+ nvlist_t *hidden_args = NULL;
char *snapname = NULL;
char *origin = NULL;
char *tosnap;
@@ -4802,9 +4814,13 @@ zfs_ioc_recv_new(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl)
if (error && error != ENOENT)
return (error);
+ error = nvlist_lookup_nvlist(innvl, ZPOOL_HIDDEN_ARGS, &hidden_args);
+ if (error && error != ENOENT)
+ return (error);
+
error = zfs_ioc_recv_impl(tofs, tosnap, origin, recvprops, localprops,
- force, resumable, input_fd, begin_record, cleanup_fd, &read_bytes,
- &errflags, &action_handle, &errors);
+ hidden_args, force, resumable, input_fd, begin_record, cleanup_fd,
+ &read_bytes, &errflags, &action_handle, &errors);
fnvlist_add_uint64(outnvl, "read_bytes", read_bytes);
fnvlist_add_uint64(outnvl, "error_flags", errflags);
diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run
index c3e3887a8..5e0dd0832 100644
--- a/tests/runfiles/linux.run
+++ b/tests/runfiles/linux.run
@@ -455,7 +455,7 @@ tests = ['zdb_001_neg', 'zfs_001_neg', 'zfs_allow_001_neg',
'zpool_offline_001_neg', 'zpool_online_001_neg', 'zpool_remove_001_neg',
'zpool_replace_001_neg', 'zpool_scrub_001_neg', 'zpool_set_001_neg',
'zpool_status_001_neg', 'zpool_upgrade_001_neg', 'arcstat_001_pos',
- 'arc_summary_001_pos', 'arc_summary_002_neg',
+ 'arc_summary_001_pos', 'arc_summary_002_neg',
'arc_summary3_001_pos', 'dbufstat_001_pos']
user =
tags = ['functional', 'cli_user', 'misc']
@@ -743,7 +743,8 @@ tests = ['rsend_001_pos', 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos',
'send-c_mixed_compression', 'send-c_stream_size_estimate', 'send-cD',
'send-c_embedded_blocks', 'send-c_resume', 'send-cpL_varied_recsize',
'send-c_recv_dedup', 'send_encrypted_files', 'send_encrypted_heirarchy',
- 'send_freeobjects', 'send_realloc_dnode_size', 'send_hole_birth']
+ 'send_encrypted_props', 'send_freeobjects', 'send_realloc_dnode_size',
+ 'send_hole_birth']
tags = ['functional', 'rsend']
[tests/functional/scrub_mirror]
diff --git a/tests/zfs-tests/tests/functional/rsend/Makefile.am b/tests/zfs-tests/tests/functional/rsend/Makefile.am
index 79e01d190..d65a9144e 100644
--- a/tests/zfs-tests/tests/functional/rsend/Makefile.am
+++ b/tests/zfs-tests/tests/functional/rsend/Makefile.am
@@ -23,6 +23,7 @@ dist_pkgdata_SCRIPTS = \
rsend_024_pos.ksh \
send_encrypted_files.ksh \
send_encrypted_heirarchy.ksh \
+ send_encrypted_props.ksh \
send-cD.ksh \
send-c_embedded_blocks.ksh \
send-c_incremental.ksh \
diff --git a/tests/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh b/tests/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh
new file mode 100755
index 000000000..a216f1c5f
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh
@@ -0,0 +1,199 @@
+#!/bin/ksh -p
+#
+# 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) 2018 by Datto Inc. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/rsend/rsend.kshlib
+
+#
+# DESCRIPTION:
+# Verify that zfs properly handles encryption properties when receiving
+# send streams.
+#
+# STRATEGY:
+# 1. Create a few unencrypted and encrypted test datasets with some data
+# 2. Take snapshots of these datasets in preparation for sending
+# 3. Verify that 'zfs recv -o keylocation=prompt' fails
+# 4. Verify that 'zfs recv -x <encryption prop>' fails on a raw send stream
+# 5. Verify that encryption properties cannot be changed on incrementals
+# 6. Verify that a simple send can be received as an encryption root
+# 7. Verify that an unencrypted props send can be received as an
+# encryption root
+# 8. Verify that an unencrypted recursive send can be received as an
+# encryption root
+# 9. Verify that an unencrypted props send can be received as an
+# encryption child
+# 10. Verify that an unencrypted recursive send can be received as an
+# encryption child
+#
+
+verify_runnable "both"
+
+function cleanup
+{
+ destroy_dataset $TESTPOOL/recv "-r"
+ destroy_dataset $TESTPOOL/crypt "-r"
+ destroy_dataset $TESTPOOL/ds "-r"
+ [[ -f $sendfile ]] && log_must rm $sendfile
+ [[ -f $keyfile ]] && log_must rm $keyfile
+}
+log_onexit cleanup
+
+log_assert "'zfs recv' must properly handle encryption properties"
+
+typeset keyfile=/$TESTPOOL/pkey
+typeset sendfile=/$TESTPOOL/sendfile
+typeset snap=$TESTPOOL/ds@snap
+typeset esnap=$TESTPOOL/crypt@snap1
+typeset esnap2=$TESTPOOL/crypt@snap2
+
+log_must eval "echo 'password' > $keyfile"
+
+log_must zfs create $TESTPOOL/ds
+log_must zfs create $TESTPOOL/ds/ds1
+
+log_must zfs create -o encryption=on -o keyformat=passphrase \
+ -o keylocation=file://$keyfile $TESTPOOL/crypt
+log_must zfs create $TESTPOOL/crypt/ds1
+log_must zfs create -o keyformat=passphrase -o keylocation=file://$keyfile \
+ $TESTPOOL/crypt/ds2
+
+log_must mkfile 1M /$TESTPOOL/ds/$TESTFILE0
+log_must cp /$TESTPOOL/ds/$TESTFILE0 /$TESTPOOL/crypt/$TESTFILE0
+typeset cksum=$(md5sum /$TESTPOOL/ds/$TESTFILE0 | awk '{ print $1 }')
+
+log_must zfs snap -r $snap
+log_must zfs snap -r $esnap
+log_must zfs snap -r $esnap2
+
+# Embedded data is incompatible with encrypted datasets, so we cannot
+# allow embedded streams to be received.
+log_note "Must not be able to receive an embedded stream as encrypted"
+log_mustnot eval "zfs send -e $TESTPOOL/crypt/ds1 | zfs recv $TESTPOOL/recv"
+
+# We currently don't have an elegant and secure way to pass the passphrase
+# in via prompt because the send stream itself is using stdin.
+log_note "Must not be able to use 'keylocation=prompt' on receive"
+log_must eval "zfs send $snap > $sendfile"
+log_mustnot eval "zfs recv -o encryption=on -o keyformat=passphrase" \
+ "$TESTPOOL/recv < $sendfile"
+log_mustnot eval "zfs recv -o encryption=on -o keyformat=passphrase" \
+ "-o keylocation=prompt $TESTPOOL/recv < $sendfile"
+
+# Raw sends do not have access to the decrypted data so we cannot override
+# the encryption settings without losing the data.
+log_note "Must not be able to disable encryption properties on raw send"
+log_must eval "zfs send -w $esnap > $sendfile"
+log_mustnot eval "zfs recv -x encryption $TESTPOOL/recv < $sendfile"
+log_mustnot eval "zfs recv -x keyformat $TESTPOOL/recv < $sendfile"
+log_mustnot eval "zfs recv -x pbkdf2iters $TESTPOOL/recv < $sendfile"
+
+# Encryption properties are set upon creating the dataset. Changing them
+# afterwards requires using 'zfs change-key' or an update from a raw send.
+log_note "Must not be able to change encryption properties on incrementals"
+log_must eval "zfs send $esnap | zfs recv -o encryption=on" \
+ "-o keyformat=passphrase -o keylocation=file://$keyfile $TESTPOOL/recv"
+log_mustnot eval "zfs send -i $esnap $esnap2 |" \
+ "zfs recv -o encryption=aes-128-ccm $TESTPOOL/recv"
+log_mustnot eval "zfs send -i $esnap $esnap2 |" \
+ "zfs recv -o keyformat=hex $TESTPOOL/recv"
+log_mustnot eval "zfs send -i $esnap $esnap2 |" \
+ "zfs recv -o pbkdf2iters=100k $TESTPOOL/recv"
+log_must zfs destroy -r $TESTPOOL/recv
+
+# Test that we can receive a simple stream as an encryption root.
+log_note "Must be able to receive stream as encryption root"
+ds=$TESTPOOL/recv
+log_must eval "zfs send $snap > $sendfile"
+log_must eval "zfs recv -o encryption=on -o keyformat=passphrase" \
+ "-o keylocation=file://$keyfile $ds < $sendfile"
+log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm"
+log_must test "$(get_prop 'encryptionroot' $ds)" == "$ds"
+log_must test "$(get_prop 'keyformat' $ds)" == "passphrase"
+log_must test "$(get_prop 'keylocation' $ds)" == "file://$keyfile"
+log_must test "$(get_prop 'mounted' $ds)" == "yes"
+recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }')
+log_must test "$recv_cksum" == "$cksum"
+log_must zfs destroy -r $ds
+
+# Test that we can override encryption properties on a properties stream
+# of an unencrypted dataset, turning it into an encryption root.
+log_note "Must be able to receive stream with props as encryption root"
+ds=$TESTPOOL/recv
+log_must eval "zfs send -p $snap > $sendfile"
+log_must eval "zfs recv -o encryption=on -o keyformat=passphrase" \
+ "-o keylocation=file://$keyfile $ds < $sendfile"
+log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm"
+log_must test "$(get_prop 'encryptionroot' $ds)" == "$ds"
+log_must test "$(get_prop 'keyformat' $ds)" == "passphrase"
+log_must test "$(get_prop 'keylocation' $ds)" == "file://$keyfile"
+log_must test "$(get_prop 'mounted' $ds)" == "yes"
+recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }')
+log_must test "$recv_cksum" == "$cksum"
+log_must zfs destroy -r $ds
+
+# Test that we can override encryption properties on a recursive stream
+# of an unencrypted dataset, turning it into an encryption root. The root
+# dataset of the stream should become an encryption root with all children
+# inheriting from it.
+log_note "Must be able to receive recursive stream as encryption root"
+ds=$TESTPOOL/recv
+log_must eval "zfs send -R $snap > $sendfile"
+log_must eval "zfs recv -o encryption=on -o keyformat=passphrase" \
+ "-o keylocation=file://$keyfile $ds < $sendfile"
+log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm"
+log_must test "$(get_prop 'encryptionroot' $ds)" == "$ds"
+log_must test "$(get_prop 'keyformat' $ds)" == "passphrase"
+log_must test "$(get_prop 'keylocation' $ds)" == "file://$keyfile"
+log_must test "$(get_prop 'mounted' $ds)" == "yes"
+recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }')
+log_must test "$recv_cksum" == "$cksum"
+log_must zfs destroy -r $ds
+
+# Test that we can override an unencrypted properties stream's encryption
+# settings, receiving it as an encrypted child.
+log_note "Must be able to receive stream with props to encrypted child"
+ds=$TESTPOOL/crypt/recv
+log_must eval "zfs send -p $snap > $sendfile"
+log_must eval "zfs recv -x encryption $ds < $sendfile"
+log_must test "$(get_prop 'encryptionroot' $ds)" == "$TESTPOOL/crypt"
+log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm"
+log_must test "$(get_prop 'keyformat' $ds)" == "passphrase"
+log_must test "$(get_prop 'mounted' $ds)" == "yes"
+recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }')
+log_must test "$recv_cksum" == "$cksum"
+log_must zfs destroy -r $ds
+
+# Test that we can override an unencrypted recursive stream's encryption
+# settings, receiving all datasets as encrypted children.
+log_note "Must be able to receive recursive stream to encrypted child"
+ds=$TESTPOOL/crypt/recv
+log_must eval "zfs send -R $snap > $sendfile"
+log_must eval "zfs recv -x encryption $ds < $sendfile"
+log_must test "$(get_prop 'encryptionroot' $ds)" == "$TESTPOOL/crypt"
+log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm"
+log_must test "$(get_prop 'keyformat' $ds)" == "passphrase"
+log_must test "$(get_prop 'mounted' $ds)" == "yes"
+recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }')
+log_must test "$recv_cksum" == "$cksum"
+log_must zfs destroy -r $ds
+
+# Check that we haven't printed the key to the zpool history log
+log_mustnot eval "zpool history -i | grep -q 'wkeydata'"
+
+log_pass "'zfs recv' properly handles encryption properties"