Skip to content

Commit

Permalink
Fix errata openzfs#4 handling for resuming streams
Browse files Browse the repository at this point in the history
Currently, the handling for errata openzfs#4 has two issues which allow
the checks for this issue to be bypassed using resumable sends.
The first issue is that drc->drc_fromsnapobj is not set in the
resuming code as it is in the non-resuming code. This causes
dsl_crypto_recv_key_check() to skip its checks for the
from_ivset_guid. The second issue is that resumable sends do not
clean up their on-disk state if they fail the checks in
dmu_recv_stream() that happen before any data is received.

As a result of these two bugs, a user can attempt a resumable send
of a dataset without a from_ivset_guid. This will fail the initial
dmu_recv_stream() checks, leaving a valid resume state. The send
can then be resumed, which skips those checks, allowing the receive
to be completed.

This commit fixes these issues by setting drc->drc_fromsnapobj in
the resuming receive path and by ensuring that resumablereceives
are properly cleaned up if they fail the initial dmu_recv_stream()
checks.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Tom Caputi <tcaputi@datto.com>
Closes openzfs#9818
Closes openzfs#9829
  • Loading branch information
Tom Caputi authored and tonyhutter committed Apr 29, 2020
1 parent 1e04287 commit e028b43
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 1 deletion.
1 change: 1 addition & 0 deletions include/sys/dmu_recv.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ typedef struct dmu_recv_cookie {
boolean_t drc_byteswap;
boolean_t drc_force;
boolean_t drc_resumable;
boolean_t drc_should_save;
boolean_t drc_raw;
boolean_t drc_clone;
boolean_t drc_spill;
Expand Down
15 changes: 14 additions & 1 deletion module/zfs/dmu_recv.c
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ dmu_recv_resume_begin_check(void *arg, dmu_tx_t *tx)
dmu_recv_begin_arg_t *drba = arg;
dsl_pool_t *dp = dmu_tx_pool(tx);
struct drr_begin *drrb = drba->drba_cookie->drc_drrb;
dmu_recv_cookie_t *drc = drba->drba_cookie;
int error;
ds_hold_flags_t dsflags = 0;
uint64_t featureflags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo);
Expand Down Expand Up @@ -694,6 +695,9 @@ dmu_recv_resume_begin_check(void *arg, dmu_tx_t *tx)
return (SET_ERROR(EINVAL));
}

if (ds->ds_prev != NULL)
drc->drc_fromsnapobj = ds->ds_prev->ds_object;

dsl_dataset_rele_flags(ds, dsflags, FTAG);
return (0);
}
Expand Down Expand Up @@ -747,6 +751,7 @@ dmu_recv_resume_begin_sync(void *arg, dmu_tx_t *tx)
rrw_exit(&ds->ds_bp_rwlock, FTAG);

drba->drba_cookie->drc_ds = ds;
drba->drba_cookie->drc_should_save = B_TRUE;

spa_history_log_internal_ds(ds, "resume receive", tx, "");
}
Expand Down Expand Up @@ -1868,7 +1873,8 @@ dmu_recv_cleanup_ds(dmu_recv_cookie_t *drc)
ds->ds_objset->os_raw_receive = B_FALSE;

rrw_enter(&ds->ds_bp_rwlock, RW_READER, FTAG);
if (drc->drc_resumable && !BP_IS_HOLE(dsl_dataset_get_blkptr(ds))) {
if (drc->drc_resumable && drc->drc_should_save &&
!BP_IS_HOLE(dsl_dataset_get_blkptr(ds))) {
rrw_exit(&ds->ds_bp_rwlock, FTAG);
dsl_dataset_disown(ds, dsflags, dmu_recv_tag);
} else {
Expand Down Expand Up @@ -2625,6 +2631,13 @@ dmu_recv_stream(dmu_recv_cookie_t *drc, vnode_t *vp, offset_t *voffp,
goto out;
}

/*
* If we failed before this point we will clean up any new resume
* state that was created. Now that we've gotten past the initial
* checks we are ok to retain that resume state.
*/
drc->drc_should_save = B_TRUE;

(void) bqueue_init(&rwa->q,
MAX(zfs_recv_queue_length, 2 * zfs_max_recordsize),
offsetof(struct receive_record_arg, node));
Expand Down

0 comments on commit e028b43

Please sign in to comment.