]> granicus.if.org Git - zfs/commitdiff
Detect and prevent mixed raw and non-raw sends
authorTom Caputi <tcaputi@datto.com>
Mon, 4 Feb 2019 19:24:55 +0000 (14:24 -0500)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Wed, 13 Mar 2019 18:00:43 +0000 (11:00 -0700)
Currently, there is an issue in the raw receive code where
raw receives are allowed to happen on top of previously
non-raw received datasets. This is a problem because the
source-side dataset doesn't know about how the blocks on
the destination were encrypted. As a result, any MAC in
the objset's checksum-of-MACs tree that is a parent of both
blocks encrypted on the source and blocks encrypted by the
destination will be incorrect. This will result in
authentication errors when we decrypt the dataset.

This patch fixes this issue by adding a new check to the
raw receive code. The code now maintains an "IVset guid",
which acts as an identifier for the set of IVs used to
encrypt a given snapshot. When a snapshot is raw received,
the destination snapshot will take this value from the
DRR_BEGIN payload. Non-raw receives and normal "zfs snap"
operations will cause ZFS to generate a new IVset guid.
When a raw incremental stream is received, ZFS will check
that the "from" IVset guid in the stream matches that of
the "from" destination snapshot. If they do not match, the
code will error out the receive, preventing the problem.

This patch requires an on-disk format change to add the
IVset guids to snapshots and bookmarks. As a result, this
patch has errata handling and a tunable to help affected
users resolve the issue with as little interruption as
possible.

Reviewed-by: Paul Dagnelie <pcd@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Matt Ahrens <mahrens@delphix.com>
Signed-off-by: Tom Caputi <tcaputi@datto.com>
Closes #8308

27 files changed:
cmd/zpool/zpool_main.c
include/sys/dmu_recv.h
include/sys/dsl_crypt.h
include/sys/dsl_dataset.h
include/sys/fs/zfs.h
lib/libzfs/libzfs_iter.c
lib/libzfs/libzfs_sendrecv.c
lib/libzfs_core/libzfs_core.c
man/man8/zfs.8
module/zcommon/zfeature_common.c
module/zcommon/zfs_prop.c
module/zfs/dmu_recv.c
module/zfs/dmu_send.c
module/zfs/dsl_bookmark.c
module/zfs/dsl_crypt.c
module/zfs/dsl_dataset.c
module/zfs/spa.c
module/zfs/zcp_get.c
tests/runfiles/linux.run
tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
tests/zfs-tests/tests/functional/cli_root/zpool_import/Makefile.am
tests/zfs-tests/tests/functional/cli_root/zpool_import/blockfiles/Makefile.am
tests/zfs-tests/tests/functional/cli_root/zpool_import/blockfiles/missing_ivset.dat.bz2 [new file with mode: 0644]
tests/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_errata3.ksh
tests/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_errata4.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/rsend/Makefile.am
tests/zfs-tests/tests/functional/rsend/send_mixed_raw.ksh [new file with mode: 0755]

index 68723a0e2a8f8e7d3c8daf12a3d253cffa3dc89a..c36003c987c8a32955ddf7f0211daa354a519bea 100644 (file)
@@ -2478,6 +2478,17 @@ show_import(nvlist_t *config)
                                    "old ones.\n"));
                                break;
 
+                       case ZPOOL_ERRATA_ZOL_8308_ENCRYPTION:
+                               (void) printf(gettext(" action: Existing "
+                                   "encrypted datasets contain an on-disk "
+                                   "incompatibility which\n\tmay cause "
+                                   "on-disk corruption with 'zfs recv' and "
+                                   "which needs to be\n\tcorrected. Enable "
+                                   "the bookmark_v2 feature and backup "
+                                   "these datasets to new encrypted "
+                                   "datasets and\n\tdestroy the "
+                                   "old ones.\n"));
+                               break;
                        default:
                                /*
                                 * All errata must contain an action message.
@@ -7401,6 +7412,17 @@ status_callback(zpool_handle_t *zhp, void *data)
                            "mount existing encrypted datasets readonly.\n"));
                        break;
 
+               case ZPOOL_ERRATA_ZOL_8308_ENCRYPTION:
+                       (void) printf(gettext("\tExisting encrypted datasets "
+                           "contain an on-disk incompatibility\n\twhich "
+                           "needs to be corrected.\n"));
+                       (void) printf(gettext("action: To correct the issue "
+                           "enable the bookmark_v2 feature and "
+                           "backup\n\texisting encrypted datasets to new "
+                           "encrypted datasets and\n\tdestroy the old "
+                           "ones.\n"));
+                       break;
+
                default:
                        /*
                         * All errata which allow the pool to be imported
index 4a0d477115898c100d7df6714d0c028681db1c5d..90002026bec95bb8039ad68647a6c28a46aad9e5 100644 (file)
@@ -51,7 +51,9 @@ typedef struct dmu_recv_cookie {
        struct avl_tree *drc_guid_to_ds_map;
        nvlist_t *drc_keynvl;
        zio_cksum_t drc_cksum;
+       uint64_t drc_fromsnapobj;
        uint64_t drc_newsnapobj;
+       uint64_t drc_ivset_guid;
        void *drc_owner;
        cred_t *drc_cred;
 } dmu_recv_cookie_t;
index e01d53527e7e840b6073d7e3c14585cdc65a4d10..c2c0a548a4880012ac08d87edac182e5a4359518 100644 (file)
@@ -189,12 +189,13 @@ void key_mapping_rele(spa_t *spa, dsl_key_mapping_t *km, void *tag);
 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_populate_key_nvlist(struct dsl_dataset *ds,
+    uint64_t from_ivset_guid, nvlist_t **nvl_out);
 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,
+int dsl_crypto_recv_raw(const char *poolname, uint64_t dsobj, uint64_t fromobj,
     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);
index 47a46f07d3fed46845b8b95d3867dbdb4b02efbb..c464c70bd2da895c01e321586f1278dae660532d 100644 (file)
@@ -114,6 +114,12 @@ struct dsl_key_mapping;
  */
 #define        DS_FIELD_REMAP_DEADLIST "com.delphix:remap_deadlist"
 
+/*
+ * This field is set to the ivset guid for encrypted snapshots. This is used
+ * for validating raw receives.
+ */
+#define        DS_FIELD_IVSET_GUID     "com.datto:ivset_guid"
+
 /*
  * DS_FLAG_CI_DATASET is set if the dataset contains a file system whose
  * name lookups should be performed case-insensitively.
index c4d26eb8788e1bf56d52ee78dd634261c91a03ca..d25e8b5a5bb5096cf7a7575a9d763e60edf8f83d 100644 (file)
@@ -183,6 +183,7 @@ typedef enum {
        ZFS_PROP_KEYSTATUS,
        ZFS_PROP_REMAPTXG,              /* not exposed to the user */
        ZFS_PROP_SPECIAL_SMALL_BLOCKS,
+       ZFS_PROP_IVSET_GUID,            /* not exposed to the user */
        ZFS_NUM_PROPS
 } zfs_prop_t;
 
