Assertion failure when logging large output of channel program

The output of ZFS channel programs is logged on-disk in the zpool
history, and printed by `zpool history -i`.  Channel programs can use
10MB of memory by default, and up to 100MB by using the `zfs program -m`
flag.  Therefore their output can be up to some fraction of 100MB.

In addition to being somewhat wasteful of the limited space reserved for
the pool history (which for large pools is 1GB), in extreme cases this
can result in a failure of `ASSERT(length <= DMU_MAX_ACCESS);` in
`dmu_buf_hold_array_by_dnode()`.

This commit limits the output size that will be logged to 1MB.  Larger
outputs will not be logged, instead a entry will be logged indicating
the size of the omitted output.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Matthew Ahrens <mahrens@delphix.com>
Closes #11194
This commit is contained in:
Matthew Ahrens 2020-11-14 10:17:16 -08:00 committed by GitHub
parent 7e3617de35
commit d66aab7c08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 39 additions and 2 deletions

View File

@ -9036,6 +9036,12 @@ print_history_records(nvlist_t *nvhis, hist_cbdata_t *cb)
dump_nvlist(fnvlist_lookup_nvlist(rec, dump_nvlist(fnvlist_lookup_nvlist(rec,
ZPOOL_HIST_OUTPUT_NVL), 8); ZPOOL_HIST_OUTPUT_NVL), 8);
} }
if (nvlist_exists(rec, ZPOOL_HIST_OUTPUT_SIZE)) {
(void) printf(" output nvlist omitted; "
"original size: %lldKB\n",
(longlong_t)fnvlist_lookup_int64(rec,
ZPOOL_HIST_OUTPUT_SIZE) / 1024);
}
if (nvlist_exists(rec, ZPOOL_HIST_ERRNO)) { if (nvlist_exists(rec, ZPOOL_HIST_ERRNO)) {
(void) printf(" errno: %lld\n", (void) printf(" errno: %lld\n",
(longlong_t)fnvlist_lookup_int64(rec, (longlong_t)fnvlist_lookup_int64(rec,

View File

@ -1452,6 +1452,7 @@ typedef enum {
#define ZPOOL_HIST_IOCTL "ioctl" #define ZPOOL_HIST_IOCTL "ioctl"
#define ZPOOL_HIST_INPUT_NVL "in_nvl" #define ZPOOL_HIST_INPUT_NVL "in_nvl"
#define ZPOOL_HIST_OUTPUT_NVL "out_nvl" #define ZPOOL_HIST_OUTPUT_NVL "out_nvl"
#define ZPOOL_HIST_OUTPUT_SIZE "out_size"
#define ZPOOL_HIST_DSNAME "dsname" #define ZPOOL_HIST_DSNAME "dsname"
#define ZPOOL_HIST_DSID "dsid" #define ZPOOL_HIST_DSID "dsid"
#define ZPOOL_HIST_ERRNO "errno" #define ZPOOL_HIST_ERRNO "errno"

View File

@ -379,6 +379,20 @@ by the test suite to facilitate testing.
Default value: \fB16,777,217\fR. Default value: \fB16,777,217\fR.
.RE .RE
.sp
.ne 2
.na
\fBzfs_history_output_max\fR (int)
.ad
.RS 12n
When attempting to log the output nvlist of an ioctl in the on-disk history, the
output will not be stored if it is larger than size (in bytes). This must be
less then DMU_MAX_ACCESS (64MB). This applies primarily to
zfs_ioc_channel_program().
.sp
Default value: \fB1MB\fR.
.RE
.sp .sp
.ne 2 .ne 2
.na .na

View File

@ -231,6 +231,13 @@ zfsdev_state_t *zfsdev_state_list;
*/ */
unsigned long zfs_max_nvlist_src_size = 0; unsigned long zfs_max_nvlist_src_size = 0;
/*
* When logging the output nvlist of an ioctl in the on-disk history, limit
* the logged size to this many bytes. This must be less then DMU_MAX_ACCESS.
* This applies primarily to zfs_ioc_channel_program().
*/
unsigned long zfs_history_output_max = 1024 * 1024;
uint_t zfs_fsyncer_key; uint_t zfs_fsyncer_key;
uint_t zfs_allow_log_key; uint_t zfs_allow_log_key;
@ -7517,8 +7524,14 @@ zfsdev_ioctl_common(uint_t vecnum, zfs_cmd_t *zc, int flag)
vec->zvec_allow_log && vec->zvec_allow_log &&
spa_open(zc->zc_name, &spa, FTAG) == 0) { spa_open(zc->zc_name, &spa, FTAG) == 0) {
if (!nvlist_empty(outnvl)) { if (!nvlist_empty(outnvl)) {
fnvlist_add_nvlist(lognv, ZPOOL_HIST_OUTPUT_NVL, size_t out_size = fnvlist_size(outnvl);
outnvl); if (out_size > zfs_history_output_max) {
fnvlist_add_int64(lognv,
ZPOOL_HIST_OUTPUT_SIZE, out_size);
} else {
fnvlist_add_nvlist(lognv,
ZPOOL_HIST_OUTPUT_NVL, outnvl);
}
} }
if (error != 0) { if (error != 0) {
fnvlist_add_int64(lognv, ZPOOL_HIST_ERRNO, fnvlist_add_int64(lognv, ZPOOL_HIST_ERRNO,
@ -7627,4 +7640,7 @@ zfs_kmod_fini(void)
/* BEGIN CSTYLED */ /* BEGIN CSTYLED */
ZFS_MODULE_PARAM(zfs, zfs_, max_nvlist_src_size, ULONG, ZMOD_RW, ZFS_MODULE_PARAM(zfs, zfs_, max_nvlist_src_size, ULONG, ZMOD_RW,
"Maximum size in bytes allowed for src nvlist passed with ZFS ioctls"); "Maximum size in bytes allowed for src nvlist passed with ZFS ioctls");
ZFS_MODULE_PARAM(zfs, zfs_, history_output_max, ULONG, ZMOD_RW,
"Maximum size in bytes of ZFS ioctl output that will be logged");
/* END CSTYLED */ /* END CSTYLED */