diff options
-rw-r--r-- | include/sys/dmu_send.h | 1 | ||||
-rw-r--r-- | include/sys/dsl_crypt.h | 8 | ||||
-rw-r--r-- | module/zfs/dmu_send.c | 34 | ||||
-rw-r--r-- | module/zfs/dsl_crypt.c | 464 | ||||
-rwxr-xr-x | tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw_incremental.ksh | 30 |
5 files changed, 312 insertions, 225 deletions
diff --git a/include/sys/dmu_send.h b/include/sys/dmu_send.h index 19d9a2d44..c0b2aafdb 100644 --- a/include/sys/dmu_send.h +++ b/include/sys/dmu_send.h @@ -64,6 +64,7 @@ typedef struct dmu_recv_cookie { boolean_t drc_raw; boolean_t drc_clone; struct avl_tree *drc_guid_to_ds_map; + nvlist_t *drc_keynvl; zio_cksum_t drc_cksum; uint64_t drc_newsnapobj; void *drc_owner; diff --git a/include/sys/dsl_crypt.h b/include/sys/dsl_crypt.h index d0c789035..efa3839f4 100644 --- a/include/sys/dsl_crypt.h +++ b/include/sys/dsl_crypt.h @@ -189,8 +189,12 @@ int spa_keystore_lookup_key(spa_t *spa, uint64_t dsobj, void *tag, dsl_crypto_key_t **dck_out); int dsl_crypto_populate_key_nvlist(struct dsl_dataset *ds, nvlist_t **nvl_out); -int dsl_crypto_recv_key(const char *poolname, uint64_t dsobj, - dmu_objset_type_t ostype, nvlist_t *nvl); +int dsl_crypto_recv_raw_key_check(struct dsl_dataset *ds, + nvlist_t *nvl, dmu_tx_t *tx); +void dsl_crypto_recv_raw_key_sync(struct dsl_dataset *ds, + nvlist_t *nvl, dmu_tx_t *tx); +int dsl_crypto_recv_raw(const char *poolname, uint64_t dsobj, + dmu_objset_type_t ostype, nvlist_t *nvl, boolean_t do_key); int spa_keystore_change_key(const char *dsname, dsl_crypto_params_t *dcp); int dsl_dir_rename_crypt_check(dsl_dir_t *dd, dsl_dir_t *newparent); diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c index bbb319822..16dc13939 100644 --- a/module/zfs/dmu_send.c +++ b/module/zfs/dmu_send.c @@ -3787,11 +3787,20 @@ dmu_recv_stream(dmu_recv_cookie_t *drc, vnode_t *vp, offset_t *voffp, if (err != 0) goto out; - err = dsl_crypto_recv_key(spa_name(ra->os->os_spa), + /* + * If this is a new dataset we set the key immediately. + * Otherwise we don't want to change the key until we + * are sure the rest of the receive succeeded so we stash + * the keynvl away until then. + */ + err = dsl_crypto_recv_raw(spa_name(ra->os->os_spa), drc->drc_ds->ds_object, drc->drc_drrb->drr_type, - keynvl); + keynvl, drc->drc_newfs); if (err != 0) goto out; + + if (!drc->drc_newfs) + drc->drc_keynvl = fnvlist_dup(keynvl); } if (featureflags & DMU_BACKUP_FEATURE_RESUMING) { @@ -3908,6 +3917,7 @@ out: * the inconsistent state. */ dmu_recv_cleanup_ds(drc); + nvlist_free(drc->drc_keynvl); } *voffp = ra->voff; @@ -3965,6 +3975,15 @@ dmu_recv_end_check(void *arg, dmu_tx_t *tx) return (error); } } + if (drc->drc_keynvl != NULL) { + error = dsl_crypto_recv_raw_key_check(drc->drc_ds, + drc->drc_keynvl, tx); + if (error != 0) { + dsl_dataset_rele(origin_head, FTAG); + return (error); + } + } + error = dsl_dataset_clone_swap_check_impl(drc->drc_ds, origin_head, drc->drc_force, drc->drc_owner, tx); if (error != 0) { @@ -4021,8 +4040,14 @@ dmu_recv_end_sync(void *arg, dmu_tx_t *tx) dsl_dataset_rele(snap, FTAG); } } - VERIFY3P(drc->drc_ds->ds_prev, ==, - origin_head->ds_prev); + if (drc->drc_keynvl != NULL) { + dsl_crypto_recv_raw_key_sync(drc->drc_ds, + drc->drc_keynvl, tx); + nvlist_free(drc->drc_keynvl); + drc->drc_keynvl = NULL; + } + + VERIFY3P(drc->drc_ds->ds_prev, ==, origin_head->ds_prev); dsl_dataset_clone_swap_sync_impl(drc->drc_ds, origin_head, tx); @@ -4174,6 +4199,7 @@ dmu_recv_end(dmu_recv_cookie_t *drc, void *owner) if (error != 0) { dmu_recv_cleanup_ds(drc); + nvlist_free(drc->drc_keynvl); } else if (drc->drc_guid_to_ds_map != NULL) { (void) add_ds_to_guidmap(drc->drc_tofs, drc->drc_guid_to_ds_map, drc->drc_newsnapobj, drc->drc_raw); diff --git a/module/zfs/dsl_crypt.c b/module/zfs/dsl_crypt.c index 98a6f8cc8..9009b526b 100644 --- a/module/zfs/dsl_crypt.c +++ b/module/zfs/dsl_crypt.c @@ -1838,7 +1838,7 @@ dsl_dataset_create_crypt_sync(uint64_t dsobj, dsl_dir_t *dd, /* * A NULL dcp at this point indicates this is the origin dataset * which does not have an objset to encrypt. Raw receives will handle - * encryption seperately later. In both cases we can simply return. + * encryption separately later. In both cases we can simply return. */ if (dcp == NULL || dcp->cp_cmd == DCP_CMD_RAW_RECV) return; @@ -1889,187 +1889,63 @@ dsl_dataset_create_crypt_sync(uint64_t dsobj, dsl_dir_t *dd, typedef struct dsl_crypto_recv_key_arg { uint64_t dcrka_dsobj; - nvlist_t *dcrka_nvl; dmu_objset_type_t dcrka_ostype; + nvlist_t *dcrka_nvl; + boolean_t dcrka_do_key; } dsl_crypto_recv_key_arg_t; -int -dsl_crypto_recv_key_check(void *arg, dmu_tx_t *tx) +static int +dsl_crypto_recv_raw_objset_check(dsl_dataset_t *ds, dmu_objset_type_t ostype, + nvlist_t *nvl, dmu_tx_t *tx) { int ret; - objset_t *mos = tx->tx_pool->dp_meta_objset; objset_t *os; dnode_t *mdn; - dsl_crypto_recv_key_arg_t *dcrka = arg; - nvlist_t *nvl = dcrka->dcrka_nvl; - dsl_dataset_t *ds = NULL; uint8_t *buf = NULL; uint_t len; - uint64_t intval, guid, nlevels, blksz, ibs, nblkptr, maxblkid, version; - boolean_t is_passphrase = B_FALSE; - - ret = dsl_dataset_hold_obj(tx->tx_pool, dcrka->dcrka_dsobj, FTAG, &ds); - if (ret != 0) - goto error; - - ASSERT(dsl_dataset_phys(ds)->ds_flags & DS_FLAG_INCONSISTENT); - - /* - * Read and check all the encryption values from the nvlist. We need - * all of the fields of a DSL Crypto Key, as well as a fully specified - * wrapping key. - */ - ret = nvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_CRYPTO_SUITE, &intval); - if (ret != 0 || intval >= ZIO_CRYPT_FUNCTIONS || - intval <= ZIO_CRYPT_OFF) { - ret = SET_ERROR(EINVAL); - goto error; - } - - ret = nvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_GUID, &intval); - if (ret != 0) { - ret = SET_ERROR(EINVAL); - goto error; - } - - /* - * If this is an incremental receive make sure the given key guid - * matches the one we already have. - */ - if (ds->ds_dir->dd_crypto_obj != 0) { - ret = zap_lookup(mos, ds->ds_dir->dd_crypto_obj, - DSL_CRYPTO_KEY_GUID, 8, 1, &guid); - if (ret != 0) - goto error; - - if (intval != guid) { - ret = SET_ERROR(EACCES); - goto error; - } - } - - ret = nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_MASTER_KEY, - &buf, &len); - if (ret != 0 || len != MASTER_KEY_MAX_LEN) { - ret = SET_ERROR(EINVAL); - goto error; - } - - ret = nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_HMAC_KEY, - &buf, &len); - if (ret != 0 || len != SHA512_HMAC_KEYLEN) { - ret = SET_ERROR(EINVAL); - goto error; - } - - ret = nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_IV, &buf, &len); - if (ret != 0 || len != WRAPPING_IV_LEN) { - ret = SET_ERROR(EINVAL); - goto error; - } + uint64_t intval, nlevels, blksz, ibs, nblkptr, maxblkid; - ret = nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_MAC, &buf, &len); - if (ret != 0 || len != WRAPPING_MAC_LEN) { - ret = SET_ERROR(EINVAL); - goto error; - } - - /* - * We don't support receiving old on-disk formats. The version 0 - * implementation protected several fields in an objset that were - * not always portable during a raw receive. As a result, we call - * the old version an on-disk errata #3. - */ - ret = nvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_VERSION, &version); - if (ret != 0 || version != ZIO_CRYPT_KEY_CURRENT_VERSION) { - ret = SET_ERROR(ENOTSUP); - goto error; - } - - ret = nvlist_lookup_uint8_array(nvl, "portable_mac", &buf, &len); - if (ret != 0 || len != ZIO_OBJSET_MAC_LEN) { - ret = SET_ERROR(EINVAL); - goto error; - } - - ret = nvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), - &intval); - if (ret != 0 || intval >= ZFS_KEYFORMAT_FORMATS || - intval == ZFS_KEYFORMAT_NONE) { - ret = SET_ERROR(EINVAL); - goto error; - } - - is_passphrase = (intval == ZFS_KEYFORMAT_PASSPHRASE); - - /* - * for raw receives we allow any number of pbkdf2iters since there - * won't be a chance for the user to change it. - */ - ret = nvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), - &intval); - if (ret != 0 || (is_passphrase == (intval == 0))) { - ret = SET_ERROR(EINVAL); - goto error; - } - - ret = nvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), - &intval); - if (ret != 0 || (is_passphrase == (intval == 0))) { - ret = SET_ERROR(EINVAL); - goto error; - } + if (ostype != DMU_OST_ZFS && ostype != DMU_OST_ZVOL) + return (SET_ERROR(EINVAL)); /* raw receives also need info about the structure of the metadnode */ - ret = nvlist_lookup_uint64(nvl, "mdn_checksum", &intval); - if (ret != 0 || intval >= ZIO_CHECKSUM_LEGACY_FUNCTIONS) { - ret = SET_ERROR(EINVAL); - goto error; - } - ret = nvlist_lookup_uint64(nvl, "mdn_compress", &intval); - if (ret != 0 || intval >= ZIO_COMPRESS_LEGACY_FUNCTIONS) { - ret = SET_ERROR(EINVAL); - goto error; - } + if (ret != 0 || intval >= ZIO_COMPRESS_LEGACY_FUNCTIONS) + return (SET_ERROR(EINVAL)); + + ret = nvlist_lookup_uint64(nvl, "mdn_checksum", &intval); + if (ret != 0 || intval >= ZIO_CHECKSUM_LEGACY_FUNCTIONS) + return (SET_ERROR(EINVAL)); ret = nvlist_lookup_uint64(nvl, "mdn_nlevels", &nlevels); - if (ret != 0 || nlevels > DN_MAX_LEVELS) { - ret = SET_ERROR(EINVAL); - goto error; - } + if (ret != 0 || nlevels > DN_MAX_LEVELS) + return (SET_ERROR(EINVAL)); ret = nvlist_lookup_uint64(nvl, "mdn_blksz", &blksz); - if (ret != 0 || blksz < SPA_MINBLOCKSIZE) { - ret = SET_ERROR(EINVAL); - goto error; - } else if (blksz > spa_maxblocksize(tx->tx_pool->dp_spa)) { - ret = SET_ERROR(ENOTSUP); - goto error; - } + if (ret != 0 || blksz < SPA_MINBLOCKSIZE) + return (SET_ERROR(EINVAL)); + else if (blksz > spa_maxblocksize(tx->tx_pool->dp_spa)) + return (SET_ERROR(ENOTSUP)); ret = nvlist_lookup_uint64(nvl, "mdn_indblkshift", &ibs); - if (ret != 0 || ibs < DN_MIN_INDBLKSHIFT || - ibs > DN_MAX_INDBLKSHIFT) { - ret = SET_ERROR(ENOTSUP); - goto error; - } + if (ret != 0 || ibs < DN_MIN_INDBLKSHIFT || ibs > DN_MAX_INDBLKSHIFT) + return (SET_ERROR(ENOTSUP)); ret = nvlist_lookup_uint64(nvl, "mdn_nblkptr", &nblkptr); - if (ret != 0 || nblkptr != DN_MAX_NBLKPTR) { - ret = SET_ERROR(ENOTSUP); - goto error; - } + if (ret != 0 || nblkptr != DN_MAX_NBLKPTR) + return (SET_ERROR(ENOTSUP)); ret = nvlist_lookup_uint64(nvl, "mdn_maxblkid", &maxblkid); - if (ret != 0) { - ret = SET_ERROR(EINVAL); - goto error; - } + if (ret != 0) + return (SET_ERROR(EINVAL)); + + ret = nvlist_lookup_uint8_array(nvl, "portable_mac", &buf, &len); + if (ret != 0 || len != ZIO_OBJSET_MAC_LEN) + return (SET_ERROR(EINVAL)); ret = dmu_objset_from_ds(ds, &os); if (ret != 0) - goto error; + return (ret); /* * Useraccounting is not portable and must be done with the keys loaded. @@ -2082,80 +1958,54 @@ dsl_crypto_recv_key_check(void *arg, dmu_tx_t *tx) mdn = DMU_META_DNODE(os); /* - * If we already created the objset, make sure its unchangable + * If we already created the objset, make sure its unchangeable * properties match the ones received in the nvlist. */ rrw_enter(&ds->ds_bp_rwlock, RW_READER, FTAG); if (!BP_IS_HOLE(dsl_dataset_get_blkptr(ds)) && (mdn->dn_nlevels != nlevels || mdn->dn_datablksz != blksz || mdn->dn_indblkshift != ibs || mdn->dn_nblkptr != nblkptr)) { - ret = SET_ERROR(EINVAL); - goto error; + rrw_exit(&ds->ds_bp_rwlock, FTAG); + return (SET_ERROR(EINVAL)); } rrw_exit(&ds->ds_bp_rwlock, FTAG); - dsl_dataset_rele(ds, FTAG); return (0); - -error: - if (ds != NULL) - dsl_dataset_rele(ds, FTAG); - return (ret); } static void -dsl_crypto_recv_key_sync(void *arg, dmu_tx_t *tx) +dsl_crypto_recv_raw_objset_sync(dsl_dataset_t *ds, dmu_objset_type_t ostype, + nvlist_t *nvl, dmu_tx_t *tx) { - dsl_crypto_recv_key_arg_t *dcrka = arg; - uint64_t dsobj = dcrka->dcrka_dsobj; - nvlist_t *nvl = dcrka->dcrka_nvl; dsl_pool_t *dp = tx->tx_pool; - objset_t *mos = dp->dp_meta_objset; - dsl_dataset_t *ds; objset_t *os; dnode_t *mdn; - uint8_t *keydata, *hmac_keydata, *iv, *mac, *portable_mac; + zio_t *zio; + uint8_t *portable_mac; uint_t len; - uint64_t rddobj, one = 1; - uint64_t version = ZIO_CRYPT_KEY_CURRENT_VERSION; - uint64_t crypt, guid, keyformat, iters, salt; uint64_t compress, checksum, nlevels, blksz, ibs, maxblkid; - char *keylocation = "prompt"; + boolean_t newds = B_FALSE; - VERIFY0(dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds)); VERIFY0(dmu_objset_from_ds(ds, &os)); mdn = DMU_META_DNODE(os); - /* lookup the values we need to create the DSL Crypto Key and objset */ - crypt = fnvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_CRYPTO_SUITE); - guid = fnvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_GUID); - keyformat = fnvlist_lookup_uint64(nvl, - zfs_prop_to_name(ZFS_PROP_KEYFORMAT)); - iters = fnvlist_lookup_uint64(nvl, - zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS)); - salt = fnvlist_lookup_uint64(nvl, - zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT)); - VERIFY0(nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_MASTER_KEY, - &keydata, &len)); - VERIFY0(nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_HMAC_KEY, - &hmac_keydata, &len)); - VERIFY0(nvlist_lookup_uint8_array(nvl, "portable_mac", &portable_mac, - &len)); - VERIFY0(nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_IV, &iv, &len)); - VERIFY0(nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_MAC, &mac, &len)); + /* fetch the values we need from the nvlist */ compress = fnvlist_lookup_uint64(nvl, "mdn_compress"); checksum = fnvlist_lookup_uint64(nvl, "mdn_checksum"); nlevels = fnvlist_lookup_uint64(nvl, "mdn_nlevels"); blksz = fnvlist_lookup_uint64(nvl, "mdn_blksz"); ibs = fnvlist_lookup_uint64(nvl, "mdn_indblkshift"); maxblkid = fnvlist_lookup_uint64(nvl, "mdn_maxblkid"); + VERIFY0(nvlist_lookup_uint8_array(nvl, "portable_mac", &portable_mac, + &len)); /* if we haven't created an objset for the ds yet, do that now */ rrw_enter(&ds->ds_bp_rwlock, RW_READER, FTAG); if (BP_IS_HOLE(dsl_dataset_get_blkptr(ds))) { (void) dmu_objset_create_impl_dnstats(dp->dp_spa, ds, - dsl_dataset_get_blkptr(ds), dcrka->dcrka_ostype, nlevels, - blksz, ibs, tx); + dsl_dataset_get_blkptr(ds), ostype, nlevels, blksz, + ibs, tx); + newds = B_TRUE; } rrw_exit(&ds->ds_bp_rwlock, FTAG); @@ -2178,35 +2028,172 @@ dsl_crypto_recv_key_sync(void *arg, dmu_tx_t *tx) dnode_new_blkid(mdn, maxblkid, tx, B_FALSE); rw_exit(&mdn->dn_struct_rwlock); - dsl_dataset_dirty(ds, tx); + /* + * We can't normally dirty the dataset in syncing context unless + * we are creating a new dataset. In this case, we perform a + * pseudo txg sync here instead. + */ + if (newds) { + dsl_dataset_dirty(ds, tx); + } else { + zio = zio_root(dp->dp_spa, NULL, NULL, ZIO_FLAG_MUSTSUCCEED); + dsl_dataset_sync(ds, zio, tx); + VERIFY0(zio_wait(zio)); + + /* dsl_dataset_sync_done will drop this reference. */ + dmu_buf_add_ref(ds->ds_dbuf, ds); + dsl_dataset_sync_done(ds, tx); + } +} + +int +dsl_crypto_recv_raw_key_check(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx) +{ + int ret; + objset_t *mos = tx->tx_pool->dp_meta_objset; + uint8_t *buf = NULL; + uint_t len; + uint64_t intval, guid, version; + boolean_t is_passphrase = B_FALSE; + + ASSERT(dsl_dataset_phys(ds)->ds_flags & DS_FLAG_INCONSISTENT); + + /* + * Read and check all the encryption values from the nvlist. We need + * all of the fields of a DSL Crypto Key, as well as a fully specified + * wrapping key. + */ + ret = nvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_CRYPTO_SUITE, &intval); + if (ret != 0 || intval >= ZIO_CRYPT_FUNCTIONS || + intval <= ZIO_CRYPT_OFF) + return (SET_ERROR(EINVAL)); + + ret = nvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_GUID, &intval); + if (ret != 0) + return (SET_ERROR(EINVAL)); + + /* + * If this is an incremental receive make sure the given key guid + * matches the one we already have. + */ + if (ds->ds_dir->dd_crypto_obj != 0) { + ret = zap_lookup(mos, ds->ds_dir->dd_crypto_obj, + DSL_CRYPTO_KEY_GUID, 8, 1, &guid); + if (ret != 0) + return (ret); + if (intval != guid) + return (SET_ERROR(EACCES)); + } + + ret = nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_MASTER_KEY, + &buf, &len); + if (ret != 0 || len != MASTER_KEY_MAX_LEN) + return (SET_ERROR(EINVAL)); + + ret = nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_HMAC_KEY, + &buf, &len); + if (ret != 0 || len != SHA512_HMAC_KEYLEN) + return (SET_ERROR(EINVAL)); + + ret = nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_IV, &buf, &len); + if (ret != 0 || len != WRAPPING_IV_LEN) + return (SET_ERROR(EINVAL)); + + ret = nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_MAC, &buf, &len); + if (ret != 0 || len != WRAPPING_MAC_LEN) + return (SET_ERROR(EINVAL)); + + /* + * We don't support receiving old on-disk formats. The version 0 + * implementation protected several fields in an objset that were + * not always portable during a raw receive. As a result, we call + * the old version an on-disk errata #3. + */ + ret = nvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_VERSION, &version); + if (ret != 0 || version != ZIO_CRYPT_KEY_CURRENT_VERSION) + return (SET_ERROR(ENOTSUP)); + + ret = nvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_KEYFORMAT), + &intval); + if (ret != 0 || intval >= ZFS_KEYFORMAT_FORMATS || + intval == ZFS_KEYFORMAT_NONE) + return (SET_ERROR(EINVAL)); + + is_passphrase = (intval == ZFS_KEYFORMAT_PASSPHRASE); + + /* + * for raw receives we allow any number of pbkdf2iters since there + * won't be a chance for the user to change it. + */ + ret = nvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), + &intval); + if (ret != 0 || (is_passphrase == (intval == 0))) + return (SET_ERROR(EINVAL)); + + ret = nvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), + &intval); + if (ret != 0 || (is_passphrase == (intval == 0))) + return (SET_ERROR(EINVAL)); + + return (0); +} + +void +dsl_crypto_recv_raw_key_sync(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx) +{ + dsl_pool_t *dp = tx->tx_pool; + objset_t *mos = dp->dp_meta_objset; + dsl_dir_t *dd = ds->ds_dir; + uint_t len; + uint64_t rddobj, one = 1; + uint8_t *keydata, *hmac_keydata, *iv, *mac; + uint64_t crypt, guid, keyformat, iters, salt; + uint64_t version = ZIO_CRYPT_KEY_CURRENT_VERSION; + char *keylocation = "prompt"; + + /* lookup the values we need to create the DSL Crypto Key */ + crypt = fnvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_CRYPTO_SUITE); + guid = fnvlist_lookup_uint64(nvl, DSL_CRYPTO_KEY_GUID); + keyformat = fnvlist_lookup_uint64(nvl, + zfs_prop_to_name(ZFS_PROP_KEYFORMAT)); + iters = fnvlist_lookup_uint64(nvl, + zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS)); + salt = fnvlist_lookup_uint64(nvl, + zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT)); + VERIFY0(nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_MASTER_KEY, + &keydata, &len)); + VERIFY0(nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_HMAC_KEY, + &hmac_keydata, &len)); + VERIFY0(nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_IV, &iv, &len)); + VERIFY0(nvlist_lookup_uint8_array(nvl, DSL_CRYPTO_KEY_MAC, &mac, &len)); /* if this is a new dataset setup the DSL Crypto Key. */ - if (ds->ds_dir->dd_crypto_obj == 0) { + if (dd->dd_crypto_obj == 0) { /* zapify the dsl dir so we can add the key object to it */ - dmu_buf_will_dirty(ds->ds_dir->dd_dbuf, tx); - dsl_dir_zapify(ds->ds_dir, tx); + dmu_buf_will_dirty(dd->dd_dbuf, tx); + dsl_dir_zapify(dd, tx); /* create the DSL Crypto Key on disk and activate the feature */ - ds->ds_dir->dd_crypto_obj = zap_create(mos, + dd->dd_crypto_obj = zap_create(mos, DMU_OTN_ZAP_METADATA, DMU_OT_NONE, 0, tx); VERIFY0(zap_update(tx->tx_pool->dp_meta_objset, - ds->ds_dir->dd_crypto_obj, DSL_CRYPTO_KEY_REFCOUNT, + dd->dd_crypto_obj, DSL_CRYPTO_KEY_REFCOUNT, sizeof (uint64_t), 1, &one, tx)); VERIFY0(zap_update(tx->tx_pool->dp_meta_objset, - ds->ds_dir->dd_crypto_obj, DSL_CRYPTO_KEY_VERSION, + dd->dd_crypto_obj, DSL_CRYPTO_KEY_VERSION, sizeof (uint64_t), 1, &version, tx)); - dsl_dataset_activate_feature(dsobj, SPA_FEATURE_ENCRYPTION, tx); + dsl_dataset_activate_feature(ds->ds_object, + SPA_FEATURE_ENCRYPTION, tx); ds->ds_feature_inuse[SPA_FEATURE_ENCRYPTION] = B_TRUE; /* save the dd_crypto_obj on disk */ - VERIFY0(zap_add(mos, ds->ds_dir->dd_object, - DD_FIELD_CRYPTO_KEY_OBJ, sizeof (uint64_t), 1, - &ds->ds_dir->dd_crypto_obj, tx)); + VERIFY0(zap_add(mos, dd->dd_object, DD_FIELD_CRYPTO_KEY_OBJ, + sizeof (uint64_t), 1, &dd->dd_crypto_obj, tx)); /* * Set the keylocation to prompt by default. If keylocation - * has been provided via the properties, this will be overriden + * has been provided via the properties, this will be overridden * later. */ dsl_prop_set_sync_impl(ds, @@ -2214,16 +2201,64 @@ dsl_crypto_recv_key_sync(void *arg, dmu_tx_t *tx) ZPROP_SRC_LOCAL, 1, strlen(keylocation) + 1, keylocation, tx); - rddobj = ds->ds_dir->dd_object; + rddobj = dd->dd_object; } else { - VERIFY0(dsl_dir_get_encryption_root_ddobj(ds->ds_dir, &rddobj)); + VERIFY0(dsl_dir_get_encryption_root_ddobj(dd, &rddobj)); } /* sync the key data to the ZAP object on disk */ - dsl_crypto_key_sync_impl(mos, ds->ds_dir->dd_crypto_obj, crypt, + dsl_crypto_key_sync_impl(mos, dd->dd_crypto_obj, crypt, rddobj, guid, iv, mac, keydata, hmac_keydata, keyformat, salt, iters, tx); +} +int +dsl_crypto_recv_key_check(void *arg, dmu_tx_t *tx) +{ + int ret; + dsl_crypto_recv_key_arg_t *dcrka = arg; + dsl_dataset_t *ds = NULL; + + ret = dsl_dataset_hold_obj(tx->tx_pool, dcrka->dcrka_dsobj, + FTAG, &ds); + if (ret != 0) + goto error; + + ret = dsl_crypto_recv_raw_objset_check(ds, + dcrka->dcrka_ostype, dcrka->dcrka_nvl, tx); + if (ret != 0) + goto error; + + /* + * We run this check even if we won't be doing this part of + * the receive now so that we don't make the user wait until + * the receive finishes to fail. + */ + ret = dsl_crypto_recv_raw_key_check(ds, dcrka->dcrka_nvl, tx); + if (ret != 0) + goto error; + + dsl_dataset_rele(ds, FTAG); + return (0); + +error: + if (ds != NULL) + dsl_dataset_rele(ds, FTAG); + return (ret); +} + +void +dsl_crypto_recv_key_sync(void *arg, dmu_tx_t *tx) +{ + dsl_crypto_recv_key_arg_t *dcrka = arg; + dsl_dataset_t *ds; + + VERIFY0(dsl_dataset_hold_obj(tx->tx_pool, dcrka->dcrka_dsobj, + FTAG, &ds)); + dsl_crypto_recv_raw_objset_sync(ds, dcrka->dcrka_ostype, + dcrka->dcrka_nvl, tx); + if (dcrka->dcrka_do_key) + dsl_crypto_recv_raw_key_sync(ds, dcrka->dcrka_nvl, tx); dsl_dataset_rele(ds, FTAG); } @@ -2233,14 +2268,15 @@ dsl_crypto_recv_key_sync(void *arg, dmu_tx_t *tx) * without wrapping it. */ int -dsl_crypto_recv_key(const char *poolname, uint64_t dsobj, - dmu_objset_type_t ostype, nvlist_t *nvl) +dsl_crypto_recv_raw(const char *poolname, uint64_t dsobj, + dmu_objset_type_t ostype, nvlist_t *nvl, boolean_t do_key) { dsl_crypto_recv_key_arg_t dcrka; dcrka.dcrka_dsobj = dsobj; - dcrka.dcrka_nvl = nvl; dcrka.dcrka_ostype = ostype; + dcrka.dcrka_nvl = nvl; + dcrka.dcrka_do_key = do_key; return (dsl_sync_task(poolname, dsl_crypto_recv_key_check, dsl_crypto_recv_key_sync, &dcrka, 1, ZFS_SPACE_CHECK_NORMAL)); diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw_incremental.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw_incremental.ksh index c813809a0..48878327b 100755 --- a/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw_incremental.ksh +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw_incremental.ksh @@ -30,9 +30,12 @@ # 3. Create a file and get its checksum # 4. Snapshot the dataset # 5. Attempt to receive a raw send stream of the first snapshot -# 6. Attempt to receive a raw incremental send stream of the second snapshot -# 7. Attempt load the key and mount the dataset -# 8. Verify the cheksum of the file is the same as the original +# 6. Change the passphrase required to unlock the original filesystem +# 7. Attempt and intentionally fail to receive the second snapshot +# 8. Verify that the required passphrase hasn't changed on the receive side +# 9. Attempt a real raw incremental send stream of the second snapshot +# 10. Attempt load the key and mount the dataset +# 11. Verify the checksum of the file is the same as the original # verify_runnable "both" @@ -44,13 +47,18 @@ function cleanup datasetexists $TESTPOOL/$TESTFS2 && \ log_must zfs destroy -r $TESTPOOL/$TESTFS2 + + [[ -f $ibackup ]] && log_must rm -f $ibackup } log_onexit cleanup log_assert "ZFS should receive streams from raw incremental sends" +typeset ibackup="/var/tmp/ibackup.$$" +typeset ibackup_trunc="/var/tmp/ibackup_trunc.$$" typeset passphrase="password" +typeset passphrase2="password2" typeset snap1="$TESTPOOL/$TESTFS1@snap1" typeset snap2="$TESTPOOL/$TESTFS1@snap2" @@ -65,8 +73,20 @@ typeset checksum=$(md5sum /$TESTPOOL/$TESTFS1/$TESTFILE0 | awk '{ print $1 }') log_must zfs snapshot $snap2 log_must eval "zfs send -w $snap1 | zfs receive $TESTPOOL/$TESTFS2" -log_must eval "zfs send -w -i $snap1 $snap2 | zfs receive $TESTPOOL/$TESTFS2" -log_must eval "echo $passphrase | zfs mount -l $TESTPOOL/$TESTFS2" +log_must eval "echo $passphrase2 | zfs change-key $TESTPOOL/$TESTFS1" +log_must eval "zfs send -w -i $snap1 $snap2 > $ibackup" + +typeset trunc_size=$(stat -c %s $ibackup) +trunc_size=$(expr $trunc_size - 64) +log_must cp $ibackup $ibackup_trunc +log_must truncate -s $trunc_size $ibackup_trunc +log_mustnot eval "zfs receive $TESTPOOL/$TESTFS2 < $ibackup_trunc" +log_mustnot eval "echo $passphrase2 | zfs load-key $TESTPOOL/$TESTFS2" +log_must eval "echo $passphrase | zfs load-key $TESTPOOL/$TESTFS2" +log_must zfs unload-key $TESTPOOL/$TESTFS2 + +log_must eval "zfs receive $TESTPOOL/$TESTFS2 < $ibackup" +log_must eval "echo $passphrase2 | zfs mount -l $TESTPOOL/$TESTFS2" typeset cksum1=$(md5sum /$TESTPOOL/$TESTFS2/$TESTFILE0 | awk '{ print $1 }') [[ "$cksum1" == "$checksum" ]] || \ |