@@ -975,6 +976,7 @@ typedef enum zpool_errata {
        ZPOOL_ERRATA_ZOL_2094_SCRUB,
        ZPOOL_ERRATA_ZOL_2094_ASYNC_DESTROY,
        ZPOOL_ERRATA_ZOL_6845_ENCRYPTION,
+       ZPOOL_ERRATA_ZOL_8308_ENCRYPTION,
 } zpool_errata_t;
 
 /*
@@ -1264,6 +1266,8 @@ typedef enum {
        ZFS_ERR_IOC_ARG_REQUIRED,
        ZFS_ERR_IOC_ARG_BADTYPE,
        ZFS_ERR_WRONG_PARENT,
+       ZFS_ERR_FROM_IVSET_GUID_MISSING,
+       ZFS_ERR_FROM_IVSET_GUID_MISMATCH,
 } zfs_errno_t;
 
 /*
index e765a7ee74f9659abfdb2764f60dd67c4cd7a717..5e9a1ecae7ea4138de5403a65a6bc9c0b1ea871f 100644 (file)
@@ -215,6 +215,7 @@ zfs_iter_bookmarks(zfs_handle_t *zhp, zfs_iter_f func, void *data)
        fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_GUID));
        fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_CREATETXG));
        fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_CREATION));
+       fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_IVSET_GUID));
 
        if ((err = lzc_get_bookmarks(zhp->zfs_name, props, &bmarks)) != 0)
                goto out;
index 8096089f62c0b7ace8d0ba495bc1e587f4e46acc..c140e5d30335389b4da9d81465baf13529561a1d 100644 (file)
@@ -4392,6 +4392,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                            "destination %s space quota exceeded."), name);
                        (void) zfs_error(hdl, EZFS_NOSPC, errbuf);
                        break;
+               case ZFS_ERR_FROM_IVSET_GUID_MISSING:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "IV set guid missing. See errata %u at"
+                           "http://zfsonlinux.org/msg/ZFS-8000-ER"),
+                           ZPOOL_ERRATA_ZOL_8308_ENCRYPTION);
+                       (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+                       break;
+               case ZFS_ERR_FROM_IVSET_GUID_MISMATCH:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "IV set guid mismatch. See the 'zfs receive' "
+                           "man page section\n discussing the limitations "
+                           "of raw encrypted send streams."));
+                       (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+                       break;
                case EBUSY:
                        if (hastoken) {
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
index 524a637e4a38e8eafed2dff8f855d95cea0841d0..6bbe76e0a426c4b405a2d158133d497639da6bd8 100644 (file)
@@ -1124,6 +1124,7 @@ lzc_bookmark(nvlist_t *bookmarks, nvlist_t **errlist)
  * "guid" - globally unique identifier of the snapshot it refers to
  * "createtxg" - txg when the snapshot it refers to was created
  * "creation" - timestamp when the snapshot it refers to was created
+ * "ivsetguid" - IVset guid for identifying encrypted snapshots
  *
  * The format of the returned nvlist as follows:
  * <short name of bookmark> -> {
index b51c91daa0a5aff2f814d8c161bcf62eb1bf82ce..52143f7fcb94f5a8f48105874c8eaa95fd3e9d0e 100644 (file)
@@ -3845,6 +3845,31 @@ parameters with the
 .Fl o
 options.
 .Pp
+The added security provided by raw sends adds some restrictions to the send
+and receive process. ZFS will not allow a mix of raw receives and non-raw
+receives. Specifically, any raw incremental receives that are attempted after
+a non-raw receive will fail. Non-raw receives do not have this restriction and,
+therefore, are always possible. Because of this, it is best practice to always
+use either raw sends for their security benefits or non-raw sends for their
+flexibility when working with encrypted datasets, but not a combination.
+.Pp
+The reason for this restriction stems from the inherent restrictions of the
+AEAD ciphers that ZFS uses to encrypt data. When using ZFS native encryption,
+each block of data is encrypted against a randomly generated number known as
+the "initialization vector" (IV), which is stored in the filesystem metadata.
+This number is required by the encryption algorithms whenever the data is to
+be decrypted. Together, all of the IVs provided for all of the blocks in a
+given snapshot are collectively called an "IV set". When ZFS performs a raw
+send, the IV set is transferred from the source to the destination in the send
+stream. When ZFS performs a non-raw send, the data is decrypted by the source
+system and re-encrypted by the destination system, creating a snapshot with
+effectively the same data, but a different IV set. In order for decryption to
+work after a raw send, ZFS must ensure that the IV set used on both the source
+and destination side match. When an incremental raw receive is performed on
+top of an existing snapshot, ZFS will check to confirm that the "from"
+snapshot on both the source and destination were using the same IV set,
+ensuring the new IV set is consistent.
+.Pp
 The name of the snapshot
 .Pq and file system, if a full stream is received
 that this subcommand creates depends on the argument type and the use of the
index 74ff2b65702e3e9d814031490050c3a21a867b70..dc0c1161f8b6529447bcdb9c1428ba779df4f35a 100644 (file)
@@ -442,6 +442,7 @@ zpool_feature_init(void)
        {
        static const spa_feature_t bookmark_v2_deps[] = {
                SPA_FEATURE_EXTENSIBLE_DATASET,
+               SPA_FEATURE_BOOKMARKS,
                SPA_FEATURE_NONE
        };
        zfeature_register(SPA_FEATURE_BOOKMARK_V2,
@@ -453,6 +454,7 @@ zpool_feature_init(void)
        {
        static const spa_feature_t encryption_deps[] = {
                SPA_FEATURE_EXTENSIBLE_DATASET,
+               SPA_FEATURE_BOOKMARK_V2,
                SPA_FEATURE_NONE
        };
        zfeature_register(SPA_FEATURE_ENCRYPTION,
index 4d5bc39e5a4af453c800ff95561fd726fc8ea259..dab749138a6b0871b3d2922d795655dc2b444379 100644 (file)
@@ -559,6 +559,9 @@ zfs_prop_init(void)
            PROP_READONLY, ZFS_TYPE_DATASET, "UNIQUE");
        zprop_register_hidden(ZFS_PROP_INCONSISTENT, "inconsistent",
            PROP_TYPE_NUMBER, PROP_READONLY, ZFS_TYPE_DATASET, "INCONSISTENT");
+       zprop_register_hidden(ZFS_PROP_IVSET_GUID, "ivsetguid",
+           PROP_TYPE_NUMBER, PROP_READONLY,
+           ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "IVSETGUID");
        zprop_register_hidden(ZFS_PROP_PREV_SNAP, "prevsnap", PROP_TYPE_STRING,
            PROP_READONLY, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, "PREVSNAP");
        zprop_register_hidden(ZFS_PROP_PBKDF2_SALT, "pbkdf2salt",
index 87946031854b9a00812773b1630d064b475bac73..e49a0f4aa467b2d145abfd76ad299fe68fb7ce87 100644 (file)
@@ -72,7 +72,6 @@ typedef struct dmu_recv_begin_arg {
        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
@@ -128,7 +127,7 @@ 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 */
+               /* Can't raw receive on top of an unencrypted dataset */
                if (!encrypted && raw)
                        return (SET_ERROR(EINVAL));
 
@@ -155,7 +154,7 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
                        return (SET_ERROR(ENODEV));
 
                if (drba->drba_cookie->drc_force) {
-                       drba->drba_snapobj = obj;
+                       drba->drba_cookie->drc_fromsnapobj = obj;
                } else {
                        /*
                         * If we are not forcing, there must be no
@@ -165,7 +164,8 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
                                dsl_dataset_rele(snap, FTAG);
                                return (SET_ERROR(ETXTBSY));
                        }
-                       drba->drba_snapobj = ds->ds_prev->ds_object;
+                       drba->drba_cookie->drc_fromsnapobj =
+                           ds->ds_prev->ds_object;
                }
 
                dsl_dataset_rele(snap, FTAG);
@@ -200,7 +200,7 @@ recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds,
                                return (SET_ERROR(EINVAL));
                }
 
-               drba->drba_snapobj = 0;
+               drba->drba_cookie->drc_fromsnapobj = 0;
        }
 
        return (0);
@@ -440,7 +440,7 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
         * 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 &&
+       if (dcp == NULL && drba->drba_cookie->drc_fromsnapobj == 0 &&
            drba->drba_origin == NULL) {
                ASSERT3P(dcp, ==, NULL);
                dcp = &dummy_dcp;
@@ -454,15 +454,15 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx)
                /* create temporary clone */
                dsl_dataset_t *snap = NULL;
 
-               if (drba->drba_snapobj != 0) {
+               if (drba->drba_cookie->drc_fromsnapobj != 0) {
                        VERIFY0(dsl_dataset_hold_obj(dp,
-                           drba->drba_snapobj, FTAG, &snap));
+                           drba->drba_cookie->drc_fromsnapobj, FTAG, &snap));
                        ASSERT3P(dcp, ==, NULL);
                }
 
                dsobj = dsl_dataset_create_sync(ds->ds_dir, recv_clone_name,
                    snap, crflags, drba->drba_cred, dcp, tx);
