git: 2318d229b76a - releng/15.0 - zfs: Roll up several upstream commits

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 30 Jun 2026 17:21:23 UTC
The branch releng/15.0 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=2318d229b76a4b6e463c7a78694e42377c3a8042

commit 2318d229b76a4b6e463c7a78694e42377c3a8042
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-06-28 22:00:26 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-06-30 02:32:57 +0000

    zfs: Roll up several upstream commits
    
    This commit squashes the following upstream commits:
      #18604 63a0ff6cd nvpair: Check for un-terminated strings in packed nvlist
      #18617 38cd6adcb Extend dataset zfs_ioc_set_prop() secpolicy
      #18615 738791027 Fix the integer type in zfs_ioc_userspace_many()
      #18623 06d8cfedb Add additional verification of size fields and strings
    
    Approved by:    so
    Security:       FreeBSD-SA-26:40.zfs
    Security:       CVE-2026-49429
    Security:       CVE-2026-49430
    Security:       CVE-2026-49431
---
 sys/contrib/openzfs/module/nvpair/nvpair.c         |  49 ++++--
 sys/contrib/openzfs/module/zfs/dmu_recv.c          |  44 ++++--
 sys/contrib/openzfs/module/zfs/vdev_label.c        |   1 +
 sys/contrib/openzfs/module/zfs/zfs_ioctl.c         |  34 +++-
 sys/contrib/openzfs/module/zfs/zfs_quota.c         |   8 +-
 .../tests/zfs-tests/cmd/libzfs_input_check.c       | 172 ++++++++++++++++++++-
 6 files changed, 272 insertions(+), 36 deletions(-)

diff --git a/sys/contrib/openzfs/module/nvpair/nvpair.c b/sys/contrib/openzfs/module/nvpair/nvpair.c
index eb8c14b4a783..525cd2711f00 100644
--- a/sys/contrib/openzfs/module/nvpair/nvpair.c
+++ b/sys/contrib/openzfs/module/nvpair/nvpair.c
@@ -135,7 +135,8 @@
 #define	NVP_SIZE_CALC(name_len, data_len) \
 	(NV_ALIGN((sizeof (nvpair_t)) + name_len) + NV_ALIGN(data_len))
 
-static int i_get_value_size(data_type_t type, const void *data, uint_t nelem);
+static int i_get_value_size(data_type_t type, const void *data, uint_t nelem,
+    size_t max_size);
 static int nvlist_add_common(nvlist_t *nvl, const char *name, data_type_t type,
     uint_t nelem, const void *data);
 
@@ -810,8 +811,10 @@ i_validate_nvpair(nvpair_t *nvp)
 	 * verify nvp_type, nvp_value_elem, and also possibly
 	 * verify string values and get the value size.
 	 */
-	size2 = i_get_value_size(type, NVP_VALUE(nvp), NVP_NELEM(nvp));
 	size1 = nvp->nvp_size - NVP_VALOFF(nvp);
+	size2 = i_get_value_size(type, NVP_VALUE(nvp), NVP_NELEM(nvp),
+	    size1);
+
 	if (size2 < 0 || size1 != NV_ALIGN(size2))
 		return (EFAULT);
 
@@ -1002,12 +1005,21 @@ nvlist_remove_nvpair(nvlist_t *nvl, nvpair_t *nvp)
  * 	DATA_TYPE_STRING    	and
  *	DATA_TYPE_STRING_ARRAY
  * Is data == NULL then the size of the string(s) is excluded.
+ *
+ * If 'max_size' is non-zero, then don't look beyond 'max_size' number of
+ * bytes when calculating a value size. Note that 'max_size' should include
+ * the NULL terminator byte when calculating string size.  If 'max_size' is 0,
+ * it is ignored.
  */
 static int
