Detect and prevent mixed raw and non-raw sends

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
This commit is contained in:
Tom Caputi
2019-02-04 14:24:55 -05:00
committed by Brian Behlendorf
parent 579ce7c5ae
commit f00ab3f22c
27 changed files with 607 additions and 53 deletions
+3 -2
View 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]
@@ -88,5 +88,6 @@ if is_linux; then
"feature@project_quota"
"feature@allocation_classes"
"feature@resilver_defer"
"feature@bookmark_v2"
)
fi
@@ -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 \
@@ -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
@@ -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"
@@ -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"
@@ -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 = \
+118
View File
@@ -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"