-               if (drba->drba_snapobj != 0)
+               if (drba->drba_cookie->drc_fromsnapobj != 0)
                        dsl_dataset_rele(snap, FTAG);
                dsl_dataset_rele_flags(ds, dsflags, FTAG);
        } else {
@@ -2526,11 +2526,16 @@ dmu_recv_stream(dmu_recv_cookie_t *drc, vnode_t *vp, offset_t *voffp,
                 * 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, drc->drc_newfs);
+                   drc->drc_ds->ds_object, drc->drc_fromsnapobj,
+                   drc->drc_drrb->drr_type, keynvl, drc->drc_newfs);
                if (err != 0)
                        goto out;
 
+               /* see comment in dmu_recv_end_sync() */
+               drc->drc_ivset_guid = 0;
+               (void) nvlist_lookup_uint64(keynvl, "to_ivset_guid",
+                   &drc->drc_ivset_guid);
+
                if (!drc->drc_newfs)
                        drc->drc_keynvl = fnvlist_dup(keynvl);
        }
@@ -2591,10 +2596,10 @@ dmu_recv_stream(dmu_recv_cookie_t *drc, vnode_t *vp, offset_t *voffp,
                    sizeof (struct receive_record_arg) + ra->rrd->payload_size);
                ra->rrd = NULL;
        }
-       if (ra->next_rrd == NULL)
-               ra->next_rrd = kmem_zalloc(sizeof (*ra->next_rrd), KM_SLEEP);
-       ra->next_rrd->eos_marker = B_TRUE;
-       bqueue_enqueue(&rwa->q, ra->next_rrd, 1);
+       ASSERT3P(ra->rrd, ==, NULL);
+       ra->rrd = kmem_zalloc(sizeof (*ra->rrd), KM_SLEEP);
+       ra->rrd->eos_marker = B_TRUE;
+       bqueue_enqueue(&rwa->q, ra->rrd, 1);
 
        mutex_enter(&rwa->mutex);
        while (!rwa->done) {
@@ -2635,6 +2640,14 @@ dmu_recv_stream(dmu_recv_cookie_t *drc, vnode_t *vp, offset_t *voffp,
                err = rwa->err;
 
 out:
+       /*
+        * If we hit an error before we started the receive_writer_thread
+        * we need to clean up the next_rrd we create by processing the
+        * DRR_BEGIN record.
+        */
+       if (ra->next_rrd != NULL)
+               kmem_free(ra->next_rrd, sizeof (*ra->next_rrd));
+
        nvlist_free(begin_nvl);
        if ((featureflags & DMU_BACKUP_FEATURE_DEDUP) && (cleanup_fd != -1))
                zfs_onexit_fd_rele(cleanup_fd);
@@ -2838,6 +2851,25 @@ dmu_recv_end_sync(void *arg, dmu_tx_t *tx)
                drc->drc_newsnapobj =
                    dsl_dataset_phys(drc->drc_ds)->ds_prev_snap_obj;
        }
+
+       /*
+        * If this is a raw receive, the crypt_keydata nvlist will include
+        * a to_ivset_guid for us to set on the new snapshot. This value
+        * will override the value generated by the snapshot code. However,
+        * this value may not be present, because older implementations of
+        * the raw send code did not include this value, and we are still
+        * allowed to receive them if the zfs_disable_ivset_guid_check
+        * tunable is set, in which case we will leave the newly-generated
+        * value.
+        */
+       if (drc->drc_raw && drc->drc_ivset_guid != 0) {
+               dmu_object_zapify(dp->dp_meta_objset, drc->drc_newsnapobj,
+                   DMU_OT_DSL_DATASET, tx);
+               VERIFY0(zap_update(dp->dp_meta_objset, drc->drc_newsnapobj,
+                   DS_FIELD_IVSET_GUID, sizeof (uint64_t), 1,
+                   &drc->drc_ivset_guid, tx));
+       }
+
        zvol_create_minors(dp->dp_spa, drc->drc_tofs, B_TRUE);
 
        /*
index 43e19ecbc1d5ff7c8d3611ed48ecdd8f337fa25d..ad64d666bee75177c0d1128114a96f81f2780141 100644 (file)
@@ -1119,9 +1119,13 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *to_ds,
                }
 
                if (featureflags & DMU_BACKUP_FEATURE_RAW) {
+                       uint64_t ivset_guid = (ancestor_zb != NULL) ?
+                           ancestor_zb->zbm_ivset_guid : 0;
+
                        ASSERT(os->os_encrypted);
 
-                       err = dsl_crypto_populate_key_nvlist(to_ds, &keynvl);
+                       err = dsl_crypto_populate_key_nvlist(to_ds,
+                           ivset_guid, &keynvl);
                        if (err != 0) {
                                fnvlist_free(nvl);
                                goto out;
@@ -1235,7 +1239,7 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
        }
 
        if (fromsnap != 0) {
-               zfs_bookmark_phys_t zb;
+               zfs_bookmark_phys_t zb = { 0 };
                boolean_t is_clone;
 
                err = dsl_dataset_hold_obj(dp, fromsnap, FTAG, &fromds);
@@ -1244,12 +1248,25 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
                        dsl_pool_rele(dp, FTAG);
                        return (err);
                }
-               if (!dsl_dataset_is_before(ds, fromds, 0))
+               if (!dsl_dataset_is_before(ds, fromds, 0)) {
                        err = SET_ERROR(EXDEV);
+                       dsl_dataset_rele(fromds, FTAG);
+                       dsl_dataset_rele_flags(ds, dsflags, FTAG);
+                       dsl_pool_rele(dp, FTAG);
+                       return (err);
+               }
+
                zb.zbm_creation_time =
                    dsl_dataset_phys(fromds)->ds_creation_time;
                zb.zbm_creation_txg = dsl_dataset_phys(fromds)->ds_creation_txg;
                zb.zbm_guid = dsl_dataset_phys(fromds)->ds_guid;
+
+               if (dsl_dataset_is_zapified(fromds)) {
+                       (void) zap_lookup(dp->dp_meta_objset,
+                           fromds->ds_object, DS_FIELD_IVSET_GUID, 8, 1,
+                           &zb.zbm_ivset_guid);
+               }
+
                is_clone = (fromds->ds_dir != ds->ds_dir);
                dsl_dataset_rele(fromds, FTAG);
                err = dmu_send_impl(FTAG, dp, ds, &zb, is_clone,
@@ -1298,7 +1315,7 @@ dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
        }
 
        if (fromsnap != NULL) {
-               zfs_bookmark_phys_t zb;
+               zfs_bookmark_phys_t zb = { 0 };
                boolean_t is_clone = B_FALSE;
                int fsnamelen = strchr(tosnap, '@') - tosnap;
 
@@ -1324,6 +1341,13 @@ dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok,
                                    dsl_dataset_phys(fromds)->ds_creation_txg;
                                zb.zbm_guid = dsl_dataset_phys(fromds)->ds_guid;
                                is_clone = (ds->ds_dir != fromds->ds_dir);
+
+                               if (dsl_dataset_is_zapified(fromds)) {
+                                       (void) zap_lookup(dp->dp_meta_objset,
+                                           fromds->ds_object,
+                                           DS_FIELD_IVSET_GUID, 8, 1,
+                                           &zb.zbm_ivset_guid);
+                               }
                                dsl_dataset_rele(fromds, FTAG);
                        }
                } else {
index 08e835541460aaa4804b9a39d19b050507463120..a32198402f4be362ce95c05d0992c60ffc9a9632 100644 (file)
@@ -221,6 +221,26 @@ dsl_bookmark_create_sync(void *arg, dmu_tx_t *tx)
                bmark_phys.zbm_creation_time =
                    dsl_dataset_phys(snapds)->ds_creation_time;
 
+               /*
+                * If the dataset is encrypted create a larger bookmark to
+                * accommodate the IVset guid. The IVset guid was added
+                * after the encryption feature to prevent a problem with
+                * raw sends. If we encounter an encrypted dataset without
+                * an IVset guid we fall back to a normal bookmark.
+                */
+               if (snapds->ds_dir->dd_crypto_obj != 0 &&
+                   spa_feature_is_enabled(dp->dp_spa,
+                   SPA_FEATURE_BOOKMARK_V2)) {
+                       int err = zap_lookup(mos, snapds->ds_object,
+                           DS_FIELD_IVSET_GUID, sizeof (uint64_t), 1,
+                           &bmark_phys.zbm_ivset_guid);
+                       if (err == 0) {
+                               bmark_len = BOOKMARK_PHYS_SIZE_V2;
+                               spa_feature_incr(dp->dp_spa,
+                                   SPA_FEATURE_BOOKMARK_V2, tx);
+                       }
+               }
+
                VERIFY0(zap_add(mos, bmark_fs->ds_bookmarks,
                    shortname, sizeof (uint64_t),
                    bmark_len / sizeof (uint64_t), &bmark_phys, tx));
@@ -273,7 +293,7 @@ dsl_get_bookmarks_impl(dsl_dataset_t *ds, nvlist_t *props, nvlist_t *outnvl)
            zap_cursor_retrieve(&zc, &attr) == 0;
            zap_cursor_advance(&zc)) {
                char *bmark_name = attr.za_name;
-               zfs_bookmark_phys_t bmark_phys;
+               zfs_bookmark_phys_t bmark_phys = { 0 };
 
                err = dsl_dataset_bmark_lookup(ds, bmark_name, &bmark_phys);
                ASSERT3U(err, !=, ENOENT);
@@ -296,6 +316,11 @@ dsl_get_bookmarks_impl(dsl_dataset_t *ds, nvlist_t *props, nvlist_t *outnvl)
                        dsl_prop_nvlist_add_uint64(out_props,
                            ZFS_PROP_CREATION, bmark_phys.zbm_creation_time);
                }
