Allow block cloning across encrypted datasets

When two datasets share the same master encryption key, it is safe
to clone encrypted blocks. Currently only snapshots and clones
of a dataset share with it the same encryption key.

Added a test for:
- Clone from encrypted sibling to encrypted sibling with
  non encrypted parent
- Clone from encrypted parent to inherited encrypted child
- Clone from child to sibling with encrypted parent
- Clone from snapshot to the original datasets
- Clone from foreign snapshot to a foreign dataset
- Cloning from non-encrypted to encrypted datasets
- Cloning from encrypted to non-encrypted datasets

Reviewed-by: Alexander Motin <mav@FreeBSD.org>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Original-patch-by: Pawel Jakub Dawidek <pawel@dawidek.net>
Signed-off-by: Kay Pedersen <mail@mkwg.de>
Closes #15544
This commit is contained in:
oromenahar
2023-12-05 20:03:48 +01:00
committed by GitHub
parent 55b764e062
commit c7b6119268
10 changed files with 236 additions and 25 deletions
+1
View File
@@ -451,6 +451,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/block_cloning/block_cloning_ficlone.ksh \
functional/block_cloning/block_cloning_ficlonerange.ksh \
functional/block_cloning/block_cloning_ficlonerange_partial.ksh \
functional/block_cloning/block_cloning_cross_enc_dataset.ksh \
functional/bootfs/bootfs_001_pos.ksh \
functional/bootfs/bootfs_002_neg.ksh \
functional/bootfs/bootfs_003_pos.ksh \
@@ -28,8 +28,8 @@
function have_same_content
{
typeset hash1=$(cat $1 | md5sum)
typeset hash2=$(cat $2 | md5sum)
typeset hash1=$(md5digest $1)
typeset hash2=$(md5digest $2)
log_must [ "$hash1" = "$hash2" ]
}
@@ -44,10 +44,14 @@ function have_same_content
#
function get_same_blocks
{
KEY=$5
if [ ${#KEY} -gt 0 ]; then
KEY="--key=$KEY"
fi
typeset zdbout=${TMPDIR:-$TEST_BASE_DIR}/zdbout.$$
zdb -vvvvv $1 -O $2 | \
zdb $KEY -vvvvv $1 -O $2 | \
awk '/ L0 / { print l++ " " $3 " " $7 }' > $zdbout.a
zdb -vvvvv $3 -O $4 | \
zdb $KEY -vvvvv $3 -O $4 | \
awk '/ L0 / { print l++ " " $3 " " $7 }' > $zdbout.b
echo $(sort $zdbout.a $zdbout.b | uniq -d | cut -f1 -d' ')
}
@@ -0,0 +1,170 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2023, Kay Pedersen <mail@mkwg.de>
#
. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/block_cloning/block_cloning.kshlib
verify_runnable "global"
if [[ $(linux_version) -lt $(linux_version "5.3") ]]; then
log_unsupported "copy_file_range can't copy cross-filesystem before Linux 5.3"
fi
claim="Block cloning across encrypted datasets."
log_assert $claim
DS1="$TESTPOOL/encrypted1"
DS2="$TESTPOOL/encrypted2"
DS1_NC="$TESTPOOL/notcrypted1"
PASSPHRASE="top_secret"
function prepare_enc
{
log_must zpool create -o feature@block_cloning=enabled $TESTPOOL $DISKS
log_must eval "echo $PASSPHRASE | zfs create -o encryption=on" \
"-o keyformat=passphrase -o keylocation=prompt $DS1"
log_must eval "echo $PASSPHRASE | zfs create -o encryption=on" \
"-o keyformat=passphrase -o keylocation=prompt $DS2"
log_must zfs create $DS1/child1
log_must zfs create $DS1/child2
log_must zfs create $DS1_NC
log_note "Create test file"
# we must wait until the src file txg is written to the disk otherwise we
# will fallback to normal copy. See "dmu_read_l0_bps" in
# "zfs/module/zfs/dmu.c" and "zfs_clone_range" in
# "zfs/module/zfs/zfs_vnops.c"
log_must dd if=/dev/urandom of=/$DS1/file bs=128K count=4
log_must dd if=/dev/urandom of=/$DS1/child1/file bs=128K count=4
log_must dd if=/dev/urandom of=/$DS1_NC/file bs=128K count=4
log_must sync_pool $TESTPOOL
}
function cleanup_enc
{
datasetexists $TESTPOOL && destroy_pool $TESTPOOL
}
function clone_and_check
{
I_FILE="$1"
O_FILE=$2
I_DS=$3
O_DS=$4
SAME_BLOCKS=$5
# the CLONE option provides a choice between copy_file_range
# which should clone and a dd which is a copy no matter what
CLONE=$6
SNAPSHOT=$7
if [ ${#SNAPSHOT} -gt 0 ]; then
I_FILE=".zfs/snapshot/$SNAPSHOT/$1"
fi
if [ $CLONE ]; then
log_must clonefile -f "/$I_DS/$I_FILE" "/$O_DS/$O_FILE" 0 0 524288
else
log_must dd if="/$I_DS/$I_FILE" of="/$O_DS/$O_FILE" bs=128K
fi
log_must sync_pool $TESTPOOL
log_must have_same_content "/$I_DS/$I_FILE" "/$O_DS/$O_FILE"
if [ ${#SNAPSHOT} -gt 0 ]; then
I_DS="$I_DS@$SNAPSHOT"
I_FILE="$1"
fi
typeset blocks=$(get_same_blocks \
$I_DS $I_FILE $O_DS $O_FILE $PASSPHRASE)
log_must [ "$blocks" = "$SAME_BLOCKS" ]
}
log_onexit cleanup_enc
prepare_enc
log_note "Cloning entire file with copy_file_range across different enc" \
"roots, should fallback"
# we are expecting no same block map.
clone_and_check "file" "clone" $DS1 $DS2 "" true
log_note "check if the file is still readable and the same after" \
"unmount and key unload, shouldn't fail"
typeset hash1=$(md5digest "/$DS1/file")
log_must zfs umount $DS1 && zfs unload-key $DS1
typeset hash2=$(md5digest "/$DS2/clone")
log_must [ "$hash1" = "$hash2" ]
cleanup_enc
prepare_enc
log_note "Cloning entire file with copy_file_range across different child datasets"
# clone shouldn't work because of deriving a new master key for the child
# we are expecting no same block map.
clone_and_check "file" "clone" $DS1 "$DS1/child1" "" true
clone_and_check "file" "clone" "$DS1/child1" "$DS1/child2" "" true
cleanup_enc
prepare_enc
log_note "Copying entire file with copy_file_range across same snapshot"
log_must zfs snapshot -r $DS1@s1
log_must sync_pool $TESTPOOL
log_must rm -f "/$DS1/file"
log_must sync_pool $TESTPOOL
clone_and_check "file" "clone" "$DS1" "$DS1" "0 1 2 3" true "s1"
cleanup_enc
prepare_enc
log_note "Copying entire file with copy_file_range across different snapshot"
clone_and_check "file" "file" $DS1 $DS2 "" true
log_must zfs snapshot -r $DS2@s1
log_must sync_pool $TESTPOOL
log_must rm -f "/$DS1/file" "/$DS2/file"
log_must sync_pool $TESTPOOL
clone_and_check "file" "clone" "$DS2" "$DS1" "" true "s1"
typeset hash1=$(md5digest "/$DS1/.zfs/snapshot/s1/file")
log_note "destroy the snapshot and check if the file is still readable and" \
"has the same content"
log_must zfs destroy -r $DS2@s1
log_must sync_pool $TESTPOOL
typeset hash2=$(md5digest "/$DS1/file")
log_must [ "$hash1" = "$hash2" ]
cleanup_enc
prepare_enc
log_note "Copying with copy_file_range from non encrypted to encrypted"
clone_and_check "file" "copy" $DS1_NC $DS1 "" true
cleanup_enc
prepare_enc
log_note "Copying with copy_file_range from encrypted to non encrypted"
clone_and_check "file" "copy" $DS1 $DS1_NC "" true
log_must sync_pool $TESTPOOL
log_pass $claim