-i_get_value_size(data_type_t type, const void *data, uint_t nelem)
+i_get_value_size(data_type_t type, const void *data, uint_t nelem,
+    size_t max_size)
 {
 	uint64_t value_sz;
 
+	if (max_size == 0)
+		max_size = INT32_MAX;
+
 	if (i_validate_type_nelem(type, nelem) != 0)
 		return (-1);
 
@@ -1052,10 +1064,15 @@ i_get_value_size(data_type_t type, const void *data, uint_t nelem)
 		break;
 #endif
 	case DATA_TYPE_STRING:
-		if (data == NULL)
+		if (data == NULL) {
 			value_sz = 0;
-		else
-			value_sz = strlen(data) + 1;
+		} else {
+			value_sz = strnlen(data, max_size);
+			if (value_sz >= max_size) {
+				return (-1);	/* string not terminated */
+			}
+			value_sz += 1;
+		}
 		break;
 	case DATA_TYPE_BOOLEAN_ARRAY:
 		value_sz = (uint64_t)nelem * sizeof (boolean_t);
@@ -1089,16 +1106,23 @@ i_get_value_size(data_type_t type, const void *data, uint_t nelem)
 		break;
 	case DATA_TYPE_STRING_ARRAY:
 		value_sz = (uint64_t)nelem * sizeof (uint64_t);
-
 		if (data != NULL) {
 			char *const *strs = data;
 			uint_t i;
+			size_t newsize;
 
 			/* no alignment requirement for strings */
 			for (i = 0; i < nelem; i++) {
 				if (strs[i] == NULL)
 					return (-1);
-				value_sz += strlen(strs[i]) + 1;
+
+				newsize = strnlen(strs[i], max_size);
+
+				if (newsize == max_size)
+					return (-1);	/* not terminated */
+
+				value_sz += newsize + 1; /* +1 for NULL */
+				max_size -= newsize + 1;
 			}
 		}
 		break;
@@ -1163,7 +1187,7 @@ nvlist_add_common(nvlist_t *nvl, const char *name,
 	 * In case of data types DATA_TYPE_STRING and DATA_TYPE_STRING_ARRAY
 	 * is the size of the string(s) included.
 	 */
-	if ((value_sz = i_get_value_size(type, data, nelem)) < 0)
+	if ((value_sz = i_get_value_size(type, data, nelem, 0)) < 0)
 		return (EINVAL);
 
 	if (i_validate_nvpair_value(type, nelem, data) != 0)
@@ -1588,7 +1612,7 @@ nvpair_value_common(const nvpair_t *nvp, data_type_t type, uint_t *nelem,
 #endif
 		if (data == NULL)
 			return (EINVAL);
-		if ((value_sz = i_get_value_size(type, NULL, 1)) < 0)
+		if ((value_sz = i_get_value_size(type, NULL, 1, 0)) < 0)
 			return (EINVAL);
 		memcpy(data, NVP_VALUE(nvp), (size_t)value_sz);
 		if (nelem != NULL)
@@ -3018,7 +3042,8 @@ nvs_native_nvp_op(nvstream_t *nvs, nvpair_t *nvp)
 	 * In case of data types DATA_TYPE_STRING and DATA_TYPE_STRING_ARRAY
 	 * is the size of the string(s) excluded.
 	 */
-	if ((value_sz = i_get_value_size(type, NULL, NVP_NELEM(nvp))) < 0)
+	if ((value_sz = i_get_value_size(type, NULL, NVP_NELEM(nvp),
+	    NVP_SIZE(nvp))) < 0)
 		return (EFAULT);
 
 	if (NVP_SIZE_CALC(nvp->nvp_name_sz, value_sz) > nvp->nvp_size)
@@ -3331,7 +3356,7 @@ nvs_xdr_nvp_op(nvstream_t *nvs, nvpair_t *nvp)
 	 * In case of data types DATA_TYPE_STRING and DATA_TYPE_STRING_ARRAY
 	 * is the size of the string(s) excluded.
 	 */
-	if ((value_sz = i_get_value_size(type, NULL, nelem)) < 0)
+	if ((value_sz = i_get_value_size(type, NULL, nelem, NVP_SIZE(nvp)) < 0))
 		return (EFAULT);
 
 	/* if there is no data to extract then return */
diff --git a/sys/contrib/openzfs/module/zfs/dmu_recv.c b/sys/contrib/openzfs/module/zfs/dmu_recv.c
index 45c7af2bdcd2..e9945b2d9660 100644
--- a/sys/contrib/openzfs/module/zfs/dmu_recv.c
+++ b/sys/contrib/openzfs/module/zfs/dmu_recv.c
@@ -2883,16 +2883,20 @@ receive_read_record(dmu_recv_cookie_t *drc)
 	{
 		struct drr_object *drro =
 		    &drc->drc_rrd->header.drr_u.drr_object;
-		uint32_t size = DRR_OBJECT_PAYLOAD_SIZE(drro);
+		uint32_t size;
 		void *buf = NULL;
 		dmu_object_info_t doi;
 
+		size = DRR_OBJECT_PAYLOAD_SIZE(drro);
+		if (size > SPA_MAXBLOCKSIZE)
+			return (SET_ERROR(ERANGE));
+
 		if (size != 0)
-			buf = kmem_zalloc(size, KM_SLEEP);
+			buf = vmem_zalloc(size, KM_SLEEP);
 
 		err = receive_read_payload_and_next_header(drc, size, buf);
 		if (err != 0) {
-			kmem_free(buf, size);
+			vmem_free(buf, size);
 			return (err);
 		}
 		err = dmu_object_info(drc->drc_os, drro->drr_object, &doi);
@@ -2916,7 +2920,11 @@ receive_read_record(dmu_recv_cookie_t *drc)
 	case DRR_WRITE:
 	{
 		struct drr_write *drrw = &drc->drc_rrd->header.drr_u.drr_write;
-		int size = DRR_WRITE_PAYLOAD_SIZE(drrw);
+		uint64_t size = DRR_WRITE_PAYLOAD_SIZE(drrw);
+
+		if (size > SPA_MAXBLOCKSIZE)
+			return (SET_ERROR(ERANGE));
+
 		abd_t *abd = abd_alloc_linear(size, B_FALSE);
 		err = receive_read_payload_and_next_header(drc, size,
 		    abd_to_buf(abd));
@@ -2933,12 +2941,18 @@ receive_read_record(dmu_recv_cookie_t *drc)
 	{
 		struct drr_write_embedded *drrwe =
 		    &drc->drc_rrd->header.drr_u.drr_write_embedded;
-		uint32_t size = P2ROUNDUP(drrwe->drr_psize, 8);
-		void *buf = kmem_zalloc(size, KM_SLEEP);
+		uint32_t size;
+		void *buf;
+
+		size = P2ROUNDUP(drrwe->drr_psize, 8);
+		if (size > SPA_MAXBLOCKSIZE)
+			return (SET_ERROR(ERANGE));
+
+		buf = vmem_zalloc(size, KM_SLEEP);
 
 		err = receive_read_payload_and_next_header(drc, size, buf);
 		if (err != 0) {
-			kmem_free(buf, size);
+			vmem_free(buf, size);
 			return (err);
 		}
 
@@ -2967,7 +2981,11 @@ receive_read_record(dmu_recv_cookie_t *drc)
 	case DRR_SPILL:
 	{
 		struct drr_spill *drrs = &drc->drc_rrd->header.drr_u.drr_spill;
-		int size = DRR_SPILL_PAYLOAD_SIZE(drrs);
+		uint64_t size = DRR_SPILL_PAYLOAD_SIZE(drrs);
+
+		if (size > SPA_MAXBLOCKSIZE)
+			return (SET_ERROR(ERANGE));
+
 		abd_t *abd = abd_alloc_linear(size, B_FALSE);
 		err = receive_read_payload_and_next_header(drc, size,
 		    abd_to_buf(abd));
@@ -3118,7 +3136,7 @@ receive_process_record(struct receive_writer_arg *rwa,
 			abd_free(rrd->abd);
 			rrd->abd = NULL;
 		} else if (rrd->payload != NULL) {
-			kmem_free(rrd->payload, rrd->payload_size);
+			vmem_free(rrd->payload, rrd->payload_size);
 			rrd->payload = NULL;
 		}
 		return (0);
@@ -3132,7 +3150,7 @@ receive_process_record(struct receive_writer_arg *rwa,
 				rrd->abd = NULL;
 				rrd->payload = NULL;
 			} else if (rrd->payload != NULL) {
-				kmem_free(rrd->payload, rrd->payload_size);
+				vmem_free(rrd->payload, rrd->payload_size);
 				rrd->payload = NULL;
 			}
 
@@ -3145,7 +3163,7 @@ receive_process_record(struct receive_writer_arg *rwa,
 	{
 		struct drr_object *drro = &rrd->header.drr_u.drr_object;
 		err = receive_object(rwa, drro, rrd->payload);
-		kmem_free(rrd->payload, rrd->payload_size);
+		vmem_free(rrd->payload, rrd->payload_size);
 		rrd->payload = NULL;
 		break;
 	}
@@ -3183,7 +3201,7 @@ receive_process_record(struct receive_writer_arg *rwa,
 		struct drr_write_embedded *drrwe =
 		    &rrd->header.drr_u.drr_write_embedded;
 		err = receive_write_embedded(rwa, drrwe, rrd->payload);
-		kmem_free(rrd->payload, rrd->payload_size);
+		vmem_free(rrd->payload, rrd->payload_size);
 		rrd->payload = NULL;
 		break;
 	}
@@ -3252,7 +3270,7 @@ receive_writer_thread(void *arg)
 			rrd->abd = NULL;
 			rrd->payload = NULL;
 		} else if (rrd->payload != NULL) {
-			kmem_free(rrd->payload, rrd->payload_size);
+			vmem_free(rrd->payload, rrd->payload_size);
 			rrd->payload = NULL;
 		}
 		/*
diff --git a/sys/contrib/openzfs/module/zfs/vdev_label.c b/sys/contrib/openzfs/module/zfs/vdev_label.c
index 7e222eac5edc..a3f78af51ba5 100644
--- a/sys/contrib/openzfs/module/zfs/vdev_label.c
+++ b/sys/contrib/openzfs/module/zfs/vdev_label.c
@@ -1371,6 +1371,7 @@ vdev_label_read_bootenv(vdev_t *rvd, nvlist_t *bootenv)
 				    VB_NVLIST);
 				break;
 			}
+			vbe->vbe_bootenv[sizeof (vbe->vbe_bootenv) - 1] = '\0';
 			fnvlist_add_string(bootenv, FREEBSD_BOOTONCE, buf);
 		}
 
diff --git a/sys/contrib/openzfs/module/zfs/zfs_ioctl.c b/sys/contrib/openzfs/module/zfs/zfs_ioctl.c
index 1b2392aeaa85..0b0dd5b682a3 100644
--- a/sys/contrib/openzfs/module/zfs/zfs_ioctl.c
+++ b/sys/contrib/openzfs/module/zfs/zfs_ioctl.c
@@ -937,6 +937,23 @@ zfs_secpolicy_recv(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
 	    ZFS_DELEG_PERM_CREATE, cr));
 }
 
+/*
+ * Policy for dataset set property operations.  Individual properties checked by
+ * zfs_check_settable(), additionally require zfs_secpolicy_recv() when setting
+ * properties as part of a receive.
+ */
+static int
+zfs_secpolicy_setprops(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
+{
+	boolean_t received = zc->zc_cookie;
+	int error;
+
+	if (received && (error = zfs_secpolicy_recv(zc, innvl, cr)))
+		return (error);
+
+	return (zfs_secpolicy_read(zc, innvl, cr));
+}
+
 int
 zfs_secpolicy_snapshot_perms(const char *name, cred_t *cr)
 {
@@ -3883,7 +3900,6 @@ static int
 zfs_ioc_log_history(const char *unused, nvlist_t *innvl, nvlist_t *outnvl)
 {
 	(void) unused, (void) outnvl;
-	const char *message;
 	char *poolname;
 	spa_t *spa;
 	int error;
@@ -3904,7 +3920,7 @@ zfs_ioc_log_history(const char *unused, nvlist_t *innvl, nvlist_t *outnvl)
 	if (error != 0)
 		return (error);
 
-	message = fnvlist_lookup_string(innvl, "message");
+	const char *message = fnvlist_lookup_string(innvl, "message");
 
 	if (spa_version(spa) < SPA_VERSION_ZPOOL_HISTORY) {
 		spa_close(spa, FTAG);
@@ -6364,21 +6380,27 @@ zfs_ioc_userspace_one(zfs_cmd_t *zc)
  * outputs:
  * zc_nvlist_dst[_size]	data buffer (array of zfs_useracct_t)
  * zc_cookie	zap cursor
+ *
+ * The zc_nvlist_dst output array is limited to 1000 entries.
  */
 static int
 zfs_ioc_userspace_many(zfs_cmd_t *zc)
 {
+	const size_t batch_limit = 1000 * sizeof (zfs_useracct_t);
+	uint64_t bufsize = MIN(zc->zc_nvlist_dst_size, batch_limit);
 	zfsvfs_t *zfsvfs;
-	int bufsize = zc->zc_nvlist_dst_size;
 
-	if (bufsize <= 0)
+	if (bufsize < sizeof (zfs_useracct_t)) {
+		zc->zc_nvlist_dst_size = sizeof (zfs_useracct_t);
 		return (SET_ERROR(ENOMEM));
+	}
 
 	int error = zfsvfs_hold(zc->zc_name, FTAG, &zfsvfs, B_FALSE);
 	if (error != 0)
 		return (error);
 
 	void *buf = vmem_alloc(bufsize, KM_SLEEP);
+	zc->zc_nvlist_dst_size = bufsize;
 
 	error = zfs_userspace_many(zfsvfs, zc->zc_objset_type, &zc->zc_cookie,
 	    buf, &zc->zc_nvlist_dst_size, &zc->zc_guid);
@@ -6869,7 +6891,7 @@ zfs_ioc_space_snaps(const char *lastsnap, nvlist_t *innvl, nvlist_t *outnvl)
 	dsl_pool_t *dp;
 	dsl_dataset_t *new, *old;
 	const char *firstsnap;
-	uint64_t used, comp, uncomp;
+	uint64_t used = 0, comp = 0, uncomp = 0;
 
 	firstsnap = fnvlist_lookup_string(innvl, "firstsnap");
 
@@ -7756,7 +7778,7 @@ zfs_ioctl_init(void)
 	    zfs_ioc_send, zfs_secpolicy_send);
 
 	zfs_ioctl_register_dataset_modify(ZFS_IOC_SET_PROP, zfs_ioc_set_prop,
-	    zfs_secpolicy_none);
+	    zfs_secpolicy_setprops);
 	zfs_ioctl_register_dataset_modify(ZFS_IOC_DESTROY, zfs_ioc_destroy,
 	    zfs_secpolicy_destroy);
 	zfs_ioctl_register_dataset_modify(ZFS_IOC_RENAME, zfs_ioc_rename,
diff --git a/sys/contrib/openzfs/module/zfs/zfs_quota.c b/sys/contrib/openzfs/module/zfs/zfs_quota.c
index 2e91ccc27d6d..e00ca67dae13 100644
--- a/sys/contrib/openzfs/module/zfs/zfs_quota.c
+++ b/sys/contrib/openzfs/module/zfs/zfs_quota.c
@@ -86,10 +86,14 @@ zpl_get_file_info(dmu_object_type_t bonustype, const void *data,
 		sa.sa_layout_info = BSWAP_16(sa.sa_layout_info);
 		swap = B_TRUE;
 	}
-	VERIFY3U(sa.sa_magic, ==, SA_MAGIC);
+
+	if (unlikely(sa.sa_magic != SA_MAGIC))
+		return (SET_ERROR(EINVAL));
 
 	int hdrsize = sa_hdrsize(&sa);
-	VERIFY3U(hdrsize, >=, sizeof (sa_hdr_phys_t));
+
+	if (unlikely(hdrsize < sizeof (sa_hdr_phys_t)))
+		return (SET_ERROR(EINVAL));
 
 	uintptr_t data_after_hdr = (uintptr_t)data + hdrsize;
 	zoi->zfi_user = *((uint64_t *)(data_after_hdr + SA_UID_OFFSET));
diff --git a/sys/contrib/openzfs/tests/zfs-tests/cmd/libzfs_input_check.c b/sys/contrib/openzfs/tests/zfs-tests/cmd/libzfs_input_check.c
index 4ef249bbd4a1..8f7e36d9efa7 100644
--- a/sys/contrib/openzfs/tests/zfs-tests/cmd/libzfs_input_check.c
+++ b/sys/contrib/openzfs/tests/zfs-tests/cmd/libzfs_input_check.c
@@ -85,7 +85,6 @@ static const zfs_ioc_t ioc_skip[] = {
 	ZFS_IOC_DSOBJ_TO_DSNAME,
 	ZFS_IOC_OBJ_TO_PATH,
 	ZFS_IOC_POOL_SET_PROPS,
-	ZFS_IOC_POOL_GET_PROPS,
 	ZFS_IOC_SET_FSACL,
 	ZFS_IOC_GET_FSACL,
 	ZFS_IOC_SHARE,
@@ -125,11 +124,136 @@ static const zfs_ioc_t ioc_skip[] = {
 		lzc_ioctl_test(ioc, name, req, opt, err, wild);	\
 	} while (0)
 
+#define	IOC_INPUT_TEST_INJECT(ioc, name, innvl)			\
+	do {							\
+		active_test = __func__ + 5;			\
+		lzc_ioctl_run_impl(ioc, name, innvl, 0, B_TRUE);	\
+	} while (0)
+
+/*
+ * Given a zfs_cmd_t containing an already packed nvlist in zc->zc_nvlist_src,
+ * and its original innvl, look in innvl for the last string nvpair, or last
+ * string array nvpair, and remove the string terminator.  The idea is to
+ * corrupt the nvlist string value so that anyone doing a strlen() on it will
+ * read past the end of the packed nvlist buffer and trigger a crash.
+ */
+static void
+do_bad_string(zfs_cmd_t *zc, nvlist_t *innvl)
+{
+	nvpair_t *elem = NULL;
+	nvpair_t *lastseen = NULL;
+	const char *str = NULL;
+	const char **arr;
+	uint_t n;
+	char *off;
+	char *packed;
+	uint64_t size, off_size;
+
+	while ((elem = nvlist_next_nvpair(innvl, elem)) != NULL) {
+		if ((nvpair_type(elem) == DATA_TYPE_STRING) ||
+		    (nvpair_type(elem) == DATA_TYPE_STRING_ARRAY))
+			lastseen = elem;
+	}
+
+	if (lastseen == NULL)
+		return;	/* No strings */
+
+	/*
+	 * Lookup either the last string, or the last string in the last
+	 * string array in the nvlist.  We will use this to corrupt from the
+	 * string to the end of the nvlist buffer.  Any attempts to strlen this
+	 * string should run pass the end of the packed buffer.
+	 */
+	if (nvpair_value_string(lastseen, &str) != 0) {
+		if (nvpair_value_string_array(lastseen, &arr, &n) == 0)
+			str = arr[n-1];
+	}
+
+	/*
+	 * We now have the last string.  Corrupt everything from the NULL
+	 * terminator byte for the last string to the end of the packed nvlist
+	 * buffer.
+	 */
+	packed = (char *)zc->zc_nvlist_src;
+	size = zc->zc_nvlist_src_size;
+
+	off = memmem(packed, size, str, strlen(str));
+	off_size = strlen(str);
+
+	memset(&off[off_size - 1], '!', (packed + size) -
+	    (&off[off_size - 1]));
+
+}
+
+/*
+ * For each byte in the packed nvlist list in zc, corrupt a single byte, then
+ * try doing the ioctl.  This tests how well the kernel handles fuzzed nvlists.
+ *
+ * NOTE - make sure you are doing this with a "safe" ioctl!  You don't want to
+ * run this on an ioctl that can potentially corrupt data (like a zpool create).
+ */
+static void
+do_fuzz(int zfs_fd, zfs_ioc_t ioc, zfs_cmd_t *zc)
+{
+	uint64_t size;
+	uint64_t i;
+	unsigned char old = 0;
+	unsigned char *pos;
+	zfs_cmd_t orig_zc = *zc;
+
+	pos = (unsigned char *) zc->zc_nvlist_src;
+	size = zc->zc_nvlist_src_size;
+
+	/*
+	 * Fuzz each byte in the packed nvlist, one byte at a time, and do the
+	 * ioctl.  If the kernel doesn't crash, then the test passed.
+	 */
+	for (i = 0; i < size; i++) {
+		/* Restore the previously corrupted byte */
+		if (i > 0)
+			pos[i-1] = old;
+
+		old = pos[i];
+
+		/* Corrupt the new byte */
+		pos[i]++;
+
+		/*
+		 * Do the ioctl and ignore the return code.  We just want to
+		 * see if the kernel panics.
+		 */
+		lzc_ioctl_fd(zfs_fd, ioc, zc);
+
+		/*
+		 * Restore 'zc' with original fields since the ioctl may
+		 * have modified them.
+		 */
+		*zc = orig_zc;
+	}
+	/* Restore last byte */
+	if (i > 0)
+		pos[i - 1] = old;
+
+	/*
+	 * Try fuzzing the packed nvlist size field.  Test it with one byte
+	 * bigger and one byte smaller than the current value.
+	 */
+	zc->zc_nvlist_src_size--;
+	lzc_ioctl_fd(zfs_fd, ioc, zc);
+
+	zc->zc_nvlist_src_size += 2;
+	lzc_ioctl_fd(zfs_fd, ioc, zc);
+
+	/* Restore to normal */
+	zc->zc_nvlist_src_size -= 1;
+}
+
 /*
  * run a zfs ioctl command, verify expected results and log failures
  */
 static void
-lzc_ioctl_run(zfs_ioc_t ioc, const char *name, nvlist_t *innvl, int expected)
+lzc_ioctl_run_impl(zfs_ioc_t ioc, const char *name, nvlist_t *innvl,
+    int expected, boolean_t do_corrupt)
 {
 	zfs_cmd_t zc = {"\0"};
 	char *packed = NULL;
@@ -160,10 +284,30 @@ lzc_ioctl_run(zfs_ioc_t ioc, const char *name, nvlist_t *innvl, int expected)
 	zc.zc_nvlist_dst_size = MAX(size * 2, 128 * 1024);
 	zc.zc_nvlist_dst = (uint64_t)(uintptr_t)malloc(zc.zc_nvlist_dst_size);
 
+	if (do_corrupt) {
+		/*
+		 * Try changing bytes in the packed nvlist to see if it will
+		 * panic the kernel when you do the ioctl.
+		 */
+		do_fuzz(zfs_fd, ioc, &zc);
+
+		/*
+		 * Corrupt the last string in the packed nvlist so it has no
+		 * NULL terminator.
+		 */
+		do_bad_string(&zc, innvl);
+
+	}
+
 	if (lzc_ioctl_fd(zfs_fd, ioc, &zc) != 0)
 		error = errno;
 
-	if (error != expected) {
+	/*
+	 * If we're corrupting the nvlist we don't care about the specific
+	 * error code that gets returned, as it could be one of many.  We only
+	 * care if it panics the kernel.
+	 */
+	if (!do_corrupt && error != expected) {
 		unexpected_failures = B_TRUE;
 		(void) fprintf(stderr, "%s: Unexpected result with %s, "
 		    "error %d (expecting %d)\n",
@@ -174,6 +318,12 @@ lzc_ioctl_run(zfs_ioc_t ioc, const char *name, nvlist_t *innvl, int expected)
 	free((void *)(uintptr_t)zc.zc_nvlist_dst);
 }
 
+static void
+lzc_ioctl_run(zfs_ioc_t ioc, const char *name, nvlist_t *innvl, int expected)
+{
+	return (lzc_ioctl_run_impl(ioc, name, innvl, expected, B_FALSE));
+}
+
 /*
  * Test each ioc for the following ioctl input errors:
  *   ZFS_ERR_IOC_ARG_UNAVAIL	an input argument is not supported by kernel
@@ -310,6 +460,7 @@ test_log_history(const char *pool)
 	fnvlist_add_string(required, "message", "input check");
 
 	IOC_INPUT_TEST(ZFS_IOC_LOG_HISTORY, pool, required, NULL, 0);
+	IOC_INPUT_TEST_INJECT(ZFS_IOC_LOG_HISTORY, pool, required);
 
 	nvlist_free(required);
 }
@@ -791,6 +942,20 @@ test_set_bootenv(const char *pool)
 	nvlist_free(required);
 }
 
+static void
+test_zpool_get(const char *pool)
+{
+	const char *strs[] = {ZPOOL_DEDUPCACHED_PROP_NAME};
+	nvlist_t *optional = fnvlist_alloc();
+
+	fnvlist_add_string_array(optional, ZPOOL_GET_PROPS_NAMES, strs, 1);
+
+	IOC_INPUT_TEST(ZFS_IOC_POOL_GET_PROPS, pool, NULL, optional, 0);
+	IOC_INPUT_TEST_INJECT(ZFS_IOC_POOL_GET_PROPS, pool, optional);
+
+	nvlist_free(optional);
+}
+
 static void
 zfs_ioc_input_tests(const char *pool)
 {
@@ -885,6 +1050,7 @@ zfs_ioc_input_tests(const char *pool)
 
 	test_scrub(pool);
 
+	test_zpool_get(pool);
 	/*
 	 * cleanup
 	 */