+               if (nvlist_exists(props,
+                   zfs_prop_to_name(ZFS_PROP_IVSET_GUID))) {
+                       dsl_prop_nvlist_add_uint64(out_props,
+                           ZFS_PROP_IVSET_GUID, bmark_phys.zbm_ivset_guid);
+               }
 
                fnvlist_add_nvlist(outnvl, bmark_name, out_props);
                fnvlist_free(out_props);
@@ -343,13 +368,26 @@ typedef struct dsl_bookmark_destroy_arg {
 static int
 dsl_dataset_bookmark_remove(dsl_dataset_t *ds, const char *name, dmu_tx_t *tx)
 {
+       int err;
        objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
        uint64_t bmark_zapobj = ds->ds_bookmarks;
        matchtype_t mt = 0;
+       uint64_t int_size, num_ints;
 
        if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET)
                mt = MT_NORMALIZE;
 
+       err = zap_length(mos, bmark_zapobj, name, &int_size, &num_ints);
+       if (err != 0)
+               return (err);
+
+       ASSERT3U(int_size, ==, sizeof (uint64_t));
+
+       if (num_ints * int_size > BOOKMARK_PHYS_SIZE_V1) {
+               spa_feature_decr(dmu_objset_spa(mos),
+                   SPA_FEATURE_BOOKMARK_V2, tx);
+       }
+
        return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx));
 }
 
index 9271128b9639631a6069c6936d91b9560a82027f..a0e7fcce479cd4c42de80688f907189bbeb9241f 100644 (file)
  * object is also refcounted.
  */
 
+/*
+ * This tunable allows datasets to be raw received even if the stream does
+ * not include IVset guids or if the guids don't match. This is used as part
+ * of the resolution for ZPOOL_ERRATA_ZOL_8308_ENCRYPTION.
+ */
+int zfs_disable_ivset_guid_check = 0;
+
 static void
 dsl_wrapping_key_hold(dsl_wrapping_key_t *wkey, void *tag)
 {
@@ -1963,21 +1970,23 @@ dsl_dataset_create_crypt_sync(uint64_t dsobj, dsl_dir_t *dd,
 
 typedef struct dsl_crypto_recv_key_arg {
        uint64_t dcrka_dsobj;
+       uint64_t dcrka_fromobj;
        dmu_objset_type_t dcrka_ostype;
        nvlist_t *dcrka_nvl;
        boolean_t dcrka_do_key;
 } dsl_crypto_recv_key_arg_t;
 
 static int
-dsl_crypto_recv_raw_objset_check(dsl_dataset_t *ds, dmu_objset_type_t ostype,
-    nvlist_t *nvl, dmu_tx_t *tx)
+dsl_crypto_recv_raw_objset_check(dsl_dataset_t *ds, dsl_dataset_t *fromds,
+    dmu_objset_type_t ostype, nvlist_t *nvl, dmu_tx_t *tx)
 {
        int ret;
        objset_t *os;
        dnode_t *mdn;
        uint8_t *buf = NULL;
        uint_t len;
-       uint64_t intval, nlevels, blksz, ibs, nblkptr, maxblkid;
+       uint64_t intval, nlevels, blksz, ibs;
+       uint64_t nblkptr, maxblkid;
 
        if (ostype != DMU_OST_ZFS && ostype != DMU_OST_ZVOL)
                return (SET_ERROR(EINVAL));
@@ -2044,6 +2053,30 @@ dsl_crypto_recv_raw_objset_check(dsl_dataset_t *ds, dmu_objset_type_t ostype,
        }
        rrw_exit(&ds->ds_bp_rwlock, FTAG);
 
+       /*
+        * Check that the ivset guid of the fromds matches the one from the
+        * send stream. Older versions of the encryption code did not have
+        * an ivset guid on the from dataset and did not send one in the
+        * stream. For these streams we provide the
+        * zfs_disable_ivset_guid_check tunable to allow these datasets to
+        * be received with a generated ivset guid.
+        */
+       if (fromds != NULL && !zfs_disable_ivset_guid_check) {
+               uint64_t from_ivset_guid = 0;
+               intval = 0;
+
+               (void) nvlist_lookup_uint64(nvl, "from_ivset_guid", &intval);
+               (void) zap_lookup(tx->tx_pool->dp_meta_objset,
+                   fromds->ds_object, DS_FIELD_IVSET_GUID,
+                   sizeof (from_ivset_guid), 1, &from_ivset_guid);
+
+               if (intval == 0 || from_ivset_guid == 0)
+                       return (SET_ERROR(ZFS_ERR_FROM_IVSET_GUID_MISSING));
+
+               if (intval != from_ivset_guid)
+                       return (SET_ERROR(ZFS_ERR_FROM_IVSET_GUID_MISMATCH));
+       }
+
        return (0);
 }
 
@@ -2063,7 +2096,11 @@ dsl_crypto_recv_raw_objset_sync(dsl_dataset_t *ds, dmu_objset_type_t ostype,
        VERIFY0(dmu_objset_from_ds(ds, &os));
        mdn = DMU_META_DNODE(os);
 
-       /* fetch the values we need from the nvlist */
+       /*
+        * Fetch the values we need from the nvlist. "to_ivset_guid" must
+        * be set on the snapshot, which doesn't exist yet. The receive
+        * code will take care of this for us later.
+        */
        compress = fnvlist_lookup_uint64(nvl, "mdn_compress");
        checksum = fnvlist_lookup_uint64(nvl, "mdn_checksum");
        nlevels = fnvlist_lookup_uint64(nvl, "mdn_nlevels");
@@ -2127,7 +2164,7 @@ dsl_crypto_recv_raw_key_check(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx)
        objset_t *mos = tx->tx_pool->dp_meta_objset;
        uint8_t *buf = NULL;
        uint_t len;
-       uint64_t intval, guid, version;
+       uint64_t intval, key_guid, version;
        boolean_t is_passphrase = B_FALSE;
 
        ASSERT(dsl_dataset_phys(ds)->ds_flags & DS_FLAG_INCONSISTENT);
@@ -2152,10 +2189,10 @@ dsl_crypto_recv_raw_key_check(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx)
         */
        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);
+                   DSL_CRYPTO_KEY_GUID, 8, 1, &key_guid);
                if (ret != 0)
                        return (ret);
-               if (intval != guid)
+               if (intval != key_guid)
                        return (SET_ERROR(EACCES));
        }
 
