Prevent raw zfs recv -F if dataset is unencrypted

The current design of ZFS encryption only allows a dataset to
have one DSL Crypto Key at a time. As a result, it is important
that the zfs receive code ensures that only one key can be in use
at a time for a given DSL Directory. zfs receive -F complicates
this, since the new dataset is received as a clone of the existing
one so that an atomic switch can be done at the end. To prevent
confusion about which dataset is actually encrypted a check was
added to ensure that encrypted datasets cannot use zfs recv -F to
completely replace existing datasets. Unfortunately, the check did
not take into account unencrypted datasets being overriden by
encrypted ones as a case.

Along the same lines, the code also failed to ensure that raw
recieves could not be done on top of existing unencrypted
datasets, which causes amny problems since the new stream cannot
be decrypted.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Tom Caputi <tcaputi@datto.com>
Closes #7199
This commit is contained in:
Tom Caputi 2018-02-21 15:30:11 -05:00 committed by Brian Behlendorf
parent b1d217338a
commit 4a385862b7
2 changed files with 33 additions and 12 deletions

View File

@ -3832,6 +3832,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
if (zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) { if (zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) {
zfs_cmd_t zc = {"\0"}; zfs_cmd_t zc = {"\0"};
zfs_handle_t *zhp; zfs_handle_t *zhp;
boolean_t encrypted;
(void) strcpy(zc.zc_name, name); (void) strcpy(zc.zc_name, name);
@ -3879,24 +3880,36 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
} }
/* /*
* zfs recv -F cant be used to blow away an existing * Raw sends can not be performed as an incremental on top
* encrypted filesystem. This is because it would require * of existing unencryppted datasets. zfs recv -F cant be
* the dsl dir to point to the the new key (or lack of a * used to blow away an existing encrypted filesystem. This
* key) and the old key at the same time. The -F flag may * is because it would require the dsl dir to point to the
* still be used for deleting intermediate snapshots that * new key (or lack of a key) and the old key at the same
* would otherwise prevent the receive from working. * time. The -F flag may still be used for deleting
* intermediate snapshots that would otherwise prevent the
* receive from working.
*/ */
if (stream_wantsnewfs && flags->force && encrypted = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) !=
zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF;
ZIO_CRYPT_OFF) { if (!stream_wantsnewfs && !encrypted && raw) {
zfs_close(zhp); zfs_close(zhp);
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"zfs receive -F cannot be used to " "cannot perform raw receive on top of "
"destroy an encrypted filesystem")); "existing unencrypted dataset"));
err = zfs_error(hdl, EZFS_BADRESTORE, errbuf); err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
goto out; goto out;
} }
if (stream_wantsnewfs && flags->force &&
((raw && !encrypted) || encrypted)) {
zfs_close(zhp);
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 && if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM &&
stream_wantsnewfs) { stream_wantsnewfs) {

View File

@ -1522,6 +1522,10 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
uint64_t val; uint64_t val;
int error; int error;
dsl_pool_t *dp = ds->ds_dir->dd_pool; 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;
/* temporary clone name must not exist */ /* temporary clone name must not exist */
error = zap_lookup(dp->dp_meta_objset, error = zap_lookup(dp->dp_meta_objset,
@ -1555,6 +1559,10 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
dsl_dataset_t *snap; dsl_dataset_t *snap;
uint64_t obj = dsl_dataset_phys(ds)->ds_prev_snap_obj; uint64_t obj = dsl_dataset_phys(ds)->ds_prev_snap_obj;
/* Can't perform a raw receive on top of a non-raw receive */
if (!encrypted && raw)
return (SET_ERROR(EINVAL));
/* Find snapshot in this dir that matches fromguid. */ /* Find snapshot in this dir that matches fromguid. */
while (obj != 0) { while (obj != 0) {
error = dsl_dataset_hold_obj(dp, obj, FTAG, error = dsl_dataset_hold_obj(dp, obj, FTAG,
@ -1599,7 +1607,7 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
* dsl dir to point to the old encryption key and * dsl dir to point to the old encryption key and
* the new one at the same time during the receive. * the new one at the same time during the receive.
*/ */
if (ds->ds_dir->dd_crypto_obj != 0) if ((!encrypted && raw) || encrypted)
return (SET_ERROR(EINVAL)); return (SET_ERROR(EINVAL));
drba->drba_snapobj = 0; drba->drba_snapobj = 0;