Deny receiving into encrypted datasets if the keys are not loaded (#14139)

Commit 68ddc06b61 introduced support
for receiving unencrypted datasets as children of encrypted ones but
unfortunately got the logic upside down. This resulted in failing to
deny receives of incremental sends into encrypted datasets without
their keys loaded. If receiving a filesystem, the receive was done
into a newly created unencrypted child dataset of the target. In
case of volumes the receive made the target volume undeletable since
a dataset was created below it, which we obviously can't handle.
Incremental streams with embedded blocks are affected as well.

We fix the broken logic to properly deny receives in such cases.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Attila Fülöp <attila@fueloep.org>
Closes #13598
Closes #14055
Closes #14119
This commit is contained in:
Attila Fülöp 2022-11-04 19:07:29 +01:00 committed by GitHub
parent b27c7a1457
commit cd1f023846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 23 additions and 10 deletions

View File

@ -602,7 +602,7 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx)
* so add the DS_HOLD_FLAG_DECRYPT flag only if we are dealing * so add the DS_HOLD_FLAG_DECRYPT flag only if we are dealing
* with a dataset we may encrypt. * with a dataset we may encrypt.
*/ */
if (drba->drba_dcp != NULL && if (drba->drba_dcp == NULL ||
drba->drba_dcp->cp_crypt != ZIO_CRYPT_OFF) { drba->drba_dcp->cp_crypt != ZIO_CRYPT_OFF) {
dsflags |= DS_HOLD_FLAG_DECRYPT; dsflags |= DS_HOLD_FLAG_DECRYPT;
} }

View File

@ -28,10 +28,13 @@
# 1. Snapshot the default dataset # 1. Snapshot the default dataset
# 2. Create an encrypted dataset # 2. Create an encrypted dataset
# 3. Attempt to receive a stream to an encrypted child # 3. Attempt to receive a stream to an encrypted child
# 4. Attempt to receive a stream with properties to an encrypted child # 4. Unload the key
# 5. Attempt to receive a replication stream to an encrypted child # 5. Attempt to receive an incremental stream to an encrypted child (must fail)
# 6. Unmount and unload the encrypted dataset keys # 6. Attempt to receive a stream with properties to an unencrypted child
# 7. Attempt to receive a snapshot stream to an encrypted child # 7. Attempt to receive an incremental stream to an unencrypted child
# 8. Attempt to receive with -o encryption=off to an unencrypted child
# 9. Attempt to receive a replication stream to an unencrypted child
# 10. Attempt to receive a snapshot stream to an encrypted child (must fail)
# #
verify_runnable "both" verify_runnable "both"
@ -39,6 +42,7 @@ verify_runnable "both"
function cleanup function cleanup
{ {
snapexists $snap && destroy_dataset $snap -f snapexists $snap && destroy_dataset $snap -f
snapexists $snap2 && destroy_dataset $snap2 -f
datasetexists $TESTPOOL/$TESTFS1 && \ datasetexists $TESTPOOL/$TESTFS1 && \
destroy_dataset $TESTPOOL/$TESTFS1 -r destroy_dataset $TESTPOOL/$TESTFS1 -r
@ -50,15 +54,17 @@ log_assert "ZFS should receive encrypted filesystems into child dataset"
typeset passphrase="password" typeset passphrase="password"
typeset snap="$TESTPOOL/$TESTFS@snap" typeset snap="$TESTPOOL/$TESTFS@snap"
typeset snap2="$TESTPOOL/$TESTFS@snap2"
typeset testfile="testfile" typeset testfile="testfile"
log_must zfs snapshot $snap log_must zfs snapshot $snap
log_must zfs snapshot $snap2
log_must eval "echo $passphrase | zfs create -o encryption=on" \ log_must eval "echo $passphrase | zfs create -o encryption=on" \
"-o keyformat=passphrase $TESTPOOL/$TESTFS1" "-o keyformat=passphrase $TESTPOOL/$TESTFS1"
log_note "Verifying ZFS will receive to an encrypted child" log_note "Verifying ZFS will receive to an encrypted child"
log_must eval "zfs send $snap | zfs receive $TESTPOOL/$TESTFS1/c1" log_must eval "zfs send $snap | zfs receive -u $TESTPOOL/$TESTFS1/c1"
log_must test "$(get_prop 'encryption' $TESTPOOL/$TESTFS1/c1)" != "off" log_must test "$(get_prop 'encryption' $TESTPOOL/$TESTFS1/c1)" != "off"
# Unload the key, the following tests won't require it and we will test # Unload the key, the following tests won't require it and we will test
@ -66,10 +72,17 @@ log_must test "$(get_prop 'encryption' $TESTPOOL/$TESTFS1/c1)" != "off"
log_must zfs unmount $TESTPOOL/$TESTFS1 log_must zfs unmount $TESTPOOL/$TESTFS1
log_must zfs unload-key $TESTPOOL/$TESTFS1 log_must zfs unload-key $TESTPOOL/$TESTFS1
log_note "Verifying ZFS will not receive an incremental into an encrypted" \
"dataset when the key is unloaded"
log_mustnot eval "zfs send -i $snap $snap2 | zfs receive $TESTPOOL/$TESTFS1/c1"
log_note "Verifying 'send -p' will receive to an unencrypted child" log_note "Verifying 'send -p' will receive to an unencrypted child"
log_must eval "zfs send -p $snap | zfs receive $TESTPOOL/$TESTFS1/c2" log_must eval "zfs send -p $snap | zfs receive -u $TESTPOOL/$TESTFS1/c2"
log_must test "$(get_prop 'encryption' $TESTPOOL/$TESTFS1/c2)" == "off" log_must test "$(get_prop 'encryption' $TESTPOOL/$TESTFS1/c2)" == "off"
log_note "Verifying 'send -i' will receive to an unencrypted child"
log_must eval "zfs send -i $snap $snap2 | zfs receive $TESTPOOL/$TESTFS1/c2"
# For completeness add the property override case. # For completeness add the property override case.
log_note "Verifying recv -o encyption=off' will receive to an unencrypted child" log_note "Verifying recv -o encyption=off' will receive to an unencrypted child"
log_must eval "zfs send $snap | \ log_must eval "zfs send $snap | \