@@ -2221,13 +2258,13 @@ dsl_crypto_recv_raw_key_sync(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx)
        uint_t len;
        uint64_t rddobj, one = 1;
        uint8_t *keydata, *hmac_keydata, *iv, *mac;
-       uint64_t crypt, guid, keyformat, iters, salt;
+       uint64_t crypt, key_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);
+       key_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,
@@ -2282,7 +2319,7 @@ dsl_crypto_recv_raw_key_sync(dsl_dataset_t *ds, nvlist_t *nvl, dmu_tx_t *tx)
 
        /* sync the key data to the ZAP object on disk */
        dsl_crypto_key_sync_impl(mos, dd->dd_crypto_obj, crypt,
-           rddobj, guid, iv, mac, keydata, hmac_keydata, keyformat, salt,
+           rddobj, key_guid, iv, mac, keydata, hmac_keydata, keyformat, salt,
            iters, tx);
 }
 
@@ -2291,17 +2328,24 @@ 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;
+       dsl_dataset_t *ds = NULL, *fromds = NULL;
 
        ret = dsl_dataset_hold_obj(tx->tx_pool, dcrka->dcrka_dsobj,
            FTAG, &ds);
        if (ret != 0)
-               goto error;
+               goto out;
 
-       ret = dsl_crypto_recv_raw_objset_check(ds,
+       if (dcrka->dcrka_fromobj != 0) {
+               ret = dsl_dataset_hold_obj(tx->tx_pool, dcrka->dcrka_fromobj,
+                   FTAG, &fromds);
+               if (ret != 0)
+                       goto out;
+       }
+
+       ret = dsl_crypto_recv_raw_objset_check(ds, fromds,
            dcrka->dcrka_ostype, dcrka->dcrka_nvl, tx);
        if (ret != 0)
-               goto error;
+               goto out;
 
        /*
         * We run this check even if we won't be doing this part of
@@ -2310,14 +2354,13 @@ dsl_crypto_recv_key_check(void *arg, dmu_tx_t *tx)
         */
        ret = dsl_crypto_recv_raw_key_check(ds, dcrka->dcrka_nvl, tx);
        if (ret != 0)
-               goto error;
-
-       dsl_dataset_rele(ds, FTAG);
-       return (0);
+               goto out;
 
-error:
+out:
        if (ds != NULL)
                dsl_dataset_rele(ds, FTAG);
+       if (fromds != NULL)
+               dsl_dataset_rele(fromds, FTAG);
        return (ret);
 }
 
@@ -2342,12 +2385,13 @@ dsl_crypto_recv_key_sync(void *arg, dmu_tx_t *tx)
  * without wrapping it.
  */
 int
