Added encryption support for zfs recv -o / -x

One small integration that was absent from b52563 was
support for zfs recv -o / -x with regards to encryption
parameters. The main use cases of this are as follows:

* Receiving an unencrypted stream as encrypted without
  needing to create a "dummy" encrypted parent so that
  encryption can be inheritted.

* Allowing users to change their keylocation on receive,
  so long as the receiving dataset is an encryption root.

* Allowing users to explicitly exclude or override the
  encryption property from an unencrypted properties stream,
  allowing it to be received as encrypted.

* Receiving a recursive heirarchy of unencrypted datasets,
  encrypting the top-level one and forcing all children to
  inherit the encryption.

Reviewed-by: Jorgen Lundman <lundman@lundman.net>
Reviewed by: Matthew Ahrens <mahrens@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Richard Elling <Richard.Elling@RichardElling.com>
Signed-off-by: Tom Caputi <tcaputi@datto.com>
Closes #7650
This commit is contained in:
Tom Caputi 2017-10-13 13:09:04 -04:00 committed by Brian Behlendorf
parent fe8a7982ca
commit d9c460a0b6
17 changed files with 507 additions and 93 deletions

View File

@ -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)

View File

@ -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 **);

View File

@ -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 *);

View File

@ -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 **);

View File

@ -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);

View File

@ -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);

View File

@ -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));
}

View File

@ -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;

View File

@ -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

View File

@ -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));
}
/*

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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));

View File

@ -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);

View File

@ -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]

View File

@ -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 \

View File

@ -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"