mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
Harden recv record validation
Refactors and enhances how we do recv record validation. I've made sure to keep all existing checks and added a few new ones. The per-record type validator functions are also used by zstream so this is now common code. Additionally, more detailed error messages have been plumbed through to user space. Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Garth Snyder <[email protected]> Signed-off-by: Alek Pinchuk <[email protected]> Closes #18725
This commit is contained in:
1 parent
817fc376f1
commit
9b7642df93
13 files changed
+1398
-145
No files matched your search
@@ -292,6 +292,73 @@ parallel_compress_writes(compression_spec_t *target)
|
||||
return (step);
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep DRR_BEGIN feature flags consistent with the WRITE payloads we emit.
|
||||
* Compressed WRITEs require DMU_BACKUP_FEATURE_COMPRESSED (and LZ4/ZSTD as
|
||||
* appropriate). Recompressing to off clears COMPRESSED for non-raw streams;
|
||||
* raw streams may retain encrypted compressed WRITEs that recompress skips.
|
||||
* LZ4/ZSTD are cleared only when neither EMBED_DATA nor RAW remains, since
|
||||
* recompress does not rewrite those record types.
|
||||
*/
|
||||
static disposition_t
|
||||
chain_update_compress_features(void *item_in, void *context_in)
|
||||
{
|
||||
drr_packet_t *item = (drr_packet_t *)item_in;
|
||||
compression_spec_t *spec = (compression_spec_t *)context_in;
|
||||
struct drr_begin *drrb;
|
||||
uint64_t flags;
|
||||
|
||||
if (item == NULL)
|
||||
return (D_OK);
|
||||
|
||||
if (item->dp_drr.drr_type != DRR_BEGIN)
|
||||
return (D_OK);
|
||||
|
||||
drrb = &item->dp_drr.drr_u.drr_begin;
|
||||
flags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo);
|
||||
|
||||
if (ctype_is_uncompressed(spec->cs_type)) {
|
||||
if (!(flags & DMU_BACKUP_FEATURE_RAW))
|
||||
flags &= ~DMU_BACKUP_FEATURE_COMPRESSED;
|
||||
if ((flags & (DMU_BACKUP_FEATURE_EMBED_DATA |
|
||||
DMU_BACKUP_FEATURE_RAW)) == 0) {
|
||||
flags &= ~(DMU_BACKUP_FEATURE_LZ4 |
|
||||
DMU_BACKUP_FEATURE_ZSTD);
|
||||
}
|
||||
} else {
|
||||
flags |= DMU_BACKUP_FEATURE_COMPRESSED;
|
||||
if (spec->cs_type == ZIO_COMPRESS_ZSTD) {
|
||||
flags |= DMU_BACKUP_FEATURE_ZSTD;
|
||||
} else if (spec->cs_type >= ZIO_COMPRESS_LZ4) {
|
||||
flags |= DMU_BACKUP_FEATURE_LZ4;
|
||||
}
|
||||
}
|
||||
|
||||
DMU_SET_FEATUREFLAGS(drrb->drr_versioninfo, flags);
|
||||
return (D_OK);
|
||||
}
|
||||
|
||||
static chain_step_t
|
||||
serial_update_compress_features(compression_spec_t *target)
|
||||
{
|
||||
int this_spec = next_spec++ % MAX_COMPRESSION_STEPS;
|
||||
compression_spec_t *context = &specs[this_spec];
|
||||
|
||||
VERIFY3P(target, !=, NULL);
|
||||
*context = *target;
|
||||
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
.cs_in_size = sizeof (drr_packet_t),
|
||||
.cs_out_size = sizeof (drr_packet_t),
|
||||
.cs_context = context,
|
||||
.cs_serial = {
|
||||
.process = chain_update_compress_features,
|
||||
}
|
||||
};
|
||||
return (step);
|
||||
}
|
||||
|
||||
int
|
||||
zstream_do_recompress(int argc, char *argv[])
|
||||
{
|
||||
@@ -350,6 +417,7 @@ zstream_do_recompress(int argc, char *argv[])
|
||||
STANDARD_INPUT_STACK(NULL),
|
||||
parallel_decompress_writes(&spec),
|
||||
parallel_compress_writes(&spec),
|
||||
serial_update_compress_features(&spec),
|
||||
STANDARD_OUTPUT_STACK(NULL)
|
||||
};
|
||||
|
||||
|
||||
@@ -21,27 +21,48 @@
|
||||
#include <err.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/dmu_recv.h>
|
||||
#include <sys/stdtypes.h>
|
||||
#include <sys/zfs_ioctl.h>
|
||||
#include <sys/zio_compress.h>
|
||||
|
||||
#include "zstream_modules.h"
|
||||
|
||||
/*
|
||||
* Validate consistency and well-formedness of the actual DRR records. I
|
||||
* have swept all the existing validation code into this module, but it's
|
||||
* still pretty sparse.
|
||||
* Validate consistency and well-formedness of the actual DRR records.
|
||||
*/
|
||||
|
||||
#define MAX_VALIDATIONS 4
|
||||
|
||||
typedef struct {
|
||||
int nesting;
|
||||
uint64_t featureflags;
|
||||
boolean_t begin_spill;
|
||||
} validate_context_t;
|
||||
|
||||
static validate_context_t contexts[MAX_VALIDATIONS];
|
||||
static int next_context = 0;
|
||||
|
||||
static void
|
||||
validate_fail(int err, const char *msg)
|
||||
{
|
||||
if (err == 0)
|
||||
return;
|
||||
if (msg != NULL && msg[0] != '\0')
|
||||
errx(1, "%s", msg);
|
||||
errx(1, "invalid receive stream record (error %d)", err);
|
||||
}
|
||||
|
||||
static boolean_t
|
||||
validate_stream_has_feature(const validate_context_t *context, uint64_t feature)
|
||||
{
|
||||
/*
|
||||
* STREAM_HAS_FEATURE() describes the first DRR_BEGIN in the input.
|
||||
* Recursive streams can contain later BEGIN records with different
|
||||
* feature flags, so validation must use the current substream flags.
|
||||
*/
|
||||
return ((context->featureflags & feature) != 0);
|
||||
}
|
||||
|
||||
static disposition_t
|
||||
chain_validate_records(void *item_in, void *context_in)
|
||||
{
|
||||
@@ -54,6 +75,14 @@ chain_validate_records(void *item_in, void *context_in)
|
||||
struct dmu_replay_record *drr = &item->dp_drr;
|
||||
struct drr_write *drrw = &drr->drr_u.drr_write;
|
||||
struct drr_object *drro = &drr->drr_u.drr_object;
|
||||
struct drr_spill *drrs = &drr->drr_u.drr_spill;
|
||||
struct drr_write_embedded *drrwe = &drr->drr_u.drr_write_embedded;
|
||||
struct drr_free *drrf = &drr->drr_u.drr_free;
|
||||
struct drr_freeobjects *drrfo = &drr->drr_u.drr_freeobjects;
|
||||
struct drr_object_range *drror = &drr->drr_u.drr_object_range;
|
||||
char errbuf[RECV_CHECK_ERRBUFLEN];
|
||||
int err;
|
||||
boolean_t is_raw;
|
||||
|
||||
if (OPTION_ENABLED(CA_DO_NOT_VALIDATE))
|
||||
return (D_OK);
|
||||
@@ -65,6 +94,10 @@ chain_validate_records(void *item_in, void *context_in)
|
||||
if (drr->drr_type == DRR_BEGIN) {
|
||||
VERIFY0(context->nesting);
|
||||
context->nesting++;
|
||||
context->featureflags = DMU_GET_FEATUREFLAGS(
|
||||
drr->drr_u.drr_begin.drr_versioninfo);
|
||||
context->begin_spill = !!(drr->drr_u.drr_begin.drr_flags &
|
||||
DRR_FLAG_SPILL_BLOCK);
|
||||
} else if (drr->drr_type == DRR_END) {
|
||||
VERIFY3S(context->nesting, >=, 0);
|
||||
if (context->nesting > 0)
|
||||
@@ -75,6 +108,8 @@ chain_validate_records(void *item_in, void *context_in)
|
||||
VERIFY3S(context->nesting, ==, 1);
|
||||
}
|
||||
|
||||
is_raw = validate_stream_has_feature(context, DMU_BACKUP_FEATURE_RAW);
|
||||
|
||||
switch (drr->drr_type) {
|
||||
|
||||
case DRR_BEGIN:
|
||||
@@ -82,27 +117,46 @@ chain_validate_records(void *item_in, void *context_in)
|
||||
break;
|
||||
|
||||
case DRR_OBJECT:
|
||||
{
|
||||
boolean_t is_raw = !!(chain_attrs->ca_feature_flags &
|
||||
DMU_BACKUP_FEATURE_RAW);
|
||||
boolean_t bonus_gt_raw = drro->drr_bonuslen >
|
||||
drro->drr_raw_bonuslen;
|
||||
if (is_raw && bonus_gt_raw) {
|
||||
fprintf(stderr,
|
||||
"Warning: object %llu has bonuslen = "
|
||||
"%u > raw_bonuslen = %u\n\n",
|
||||
(u_longlong_t)drro->drr_object,
|
||||
drro->drr_bonuslen,
|
||||
drro->drr_raw_bonuslen);
|
||||
}
|
||||
break;
|
||||
}
|
||||
err = recv_check_drr_object(drro, NULL, is_raw,
|
||||
context->begin_spill, context->featureflags, errbuf,
|
||||
sizeof (errbuf));
|
||||
validate_fail(err, errbuf);
|
||||
break;
|
||||
|
||||
case DRR_WRITE:
|
||||
if (drrw->drr_compressiontype >= ZIO_COMPRESS_FUNCTIONS) {
|
||||
errx(1, "invalid compression type: %d",
|
||||
drrw->drr_compressiontype);
|
||||
}
|
||||
err = recv_check_drr_write(drrw, NULL, is_raw,
|
||||
context->featureflags, errbuf, sizeof (errbuf));
|
||||
validate_fail(err, errbuf);
|
||||
break;
|
||||
|
||||
case DRR_WRITE_EMBEDDED:
|
||||
err = recv_check_drr_write_embedded(drrwe, NULL, is_raw,
|
||||
context->featureflags, errbuf, sizeof (errbuf));
|
||||
validate_fail(err, errbuf);
|
||||
break;
|
||||
|
||||
case DRR_SPILL:
|
||||
err = recv_check_drr_spill(drrs, NULL, is_raw,
|
||||
context->featureflags, errbuf, sizeof (errbuf));
|
||||
validate_fail(err, errbuf);
|
||||
break;
|
||||
|
||||
case DRR_FREE:
|
||||
case DRR_REDACT:
|
||||
err = recv_check_drr_free(drrf, errbuf, sizeof (errbuf));
|
||||
validate_fail(err, errbuf);
|
||||
break;
|
||||
|
||||
case DRR_FREEOBJECTS:
|
||||
err = recv_check_drr_freeobjects(drrfo, errbuf,
|
||||
sizeof (errbuf));
|
||||
validate_fail(err, errbuf);
|
||||
break;
|
||||
|
||||
case DRR_OBJECT_RANGE:
|
||||
err = recv_check_drr_object_range(drror, is_raw, errbuf,
|
||||
sizeof (errbuf));
|
||||
validate_fail(err, errbuf);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -118,6 +172,8 @@ serial_validate_records(void)
|
||||
int context_ix = next_context++ % MAX_VALIDATIONS;
|
||||
validate_context_t *context = &contexts[context_ix];
|
||||
context->nesting = 0;
|
||||
context->featureflags = 0;
|
||||
context->begin_spill = B_FALSE;
|
||||
|
||||
chain_step_t step = {
|
||||
.cs_type = CS_SERIAL,
|
||||
|
||||
@@ -32,10 +32,12 @@
|
||||
#define _DMU_RECV_H
|
||||
|
||||
#include <sys/inttypes.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/dsl_bookmark.h>
|
||||
#include <sys/dsl_dataset.h>
|
||||
#include <sys/spa.h>
|
||||
#include <sys/objlist.h>
|
||||
#include <sys/zfs_ioctl.h>
|
||||
|
||||
extern const char *const recv_clone_name;
|
||||
|
||||
@@ -61,6 +63,7 @@ typedef struct dmu_recv_cookie {
|
||||
void *drc_owner;
|
||||
cred_t *drc_cred;
|
||||
nvlist_t *drc_begin_nvl;
|
||||
nvlist_t *drc_errors;
|
||||
|
||||
objset_t *drc_os;
|
||||
zfs_file_t *drc_fp; /* The file to read the stream from */
|
||||
@@ -86,4 +89,31 @@ int dmu_recv_stream(dmu_recv_cookie_t *, offset_t *);
|
||||
int dmu_recv_end(dmu_recv_cookie_t *, void *);
|
||||
boolean_t dmu_objset_is_receiving(objset_t *);
|
||||
|
||||
/*
|
||||
* Receive stream record validators. spa may be NULL to validate against the
|
||||
* largest supported pool limits (for userland tools such as zstream). errbuf
|
||||
* is optional; when provided it receives a short description on failure.
|
||||
*
|
||||
* Size fields that exceed a pool or on-wire maximum return ERANGE; other
|
||||
* malformed or inconsistent records return EINVAL. Callers of lzc_receive*
|
||||
* may therefore observe ERANGE where older OpenZFS modules returned EINVAL
|
||||
* for the same oversized record.
|
||||
*/
|
||||
#define RECV_CHECK_ERRBUFLEN 256
|
||||
|
||||
int recv_check_drr_object(const struct drr_object *, spa_t *, boolean_t raw,
|
||||
boolean_t spill, uint64_t featureflags, char *errbuf, size_t errbuflen);
|
||||
int recv_check_drr_free(const struct drr_free *, char *errbuf,
|
||||
size_t errbuflen);
|
||||
int recv_check_drr_freeobjects(const struct drr_freeobjects *, char *errbuf,
|
||||
size_t errbuflen);
|
||||
int recv_check_drr_object_range(const struct drr_object_range *, boolean_t raw,
|
||||
char *errbuf, size_t errbuflen);
|
||||
int recv_check_drr_spill(const struct drr_spill *, spa_t *, boolean_t raw,
|
||||
uint64_t featureflags, char *errbuf, size_t errbuflen);
|
||||
int recv_check_drr_write(const struct drr_write *, spa_t *, boolean_t raw,
|
||||
uint64_t featureflags, char *errbuf, size_t errbuflen);
|
||||
int recv_check_drr_write_embedded(const struct drr_write_embedded *, spa_t *,
|
||||
boolean_t raw, uint64_t featureflags, char *errbuf, size_t errbuflen);
|
||||
|
||||
#endif /* _DMU_RECV_H */
|
||||
@@ -381,6 +381,7 @@ typedef enum {
|
||||
|
||||
#define ZPROP_SOURCE_VAL_RECVD "$recvd"
|
||||
#define ZPROP_N_MORE_ERRORS "N_MORE_ERRORS"
|
||||
#define ZFS_RECV_ERR_STREAM "stream"
|
||||
|
||||
/*
|
||||
* Dataset flag implemented as a special entry in the props zap object
|
||||
|
||||
@@ -5099,16 +5099,24 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
|
||||
char tbuf[1024];
|
||||
zfs_prop_t prop;
|
||||
int intval;
|
||||
const char *pname = nvpair_name(prop_err);
|
||||
|
||||
prop = zfs_name_to_prop(nvpair_name(prop_err));
|
||||
/*
|
||||
* errors may also carry non-property keys (e.g.
|
||||
* ZFS_RECV_ERR_STREAM). Only int32 pairs are
|
||||
* property apply failures.
|
||||
*/
|
||||
if (nvpair_type(prop_err) != DATA_TYPE_INT32)
|
||||
continue;
|
||||
|
||||
prop = zfs_name_to_prop(pname);
|
||||
(void) nvpair_value_int32(prop_err, &intval);
|
||||
if (strcmp(nvpair_name(prop_err),
|
||||
ZPROP_N_MORE_ERRORS) == 0) {
|
||||
if (strcmp(pname, ZPROP_N_MORE_ERRORS) == 0) {
|
||||
trunc_prop_errs(intval);
|
||||
break;
|
||||
} else if (snapname == NULL || finalsnap == NULL ||
|
||||
strcmp(finalsnap, snapname) == 0 ||
|
||||
strcmp(nvpair_name(prop_err),
|
||||
strcmp(pname,
|
||||
zfs_prop_to_name(ZFS_PROP_REFQUOTA)) != 0) {
|
||||
/*
|
||||
* Skip the special case of, for example,
|
||||
@@ -5124,7 +5132,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
|
||||
(void) snprintf(tbuf, sizeof (tbuf),
|
||||
dgettext(TEXT_DOMAIN,
|
||||
"cannot receive %s property on %s"),
|
||||
nvpair_name(prop_err), name);
|
||||
pname, name);
|
||||
zfs_setprop_error(hdl, prop, intval, tbuf);
|
||||
}
|
||||
}
|
||||
@@ -5193,6 +5201,13 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
|
||||
}
|
||||
|
||||
if (ioctl_err != 0) {
|
||||
const char *stream_err = NULL;
|
||||
|
||||
if (prop_errors != NULL) {
|
||||
(void) nvlist_lookup_string(prop_errors,
|
||||
ZFS_RECV_ERR_STREAM, &stream_err);
|
||||
}
|
||||
|
||||
switch (ioctl_errno) {
|
||||
case ENODEV:
|
||||
cp = strchr(destsnap, '@');
|
||||
@@ -5242,11 +5257,14 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
|
||||
*cp = '@';
|
||||
break;
|
||||
case EINVAL:
|
||||
if (embedded && !raw) {
|
||||
case ERANGE:
|
||||
if (stream_err != NULL) {
|
||||
zfs_error_aux(hdl, "%s", stream_err);
|
||||
} else if (ioctl_errno == EINVAL && embedded && !raw) {
|
||||
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
|
||||
"incompatible embedded data stream "
|
||||
"feature with encrypted receive."));
|
||||
} else if (flags->resumable) {
|
||||
} else if (ioctl_errno == EINVAL && flags->resumable) {
|
||||
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
|
||||
"kernel modules must be upgraded to "
|
||||
"receive this stream."));
|
||||
|
||||
@@ -1205,11 +1205,22 @@ recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
|
||||
error = nvlist_lookup_uint64(outnvl, "error_flags",
|
||||
errflags);
|
||||
|
||||
if (error == 0 && errors != NULL) {
|
||||
/*
|
||||
* Copy the kernel "errors" nvlist when present. On success
|
||||
* this carries property apply failures; on failure it may
|
||||
* also include ZFS_RECV_ERR_STREAM. Do not allocate an
|
||||
* empty nvlist when the key is absent — leave *errors
|
||||
* unchanged (callers must initialize it to NULL). If the
|
||||
* caller requested errors and the ioctl succeeded but the
|
||||
* key is missing, treat that as ENOENT (incomplete output).
|
||||
*/
|
||||
if (errors != NULL && outnvl != NULL) {
|
||||
nvlist_t *nvl;
|
||||
error = nvlist_lookup_nvlist(outnvl, "errors", &nvl);
|
||||
if (error == 0)
|
||||
|
||||
if (nvlist_lookup_nvlist(outnvl, "errors", &nvl) == 0)
|
||||
*errors = fnvlist_dup(nvl);
|
||||
else if (error == 0)
|
||||
error = ENOENT;
|
||||
}
|
||||
|
||||
fnvlist_free(innvl);
|
||||
@@ -1261,11 +1272,19 @@ recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
|
||||
|
||||
if (errflags != NULL)
|
||||
*errflags = zc.zc_obj;
|
||||
}
|
||||
|
||||
if (errors != NULL)
|
||||
VERIFY0(nvlist_unpack(
|
||||
(void *)(uintptr_t)zc.zc_nvlist_dst,
|
||||
zc.zc_nvlist_dst_size, errors, KM_SLEEP));
|
||||
/*
|
||||
* Unpack errors when the kernel filled the dst buffer, on
|
||||
* success or failure (stream/property details). The caller's
|
||||
* pointer is unchanged when the buffer was not filled.
|
||||
*/
|
||||
if (errors != NULL && zc.zc_nvlist_dst_filled) {
|
||||
if (nvlist_unpack((void *)(uintptr_t)zc.zc_nvlist_dst,
|
||||
zc.zc_nvlist_dst_size, errors, KM_SLEEP) != 0 &&
|
||||
error == 0) {
|
||||
error = EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (rp_packed != NULL)
|
||||
@@ -1352,7 +1371,15 @@ lzc_receive_with_header(const char *snapname, nvlist_t *props,
|
||||
* The 'action_handle' and 'cleanup_fd' are no longer used, and are ignored.
|
||||
*
|
||||
* The 'errors' nvlist contains an entry for each unapplied received
|
||||
* property. Callers are responsible for freeing this nvlist.
|
||||
* property, and may also include ZFS_RECV_ERR_STREAM when the kernel
|
||||
* rejected the stream. It is set when the kernel returned an errors
|
||||
* payload (including on ioctl failure); otherwise the caller's pointer
|
||||
* is unchanged and must be initialized to NULL. Callers are responsible
|
||||
* for freeing this nvlist when it is set.
|
||||
*
|
||||
* Oversized stream records return ERANGE (libzfs maps this to
|
||||
* EZFS_BADSTREAM along with EINVAL). Older OpenZFS modules often
|
||||
* returned EINVAL for the same condition.
|
||||
*/
|
||||
int
|
||||
lzc_receive_one(const char *snapname, nvlist_t *props,
|
||||
|
||||
+1019
-93
File diff suppressed because it is too large.
Load diff
+30
-12
@@ -1632,17 +1632,25 @@ put_nvlist(zfs_cmd_t *zc, nvlist_t *nvl)
|
||||
size = fnvlist_size(nvl);
|
||||
|
||||
if (size > zc->zc_nvlist_dst_size) {
|
||||
error = SET_ERROR(ENOMEM);
|
||||
} else {
|
||||
packed = fnvlist_pack(nvl, &size);
|
||||
if (ddi_copyout(packed, (void *)(uintptr_t)zc->zc_nvlist_dst,
|
||||
size, zc->zc_iflags) != 0)
|
||||
error = SET_ERROR(EFAULT);
|
||||
fnvlist_pack_free(packed, size);
|
||||
/*
|
||||
* Report the required size so the caller can retry, but do
|
||||
* not claim the destination buffer was filled.
|
||||
*/
|
||||
zc->zc_nvlist_dst_size = size;
|
||||
zc->zc_nvlist_dst_filled = B_FALSE;
|
||||
return (SET_ERROR(ENOMEM));
|
||||
}
|
||||
|
||||
zc->zc_nvlist_dst_size = size;
|
||||
zc->zc_nvlist_dst_filled = B_TRUE;
|
||||
packed = fnvlist_pack(nvl, &size);
|
||||
if (ddi_copyout(packed, (void *)(uintptr_t)zc->zc_nvlist_dst,
|
||||
size, zc->zc_iflags) != 0) {
|
||||
zc->zc_nvlist_dst_filled = B_FALSE;
|
||||
error = SET_ERROR(EFAULT);
|
||||
} else {
|
||||
zc->zc_nvlist_dst_size = size;
|
||||
zc->zc_nvlist_dst_filled = B_TRUE;
|
||||
}
|
||||
fnvlist_pack_free(packed, size);
|
||||
return (error);
|
||||
}
|
||||
|
||||
@@ -5765,6 +5773,7 @@ zfs_ioc_recv_impl(char *tofs, char *tosnap, const char *origin,
|
||||
&off);
|
||||
if (error != 0)
|
||||
goto out;
|
||||
drc.drc_errors = *errors;
|
||||
tofs_was_redacted = dsl_get_redacted(drc.drc_ds);
|
||||
|
||||
/*
|
||||
@@ -6120,7 +6129,9 @@ zfs_ioc_recv(zfs_cmd_t *zc)
|
||||
|
||||
/*
|
||||
* Now that all props, initial and delayed, are set, report the prop
|
||||
* errors to the caller.
|
||||
* errors to the caller. Do not overwrite a non-zero receive errno
|
||||
* (e.g. ERANGE/EINVAL from stream validation) if the errors nvlist
|
||||
* cannot be copied out.
|
||||
*/
|
||||
if (zc->zc_nvlist_dst_size != 0 && errors != NULL &&
|
||||
(nvlist_smush(errors, zc->zc_nvlist_dst_size) != 0 ||
|
||||
@@ -6129,7 +6140,8 @@ zfs_ioc_recv(zfs_cmd_t *zc)
|
||||
* Caller made zc->zc_nvlist_dst less than the minimum expected
|
||||
* size or supplied an invalid address.
|
||||
*/
|
||||
error = SET_ERROR(EINVAL);
|
||||
if (error == 0)
|
||||
error = SET_ERROR(EINVAL);
|
||||
}
|
||||
|
||||
out:
|
||||
@@ -8764,9 +8776,15 @@ zfsdev_ioctl_common(uint_t vecnum, zfs_cmd_t *zc, int flag)
|
||||
}
|
||||
if (smusherror == 0)
|
||||
puterror = put_nvlist(zc, outnvl);
|
||||
else
|
||||
puterror = smusherror;
|
||||
}
|
||||
|
||||
if (puterror != 0)
|
||||
/*
|
||||
* Prefer the operation's errno over a copyout/smush failure
|
||||
* so stream validation errors are not replaced by EINVAL.
|
||||
*/
|
||||
if (puterror != 0 && error == 0)
|
||||
error = puterror;
|
||||
|
||||
nvlist_free(outnvl);
|
||||
|
||||
@@ -1001,7 +1001,7 @@ tests = ['recv_dedup', 'recv_dedup_encrypted_zvol', 'rsend_001_pos',
|
||||
'rsend_021_pos', 'rsend_022_pos', 'rsend_024_pos', 'rsend_025_pos',
|
||||
'rsend_026_neg', 'rsend_027_pos', 'rsend_028_neg', 'rsend_029_neg',
|
||||
'rsend_030_pos', 'rsend_031_pos', 'rsend-exclude_001_pos',
|
||||
'rsend-exclude_002_pos', 'send-c_verify_ratio',
|
||||
'rsend-exclude_002_pos', 'recv_validate_001_neg', 'send-c_verify_ratio',
|
||||
'send-c_verify_contents', 'send-c_props', 'send-c_incremental',
|
||||
'send-c_volume',
|
||||
'send-c_lz4_disabled', 'send-c_recv_lz4_disabled',
|
||||
@@ -1163,7 +1163,8 @@ tests = ['zstream_checksum_001_pos',
|
||||
'zstream_recompress_001_pos', 'zstream_recompress_002_pos',
|
||||
'zstream_recompress_003_pos', 'zstream_recompress_004_pos',
|
||||
'zstream_recompress_005_pos',
|
||||
'zstream_redup_001_pos']
|
||||
'zstream_redup_001_pos',
|
||||
'zstream_validate_001_neg']
|
||||
tags = ['functional', 'zstream']
|
||||
|
||||
[tests/functional/zvol/zvol_ENOSPC]
|
||||
|
||||
@@ -418,6 +418,7 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \
|
||||
functional/zstream/little-endian-all-drr-types-incr-XDR.zsend.bz2 \
|
||||
functional/zstream/decompress.zsend.bz2 \
|
||||
functional/zstream/decompress-crypt.zsend.bz2 \
|
||||
functional/zstream/undersized-write-lsize.zsend.bz2 \
|
||||
functional/zstream/big-endian-long-payloads.zsend.bz2 \
|
||||
functional/zstream/little-endian-long-payloads.zsend.bz2 \
|
||||
functional/zstream/beadtbn-new.dump.bz2 \
|
||||
@@ -2108,6 +2109,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
|
||||
functional/rsend/cleanup.ksh \
|
||||
functional/rsend/recv_dedup_encrypted_zvol.ksh \
|
||||
functional/rsend/recv_dedup.ksh \
|
||||
functional/rsend/recv_validate_001_neg.ksh \
|
||||
functional/rsend/rsend_001_pos.ksh \
|
||||
functional/rsend/rsend_002_pos.ksh \
|
||||
functional/rsend/rsend_003_pos.ksh \
|
||||
@@ -2439,6 +2441,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
|
||||
functional/zstream/zstream_recompress_004_pos.ksh \
|
||||
functional/zstream/zstream_recompress_005_pos.ksh \
|
||||
functional/zstream/zstream_redup_001_pos.ksh \
|
||||
functional/zstream/zstream_validate_001_neg.ksh \
|
||||
functional/zvol/zvol_cli/cleanup.ksh \
|
||||
functional/zvol/zvol_cli/setup.ksh \
|
||||
functional/zvol/zvol_cli/zvol_cli_001_pos.ksh \
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/bin/ksh -p
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2026 by ConnectWise. All rights reserved.
|
||||
#
|
||||
|
||||
. $STF_SUITE/tests/functional/rsend/rsend.kshlib
|
||||
|
||||
#
|
||||
# Description:
|
||||
# Verify that zfs recv rejects a send stream whose first compressed
|
||||
# DRR_WRITE has logical_size below SPA_MINBLOCKSIZE.
|
||||
#
|
||||
# Strategy:
|
||||
# 1. Load the prebuilt mutated stream from the zstream testdata
|
||||
# (undersized-write-lsize.zsend.bz2)
|
||||
# 2. Confirm zfs recv fails with the shared recv_check
|
||||
# "logical size ... below minimum" message
|
||||
#
|
||||
|
||||
verify_runnable "both"
|
||||
|
||||
function cleanup
|
||||
{
|
||||
datasetexists $badrecv && destroy_dataset $badrecv -r
|
||||
[[ -f $bad ]] && rm -f $bad
|
||||
[[ -f $err ]] && rm -f $err
|
||||
}
|
||||
log_onexit cleanup
|
||||
|
||||
log_assert "undersized WRITE logical_size is rejected by zfs recv"
|
||||
|
||||
typeset src=$STF_SUITE/tests/functional/zstream/undersized-write-lsize.zsend.bz2
|
||||
typeset bad=$BACKDIR/undersized-write-lsize.zsend
|
||||
typeset err=$BACKDIR/undersized-write-lsize.err
|
||||
typeset badrecv=$POOL/badrecv
|
||||
|
||||
log_must mkdir -p $BACKDIR
|
||||
log_must eval "bzcat <$src >$bad"
|
||||
datasetexists $badrecv && log_must zfs destroy -r $badrecv
|
||||
log_mustnot eval "zfs recv $badrecv < $bad >$err 2>&1"
|
||||
log_must eval "grep -E -q 'DRR_WRITE logical size .* below minimum' $err"
|
||||
log_note "recv rejected: $(head -1 $err)"
|
||||
datasetexists $badrecv && log_must zfs destroy -r $badrecv
|
||||
|
||||
log_pass "undersized WRITE logical_size is rejected by zfs recv"
|
||||
Binary file not shown.
@@ -0,0 +1,47 @@
|
||||
#!/bin/ksh -p
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2026 by ConnectWise. All rights reserved.
|
||||
#
|
||||
|
||||
. $STF_SUITE/tests/functional/zstream/zstream.kshlib
|
||||
|
||||
#
|
||||
# Description:
|
||||
# Verify that zstream dump rejects a send stream whose first compressed
|
||||
# DRR_WRITE has logical_size below SPA_MINBLOCKSIZE.
|
||||
#
|
||||
# Strategy:
|
||||
# 1. Load the prebuilt mutated stream
|
||||
# (undersized-write-lsize.zsend.bz2)
|
||||
# 2. Confirm zstream dump fails with the shared recv_check
|
||||
# "logical size ... below minimum" message
|
||||
#
|
||||
|
||||
verify_runnable "both"
|
||||
|
||||
log_assert "undersized WRITE logical_size is rejected by zstream dump"
|
||||
log_onexit cleanup_pool $POOL
|
||||
|
||||
typeset src="$ZSTREAM_DATADIR/undersized-write-lsize.zsend.bz2"
|
||||
typeset bad=$BACKDIR/bad
|
||||
typeset err=$BACKDIR/err
|
||||
|
||||
log_must eval "bzcat <$src >$bad"
|
||||
log_mustnot eval "zstream dump $bad >$err 2>&1"
|
||||
log_must eval "grep -E -q 'DRR_WRITE logical size .* below minimum' $err"
|
||||
log_note "dump rejected: $(head -1 $err)"
|
||||
|
||||
log_pass "undersized WRITE logical_size is rejected by zstream dump"
|
||||
Reference in new issue
Block a user