-dsl_crypto_recv_raw(const char *poolname, uint64_t dsobj,
+dsl_crypto_recv_raw(const char *poolname, uint64_t dsobj, uint64_t fromobj,
     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_fromobj = fromobj;
        dcrka.dcrka_ostype = ostype;
        dcrka.dcrka_nvl = nvl;
        dcrka.dcrka_do_key = do_key;
@@ -2357,7 +2401,8 @@ dsl_crypto_recv_raw(const char *poolname, uint64_t dsobj,
 }
 
 int
-dsl_crypto_populate_key_nvlist(dsl_dataset_t *ds, nvlist_t **nvl_out)
+dsl_crypto_populate_key_nvlist(dsl_dataset_t *ds, uint64_t from_ivset_guid,
+    nvlist_t **nvl_out)
 {
        int ret;
        objset_t *os;
@@ -2368,8 +2413,9 @@ dsl_crypto_populate_key_nvlist(dsl_dataset_t *ds, nvlist_t **nvl_out)
        dsl_dir_t *rdd = NULL;
        dsl_pool_t *dp = ds->ds_dir->dd_pool;
        objset_t *mos = dp->dp_meta_objset;
-       uint64_t crypt = 0, guid = 0, format = 0;
+       uint64_t crypt = 0, key_guid = 0, format = 0;
        uint64_t iters = 0, salt = 0, version = 0;
+       uint64_t to_ivset_guid = 0;
        uint8_t raw_keydata[MASTER_KEY_MAX_LEN];
        uint8_t raw_hmac_keydata[SHA512_HMAC_KEYLEN];
        uint8_t iv[WRAPPING_IV_LEN];
@@ -2390,7 +2436,7 @@ dsl_crypto_populate_key_nvlist(dsl_dataset_t *ds, nvlist_t **nvl_out)
        if (ret != 0)
                goto error;
 
-       ret = zap_lookup(mos, dckobj, DSL_CRYPTO_KEY_GUID, 8, 1, &guid);
+       ret = zap_lookup(mos, dckobj, DSL_CRYPTO_KEY_GUID, 8, 1, &key_guid);
        if (ret != 0)
                goto error;
 
@@ -2414,6 +2460,12 @@ dsl_crypto_populate_key_nvlist(dsl_dataset_t *ds, nvlist_t **nvl_out)
        if (ret != 0)
                goto error;
 
+       /* see zfs_disable_ivset_guid_check tunable for errata info */
+       ret = zap_lookup(mos, ds->ds_object, DS_FIELD_IVSET_GUID, 8, 1,
+           &to_ivset_guid);
+       if (ret != 0)
+               ASSERT3U(dp->dp_spa->spa_errata, !=, 0);
+
        /*
         * We don't support raw sends of legacy on-disk formats. See the
         * comment in dsl_crypto_recv_key_check() for details.
@@ -2463,7 +2515,7 @@ dsl_crypto_populate_key_nvlist(dsl_dataset_t *ds, nvlist_t **nvl_out)
        dsl_pool_config_exit(dp, FTAG);
 
        fnvlist_add_uint64(nvl, DSL_CRYPTO_KEY_CRYPTO_SUITE, crypt);
-       fnvlist_add_uint64(nvl, DSL_CRYPTO_KEY_GUID, guid);
+       fnvlist_add_uint64(nvl, DSL_CRYPTO_KEY_GUID, key_guid);
        fnvlist_add_uint64(nvl, DSL_CRYPTO_KEY_VERSION, version);
        VERIFY0(nvlist_add_uint8_array(nvl, DSL_CRYPTO_KEY_MASTER_KEY,
            raw_keydata, MASTER_KEY_MAX_LEN));
@@ -2485,6 +2537,8 @@ dsl_crypto_populate_key_nvlist(dsl_dataset_t *ds, nvlist_t **nvl_out)
        fnvlist_add_uint64(nvl, "mdn_indblkshift", mdn->dn_indblkshift);
        fnvlist_add_uint64(nvl, "mdn_nblkptr", mdn->dn_nblkptr);
        fnvlist_add_uint64(nvl, "mdn_maxblkid", mdn->dn_maxblkid);
+       fnvlist_add_uint64(nvl, "to_ivset_guid", to_ivset_guid);
+       fnvlist_add_uint64(nvl, "from_ivset_guid", from_ivset_guid);
 
        *nvl_out = nvl;
        return (0);
@@ -2595,6 +2649,10 @@ dsl_dataset_crypt_stats(dsl_dataset_t *ds, nvlist_t *nv)
            zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), 8, 1, &intval) == 0) {
                dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_PBKDF2_ITERS, intval);
        }
+       if (zap_lookup(dd->dd_pool->dp_meta_objset, ds->ds_object,
+           DS_FIELD_IVSET_GUID, 8, 1, &intval) == 0) {
+               dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_IVSET_GUID, intval);
+       }
 
        if (dsl_dir_get_encryption_root_ddobj(dd, &intval) == 0) {
                VERIFY0(dsl_dir_hold_obj(dd->dd_pool, intval, NULL, FTAG,
@@ -2829,3 +2887,9 @@ error:
 
        return (ret);
 }
+
+#if defined(_KERNEL)
+module_param(zfs_disable_ivset_guid_check, int, 0644);
+MODULE_PARM_DESC(zfs_disable_ivset_guid_check,
+       "Set to allow raw receives without IVset guids");
+#endif
index ad944e5b8ea21c10093584a1fa32cbc9f7603cd9..086750fed9355f9ce7b34d5cf6c69bbcfde0cdcf 100644 (file)
@@ -621,6 +621,13 @@ dsl_dataset_hold_obj(dsl_pool_t *dp, uint64_t dsobj, void *tag,
                        ds->ds_reserved = ds->ds_quota = 0;
                }
 
+               if (err == 0 && ds->ds_dir->dd_crypto_obj != 0 &&
+                   ds->ds_is_snapshot &&
+                   zap_contains(mos, dsobj, DS_FIELD_IVSET_GUID) != 0) {
+                       dp->dp_spa->spa_errata =
+                           ZPOOL_ERRATA_ZOL_8308_ENCRYPTION;
+               }
+
                dsl_deadlist_open(&ds->ds_deadlist,
                    mos, dsl_dataset_phys(ds)->ds_deadlist_obj);
                uint64_t remap_deadlist_obj =
@@ -1702,6 +1709,30 @@ dsl_dataset_snapshot_sync_impl(dsl_dataset_t *ds, const char *snapname,
                    sizeof (remap_deadlist_obj), 1, &remap_deadlist_obj, tx));
        }
 
+       /*
+        * Create a ivset guid for this snapshot if the dataset is
+        * encrypted. This may be overridden by a raw receive. A
+        * previous implementation of this code did not have this
+        * field as part of the on-disk format for ZFS encryption
+        * (see errata #4). As part of the remediation for this
+        * issue, we ask the user to enable the bookmark_v2 feature
+        * which is now a dependency of the encryption feature. We
+        * use this as a heuristic to determine when the user has
+        * elected to correct any datasets created with the old code.
+        * As a result, we only do this step if the bookmark_v2
+        * feature is enabled, which limits the number of states a
+        * given pool / dataset can be in with regards to terms of
+        * correcting the issue.
+        */
+       if (ds->ds_dir->dd_crypto_obj != 0 &&
+           spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARK_V2)) {
+               uint64_t ivset_guid = unique_create();
+
+               dmu_object_zapify(mos, dsobj, DMU_OT_DSL_DATASET, tx);
+               VERIFY0(zap_add(mos, dsobj, DS_FIELD_IVSET_GUID,
+                   sizeof (ivset_guid), 1, &ivset_guid, tx));
+       }
+
        ASSERT3U(dsl_dataset_phys(ds)->ds_prev_snap_txg, <, tx->tx_txg);
        dsl_dataset_phys(ds)->ds_prev_snap_obj = dsobj;
        dsl_dataset_phys(ds)->ds_prev_snap_txg = crtxg;
index bbe2f89629a5ab1a905522dbf396e53728f3e8a9..d95a43001e29b7d5dca1cf7a291e38bbbdb05777 100644 (file)
@@ -3360,6 +3360,16 @@ spa_ld_check_features(spa_t *spa, boolean_t *missing_feat_writep)
                        return (spa_vdev_err(rvd, VDEV_AUX_CORRUPT_DATA, EIO));
        }
 
+       /*
+        * Encryption was added before bookmark_v2, even though bookmark_v2
+        * is now a dependency. If this pool has encryption enabled without
+        * bookmark_v2, trigger an errata message.
+        */
+       if (spa_feature_is_enabled(spa, SPA_FEATURE_ENCRYPTION) &&
+           !spa_feature_is_enabled(spa, SPA_FEATURE_BOOKMARK_V2)) {
+               spa->spa_errata = ZPOOL_ERRATA_ZOL_8308_ENCRYPTION;
+       }
+
        return (0);
 }
 
