From 4a385862b7a9c62f5ec46462e92db48c3c5ec7d9 Mon Sep 17 00:00:00 2001 From: Tom Caputi Date: Wed, 21 Feb 2018 15:30:11 -0500 Subject: 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 Signed-off-by: Tom Caputi Closes #7199 --- lib/libzfs/libzfs_sendrecv.c | 35 ++++++++++++++++++++++++----------- module/zfs/dmu_send.c | 10 +++++++++- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 9ac2a9026..c850c8ba6 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -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)) { zfs_cmd_t zc = {"\0"}; zfs_handle_t *zhp; + boolean_t encrypted; (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 - * encrypted filesystem. This is because it would require - * the dsl dir to point to the 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. + * Raw sends can not be performed as an incremental on top + * of existing unencryppted 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. */ - if (stream_wantsnewfs && flags->force && - zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != - ZIO_CRYPT_OFF) { + encrypted = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != + ZIO_CRYPT_OFF; + if (!stream_wantsnewfs && !encrypted && raw) { zfs_close(zhp); zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "zfs receive -F cannot be used to " - "destroy an encrypted filesystem")); + "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); + 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 && stream_wantsnewfs) { diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c index 8ca77e95d..bbb319822 100644 --- a/module/zfs/dmu_send.c +++ b/module/zfs/dmu_send.c @@ -1522,6 +1522,10 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds, 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; /* temporary clone name must not exist */ 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; 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. */ while (obj != 0) { 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 * 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)); drba->drba_snapobj = 0; -- cgit v1.2.3