Skip to content

Commit

Permalink
Detect and prevent mixed raw and non-raw sends
Browse files Browse the repository at this point in the history
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.

Signed-off-by: Tom Caputi <[email protected]>
  • Loading branch information
Tom Caputi committed Feb 12, 2019
1 parent 2631cac commit 7c00f93
Show file tree
Hide file tree
Showing 26 changed files with 573 additions and 42 deletions.
21 changes: 21 additions & 0 deletions cmd/zpool/zpool_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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\n\tand destroy the "
"old ones.\n"));
break;
default:
/*
* All errata must contain an action message.
Expand Down Expand Up @@ -7401,6 +7412,16 @@ 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 destroy the old ones.\n"));
break;

default:
/*
* All errata which allow the pool to be imported
Expand Down
2 changes: 2 additions & 0 deletions include/sys/dmu_recv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions include/sys/dsl_crypt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions include/sys/dsl_dataset.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions include/sys/fs/zfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

/*
Expand Down Expand Up @@ -1257,6 +1259,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;

/*
Expand Down
1 change: 1 addition & 0 deletions lib/libzfs/libzfs_iter.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,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;
Expand Down
14 changes: 14 additions & 0 deletions lib/libzfs/libzfs_sendrecv.c
Original file line number Diff line number Diff line change
Expand Up @@ -4309,6 +4309,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,
Expand Down
1 change: 1 addition & 0 deletions lib/libzfs_core/libzfs_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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> -> {
Expand Down
25 changes: 25 additions & 0 deletions man/man8/zfs.8
Original file line number Diff line number Diff line change
Expand Up @@ -3818,6 +3818,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
Expand Down
1 change: 1 addition & 0 deletions module/zcommon/zfeature_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,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,
Expand Down
3 changes: 3 additions & 0 deletions module/zcommon/zfs_prop.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
44 changes: 33 additions & 11 deletions module/zfs/dmu_recv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));

Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -2486,11 +2486,15 @@ 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() */
(void) nvlist_lookup_uint64(keynvl, "to_ivset_guid",
&drc->drc_ivset_guid);

if (!drc->drc_newfs)
drc->drc_keynvl = fnvlist_dup(keynvl);
}
Expand Down Expand Up @@ -2798,6 +2802,24 @@ 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. Older
* implementations of the raw send code did not include this
* value. By setting the zfs_disable_ivset_guid_check tunable, the
* user can still receive these streams, using the generated value
* if one hasn't been provided.
*/
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);

/*
Expand Down
32 changes: 28 additions & 4 deletions module/zfs/dmu_send.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -1299,7 +1316,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;

Expand All @@ -1325,6 +1342,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 {
Expand Down
Loading

0 comments on commit 7c00f93

Please sign in to comment.