index 2481bb1fe230028489c018720533f47fa2f420bc..ed98f0d1025bd57e01dd33178e2c1574b42e7ff6 100644 (file)
@@ -411,6 +411,15 @@ get_special_prop(lua_State *state, dsl_dataset_t *ds, const char *dsname,
        case ZFS_PROP_INCONSISTENT:
                numval = dsl_get_inconsistent(ds);
                break;
+       case ZFS_PROP_IVSET_GUID:
+               if (dsl_dataset_is_zapified(ds)) {
+                       error = zap_lookup(ds->ds_dir->dd_pool->dp_meta_objset,
+                           ds->ds_object, DS_FIELD_IVSET_GUID,
+                           sizeof (numval), 1, &numval);
+               } else {
+                       error = ENOENT;
+               }
+               break;
        case ZFS_PROP_RECEIVE_RESUME_TOKEN: {
                char *token = get_receive_resume_stats_impl(ds);
 
index 917bf24f925109381774687fd0daf1b2b9b781df..698717335385222b35670c9413901a6d90caacc1 100644 (file)
@@ -378,7 +378,7 @@ tests = ['zpool_import_001_pos', 'zpool_import_002_pos',
     'zpool_import_missing_002_pos', 'zpool_import_missing_003_pos',
     'zpool_import_rename_001_pos', 'zpool_import_all_001_pos',
     'zpool_import_encrypted', 'zpool_import_encrypted_load',
-    'zpool_import_errata3',
+    'zpool_import_errata3', 'zpool_import_errata4',
     'import_cachefile_device_added',
     'import_cachefile_device_removed',
     'import_cachefile_device_replaced',
@@ -793,7 +793,8 @@ tests = ['rsend_001_pos', 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos',
     'send-c_embedded_blocks', 'send-c_resume', 'send-cpL_varied_recsize',
     'send-c_recv_dedup', 'send_encrypted_files', 'send_encrypted_heirarchy',
     'send_encrypted_props', 'send_freeobjects', 'send_realloc_dnode_size',
-    'send_holds', 'send_hole_birth', 'send-wDR_encrypted_zvol']
+    'send_holds', 'send_hole_birth', 'send_mixed_raw',
+    'send-wDR_encrypted_zvol']
 tags = ['functional', 'rsend']
 
 [tests/functional/scrub_mirror]
index 48a32174fa36b1546749ad2902b276c98da94974..b9ede86ede993eb3cdf6c0b3c214c4d77467e57c 100644 (file)
@@ -88,5 +88,6 @@ if is_linux; then
            "feature@project_quota"
            "feature@allocation_classes"
            "feature@resilver_defer"
+           "feature@bookmark_v2"
        )
 fi
index 6f0f5d0b867a20e4b22976f9679507a4d974a1f9..ad0f9c46edc79a607dff6941c69db31bcda78839 100644 (file)
@@ -39,7 +39,8 @@ dist_pkgdata_SCRIPTS = \
        zpool_import_rename_001_pos.ksh \
        zpool_import_encrypted.ksh \
        zpool_import_encrypted_load.ksh \
-       zpool_import_errata3.ksh
+       zpool_import_errata3.ksh \
+       zpool_import_errata4.ksh
 
 dist_pkgdata_DATA = \
        zpool_import.cfg \
index 74ae609798404314d7380c2794178dbba75ecbf6..dc3685e4b9cca4d99d2a9c1877db0f4cd2df8c51 100644 (file)
@@ -1,4 +1,5 @@
 pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/cli_root/zpool_import/blockfiles
 dist_pkgdata_DATA = \
        unclean_export.dat.bz2 \
-       cryptv0.dat.bz2
+       cryptv0.dat.bz2 \
+       missing_ivset.dat.bz2
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_import/blockfiles/missing_ivset.dat.bz2 b/tests/zfs-tests/tests/functional/cli_root/zpool_import/blockfiles/missing_ivset.dat.bz2
new file mode 100644 (file)
index 0000000..2b91d90
Binary files /dev/null and b/tests/zfs-tests/tests/functional/cli_root/zpool_import/blockfiles/missing_ivset.dat.bz2 differ
index f51f81ce830467726b4afe42f26e0c21c4e16a32..b1e37a550999a94464fff05c004d9ab0e11d27e3 100755 (executable)
@@ -62,7 +62,7 @@ log_assert "Verify that Errata 3 is properly handled"
 
 uncompress_pool
 log_must zpool import -d /$TESTPOOL/ $POOL_NAME
-log_must eval "zpool status $POOL_NAME | grep -q Errata"
+log_must eval "zpool status $POOL_NAME | grep -q Errata" # also detects 'Errata #4'
 log_must eval "zpool status $POOL_NAME | grep -q ZFS-8000-ER"
 log_must eval "echo 'password' | zfs load-key $POOL_NAME/testfs"
 log_must eval "echo 'password' | zfs load-key $POOL_NAME/testvol"
@@ -96,6 +96,5 @@ log_must zfs destroy -r $POOL_NAME/testvol
 
 log_must zpool export $POOL_NAME
 log_must zpool import -d /$TESTPOOL/ $POOL_NAME
-log_mustnot eval "zpool status $POOL_NAME | grep -q Errata"
-log_mustnot eval "zpool status $POOL_NAME | grep -q ZFS-8000-ER"
+log_mustnot eval "zpool status $POOL_NAME | grep -q 'Errata #3'"
 log_pass "Errata 3 is properly handled"
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_errata4.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_errata4.ksh
new file mode 100755 (executable)
index 0000000..d06a9cd
--- /dev/null
@@ -0,0 +1,143 @@
+#!/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) 2019 Datto, Inc. All rights reserved.
+#
+
+. $STF_SUITE/include/libtest.shlib
+
+#
+# DESCRIPTION:
+# 'zpool import' should import a pool with Errata #4. Users should be
+# able to set the zfs_disable_ivset_guid_check to continue normal
+# operation and the errata should disappear when no more effected
+# datasets remain.
+#
+# STRATEGY:
+# 1. Import a pre-packaged pool with Errata #4 and verify its state
+# 2. Prepare pool to fix existing datasets
+# 3. Use raw sends to fix datasets
+# 4. Ensure fixed datasets match their initial counterparts
+# 5. Destroy the initial datasets and verify the errata is gone
+#
+
+verify_runnable "global"
+
+POOL_NAME=missing_ivset
+POOL_FILE=missing_ivset.dat
+
+function uncompress_pool
+{
+       log_note "Creating pool from $POOL_FILE"
+       log_must bzcat \
+           $STF_SUITE/tests/functional/cli_root/zpool_import/blockfiles/$POOL_FILE.bz2 \
+           > /$TESTPOOL/$POOL_FILE
+       return 0
+}
+
+function cleanup
+{
+       log_must set_tunable32 zfs_disable_ivset_guid_check 0
+       poolexists $POOL_NAME && log_must zpool destroy $POOL_NAME
+       [[ -e /$TESTPOOL/$POOL_FILE ]] && rm /$TESTPOOL/$POOL_FILE
+       return 0
+}
+log_onexit cleanup
+
+log_assert "Verify that Errata 4 is properly handled"
+
+function has_ivset_guid # dataset
+{
+       ds="$1"
+       ivset_guid=$(get_prop ivsetguid $ds)
+
+       if [ "$ivset_guid" == "-" ]; then
+               return 1
+       else
+               return 0
+       fi
+}
+
+# 1. Import a pre-packaged pool with Errata #4 and verify its state
+uncompress_pool
+log_must zpool import -d /$TESTPOOL/ $POOL_NAME
+log_must eval "zpool status $POOL_NAME | grep -q 'Errata #4'"
+log_must eval "zpool status $POOL_NAME | grep -q ZFS-8000-ER"
+bm2_value=$(zpool get -H -o value feature@bookmark_v2 $POOL_NAME)
+if [ "$bm2_value" != "disabled" ]; then
+       log_fail "initial pool's bookmark_v2 feature is not disabled"
+fi
+
+log_mustnot has_ivset_guid $POOL_NAME/testfs@snap1
+log_mustnot has_ivset_guid $POOL_NAME/testfs@snap2
+log_mustnot has_ivset_guid $POOL_NAME/testfs@snap3
+log_mustnot has_ivset_guid $POOL_NAME/testvol@snap1
+log_mustnot has_ivset_guid $POOL_NAME/testvol@snap2
+log_mustnot has_ivset_guid $POOL_NAME/testvol@snap3
+
+# 2. Prepare pool to fix existing datasets
+log_must zpool set feature@bookmark_v2=enabled $POOL_NAME
+log_must set_tunable32 zfs_disable_ivset_guid_check 1
+log_must zfs create $POOL_NAME/fixed
+
+# 3. Use raw sends to fix datasets
+log_must eval "zfs send -w $POOL_NAME/testfs@snap1 | \
+       zfs recv $POOL_NAME/fixed/testfs"
+log_must eval "zfs send -w -i @snap1 $POOL_NAME/testfs@snap2 | \
+       zfs recv $POOL_NAME/fixed/testfs"
+log_must eval \
+       "zfs send -w -i $POOL_NAME/testfs#snap2 $POOL_NAME/testfs@snap3 | \
+       zfs recv $POOL_NAME/fixed/testfs"
+
+log_must eval "zfs send -w $POOL_NAME/testvol@snap1 | \
+       zfs recv $POOL_NAME/fixed/testvol"
+log_must eval "zfs send -w -i @snap1 $POOL_NAME/testvol@snap2 | \
+       zfs recv $POOL_NAME/fixed/testvol"
+log_must eval \
+       "zfs send -w -i $POOL_NAME/testvol#snap2 $POOL_NAME/testvol@snap3 | \
+       zfs recv $POOL_NAME/fixed/testvol"
+
+# 4. Ensure fixed datasets match their initial counterparts
+log_must eval "echo 'password' | zfs load-key $POOL_NAME/testfs"
+log_must eval "echo 'password' | zfs load-key $POOL_NAME/testvol"
+log_must eval "echo 'password' | zfs load-key $POOL_NAME/fixed/testfs"
+log_must eval "echo 'password' | zfs load-key $POOL_NAME/fixed/testvol"
+log_must zfs mount $POOL_NAME/testfs
+log_must zfs mount $POOL_NAME/fixed/testfs
+block_device_wait
+
+old_mntpnt=$(get_prop mountpoint $POOL_NAME/testfs)
+new_mntpnt=$(get_prop mountpoint $POOL_NAME/fixed/testfs)
+log_must diff -r "$old_mntpnt" "$new_mntpnt"
+log_must diff /dev/zvol/$POOL_NAME/testvol /dev/zvol/$POOL_NAME/fixed/testvol
+
+log_must has_ivset_guid $POOL_NAME/fixed/testfs@snap1
+log_must has_ivset_guid $POOL_NAME/fixed/testfs@snap2
+log_must has_ivset_guid $POOL_NAME/fixed/testfs@snap3
+log_must has_ivset_guid $POOL_NAME/fixed/testvol@snap1
+log_must has_ivset_guid $POOL_NAME/fixed/testvol@snap2
+log_must has_ivset_guid $POOL_NAME/fixed/testvol@snap3
+
+# 5. Destroy the initial datasets and verify the errata is gone
+log_must zfs destroy -r $POOL_NAME/testfs
+log_must zfs destroy -r $POOL_NAME/testvol
+
+log_must zpool export $POOL_NAME
+log_must zpool import -d /$TESTPOOL/ $POOL_NAME
+log_mustnot eval "zpool status $POOL_NAME | grep -q 'Errata #4'"
+log_mustnot eval "zpool status $POOL_NAME | grep -q ZFS-8000-ER"
+log_pass "Errata 4 is properly handled"
index 0b15f8d2bb86b3350569361655ea24538fcf6cbf..b0c68c5be58748ac3a93c73b9ca455dd0652e503 100644 (file)
@@ -43,6 +43,7 @@ dist_pkgdata_SCRIPTS = \
        send_realloc_dnode_size.ksh \
        send_holds.ksh \
        send_hole_birth.ksh \
+       send_mixed_raw.ksh \
        send-wDR_encrypted_zvol.ksh
 
 dist_pkgdata_DATA = \
diff --git a/tests/zfs-tests/tests/functional/rsend/send_mixed_raw.ksh b/tests/zfs-tests/tests/functional/rsend/send_mixed_raw.ksh
new file mode 100755 (executable)
index 0000000..eea535a
--- /dev/null
@@ -0,0 +1,118 @@
+#!/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) 2019 Datto, Inc. All rights reserved.
+#
+
+. $STF_SUITE/include/libtest.shlib
+
+#
+# DESCRIPTION:
+#      Verify that 'zfs receive' produces an error when mixing
+#      raw and non-raw sends in a way that would break IV set
+#      consistency.
+#
+# STRATEGY:
+#      1. Create an initial dataset with 3 snapshots.
+#      2. Perform a raw send of the first snapshot to 2 other datasets.
+#      3. Perform a non-raw send of the second snapshot to one of
+#         the other datasets. Perform a raw send from this dataset to
+#         the last one.
+#      4. Attempt to raw send the final snapshot of the first dataset
+#         to the other 2 datasets, which should fail.
+#      5. Repeat steps 1-4, but using bookmarks for incremental sends.
+#
+#
+# A                 B             C     notes
+# ------------------------------------------------------------------------------
+# snap1 ---raw---> snap1 --raw--> snap1 # all snaps initialized via raw send
+# snap2 -non-raw-> snap2 --raw--> snap2 # A sends non-raw to B, B sends raw to C
+# snap3 ------------raw---------> snap3 # attempt send to C (should fail)
+#
+
+
+verify_runnable "both"
+
+function cleanup
+{
+    datasetexists $TESTPOOL/$TESTFS3 && \
+        log_must zfs destroy -r $TESTPOOL/$TESTFS3
+    datasetexists $TESTPOOL/$TESTFS2 && \
+        log_must zfs destroy -r $TESTPOOL/$TESTFS2
+    datasetexists $TESTPOOL/$TESTFS1 && \
+        log_must zfs destroy -r $TESTPOOL/$TESTFS1
+}
+log_onexit cleanup
+
+log_assert "Mixing raw and non-raw receives should fail"
+
+typeset passphrase="password"
+
+log_must eval "echo $passphrase | zfs create -o encryption=on" \
+    "-o keyformat=passphrase  $TESTPOOL/$TESTFS1"
+
+log_must zfs snapshot $TESTPOOL/$TESTFS1@1
+log_must touch /$TESTPOOL/$TESTFS1/a
+log_must zfs snapshot $TESTPOOL/$TESTFS1@2
+log_must touch /$TESTPOOL/$TESTFS1/b
+log_must zfs snapshot $TESTPOOL/$TESTFS1@3
+
+# Testing with snapshots
+log_must eval "zfs send -w $TESTPOOL/$TESTFS1@1 |" \
+    "zfs receive $TESTPOOL/$TESTFS2"
+log_must eval "echo $passphrase | zfs load-key $TESTPOOL/$TESTFS2"
+log_must eval "zfs send -w $TESTPOOL/$TESTFS2@1 |" \
+    "zfs receive $TESTPOOL/$TESTFS3"
+log_must eval "echo $passphrase | zfs load-key $TESTPOOL/$TESTFS3"
+
+log_must eval "zfs send -i $TESTPOOL/$TESTFS1@1 $TESTPOOL/$TESTFS1@2 |" \
+    "zfs receive $TESTPOOL/$TESTFS2"
+log_must eval "zfs send -w -i $TESTPOOL/$TESTFS2@1 $TESTPOOL/$TESTFS2@2 |" \
+    "zfs receive $TESTPOOL/$TESTFS3"
+
+log_mustnot eval "zfs send -w -i $TESTPOOL/$TESTFS1@2 $TESTPOOL/$TESTFS1@3 |" \
+    "zfs receive $TESTPOOL/$TESTFS2"
+log_mustnot eval "zfs send -w -i $TESTPOOL/$TESTFS2@2 $TESTPOOL/$TESTFS2@3 |" \
+    "zfs receive $TESTPOOL/$TESTFS3"
+
+log_must zfs destroy -r $TESTPOOL/$TESTFS3
+log_must zfs destroy -r $TESTPOOL/$TESTFS2
+
+# Testing with bookmarks
+log_must zfs bookmark $TESTPOOL/$TESTFS1@1 $TESTPOOL/$TESTFS1#b1
+log_must zfs bookmark $TESTPOOL/$TESTFS1@2 $TESTPOOL/$TESTFS1#b2
+
+log_must eval "zfs send -w $TESTPOOL/$TESTFS1@1 |" \
+    "zfs receive $TESTPOOL/$TESTFS2"
+log_must eval "echo $passphrase | zfs load-key $TESTPOOL/$TESTFS2"
+
+log_must zfs bookmark $TESTPOOL/$TESTFS2@1 $TESTPOOL/$TESTFS2#b1
+
+log_must eval "zfs send -w $TESTPOOL/$TESTFS2@1 |" \
+    "zfs receive $TESTPOOL/$TESTFS3"
+log_must eval "echo $passphrase | zfs load-key $TESTPOOL/$TESTFS3"
+
+log_must eval "zfs send -i $TESTPOOL/$TESTFS1#b1 $TESTPOOL/$TESTFS1@2 |" \
+    "zfs receive $TESTPOOL/$TESTFS2"
+log_must eval "zfs send -w -i $TESTPOOL/$TESTFS2#b1 $TESTPOOL/$TESTFS2@2 |" \
+    "zfs receive $TESTPOOL/$TESTFS3"
+
+log_mustnot eval "zfs send -w -i $TESTPOOL/$TESTFS1#b2" \
+    "$TESTPOOL/$TESTFS1@3 | zfs receive $TESTPOOL/$TESTFS2"
+log_mustnot eval "zfs send -w -i $TESTPOOL/$TESTFS2#b2" \
+    "$TESTPOOL/$TESTFS2@3 | zfs receive $TESTPOOL/$TESTFS3"
+
+log_pass "Mixing raw and non-raw receives fail as expected"