diff --git a/contrib/pyzfs/libzfs_core/_constants.py b/contrib/pyzfs/libzfs_core/_constants.py
index 95c9a673828..4d52502bd21 100644
--- a/contrib/pyzfs/libzfs_core/_constants.py
+++ b/contrib/pyzfs/libzfs_core/_constants.py
@@ -105,6 +105,10 @@ def enum(*sequential, **named):
'ZFS_ERR_RESUME_EXISTS',
'ZFS_ERR_CRYPTO_NOTSUP',
'ZFS_ERR_RAIDZ_EXPAND_IN_PROGRESS',
+ 'ZFS_ERR_ASHIFT_MISMATCH',
+ 'ZFS_ERR_STREAM_LARGE_MICROZAP',
+ 'ZFS_ERR_TOO_MANY_SITOUTS',
+ 'ZFS_ERR_NO_USER_NS_SUPPORT',
],
{}
)
diff --git a/include/libzfs.h b/include/libzfs.h
index a9a31c90ae7..ee0e46761fc 100644
--- a/include/libzfs.h
+++ b/include/libzfs.h
@@ -160,6 +160,7 @@ typedef enum zfs_error {
EZFS_SHAREFAILED, /* filesystem share failed */
EZFS_RAIDZ_EXPAND_IN_PROGRESS, /* a raidz is currently expanding */
EZFS_ASHIFT_MISMATCH, /* can't add vdevs with different ashifts */
+ EZFS_NO_USER_NS_SUPPORT, /* kernel built without CONFIG_USER_NS */
EZFS_UNKNOWN
} zfs_error_t;
diff --git a/include/os/freebsd/spl/sys/zone.h b/include/os/freebsd/spl/sys/zone.h
index cfe63946706..56f762d4061 100644
--- a/include/os/freebsd/spl/sys/zone.h
+++ b/include/os/freebsd/spl/sys/zone.h
@@ -65,4 +65,76 @@ extern int zone_dataset_visible(const char *, int *);
*/
extern uint32_t zone_get_hostid(void *);
+/*
+ * Operations that can be authorized via zoned_uid delegation.
+ * Shared with Linux; on FreeBSD these are defined but the check
+ * always returns NOT_APPLICABLE (no user namespace support).
+ */
+typedef enum zone_uid_op {
+ ZONE_OP_CREATE,
+ ZONE_OP_SNAPSHOT,
+ ZONE_OP_CLONE,
+ ZONE_OP_DESTROY,
+ ZONE_OP_RENAME,
+ ZONE_OP_SETPROP
+} zone_uid_op_t;
+
+typedef enum zone_admin_result {
+ ZONE_ADMIN_NOT_APPLICABLE,
+ ZONE_ADMIN_ALLOWED,
+ ZONE_ADMIN_DENIED
+} zone_admin_result_t;
+
+/*
+ * FreeBSD stub: zoned_uid delegation is not applicable (no user namespaces).
+ * Always returns NOT_APPLICABLE so callers fall through to existing
+ * jail-based permission checks.
+ */
+static inline zone_admin_result_t
+zone_dataset_admin_check(const char *dataset, zone_uid_op_t op,
+ const char *aux_dataset)
+{
+ (void) dataset, (void) op, (void) aux_dataset;
+ return (ZONE_ADMIN_NOT_APPLICABLE);
+}
+
+/*
+ * Callback type for looking up zoned_uid property.
+ */
+typedef uid_t (*zone_get_zoned_uid_fn_t)(const char *dataset,
+ char *root_out, size_t root_size);
+
+/*
+ * FreeBSD stubs: zoned_uid attach/detach require user namespaces
+ * which FreeBSD does not have. Return ENXIO (consistent with the
+ * Linux fallback when CONFIG_USER_NS is not defined).
+ */
+static inline int
+zone_dataset_attach_uid(struct ucred *cred, const char *dataset, uid_t uid)
+{
+ (void) cred, (void) dataset, (void) uid;
+ return (ENXIO);
+}
+
+static inline int
+zone_dataset_detach_uid(struct ucred *cred, const char *dataset, uid_t uid)
+{
+ (void) cred, (void) dataset, (void) uid;
+ return (ENXIO);
+}
+
+/*
+ * FreeBSD stubs: no-op since zoned_uid delegation requires user namespaces.
+ */
+static inline void
+zone_register_zoned_uid_callback(zone_get_zoned_uid_fn_t fn)
+{
+ (void) fn;
+}
+
+static inline void
+zone_unregister_zoned_uid_callback(void)
+{
+}
+
#endif /* !_OPENSOLARIS_SYS_ZONE_H_ */
diff --git a/include/os/linux/spl/sys/zone.h b/include/os/linux/spl/sys/zone.h
index 4e75202fbdd..2933c5a5c63 100644
--- a/include/os/linux/spl/sys/zone.h
+++ b/include/os/linux/spl/sys/zone.h
@@ -41,11 +41,69 @@ extern int zone_dataset_attach(cred_t *, const char *, int);
*/
extern int zone_dataset_detach(cred_t *, const char *, int);
+/*
+ * Attach the given dataset to all user namespaces owned by the given UID.
+ */
+extern int zone_dataset_attach_uid(cred_t *, const char *, uid_t);
+
+/*
+ * Detach the given dataset from UID-based zoning.
+ */
+extern int zone_dataset_detach_uid(cred_t *, const char *, uid_t);
+
/*
* Returns true if the named pool/dataset is visible in the current zone.
*/
extern int zone_dataset_visible(const char *dataset, int *write);
+/*
+ * Operations that can be authorized via zoned_uid delegation.
+ * Used by zone_dataset_admin_check() to apply operation-specific constraints.
+ */
+typedef enum zone_uid_op {
+ ZONE_OP_CREATE, /* Create child dataset */
+ ZONE_OP_SNAPSHOT, /* Create snapshot */
+ ZONE_OP_CLONE, /* Clone from snapshot */
+ ZONE_OP_DESTROY, /* Destroy dataset/snapshot */
+ ZONE_OP_RENAME, /* Rename (both src and dst checked) */
+ ZONE_OP_SETPROP /* Set properties */
+} zone_uid_op_t;
+
+/*
+ * Result of admin authorization check for zoned_uid delegation.
+ */
+typedef enum zone_admin_result {
+ ZONE_ADMIN_NOT_APPLICABLE, /* In global zone, use normal checks */
+ ZONE_ADMIN_ALLOWED, /* Authorized via zoned_uid */
+ ZONE_ADMIN_DENIED /* In user ns but not authorized */
+} zone_admin_result_t;
+
+/*
+ * Check if a dataset operation is authorized via zoned_uid delegation.
+ * For ZONE_OP_RENAME and ZONE_OP_CLONE, aux_dataset provides the
+ * second dataset (destination for rename, origin for clone).
+ * Returns ZONE_ADMIN_ALLOWED if authorized, ZONE_ADMIN_DENIED if in a
+ * user namespace but not authorized, or ZONE_ADMIN_NOT_APPLICABLE if
+ * in the global zone (caller should use normal permission checks).
+ */
+extern zone_admin_result_t zone_dataset_admin_check(const char *dataset,
+ zone_uid_op_t op, const char *aux_dataset);
+
+/*
+ * Callback type for looking up zoned_uid property.
+ * Returns the zoned_uid value if found, 0 if not set or on error.
+ * If root_out is non-NULL, copies the delegation root dataset name.
+ */
+typedef uid_t (*zone_get_zoned_uid_fn_t)(const char *dataset,
+ char *root_out, size_t root_size);
+
+/*
+ * Register/unregister the zoned_uid property lookup callback.
+ * Called by ZFS module during init/fini.
+ */
+extern void zone_register_zoned_uid_callback(zone_get_zoned_uid_fn_t fn);
+extern void zone_unregister_zoned_uid_callback(void);
+
int spl_zone_init(void);
void spl_zone_fini(void);
diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h
index ba79a674e73..1ddf3ba9ba1 100644
--- a/include/sys/fs/zfs.h
+++ b/include/sys/fs/zfs.h
@@ -204,6 +204,7 @@ typedef enum {
ZFS_PROP_DEFAULTGROUPOBJQUOTA,
ZFS_PROP_DEFAULTPROJECTOBJQUOTA,
ZFS_PROP_SNAPSHOTS_CHANGED_NSECS,
+ ZFS_PROP_ZONED_UID,
ZFS_NUM_PROPS
} zfs_prop_t;
@@ -1782,6 +1783,7 @@ typedef enum {
ZFS_ERR_ASHIFT_MISMATCH,
ZFS_ERR_STREAM_LARGE_MICROZAP,
ZFS_ERR_TOO_MANY_SITOUTS,
+ ZFS_ERR_NO_USER_NS_SUPPORT,
} zfs_errno_t;
/*
diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi
index bed2c7979a1..c69f9af5607 100644
--- a/lib/libzfs/libzfs.abi
+++ b/lib/libzfs/libzfs.abi
@@ -2292,7 +2292,8 @@
-
+
+
diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c
index bf276a3aa91..e5a7ca9ba3f 100644
--- a/lib/libzfs/libzfs_dataset.c
+++ b/lib/libzfs/libzfs_dataset.c
@@ -3347,9 +3347,13 @@ check_parents(libzfs_handle_t *hdl, const char *path, uint64_t *zoned,
/* we are in a non-global zone, but parent is in the global zone */
if (getzoneid() != GLOBAL_ZONEID && !is_zoned) {
- (void) zfs_standard_error(hdl, EPERM, errbuf);
- zfs_close(zhp);
- return (-1);
+ uint64_t zoned_uid = zfs_prop_get_int(zhp, ZFS_PROP_ZONED_UID);
+ if (zoned_uid == 0) {
+ (void) zfs_standard_error(hdl, EPERM, errbuf);
+ zfs_close(zhp);
+ return (-1);
+ }
+ /* zoned_uid set - let kernel decide */
}
/* make sure parent is a filesystem */
diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c
index 021a1d8a407..d886bdb9786 100644
--- a/lib/libzfs/libzfs_util.c
+++ b/lib/libzfs/libzfs_util.c
@@ -324,6 +324,9 @@ libzfs_error_description(libzfs_handle_t *hdl)
case EZFS_ASHIFT_MISMATCH:
return (dgettext(TEXT_DOMAIN, "adding devices with "
"different physical sector sizes is not allowed"));
+ case EZFS_NO_USER_NS_SUPPORT:
+ return (dgettext(TEXT_DOMAIN, "kernel was built without "
+ "user namespace support (CONFIG_USER_NS)"));
case EZFS_UNKNOWN:
return (dgettext(TEXT_DOMAIN, "unknown error"));
default:
@@ -517,6 +520,9 @@ zfs_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
case ZFS_ERR_NOT_USER_NAMESPACE:
zfs_verror(hdl, EZFS_NOT_USER_NAMESPACE, fmt, ap);
break;
+ case ZFS_ERR_NO_USER_NS_SUPPORT:
+ zfs_verror(hdl, EZFS_NO_USER_NS_SUPPORT, fmt, ap);
+ break;
default:
zfs_error_aux(hdl, "%s", zfs_strerror(error));
zfs_verror(hdl, EZFS_UNKNOWN, fmt, ap);
diff --git a/man/man7/zfsprops.7 b/man/man7/zfsprops.7
index 448a7ec05cc..183e6ea9574 100644
--- a/man/man7/zfsprops.7
+++ b/man/man7/zfsprops.7
@@ -2112,6 +2112,98 @@ for more information.
Zoning is a
Linux
feature and this property is not available on other platforms.
+.It Sy zoned_uid Ns = Ns Ar uid
+Delegates dataset visibility and administration to all user namespaces
+owned by the specified UID.
+This property enables rootless container support with native ZFS storage.
+For example, setting
+.Sy zoned_uid Ns = Ns 1000
+allows user 1000's rootless Podman containers to use ZFS for storage layers.
+This is a Linux-only feature.
+.Pp
+Authorization uses an additive three-layer model where all layers must pass:
+.Bl -tag -width "L2 (capability tier)" -compact
+.It Sy L0 (authentication)
+The user namespace owner UID must match the
+.Sy zoned_uid
+value.
+.It Sy L1 (dsl_deleg)
+The pool administrator must grant per-operation permissions on the
+delegation root using
+.Xr zfs-allow 8 .
+When pool delegation is OFF
+.Pq Nm zpool Cm set Sy delegation Ns = Ns Sy off ,
+all write operations are denied regardless of capabilities.
+.It Sy L2 (capability tier)
+Linux capabilities within the user namespace determine the permitted
+operation class:
+.Sy CAP_FOWNER
+for non-destructive operations
+.Pq create, snapshot, set property ,
+.Sy CAP_SYS_ADMIN
+for destructive operations
+.Pq destroy, rename, clone .
+Both are namespaced capabilities scoped to the user namespace,
+not the init namespace.
+.El
+.Pp
+Read-only operations
+.Pq Nm zfs Cm list , Nm zfs Cm get
+require no capabilities and no
+.Nm zfs Cm allow
+grants; visibility is controlled solely by the
+.Sy zoned_uid
+delegation scoping.
+.Pp
+Write operations that can be delegated include
+.Nm zfs Cm create ,
+.Nm zfs Cm destroy ,
+.Nm zfs Cm snapshot ,
+.Nm zfs Cm clone ,
+.Nm zfs Cm rename
+.Pq within the delegation subtree ,
+and
+.Nm zfs Cm set .
+.Pp
+The delegation root dataset
+.Pq where zoned_uid is locally set
+cannot be destroyed from within the user namespace, protecting the
+parent dataset from unauthorized removal.
+Renames are also constrained to remain within the delegation subtree.
+The namespace user cannot modify the
+.Sy zoned_uid
+or
+.Sy zoned
+properties, and cannot override
+.Sy filesystem_limit
+or
+.Sy snapshot_limit
+set by the administrator on the delegation root
+.Pq but can impose tighter sub-limits on child datasets .
+.Pp
+Set to
+.Sy 0
+.Pq or inherit
+to disable UID-based delegation.
+.Pp
+Unlike
+.Nm zfs Cm zone
+which requires an existing namespace file,
+.Sy zoned_uid
+applies to any user namespace owned by the specified UID,
+making it suitable for container runtimes that create new namespaces
+on each invocation.
+See
+.Xr zfs-zone 8
+for namespace-specific delegation.
+.Pp
+Example setup for rootless Podman:
+.Bd -literal -offset indent
+# zfs create tank/containers
+# zfs set zoned_uid=1000 tank/containers
+# zfs set mountpoint=none tank/containers
+# zfs allow -u 1000 create,destroy,mount,snapshot,rename,clone tank/containers
+.Ed
.El
.Pp
The following three properties cannot be changed after the file system is
diff --git a/man/man8/zfs-zone.8 b/man/man8/zfs-zone.8
index a56a304e82b..d00b2e217a5 100644
--- a/man/man8/zfs-zone.8
+++ b/man/man8/zfs-zone.8
@@ -114,4 +114,17 @@ dataset to a user namespace identified by user namespace file
.Dl # Nm zfs Cm zone Ar /proc/1234/ns/user Ar tank/users
.
.Sh SEE ALSO
-.Xr zfsprops 7
+.Xr zfsprops 7 ,
+.Xr zfs-allow 8
+.Pp
+For rootless container use cases where the namespace is ephemeral,
+consider using the
+.Sy zoned_uid
+property instead, which delegates to all namespaces owned by a UID
+rather than requiring attachment to a specific namespace file.
+The
+.Sy zoned_uid
+property uses a three-layer additive authorization model
+.Pq UID match, dsl_deleg grants, capability tiers
+described in
+.Xr zfsprops 7 .
diff --git a/module/os/linux/spl/spl-zone.c b/module/os/linux/spl/spl-zone.c
index b2eae5d00b1..5992957280e 100644
--- a/module/os/linux/spl/spl-zone.c
+++ b/module/os/linux/spl/spl-zone.c
@@ -59,6 +59,18 @@ typedef struct zone_dataset {
char zd_dsname[]; /* name of the member dataset */
} zone_dataset_t;
+/*
+ * UID-based dataset zoning: allows delegating datasets to all user
+ * namespaces owned by a specific UID, enabling rootless container support.
+ */
+typedef struct zone_uid_datasets {
+ struct list_head zuds_list; /* zone_uid_datasets linkage */
+ kuid_t zuds_owner; /* owner UID */
+ struct list_head zuds_datasets; /* datasets for this UID */
+} zone_uid_datasets_t;
+
+static struct list_head zone_uid_datasets;
+
#ifdef CONFIG_USER_NS
/*
@@ -138,6 +150,18 @@ zone_datasets_lookup(unsigned int nsinum)
}
#ifdef CONFIG_USER_NS
+static zone_uid_datasets_t *
+zone_uid_datasets_lookup(kuid_t owner)
+{
+ zone_uid_datasets_t *zuds;
+
+ list_for_each_entry(zuds, &zone_uid_datasets, zuds_list) {
+ if (uid_eq(zuds->zuds_owner, owner))
+ return (zuds);
+ }
+ return (NULL);
+}
+
static struct zone_dataset *
zone_dataset_lookup(zone_datasets_t *zds, const char *dataset, size_t dsnamelen)
{
@@ -231,6 +255,62 @@ zone_dataset_attach(cred_t *cred, const char *dataset, int userns_fd)
}
EXPORT_SYMBOL(zone_dataset_attach);
+int
+zone_dataset_attach_uid(cred_t *cred, const char *dataset, uid_t owner_uid)
+{
+#ifdef CONFIG_USER_NS
+ zone_uid_datasets_t *zuds;
+ zone_dataset_t *zd;
+ int error;
+ size_t dsnamelen;
+ kuid_t kowner;
+
+ /* Only root can attach datasets to UIDs */
+ if ((error = zone_dataset_cred_check(cred)) != 0)
+ return (error);
+ if ((error = zone_dataset_name_check(dataset, &dsnamelen)) != 0)
+ return (error);
+
+ kowner = make_kuid(current_user_ns(), owner_uid);
+ if (!uid_valid(kowner))
+ return (EINVAL);
+
+ mutex_enter(&zone_datasets_lock);
+
+ /* Find or create UID entry */
+ zuds = zone_uid_datasets_lookup(kowner);
+ if (zuds == NULL) {
+ zuds = kmem_alloc(sizeof (zone_uid_datasets_t), KM_SLEEP);
+ INIT_LIST_HEAD(&zuds->zuds_list);
+ INIT_LIST_HEAD(&zuds->zuds_datasets);
+ zuds->zuds_owner = kowner;
+ list_add_tail(&zuds->zuds_list, &zone_uid_datasets);
+ } else {
+ /* Check if dataset already attached */
+ list_for_each_entry(zd, &zuds->zuds_datasets, zd_list) {
+ if (zd->zd_dsnamelen == dsnamelen &&
+ strncmp(zd->zd_dsname, dataset, dsnamelen) == 0) {
+ mutex_exit(&zone_datasets_lock);
+ return (EEXIST);
+ }
+ }
+ }
+
+ /* Add dataset to UID's list */
+ zd = kmem_alloc(sizeof (zone_dataset_t) + dsnamelen + 1, KM_SLEEP);
+ zd->zd_dsnamelen = dsnamelen;
+ strlcpy(zd->zd_dsname, dataset, dsnamelen + 1);
+ INIT_LIST_HEAD(&zd->zd_list);
+ list_add_tail(&zd->zd_list, &zuds->zuds_datasets);
+
+ mutex_exit(&zone_datasets_lock);
+ return (0);
+#else
+ return (ENXIO);
+#endif /* CONFIG_USER_NS */
+}
+EXPORT_SYMBOL(zone_dataset_attach_uid);
+
int
zone_dataset_detach(cred_t *cred, const char *dataset, int userns_fd)
{
@@ -280,6 +360,217 @@ zone_dataset_detach(cred_t *cred, const char *dataset, int userns_fd)
}
EXPORT_SYMBOL(zone_dataset_detach);
+int
+zone_dataset_detach_uid(cred_t *cred, const char *dataset, uid_t owner_uid)
+{
+#ifdef CONFIG_USER_NS
+ zone_uid_datasets_t *zuds;
+ zone_dataset_t *zd;
+ int error;
+ size_t dsnamelen;
+ kuid_t kowner;
+
+ if ((error = zone_dataset_cred_check(cred)) != 0)
+ return (error);
+ if ((error = zone_dataset_name_check(dataset, &dsnamelen)) != 0)
+ return (error);
+
+ kowner = make_kuid(current_user_ns(), owner_uid);
+ if (!uid_valid(kowner))
+ return (EINVAL);
+
+ mutex_enter(&zone_datasets_lock);
+
+ zuds = zone_uid_datasets_lookup(kowner);
+ if (zuds == NULL) {
+ mutex_exit(&zone_datasets_lock);
+ return (ENOENT);
+ }
+
+ /* Find and remove dataset */
+ list_for_each_entry(zd, &zuds->zuds_datasets, zd_list) {
+ if (zd->zd_dsnamelen == dsnamelen &&
+ strncmp(zd->zd_dsname, dataset, dsnamelen) == 0) {
+ list_del(&zd->zd_list);
+ kmem_free(zd, sizeof (*zd) + zd->zd_dsnamelen + 1);
+
+ /* Remove UID entry if no more datasets */
+ if (list_empty(&zuds->zuds_datasets)) {
+ list_del(&zuds->zuds_list);
+ kmem_free(zuds, sizeof (*zuds));
+ }
+
+ mutex_exit(&zone_datasets_lock);
+ return (0);
+ }
+ }
+
+ mutex_exit(&zone_datasets_lock);
+ return (ENOENT);
+#else
+ return (ENXIO);
+#endif /* CONFIG_USER_NS */
+}
+EXPORT_SYMBOL(zone_dataset_detach_uid);
+
+/*
+ * Callback for looking up zoned_uid property (registered by ZFS module).
+ */
+static zone_get_zoned_uid_fn_t zone_get_zoned_uid_fn = NULL;
+
+void
+zone_register_zoned_uid_callback(zone_get_zoned_uid_fn_t fn)
+{
+ zone_get_zoned_uid_fn = fn;
+}
+EXPORT_SYMBOL(zone_register_zoned_uid_callback);
+
+void
+zone_unregister_zoned_uid_callback(void)
+{
+ zone_get_zoned_uid_fn = NULL;
+}
+EXPORT_SYMBOL(zone_unregister_zoned_uid_callback);
+
+#ifdef CONFIG_USER_NS
+/*
+ * Check if a dataset is the delegation root (has zoned_uid set locally).
+ */
+static boolean_t
+zone_dataset_is_zoned_uid_root(const char *dataset, uid_t zoned_uid)
+{
+ char *root;
+ uid_t found_uid;
+ boolean_t is_root;
+
+ if (zone_get_zoned_uid_fn == NULL)
+ return (B_FALSE);
+
+ root = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ found_uid = zone_get_zoned_uid_fn(dataset, root, MAXPATHLEN);
+ is_root = (found_uid == zoned_uid && strcmp(root, dataset) == 0);
+ kmem_free(root, MAXPATHLEN);
+ return (is_root);
+}
+#endif /* CONFIG_USER_NS */
+
+/*
+ * Core authorization check for zoned_uid write delegation.
+ */
+zone_admin_result_t
+zone_dataset_admin_check(const char *dataset, zone_uid_op_t op,
+ const char *aux_dataset)
+{
+#ifdef CONFIG_USER_NS
+ struct user_namespace *user_ns;
+ char *delegation_root;
+ uid_t zoned_uid, ns_owner_uid;
+ int write_unused;
+ zone_admin_result_t result = ZONE_ADMIN_NOT_APPLICABLE;
+
+ /* Step 1: If in global zone, not applicable */
+ if (INGLOBALZONE(curproc))
+ return (ZONE_ADMIN_NOT_APPLICABLE);
+
+ /* Step 2: Need callback to be registered */
+ if (zone_get_zoned_uid_fn == NULL)
+ return (ZONE_ADMIN_NOT_APPLICABLE);
+
+ delegation_root = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+
+ /* Step 3: Find delegation root */
+ zoned_uid = zone_get_zoned_uid_fn(dataset, delegation_root,
+ MAXPATHLEN);
+ if (zoned_uid == 0)
+ goto out;
+
+ /* Step 4: Verify namespace owner matches */
+ user_ns = current_user_ns();
+ ns_owner_uid = from_kuid(&init_user_ns, user_ns->owner);
+ if (ns_owner_uid != zoned_uid)
+ goto out;
+
+ /* Step 5: Tiered capability check based on operation class */
+ {
+ int required_cap;
+ switch (op) {
+ case ZONE_OP_DESTROY:
+ case ZONE_OP_RENAME:
+ case ZONE_OP_CLONE:
+ required_cap = CAP_SYS_ADMIN;
+ break;
+ case ZONE_OP_CREATE:
+ case ZONE_OP_SNAPSHOT:
+ case ZONE_OP_SETPROP:
+ required_cap = CAP_FOWNER;
+ break;
+ default:
+ required_cap = CAP_SYS_ADMIN;
+ break;
+ }
+ if (!ns_capable(user_ns, required_cap)) {
+ result = ZONE_ADMIN_DENIED;
+ goto out;
+ }
+ }
+
+ /* Step 6: Operation-specific constraints */
+ switch (op) {
+ case ZONE_OP_DESTROY:
+ /* Cannot destroy the delegation root itself */
+ if (zone_dataset_is_zoned_uid_root(dataset, zoned_uid)) {
+ result = ZONE_ADMIN_DENIED;
+ goto out;
+ }
+ break;
+
+ case ZONE_OP_RENAME:
+ /* Cannot rename outside delegation subtree */
+ if (aux_dataset != NULL) {
+ char *dst_root;
+ uid_t dst_uid;
+
+ dst_root = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+ dst_uid = zone_get_zoned_uid_fn(aux_dataset,
+ dst_root, MAXPATHLEN);
+ if (dst_uid != zoned_uid ||
+ strcmp(dst_root, delegation_root) != 0) {
+ kmem_free(dst_root, MAXPATHLEN);
+ result = ZONE_ADMIN_DENIED;
+ goto out;
+ }
+ kmem_free(dst_root, MAXPATHLEN);
+ }
+ break;
+
+ case ZONE_OP_CLONE:
+ /* Clone source must be visible */
+ if (aux_dataset != NULL) {
+ if (!zone_dataset_visible(aux_dataset, &write_unused)) {
+ result = ZONE_ADMIN_DENIED;
+ goto out;
+ }
+ }
+ break;
+
+ case ZONE_OP_CREATE:
+ case ZONE_OP_SNAPSHOT:
+ case ZONE_OP_SETPROP:
+ /* No additional constraints */
+ break;
+ }
+
+ result = ZONE_ADMIN_ALLOWED;
+out:
+ kmem_free(delegation_root, MAXPATHLEN);
+ return (result);
+#else
+ (void) dataset, (void) op, (void) aux_dataset;
+ return (ZONE_ADMIN_NOT_APPLICABLE);
+#endif
+}
+EXPORT_SYMBOL(zone_dataset_admin_check);
+
/*
* A dataset is visible if:
* - It is a parent of a namespace entry.
@@ -293,34 +584,19 @@ EXPORT_SYMBOL(zone_dataset_detach);
* The parent datasets of namespace entries are visible and
* read-only to provide a path back to the root of the pool.
*/
-int
-zone_dataset_visible(const char *dataset, int *write)
+/*
+ * Helper function to check if a dataset matches against a list of
+ * delegated datasets. Returns visibility and sets write permission.
+ */
+static int
+zone_dataset_check_list(struct list_head *datasets, const char *dataset,
+ size_t dsnamelen, int *write)
{
- zone_datasets_t *zds;
zone_dataset_t *zd;
- size_t dsnamelen, zd_len;
- int visible;
+ size_t zd_len;
+ int visible = 0;
- /* Default to read-only, in case visible is returned. */
- if (write != NULL)
- *write = 0;
- if (zone_dataset_name_check(dataset, &dsnamelen) != 0)
- return (0);
- if (INGLOBALZONE(curproc)) {
- if (write != NULL)
- *write = 1;
- return (1);
- }
-
- mutex_enter(&zone_datasets_lock);
- zds = zone_datasets_lookup(crgetzoneid(curproc->cred));
- if (zds == NULL) {
- mutex_exit(&zone_datasets_lock);
- return (0);
- }
-
- visible = 0;
- list_for_each_entry(zd, &zds->zds_datasets, zd_list) {
+ list_for_each_entry(zd, datasets, zd_list) {
zd_len = strlen(zd->zd_dsname);
if (zd_len > dsnamelen) {
/*
@@ -352,7 +628,8 @@ zone_dataset_visible(const char *dataset, int *write)
* the namespace entry.
*/
visible = memcmp(zd->zd_dsname, dataset,
- zd_len) == 0 && dataset[zd_len] == '/';
+ zd_len) == 0 && (dataset[zd_len] == '/' ||
+ dataset[zd_len] == '@' || dataset[zd_len] == '#');
if (visible) {
if (write != NULL)
*write = 1;
@@ -361,9 +638,70 @@ zone_dataset_visible(const char *dataset, int *write)
}
}
- mutex_exit(&zone_datasets_lock);
return (visible);
}
+
+#if defined(CONFIG_USER_NS)
+/*
+ * Check UID-based zoning visibility for the current process.
+ * Must be called with zone_datasets_lock held.
+ */
+static int
+zone_dataset_visible_uid(const char *dataset, size_t dsnamelen, int *write)
+{
+ zone_uid_datasets_t *zuds;
+
+ zuds = zone_uid_datasets_lookup(curproc->cred->user_ns->owner);
+ if (zuds != NULL)
+ return (zone_dataset_check_list(&zuds->zuds_datasets, dataset,
+ dsnamelen, write));
+ return (0);
+}
+#endif
+
+int
+zone_dataset_visible(const char *dataset, int *write)
+{
+ zone_datasets_t *zds;
+ size_t dsnamelen;
+ int visible;
+
+ /* Default to read-only, in case visible is returned. */
+ if (write != NULL)
+ *write = 0;
+ if (zone_dataset_name_check(dataset, &dsnamelen) != 0)
+ return (0);
+ if (INGLOBALZONE(curproc)) {
+ if (write != NULL)
+ *write = 1;
+ return (1);
+ }
+
+ mutex_enter(&zone_datasets_lock);
+
+ /* First, check namespace-specific zoning (existing behavior) */
+ zds = zone_datasets_lookup(crgetzoneid(curproc->cred));
+ if (zds != NULL) {
+ visible = zone_dataset_check_list(&zds->zds_datasets, dataset,
+ dsnamelen, write);
+ if (visible) {
+ mutex_exit(&zone_datasets_lock);
+ return (visible);
+ }
+ }
+
+ /* Second, check UID-based zoning */
+#if defined(CONFIG_USER_NS)
+ visible = zone_dataset_visible_uid(dataset, dsnamelen, write);
+ if (visible) {
+ mutex_exit(&zone_datasets_lock);
+ return (visible);
+ }
+#endif
+
+ mutex_exit(&zone_datasets_lock);
+ return (0);
+}
EXPORT_SYMBOL(zone_dataset_visible);
unsigned int
@@ -395,8 +733,9 @@ EXPORT_SYMBOL(crgetzoneid);
boolean_t
inglobalzone(proc_t *proc)
{
+ (void) proc;
#if defined(CONFIG_USER_NS)
- return (proc->cred->user_ns == &init_user_ns);
+ return (current_user_ns() == &init_user_ns);
#else
return (B_TRUE);
#endif
@@ -408,6 +747,7 @@ spl_zone_init(void)
{
mutex_init(&zone_datasets_lock, NULL, MUTEX_DEFAULT, NULL);
INIT_LIST_HEAD(&zone_datasets);
+ INIT_LIST_HEAD(&zone_uid_datasets);
return (0);
}
@@ -415,6 +755,7 @@ void
spl_zone_fini(void)
{
zone_datasets_t *zds;
+ zone_uid_datasets_t *zuds;
zone_dataset_t *zd;
/*
@@ -423,6 +764,22 @@ spl_zone_fini(void)
* namespace is destroyed, just do it here, since spl is about to go
* out of context.
*/
+
+ /* Clean up UID-based delegations */
+ while (!list_empty(&zone_uid_datasets)) {
+ zuds = list_entry(zone_uid_datasets.next,
+ zone_uid_datasets_t, zuds_list);
+ while (!list_empty(&zuds->zuds_datasets)) {
+ zd = list_entry(zuds->zuds_datasets.next,
+ zone_dataset_t, zd_list);
+ list_del(&zd->zd_list);
+ kmem_free(zd, sizeof (*zd) + zd->zd_dsnamelen + 1);
+ }
+ list_del(&zuds->zuds_list);
+ kmem_free(zuds, sizeof (*zuds));
+ }
+
+ /* Clean up namespace-based delegations */
while (!list_empty(&zone_datasets)) {
zds = list_entry(zone_datasets.next, zone_datasets_t, zds_list);
while (!list_empty(&zds->zds_datasets)) {
diff --git a/module/os/linux/zfs/spa_misc_os.c b/module/os/linux/zfs/spa_misc_os.c
index d6323fd56a8..91010bdf642 100644
--- a/module/os/linux/zfs/spa_misc_os.c
+++ b/module/os/linux/zfs/spa_misc_os.c
@@ -39,8 +39,10 @@
#include
#include
#include
+#include
#include
#include
+#include
#include "zfs_prop.h"
@@ -122,16 +124,60 @@ spa_history_zone(void)
return ("linux");
}
+static int
+spa_restore_zoned_uid_cb(const char *dsname, void *arg)
+{
+ (void) arg;
+ uint64_t zoned_uid = 0;
+
+ if (dsl_prop_get(dsname, "zoned_uid", 8, 1, &zoned_uid, NULL) != 0)
+ return (0);
+
+ if (zoned_uid != 0) {
+ int err = zone_dataset_attach_uid(kcred, dsname,
+ (uid_t)zoned_uid);
+ if (err != 0 && err != EEXIST) {
+ cmn_err(CE_WARN, "failed to restore zoned_uid for "
+ "'%s' (uid %llu): %d", dsname,
+ (unsigned long long)zoned_uid, err);
+ }
+ }
+ return (0);
+}
+
void
spa_import_os(spa_t *spa)
{
- (void) spa;
+ (void) dmu_objset_find(spa_name(spa),
+ spa_restore_zoned_uid_cb, NULL, DS_FIND_CHILDREN);
+}
+
+static int
+spa_cleanup_zoned_uid_cb(const char *dsname, void *arg)
+{
+ (void) arg;
+ uint64_t zoned_uid = 0;
+
+ if (dsl_prop_get(dsname, "zoned_uid", 8, 1, &zoned_uid, NULL) != 0)
+ return (0);
+
+ if (zoned_uid != 0) {
+ int err = zone_dataset_detach_uid(kcred, dsname,
+ (uid_t)zoned_uid);
+ if (err != 0 && err != ENOENT) {
+ cmn_err(CE_WARN, "failed to detach zoned_uid for "
+ "'%s' (uid %llu): %d", dsname,
+ (unsigned long long)zoned_uid, err);
+ }
+ }
+ return (0);
}
void
spa_export_os(spa_t *spa)
{
- (void) spa;
+ (void) dmu_objset_find(spa_name(spa),
+ spa_cleanup_zoned_uid_cb, NULL, DS_FIND_CHILDREN);
}
void
diff --git a/module/os/linux/zfs/zfs_ioctl_os.c b/module/os/linux/zfs/zfs_ioctl_os.c
index 5421a441b32..ce6092be1da 100644
--- a/module/os/linux/zfs/zfs_ioctl_os.c
+++ b/module/os/linux/zfs/zfs_ioctl_os.c
@@ -170,6 +170,8 @@ zfs_ioc_userns_attach(zfs_cmd_t *zc)
*/
if (error == ENOTTY)
error = ZFS_ERR_NOT_USER_NAMESPACE;
+ if (error == ENXIO)
+ error = ZFS_ERR_NO_USER_NS_SUPPORT;
return (error);
}
@@ -190,6 +192,8 @@ zfs_ioc_userns_detach(zfs_cmd_t *zc)
*/
if (error == ENOTTY)
error = ZFS_ERR_NOT_USER_NAMESPACE;
+ if (error == ENXIO)
+ error = ZFS_ERR_NO_USER_NS_SUPPORT;
return (error);
}
diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c
index 71482c8d795..0866caf8795 100644
--- a/module/zcommon/zfs_prop.c
+++ b/module/zcommon/zfs_prop.c
@@ -525,6 +525,10 @@ zfs_prop_init(void)
zprop_register_index(ZFS_PROP_ZONED, "zoned", 0, PROP_INHERIT,
ZFS_TYPE_FILESYSTEM, "on | off", "ZONED", boolean_table, sfeatures);
#endif
+ /* UID-based zoning for rootless containers */
+ zprop_register_number(ZFS_PROP_ZONED_UID, "zoned_uid", 0,
+ PROP_INHERIT, ZFS_TYPE_FILESYSTEM, " | none", "ZONED_UID",
+ B_FALSE, sfeatures);
zprop_register_index(ZFS_PROP_VSCAN, "vscan", 0, PROP_INHERIT,
ZFS_TYPE_FILESYSTEM, "on | off", "VSCAN", boolean_table, sfeatures);
zprop_register_index(ZFS_PROP_NBMAND, "nbmand", 0, PROP_INHERIT,
diff --git a/module/zfs/dsl_deleg.c b/module/zfs/dsl_deleg.c
index 200bee200d3..f3153d6901c 100644
--- a/module/zfs/dsl_deleg.c
+++ b/module/zfs/dsl_deleg.c
@@ -591,13 +591,16 @@ dsl_deleg_access_impl(dsl_dataset_t *ds, const char *perm, cred_t *cr)
* the zoned property is set
*/
if (!INGLOBALZONE(curproc)) {
- uint64_t zoned;
+ uint64_t zoned = 0;
+ uint64_t zoned_uid_val = 0;
- if (dsl_prop_get_dd(dd,
+ (void) dsl_prop_get_dd(dd,
zfs_prop_to_name(ZFS_PROP_ZONED),
- 8, 1, &zoned, NULL, B_FALSE) != 0)
- break;
- if (!zoned)
+ 8, 1, &zoned, NULL, B_FALSE);
+ (void) dsl_prop_get_dd(dd,
+ zfs_prop_to_name(ZFS_PROP_ZONED_UID),
+ 8, 1, &zoned_uid_val, NULL, B_FALSE);
+ if (!zoned && zoned_uid_val == 0)
break;
}
zapobj = dsl_dir_phys(dd)->dd_deleg_zapobj;
diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c
index 3bbc9107ae2..e2498f3e3a4 100644
--- a/module/zfs/zfs_ioctl.c
+++ b/module/zfs/zfs_ioctl.c
@@ -286,6 +286,59 @@ static int zfs_fill_zplprops_root(uint64_t, nvlist_t *, nvlist_t *,
int zfs_set_prop_nvlist(const char *, zprop_source_t, nvlist_t *, nvlist_t *);
static int get_nvlist(uint64_t nvl, uint64_t size, int iflag, nvlist_t **nvp);
+/*
+ * Callback for SPL to look up zoned_uid property.
+ * Walks ancestors to find the delegation root with zoned_uid set.
+ * Returns the zoned_uid value if found, or 0 if not set.
+ */
+static uid_t
+zfs_get_zoned_uid(const char *dataset, char *root_out, size_t root_size)
+{
+ char path[ZFS_MAX_DATASET_NAME_LEN];
+ char setpoint[ZFS_MAX_DATASET_NAME_LEN];
+ char *slash, *at;
+ uint64_t zoned_uid_val = 0;
+ int error;
+
+ (void) strlcpy(path, dataset, sizeof (path));
+
+ /*
+ * Strip snapshot suffix if present — snapshots inherit properties
+ * from their parent filesystem.
+ */
+ at = strchr(path, '@');
+ if (at != NULL)
+ *at = '\0';
+
+ /*
+ * Walk up the hierarchy until we find a dataset with zoned_uid set.
+ * This handles the case where the dataset doesn't exist yet (e.g.,
+ * rename destination) — dsl_prop_get fails on non-existent datasets,
+ * so we walk up to find an existing ancestor.
+ *
+ * When the property is found (possibly via inheritance), setpoint
+ * tells us the actual delegation root where zoned_uid is locally
+ * set, rather than the dataset where we happened to query it.
+ */
+ while (path[0] != '\0') {
+ error = dsl_prop_get(path, "zoned_uid", 8, 1,
+ &zoned_uid_val, setpoint);
+
+ if (error == 0 && zoned_uid_val != 0) {
+ if (root_out != NULL)
+ (void) strlcpy(root_out, setpoint, root_size);
+ return ((uid_t)zoned_uid_val);
+ }
+
+ slash = strrchr(path, '/');
+ if (slash == NULL)
+ break;
+ *slash = '\0';
+ }
+
+ return (0);
+}
+
static void
history_str_free(char *buf)
{
@@ -501,6 +554,42 @@ zfs_secpolicy_write_perms(const char *name, const char *perm, cred_t *cr)
return (error);
}
+/*
+ * Check dsl_deleg permission for zoned_uid datasets.
+ *
+ * This bypasses zfs_dozonecheck_ds() (which requires the 'zoned' property)
+ * because zoned_uid datasets use a different authentication model. The zone
+ * check was already performed by zone_dataset_admin_check().
+ *
+ * Returns 0 if permission is granted, error otherwise.
+ * ECANCELED from dsl_deleg_access_impl() means delegation is disabled on the
+ * pool — in that case we deny access (POLP: no delegation = no access).
+ */
+static int
+zfs_secpolicy_zoned_uid_deleg(const char *name, const char *perm, cred_t *cr)
+{
+ dsl_pool_t *dp;
+ dsl_dataset_t *ds;
+ int error;
+
+ error = dsl_pool_hold(name, FTAG, &dp);
+ if (error != 0)
+ return (error);
+ error = dsl_dataset_hold(dp, name, FTAG, &ds);
+ if (error != 0) {
+ dsl_pool_rele(dp, FTAG);
+ return (error);
+ }
+ error = dsl_deleg_access_impl(ds, perm, cr);
+ dsl_dataset_rele(ds, FTAG);
+ dsl_pool_rele(dp, FTAG);
+
+ /* ECANCELED = delegation disabled on pool; deny access (POLP) */
+ if (error == ECANCELED)
+ return (SET_ERROR(EPERM));
+ return (error);
+}
+
/*
* Policy for setting the security label property.
*
@@ -607,6 +696,31 @@ zfs_secpolicy_setprop(const char *dsname, zfs_prop_t prop, nvpair_t *propval,
cred_t *cr)
{
const char *strval;
+ zone_admin_result_t zone_result;
+
+ /*
+ * Check zoned_uid delegation first. However, even delegated
+ * namespace users must not be allowed to modify zoned_uid itself.
+ */
+ zone_result = zone_dataset_admin_check(dsname, ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_ALLOWED) {
+ if (prop == ZFS_PROP_ZONED_UID)
+ return (SET_ERROR(EPERM));
+ if (prop == ZFS_PROP_FILESYSTEM_LIMIT ||
+ prop == ZFS_PROP_SNAPSHOT_LIMIT) {
+ char setpoint[ZFS_MAX_DATASET_NAME_LEN];
+ uint64_t zoned_uid_val = 0;
+ if (dsl_prop_get(dsname, "zoned_uid", 8, 1,
+ &zoned_uid_val, setpoint) == 0 &&
+ zoned_uid_val != 0 &&
+ strcmp(dsname, setpoint) == 0)
+ return (SET_ERROR(EPERM));
+ }
+ return (zfs_secpolicy_zoned_uid_deleg(dsname,
+ zfs_prop_to_name(prop), cr));
+ }
+ if (zone_result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
/*
* Check permissions for special properties.
@@ -621,6 +735,15 @@ zfs_secpolicy_setprop(const char *dsname, zfs_prop_t prop, nvpair_t *propval,
if (!INGLOBALZONE(curproc))
return (SET_ERROR(EPERM));
break;
+ case ZFS_PROP_ZONED_UID:
+ /*
+ * Disallow setting of 'zoned_uid' from within a
+ * delegated namespace -- only global zone can manage
+ * delegation assignments.
+ */
+ if (!INGLOBALZONE(curproc))
+ return (SET_ERROR(EPERM));
+ break;
case ZFS_PROP_QUOTA:
case ZFS_PROP_FILESYSTEM_LIMIT:
@@ -774,7 +897,21 @@ int
zfs_secpolicy_destroy_perms(const char *name, cred_t *cr)
{
int error;
+ zone_admin_result_t result;
+ /* Check zoned_uid delegation first */
+ result = zone_dataset_admin_check(name, ZONE_OP_DESTROY, NULL);
+ if (result == ZONE_ADMIN_ALLOWED) {
+ if ((error = zfs_secpolicy_zoned_uid_deleg(name,
+ ZFS_DELEG_PERM_DESTROY, cr)) != 0)
+ return (error);
+ return (zfs_secpolicy_zoned_uid_deleg(name,
+ ZFS_DELEG_PERM_MOUNT, cr));
+ }
+ if (result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
+
+ /* NOT_APPLICABLE: continue with existing checks */
if ((error = zfs_secpolicy_write_perms(name,
ZFS_DELEG_PERM_MOUNT, cr)) != 0)
return (error);
@@ -831,7 +968,21 @@ zfs_secpolicy_rename_perms(const char *from, const char *to, cred_t *cr)
{
char parentname[ZFS_MAX_DATASET_NAME_LEN];
int error;
+ zone_admin_result_t result;
+ /* Check zoned_uid delegation first */
+ result = zone_dataset_admin_check(from, ZONE_OP_RENAME, to);
+ if (result == ZONE_ADMIN_ALLOWED) {
+ if ((error = zfs_secpolicy_zoned_uid_deleg(from,
+ ZFS_DELEG_PERM_RENAME, cr)) != 0)
+ return (error);
+ return (zfs_secpolicy_zoned_uid_deleg(from,
+ ZFS_DELEG_PERM_MOUNT, cr));
+ }
+ if (result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
+
+ /* NOT_APPLICABLE: continue with existing checks */
if ((error = zfs_secpolicy_write_perms(from,
ZFS_DELEG_PERM_RENAME, cr)) != 0)
return (error);
@@ -940,6 +1091,17 @@ zfs_secpolicy_recv(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
int
zfs_secpolicy_snapshot_perms(const char *name, cred_t *cr)
{
+ zone_admin_result_t result;
+
+ /* Check zoned_uid delegation first */
+ result = zone_dataset_admin_check(name, ZONE_OP_SNAPSHOT, NULL);
+ if (result == ZONE_ADMIN_ALLOWED)
+ return (zfs_secpolicy_zoned_uid_deleg(name,
+ ZFS_DELEG_PERM_SNAPSHOT, cr));
+ if (result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
+
+ /* NOT_APPLICABLE: continue with existing checks */
return (zfs_secpolicy_write_perms(name,
ZFS_DELEG_PERM_SNAPSHOT, cr));
}
@@ -1062,13 +1224,35 @@ zfs_secpolicy_create_clone(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
{
char parentname[ZFS_MAX_DATASET_NAME_LEN];
int error;
- const char *origin;
+ const char *origin = NULL;
+ zone_admin_result_t result;
if ((error = zfs_get_parent(zc->zc_name, parentname,
sizeof (parentname))) != 0)
return (error);
- if (nvlist_lookup_string(innvl, "origin", &origin) == 0 &&
+ (void) nvlist_lookup_string(innvl, "origin", &origin);
+
+ /* Check zoned_uid delegation first */
+ result = zone_dataset_admin_check(parentname,
+ origin != NULL ? ZONE_OP_CLONE : ZONE_OP_CREATE, origin);
+ if (result == ZONE_ADMIN_ALLOWED) {
+ if (origin != NULL) {
+ if ((error = zfs_secpolicy_zoned_uid_deleg(origin,
+ ZFS_DELEG_PERM_CLONE, cr)) != 0)
+ return (error);
+ }
+ if ((error = zfs_secpolicy_zoned_uid_deleg(parentname,
+ ZFS_DELEG_PERM_CREATE, cr)) != 0)
+ return (error);
+ return (zfs_secpolicy_zoned_uid_deleg(parentname,
+ ZFS_DELEG_PERM_MOUNT, cr));
+ }
+ if (result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
+
+ /* NOT_APPLICABLE: continue with existing checks */
+ if (origin != NULL &&
(error = zfs_secpolicy_write_perms(origin,
ZFS_DELEG_PERM_CLONE, cr)) != 0)
return (error);
@@ -1131,6 +1315,14 @@ zfs_secpolicy_inherit_prop(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
if (prop == ZPROP_USERPROP) {
if (!zfs_prop_user(zc->zc_value))
return (SET_ERROR(EINVAL));
+ zone_admin_result_t zone_result;
+ zone_result = zone_dataset_admin_check(zc->zc_name,
+ ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_ALLOWED)
+ return (zfs_secpolicy_zoned_uid_deleg(zc->zc_name,
+ ZFS_DELEG_PERM_USERPROP, cr));
+ if (zone_result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
return (zfs_secpolicy_write_perms(zc->zc_name,
ZFS_DELEG_PERM_USERPROP, cr));
} else {
@@ -2707,6 +2899,28 @@ zfs_prop_set_special(const char *dsname, zprop_source_t source,
zfsvfs_rele(zfsvfs, FTAG);
break;
}
+ case ZFS_PROP_ZONED_UID:
+ {
+ uint64_t old_uid = 0;
+ (void) dsl_prop_get(dsname, "zoned_uid", 8, 1, &old_uid, NULL);
+ if (old_uid != 0)
+ (void) zone_dataset_detach_uid(CRED(), dsname,
+ (uid_t)old_uid);
+ if (intval != 0) {
+ err = zone_dataset_attach_uid(CRED(), dsname,
+ (uid_t)intval);
+ if (err == ENXIO)
+ err = ZFS_ERR_NO_USER_NS_SUPPORT;
+ if (err != 0)
+ break;
+ }
+ /*
+ * Set err to -1 to force the zfs_set_prop_nvlist code down the
+ * default path to set the value in the nvlist.
+ */
+ err = -1;
+ break;
+ }
default:
err = -1;
}
@@ -3850,8 +4064,20 @@ zfs_ioc_snapshot(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
*/
if (!nvlist_empty(props)) {
*cp = '\0';
- error = zfs_secpolicy_write_perms(name,
- ZFS_DELEG_PERM_USERPROP, CRED());
+ zone_admin_result_t zone_result;
+ zone_result = zone_dataset_admin_check(name,
+ ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_DENIED) {
+ *cp = '@';
+ return (SET_ERROR(EPERM));
+ }
+ if (zone_result == ZONE_ADMIN_ALLOWED) {
+ error = zfs_secpolicy_zoned_uid_deleg(name,
+ ZFS_DELEG_PERM_USERPROP, CRED());
+ } else {
+ error = zfs_secpolicy_write_perms(name,
+ ZFS_DELEG_PERM_USERPROP, CRED());
+ }
*cp = '@';
if (error != 0)
return (error);
@@ -4333,6 +4559,14 @@ zfs_ioc_destroy(zfs_cmd_t *zc)
if (strchr(zc->zc_name, '@')) {
err = dsl_destroy_snapshot(zc->zc_name, zc->zc_defer_destroy);
} else {
+ /*
+ * Save zoned_uid before destroying so we can clean up
+ * kernel-side zone tracking after a successful destroy.
+ */
+ uint64_t zoned_uid = 0;
+ (void) dsl_prop_get(zc->zc_name, "zoned_uid",
+ 8, 1, &zoned_uid, NULL);
+
err = dsl_destroy_head(zc->zc_name);
if (err == EEXIST) {
/*
@@ -4362,6 +4596,11 @@ zfs_ioc_destroy(zfs_cmd_t *zc)
else if (err == ENOENT)
err = SET_ERROR(EEXIST);
}
+
+ if (err == 0 && zoned_uid != 0) {
+ (void) zone_dataset_detach_uid(kcred,
+ zc->zc_name, (uid_t)zoned_uid);
+ }
}
return (err);
@@ -4859,7 +5098,24 @@ zfs_ioc_rename(zfs_cmd_t *zc)
return (error);
} else {
- return (dsl_dir_rename(zc->zc_name, zc->zc_value));
+ /*
+ * For dataset renames, update kernel-side zone tracking
+ * if the dataset has a zoned_uid delegation. Read the
+ * property before rename, then detach old / attach new.
+ */
+ uint64_t zoned_uid = 0;
+ (void) dsl_prop_get(zc->zc_name, "zoned_uid",
+ 8, 1, &zoned_uid, NULL);
+
+ err = dsl_dir_rename(zc->zc_name, zc->zc_value);
+
+ if (err == 0 && zoned_uid != 0) {
+ (void) zone_dataset_detach_uid(kcred,
+ zc->zc_name, (uid_t)zoned_uid);
+ (void) zone_dataset_attach_uid(kcred,
+ zc->zc_value, (uid_t)zoned_uid);
+ }
+ return (err);
}
}
@@ -4874,6 +5130,14 @@ zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr)
if (prop == ZPROP_USERPROP) {
if (zfs_prop_user(propname)) {
+ zone_admin_result_t zone_result;
+ zone_result = zone_dataset_admin_check(dsname,
+ ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_ALLOWED)
+ return (zfs_secpolicy_zoned_uid_deleg(dsname,
+ ZFS_DELEG_PERM_USERPROP, cr));
+ if (zone_result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
if ((err = zfs_secpolicy_write_perms(dsname,
ZFS_DELEG_PERM_USERPROP, cr)))
return (err);
@@ -4918,6 +5182,14 @@ zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr)
return (SET_ERROR(EINVAL));
}
+ zone_admin_result_t zone_result;
+ zone_result = zone_dataset_admin_check(dsname,
+ ZONE_OP_SETPROP, NULL);
+ if (zone_result == ZONE_ADMIN_ALLOWED)
+ return (zfs_secpolicy_zoned_uid_deleg(dsname,
+ perm, cr));
+ if (zone_result == ZONE_ADMIN_DENIED)
+ return (SET_ERROR(EPERM));
if ((err = zfs_secpolicy_write_perms(dsname, perm, cr)))
return (err);
return (0);
@@ -8267,6 +8539,9 @@ zfs_kmod_init(void)
zfs_ioctl_init();
+ /* Register zoned_uid property lookup callback with SPL */
+ zone_register_zoned_uid_callback(zfs_get_zoned_uid);
+
mutex_init(&zfsdev_state_lock, NULL, MUTEX_DEFAULT, NULL);
zfsdev_state_listhead.zs_minor = -1;
@@ -8305,6 +8580,10 @@ zfs_kmod_fini(void)
}
zfs_ereport_taskq_fini(); /* run before zfs_fini() on Linux */
+
+ /* Unregister zoned_uid callback before ZFS layer is torn down */
+ zone_unregister_zoned_uid_callback();
+
zfs_fini();
spa_fini();
zvol_fini();
diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run
index 391609332f4..7a079c17edc 100644
--- a/tests/runfiles/common.run
+++ b/tests/runfiles/common.run
@@ -1103,6 +1103,22 @@ tests = ['xattr_001_pos', 'xattr_002_neg', 'xattr_003_neg', 'xattr_004_pos',
'xattr_compat']
tags = ['functional', 'xattr']
+[tests/functional/zoned_uid:Linux]
+tests = ['zoned_uid_001_pos', 'zoned_uid_002_pos', 'zoned_uid_003_pos',
+ 'zoned_uid_004_pos', 'zoned_uid_005_neg', 'zoned_uid_006_pos',
+ 'zoned_uid_007_pos', 'zoned_uid_008_pos', 'zoned_uid_009_pos',
+ 'zoned_uid_010_pos', 'zoned_uid_011_neg', 'zoned_uid_012_pos',
+ 'zoned_uid_013_pos', 'zoned_uid_014_pos',
+ 'zoned_uid_015_pos', 'zoned_uid_016_pos', 'zoned_uid_017_neg',
+ 'zoned_uid_018_pos', 'zoned_uid_019_neg', 'zoned_uid_020_neg',
+ 'zoned_uid_021_neg', 'zoned_uid_022_neg',
+ 'zoned_uid_030_pos',
+ 'zoned_uid_023_pos', 'zoned_uid_024_neg',
+ 'zoned_uid_025_pos', 'zoned_uid_026_pos',
+ 'zoned_uid_027_pos', 'zoned_uid_028_neg',
+ 'zoned_uid_029_neg', 'zoned_uid_031_pos']
+tags = ['functional', 'zoned_uid']
+
[tests/functional/zvol/zvol_ENOSPC]
tests = ['zvol_ENOSPC_001_pos']
tags = ['functional', 'zvol', 'zvol_ENOSPC']
diff --git a/tests/test-runner/bin/zts-report.py.in b/tests/test-runner/bin/zts-report.py.in
index cc30b06a158..5006b3d4d90 100755
--- a/tests/test-runner/bin/zts-report.py.in
+++ b/tests/test-runner/bin/zts-report.py.in
@@ -172,6 +172,7 @@ if sys.platform.startswith('freebsd'):
['FAIL', known_reason],
'cli_root/zpool_resilver/zpool_resilver_concurrent':
['SKIP', na_reason],
+ 'zoned_uid/setup': ['SKIP', na_reason],
'cli_root/zpool_wait/zpool_wait_trim_basic': ['SKIP', trim_reason],
'cli_root/zpool_wait/zpool_wait_trim_cancel': ['SKIP', trim_reason],
'cli_root/zpool_wait/zpool_wait_trim_flag': ['SKIP', trim_reason],
@@ -367,6 +368,7 @@ elif sys.platform.startswith('linux'):
'limits/filesystem_limit': ['SKIP', known_reason],
'limits/snapshot_limit': ['SKIP', known_reason],
'stat/statx_dioalign': ['SKIP', 'statx_reason'],
+ 'zoned_uid/setup': ['SKIP', user_ns_reason],
})
diff --git a/tests/zfs-tests/include/commands.cfg b/tests/zfs-tests/include/commands.cfg
index 4ba9aa7c8b6..ed3e9250ae5 100644
--- a/tests/zfs-tests/include/commands.cfg
+++ b/tests/zfs-tests/include/commands.cfg
@@ -129,6 +129,7 @@ export SYSTEM_FILES_LINUX='attr
blkid
blkdiscard
blockdev
+ capsh
chattr
cryptsetup
exportfs
diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am
index 6c2f7aa7782..7f8f2eac8b8 100644
--- a/tests/zfs-tests/tests/Makefile.am
+++ b/tests/zfs-tests/tests/Makefile.am
@@ -393,6 +393,8 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \
functional/vdev_zaps/vdev_zaps.kshlib \
functional/xattr/xattr.cfg \
functional/xattr/xattr_common.kshlib \
+ functional/zoned_uid/zoned_uid.cfg \
+ functional/zoned_uid/zoned_uid_common.kshlib \
functional/zvol/zvol.cfg \
functional/zvol/zvol_cli/zvol_cli.cfg \
functional/zvol/zvol_common.shlib \
@@ -2267,6 +2269,39 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/xattr/xattr_013_pos.ksh \
functional/xattr/xattr_014_pos.ksh \
functional/xattr/xattr_compat.ksh \
+ functional/zoned_uid/cleanup.ksh \
+ functional/zoned_uid/setup.ksh \
+ functional/zoned_uid/zoned_uid_001_pos.ksh \
+ functional/zoned_uid/zoned_uid_002_pos.ksh \
+ functional/zoned_uid/zoned_uid_003_pos.ksh \
+ functional/zoned_uid/zoned_uid_004_pos.ksh \
+ functional/zoned_uid/zoned_uid_005_neg.ksh \
+ functional/zoned_uid/zoned_uid_006_pos.ksh \
+ functional/zoned_uid/zoned_uid_007_pos.ksh \
+ functional/zoned_uid/zoned_uid_008_pos.ksh \
+ functional/zoned_uid/zoned_uid_009_pos.ksh \
+ functional/zoned_uid/zoned_uid_010_pos.ksh \
+ functional/zoned_uid/zoned_uid_011_neg.ksh \
+ functional/zoned_uid/zoned_uid_012_pos.ksh \
+ functional/zoned_uid/zoned_uid_013_pos.ksh \
+ functional/zoned_uid/zoned_uid_014_pos.ksh \
+ functional/zoned_uid/zoned_uid_015_pos.ksh \
+ functional/zoned_uid/zoned_uid_016_pos.ksh \
+ functional/zoned_uid/zoned_uid_017_neg.ksh \
+ functional/zoned_uid/zoned_uid_018_pos.ksh \
+ functional/zoned_uid/zoned_uid_019_neg.ksh \
+ functional/zoned_uid/zoned_uid_020_neg.ksh \
+ functional/zoned_uid/zoned_uid_021_neg.ksh \
+ functional/zoned_uid/zoned_uid_022_neg.ksh \
+ functional/zoned_uid/zoned_uid_023_pos.ksh \
+ functional/zoned_uid/zoned_uid_024_neg.ksh \
+ functional/zoned_uid/zoned_uid_025_pos.ksh \
+ functional/zoned_uid/zoned_uid_026_pos.ksh \
+ functional/zoned_uid/zoned_uid_027_pos.ksh \
+ functional/zoned_uid/zoned_uid_028_neg.ksh \
+ functional/zoned_uid/zoned_uid_029_neg.ksh \
+ functional/zoned_uid/zoned_uid_030_pos.ksh \
+ functional/zoned_uid/zoned_uid_031_pos.ksh \
functional/zap_shrink/cleanup.ksh \
functional/zap_shrink/zap_shrink_001_pos.ksh \
functional/zap_shrink/setup.ksh \
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/cleanup.ksh b/tests/zfs-tests/tests/functional/zoned_uid/cleanup.ksh
new file mode 100755
index 00000000000..c611e5a4d03
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/cleanup.ksh
@@ -0,0 +1,46 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid.cfg
+
+# Restore AppArmor user namespace restriction if we relaxed it
+APPARMOR_USERNS=/proc/sys/kernel/apparmor_restrict_unprivileged_userns
+APPARMOR_RESTORE=/tmp/zoned_uid_apparmor_restore
+if [ -f "$APPARMOR_RESTORE" ]; then
+ cat "$APPARMOR_RESTORE" > "$APPARMOR_USERNS"
+ rm -f "$APPARMOR_RESTORE"
+fi
+
+# Remove test users created during setup
+for uid in "$ZONED_TEST_UID" "$ZONED_OTHER_UID"; do
+ if id "zfs_test_$uid" >/dev/null 2>&1; then
+ userdel "zfs_test_$uid" 2>/dev/null
+ fi
+done
+
+default_cleanup
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/setup.ksh b/tests/zfs-tests/tests/functional/zoned_uid/setup.ksh
new file mode 100755
index 00000000000..3345a5981a3
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/setup.ksh
@@ -0,0 +1,99 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+# Only run on Linux - zoned_uid is Linux-specific
+if ! is_linux; then
+ log_unsupported "zoned_uid is only supported on Linux"
+fi
+
+# Check kernel supports user namespaces
+if ! [ -f /proc/self/uid_map ]; then
+ log_unsupported "The kernel doesn't support user namespaces."
+fi
+
+verify_runnable "global"
+
+DISK=${DISKS%% *}
+default_setup_noexit $DISK
+
+# Check if zoned_uid property is supported (requires pool to exist)
+if ! zoned_uid_supported; then
+ default_cleanup_noexit
+ log_unsupported "zoned_uid property not supported by this kernel"
+fi
+
+#
+# Provision test users if they don't exist.
+# Tests use "sudo -u #" which requires the UID to have a passwd entry.
+# CI environments (e.g. GitHub Actions QEMU VMs) typically don't have these.
+#
+for uid in "$ZONED_TEST_UID" "$ZONED_OTHER_UID"; do
+ if ! id "$uid" >/dev/null 2>&1; then
+ log_note "Creating test user for UID $uid"
+ log_must useradd -u "$uid" -M -N -s /usr/sbin/nologin \
+ "zfs_test_$uid"
+ fi
+done
+
+# Some environments (e.g., Ubuntu with AppArmor) restrict unprivileged
+# user namespace creation. Try to relax the restriction for testing.
+APPARMOR_USERNS=/proc/sys/kernel/apparmor_restrict_unprivileged_userns
+APPARMOR_RESTORE=/tmp/zoned_uid_apparmor_restore
+if [ -f "$APPARMOR_USERNS" ]; then
+ orig=$(cat "$APPARMOR_USERNS")
+ if [ "$orig" != "0" ]; then
+ echo "$orig" > "$APPARMOR_RESTORE"
+ echo 0 > "$APPARMOR_USERNS"
+ log_note "Relaxed AppArmor user namespace restriction for testing"
+ fi
+fi
+
+# Verify user namespace creation works with the test UIDs.
+if ! sudo -u \#${ZONED_TEST_UID} unshare --user --map-root-user \
+ true 2>/dev/null; then
+ default_cleanup_noexit
+ log_unsupported "Cannot create user namespaces as UID $ZONED_TEST_UID"
+fi
+
+# Verify capsh is available and works for capability control tests.
+# Tests 023+ use run_in_userns_caps which requires capsh.
+typeset _capsh_found
+_capsh_found="$(which capsh)"
+if [[ -z "$_capsh_found" ]]; then
+ log_note "WARNING: capsh not found; capability-tier tests will be skipped"
+else
+ if ! verify_capsh_works; then
+ log_note "WARNING: capsh cap control broken; capability-tier tests may fail"
+ else
+ log_note "capsh capability control verified"
+ fi
+fi
+
+log_pass
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid.cfg b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid.cfg
new file mode 100644
index 00000000000..e3a98d38e96
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid.cfg
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+# Test UID for zoned_uid - the user namespace owner's UID
+# On this system, the "container" user (UID 956) owns subuid range 100000-165535
+# The zoned_uid should match the user who will create user namespaces
+export ZONED_TEST_UID=${ZONED_TEST_UID:-956}
+
+# A different UID to test non-matching case (colin)
+export ZONED_OTHER_UID=${ZONED_OTHER_UID:-1000}
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_001_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_001_pos.ksh
new file mode 100755
index 00000000000..775baf188bc
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_001_pos.ksh
@@ -0,0 +1,85 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that the zoned_uid property can be set and retrieved.
+#
+# STRATEGY:
+# 1. Verify default zoned_uid is 0 (none)
+# 2. Set zoned_uid to a test UID
+# 3. Verify the property value is correct
+# 4. Clear zoned_uid (set to 0)
+# 5. Verify it returns to 0
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ log_must zfs destroy -rf "$TESTPOOL/$TESTFS/zoned_test"
+}
+
+log_assert "zoned_uid property can be set and retrieved"
+log_onexit cleanup
+
+# Create test dataset
+log_must zfs create "$TESTPOOL/$TESTFS/zoned_test"
+
+# Verify default is 0
+typeset default_val
+default_val=$(get_zoned_uid "$TESTPOOL/$TESTFS/zoned_test")
+if [[ "$default_val" != "0" ]]; then
+ log_fail "Default zoned_uid should be 0, got: $default_val"
+fi
+log_note "Default zoned_uid is 0 as expected"
+
+# Set zoned_uid
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/zoned_test" "$ZONED_TEST_UID"
+
+# Verify the value
+typeset set_val
+set_val=$(get_zoned_uid "$TESTPOOL/$TESTFS/zoned_test")
+if [[ "$set_val" != "$ZONED_TEST_UID" ]]; then
+ log_fail "zoned_uid should be $ZONED_TEST_UID, got: $set_val"
+fi
+log_note "zoned_uid set to $ZONED_TEST_UID successfully"
+
+# Clear zoned_uid
+log_must clear_zoned_uid "$TESTPOOL/$TESTFS/zoned_test"
+
+# Verify it's back to 0
+typeset cleared_val
+cleared_val=$(get_zoned_uid "$TESTPOOL/$TESTFS/zoned_test")
+if [[ "$cleared_val" != "0" ]]; then
+ log_fail "Cleared zoned_uid should be 0, got: $cleared_val"
+fi
+log_note "zoned_uid cleared to 0 successfully"
+
+log_pass "zoned_uid property can be set and retrieved"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_002_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_002_pos.ksh
new file mode 100755
index 00000000000..51cd5be3638
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_002_pos.ksh
@@ -0,0 +1,83 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that zoned_uid property persists through pool export/import.
+#
+# STRATEGY:
+# 1. Create a test dataset
+# 2. Set zoned_uid property
+# 3. Export the pool
+# 4. Import the pool
+# 5. Verify zoned_uid property is preserved
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -f "$TESTPOOL/$TESTFS/persist_test" 2>/dev/null
+}
+
+log_assert "zoned_uid property persists through pool export/import"
+log_onexit cleanup
+
+# Create test dataset
+log_must zfs create "$TESTPOOL/$TESTFS/persist_test"
+
+# Set zoned_uid
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/persist_test" "$ZONED_TEST_UID"
+
+# Verify before export
+typeset before_val
+before_val=$(get_zoned_uid "$TESTPOOL/$TESTFS/persist_test")
+if [[ "$before_val" != "$ZONED_TEST_UID" ]]; then
+ log_fail "Before export: zoned_uid should be $ZONED_TEST_UID, got: $before_val"
+fi
+log_note "zoned_uid is $ZONED_TEST_UID before export"
+
+# Export the pool
+log_must zpool export "$TESTPOOL"
+
+# Import the pool
+log_must zpool import "$TESTPOOL"
+
+# Verify after import
+typeset after_val
+after_val=$(get_zoned_uid "$TESTPOOL/$TESTFS/persist_test")
+if [[ "$after_val" != "$ZONED_TEST_UID" ]]; then
+ log_fail "After import: zoned_uid should be $ZONED_TEST_UID, got: $after_val"
+fi
+log_note "zoned_uid is $ZONED_TEST_UID after import"
+
+# Cleanup
+log_must zfs destroy "$TESTPOOL/$TESTFS/persist_test"
+
+log_pass "zoned_uid property persists through pool export/import"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_003_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_003_pos.ksh
new file mode 100755
index 00000000000..8be7d5cc092
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_003_pos.ksh
@@ -0,0 +1,100 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that setting zoned_uid property does not break normal
+# dataset operations from the global zone.
+#
+# STRATEGY:
+# 1. Create a test dataset with zoned_uid set
+# 2. Verify dataset is still visible and accessible from global zone
+# 3. Create a child dataset
+# 4. Verify child dataset operations work
+# 5. Verify the property is shown in zfs list output
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ log_must zfs destroy -rf "$TESTPOOL/$TESTFS/zoned_test"
+}
+
+log_assert "zoned_uid property does not break global zone operations"
+log_onexit cleanup
+
+# Create test dataset with zoned_uid
+log_must zfs create "$TESTPOOL/$TESTFS/zoned_test"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/zoned_test" "$ZONED_TEST_UID"
+
+# Verify dataset is visible
+log_must zfs list "$TESTPOOL/$TESTFS/zoned_test"
+log_note "Dataset is visible from global zone"
+
+# Verify we can get properties
+log_must zfs get all "$TESTPOOL/$TESTFS/zoned_test"
+log_note "Can retrieve properties from global zone"
+
+# Verify zoned_uid appears in output
+typeset list_output
+list_output=$(zfs get -H -o property,value all "$TESTPOOL/$TESTFS/zoned_test" | grep zoned_uid)
+if [[ -z "$list_output" ]]; then
+ log_fail "zoned_uid not shown in property listing"
+fi
+log_note "zoned_uid appears in property listing: $list_output"
+
+# Create child dataset
+log_must zfs create "$TESTPOOL/$TESTFS/zoned_test/child"
+log_note "Can create child dataset"
+
+# Verify child is visible
+log_must zfs list "$TESTPOOL/$TESTFS/zoned_test/child"
+log_note "Child dataset is visible"
+
+# Write data to the dataset
+typeset mntpt
+mntpt=$(get_prop mountpoint "$TESTPOOL/$TESTFS/zoned_test")
+log_must touch "$mntpt/testfile"
+log_must echo "test data" > "$mntpt/testfile"
+log_note "Can write data to dataset"
+
+# Read data back
+log_must cat "$mntpt/testfile"
+log_note "Can read data from dataset"
+
+# Take a snapshot
+log_must zfs snapshot "$TESTPOOL/$TESTFS/zoned_test@snap1"
+log_note "Can create snapshot"
+
+# List snapshots
+log_must zfs list -t snapshot "$TESTPOOL/$TESTFS/zoned_test@snap1"
+log_note "Snapshot is visible"
+
+log_pass "zoned_uid property does not break global zone operations"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_004_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_004_pos.ksh
new file mode 100755
index 00000000000..3692f9df5d0
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_004_pos.ksh
@@ -0,0 +1,91 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that zoned_uid property is inherited by child datasets
+# and can be overridden with a different value.
+#
+# STRATEGY:
+# 1. Create parent dataset with zoned_uid
+# 2. Create child dataset
+# 3. Verify child inherits parent's zoned_uid value
+# 4. Override zoned_uid on child with a different value
+# 5. Verify each dataset has its own value
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ log_must zfs destroy -rf "$TESTPOOL/$TESTFS/parent"
+}
+
+log_assert "zoned_uid property is inherited by child datasets"
+log_onexit cleanup
+
+# Create parent dataset with zoned_uid
+log_must zfs create "$TESTPOOL/$TESTFS/parent"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/parent" "$ZONED_TEST_UID"
+
+# Create child dataset
+log_must zfs create "$TESTPOOL/$TESTFS/parent/child"
+
+# Verify child inherits parent's value
+typeset child_val
+child_val=$(get_zoned_uid "$TESTPOOL/$TESTFS/parent/child")
+if [[ "$child_val" != "$ZONED_TEST_UID" ]]; then
+ log_fail "Child zoned_uid should inherit $ZONED_TEST_UID, got: $child_val"
+fi
+log_note "Child dataset inherits zoned_uid=$ZONED_TEST_UID from parent"
+
+# Verify parent still has its value
+typeset parent_val
+parent_val=$(get_zoned_uid "$TESTPOOL/$TESTFS/parent")
+if [[ "$parent_val" != "$ZONED_TEST_UID" ]]; then
+ log_fail "Parent zoned_uid should be $ZONED_TEST_UID, got: $parent_val"
+fi
+log_note "Parent dataset retains zoned_uid=$ZONED_TEST_UID"
+
+# Override with different value on child
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/parent/child" "$ZONED_OTHER_UID"
+
+# Verify each has independent value
+parent_val=$(get_zoned_uid "$TESTPOOL/$TESTFS/parent")
+child_val=$(get_zoned_uid "$TESTPOOL/$TESTFS/parent/child")
+
+if [[ "$parent_val" != "$ZONED_TEST_UID" ]]; then
+ log_fail "Parent zoned_uid changed unexpectedly to: $parent_val"
+fi
+if [[ "$child_val" != "$ZONED_OTHER_UID" ]]; then
+ log_fail "Child zoned_uid should be $ZONED_OTHER_UID, got: $child_val"
+fi
+log_note "Parent and child have independent zoned_uid values after override"
+
+log_pass "zoned_uid property is inherited by child datasets"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_005_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_005_neg.ksh
new file mode 100755
index 00000000000..5cc0577ba67
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_005_neg.ksh
@@ -0,0 +1,72 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that invalid zoned_uid values are rejected.
+#
+# STRATEGY:
+# 1. Try to set zoned_uid with invalid string value
+# 2. Verify it fails
+# 3. Try to set zoned_uid with negative value
+# 4. Verify it fails
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ if datasetexists "$TESTPOOL/$TESTFS/neg_test"; then
+ log_must zfs destroy -rf "$TESTPOOL/$TESTFS/neg_test"
+ fi
+}
+
+log_assert "Invalid zoned_uid values are rejected"
+log_onexit cleanup
+
+# Create test dataset
+log_must zfs create "$TESTPOOL/$TESTFS/neg_test"
+
+# Try invalid string value
+log_mustnot zfs set zoned_uid=invalid "$TESTPOOL/$TESTFS/neg_test"
+log_note "Invalid string value rejected"
+
+# Try negative value (if shell allows it)
+log_mustnot zfs set zoned_uid=-1 "$TESTPOOL/$TESTFS/neg_test"
+log_note "Negative value rejected"
+
+# Verify dataset still has default value
+typeset val
+val=$(get_zoned_uid "$TESTPOOL/$TESTFS/neg_test")
+if [[ "$val" != "0" ]]; then
+ log_fail "zoned_uid should still be 0 after failed sets, got: $val"
+fi
+log_note "zoned_uid unchanged after invalid set attempts"
+
+log_pass "Invalid zoned_uid values are rejected"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_006_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_006_pos.ksh
new file mode 100755
index 00000000000..3322515edd4
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_006_pos.ksh
@@ -0,0 +1,109 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that an authorized user namespace can create child datasets
+# under a delegation root with matching zoned_uid.
+#
+# STRATEGY:
+# 1. Create a test dataset and set zoned_uid to test UID
+# 2. Enter a user namespace owned by that UID
+# 3. Verify CAP_SYS_ADMIN is present in the namespace
+# 4. Attempt to create a child dataset
+# 5. Verify the child dataset was created successfully
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ # Clean up from global zone
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Authorized user namespace can create child datasets"
+log_onexit cleanup
+
+# Create delegation root and set zoned_uid
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ create,mount
+
+# Verify zoned_uid is set
+typeset actual_uid
+actual_uid=$(get_zoned_uid "$TESTPOOL/$TESTFS/deleg_root")
+if [[ "$actual_uid" != "$ZONED_TEST_UID" ]]; then
+ log_fail "zoned_uid not set correctly: expected $ZONED_TEST_UID, got $actual_uid"
+fi
+log_note "Delegation root created with zoned_uid=$ZONED_TEST_UID"
+
+#
+# Enter user namespace and attempt to create child dataset.
+# unshare --user creates a new user namespace where the caller
+# has CAP_SYS_ADMIN (and all other capabilities) within that namespace.
+#
+# The --map-user option maps the current user to root inside the namespace,
+# which is the standard rootless container setup.
+#
+log_note "Attempting to create child dataset from user namespace..."
+
+# Use sudo -u to run as the zoned_uid owner, then unshare into user namespace
+# The user namespace owner will be ZONED_TEST_UID
+typeset create_result
+create_result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+create_status=$?
+
+if [[ $create_status -ne 0 ]]; then
+ log_note "Create output: $create_result"
+ log_fail "Failed to create child dataset from user namespace (status=$create_status)"
+fi
+
+log_note "Child dataset created successfully from user namespace"
+
+# Verify the child exists (from global zone)
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child"
+log_note "Child dataset verified from global zone"
+
+# Verify the child is visible from the user namespace
+typeset list_result
+list_result=$(run_in_userns "$ZONED_TEST_UID" \
+ list "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+list_status=$?
+
+if [[ $list_status -ne 0 ]]; then
+ log_note "List output: $list_result"
+ log_fail "Child dataset not visible from user namespace"
+fi
+
+log_note "Child dataset visible from user namespace"
+
+log_pass "Authorized user namespace can create child datasets"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_007_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_007_pos.ksh
new file mode 100755
index 00000000000..64de7663a5b
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_007_pos.ksh
@@ -0,0 +1,110 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that an authorized user namespace can create snapshots
+# of datasets under the delegation root.
+#
+# STRATEGY:
+# 1. Create a delegation root with zoned_uid set
+# 2. Create a child dataset (from global zone for setup)
+# 3. Enter user namespace owned by the zoned_uid
+# 4. Create a snapshot from within the user namespace
+# 5. Verify the snapshot was created successfully
+# 6. Verify the snapshot is visible from both namespaces
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Authorized user namespace can create snapshots"
+log_onexit cleanup
+
+# Create delegation root and child dataset
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ snapshot
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+
+log_note "Delegation root created with zoned_uid=$ZONED_TEST_UID"
+
+# Enter user namespace and create snapshot
+log_note "Attempting to create snapshot from user namespace..."
+
+typeset snap_result
+snap_result=$(run_in_userns "$ZONED_TEST_UID" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root/child@snap1" 2>&1)
+typeset snap_status=$?
+
+if [[ $snap_status -ne 0 ]]; then
+ log_note "Snapshot output: $snap_result"
+ log_fail "Failed to create snapshot from user namespace (status=$snap_status)"
+fi
+
+log_note "Snapshot created successfully from user namespace"
+
+# Verify snapshot exists from global zone
+log_must zfs list -t snapshot "$TESTPOOL/$TESTFS/deleg_root/child@snap1"
+log_note "Snapshot verified from global zone"
+
+# Verify snapshot is visible from user namespace
+typeset list_result
+list_result=$(run_in_userns "$ZONED_TEST_UID" \
+ list -t snapshot "$TESTPOOL/$TESTFS/deleg_root/child@snap1" 2>&1)
+typeset list_status=$?
+
+if [[ $list_status -ne 0 ]]; then
+ log_note "List output: $list_result"
+ log_fail "Snapshot not visible from user namespace"
+fi
+
+log_note "Snapshot visible from user namespace"
+
+# Also test snapshot of the delegation root itself
+log_note "Testing snapshot of delegation root..."
+typeset root_snap_result
+root_snap_result=$(run_in_userns "$ZONED_TEST_UID" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root@rootsnap" 2>&1)
+typeset root_snap_status=$?
+
+if [[ $root_snap_status -ne 0 ]]; then
+ log_note "Root snapshot output: $root_snap_result"
+ log_fail "Failed to snapshot delegation root from user namespace"
+fi
+
+log_must zfs list -t snapshot "$TESTPOOL/$TESTFS/deleg_root@rootsnap"
+log_note "Delegation root snapshot created successfully"
+
+log_pass "Authorized user namespace can create snapshots"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_008_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_008_pos.ksh
new file mode 100755
index 00000000000..fa525166559
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_008_pos.ksh
@@ -0,0 +1,128 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that an authorized user namespace can destroy child datasets
+# and snapshots, but cannot destroy the delegation root itself.
+#
+# STRATEGY:
+# 1. Create a delegation root with zoned_uid set
+# 2. Create child datasets and snapshots
+# 3. Enter user namespace and destroy a snapshot (should succeed)
+# 4. Destroy a child dataset (should succeed)
+# 5. Attempt to destroy the delegation root (should fail - protected)
+# 6. Verify the delegation root still exists
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Authorized user namespace can destroy children but not delegation root"
+log_onexit cleanup
+
+# Create delegation root with children
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ destroy,mount
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child1"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child2"
+log_must zfs snapshot "$TESTPOOL/$TESTFS/deleg_root/child1@snap1"
+
+log_note "Created delegation root with children and snapshot"
+
+# Unmount child datasets from global zone before entering user namespace.
+# Mounts inherited from the parent mount namespace are MNT_LOCKED by the
+# kernel and cannot be unmounted from a child mount namespace.
+log_must zfs unmount "$TESTPOOL/$TESTFS/deleg_root/child1"
+log_must zfs unmount "$TESTPOOL/$TESTFS/deleg_root/child2"
+
+# Test 1: Destroy snapshot from user namespace (should succeed)
+log_note "Test 1: Destroying snapshot from user namespace..."
+typeset snap_result
+snap_result=$(run_in_userns "$ZONED_TEST_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/child1@snap1" 2>&1)
+typeset snap_status=$?
+
+if [[ $snap_status -ne 0 ]]; then
+ log_note "Destroy snapshot output: $snap_result"
+ log_fail "Failed to destroy snapshot from user namespace"
+fi
+
+# Verify snapshot is gone
+if zfs list -t snapshot "$TESTPOOL/$TESTFS/deleg_root/child1@snap1" 2>/dev/null; then
+ log_fail "Snapshot should have been destroyed"
+fi
+log_note "Snapshot destroyed successfully"
+
+# Test 2: Destroy child dataset from user namespace (should succeed)
+log_note "Test 2: Destroying child dataset from user namespace..."
+typeset child_result
+child_result=$(run_in_userns "$ZONED_TEST_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/child1" 2>&1)
+typeset child_status=$?
+
+if [[ $child_status -ne 0 ]]; then
+ log_note "Destroy child output: $child_result"
+ log_fail "Failed to destroy child dataset from user namespace"
+fi
+
+# Verify child is gone
+if zfs list "$TESTPOOL/$TESTFS/deleg_root/child1" 2>/dev/null; then
+ log_fail "Child dataset should have been destroyed"
+fi
+log_note "Child dataset destroyed successfully"
+
+# Test 3: Attempt to destroy delegation root (should FAIL - protected)
+log_note "Test 3: Attempting to destroy delegation root (should fail)..."
+typeset root_result
+root_result=$(run_in_userns "$ZONED_TEST_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+typeset root_status=$?
+
+if [[ $root_status -eq 0 ]]; then
+ log_fail "Destroying delegation root should have been denied"
+fi
+
+log_note "Delegation root destruction correctly denied: $root_result"
+
+# Verify delegation root still exists
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root"
+log_note "Delegation root still exists (protected)"
+
+# Verify remaining child still exists
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child2"
+log_note "Remaining child dataset unaffected"
+
+log_pass "Authorized user namespace can destroy children but not delegation root"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_009_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_009_pos.ksh
new file mode 100755
index 00000000000..4fd66d5bbce
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_009_pos.ksh
@@ -0,0 +1,149 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that an authorized user namespace can rename datasets within
+# the delegation subtree, but cannot rename datasets outside of it.
+#
+# STRATEGY:
+# 1. Create a delegation root with zoned_uid set
+# 2. Create child datasets
+# 3. Enter user namespace and rename within the subtree (should succeed)
+# 4. Attempt to rename outside the subtree (should fail)
+# 5. Verify the rename operations behaved correctly
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+ zfs destroy -rf "$TESTPOOL/$TESTFS/outside" 2>/dev/null
+}
+
+log_assert "Authorized user namespace can rename within delegation subtree only"
+log_onexit cleanup
+
+# Create delegation root with children
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ rename,mount,create
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child1"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/subdir"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/subdir/nested"
+
+# Create a dataset outside the delegation root (for escape test)
+log_must zfs create "$TESTPOOL/$TESTFS/outside"
+
+log_note "Created delegation root with children and outside dataset"
+
+# Unmount datasets from global zone before entering user namespace.
+# Mounts inherited from the parent mount namespace are MNT_LOCKED by the
+# kernel and cannot be unmounted from a child mount namespace.
+log_must zfs unmount "$TESTPOOL/$TESTFS/deleg_root/subdir/nested"
+log_must zfs unmount "$TESTPOOL/$TESTFS/deleg_root/subdir"
+log_must zfs unmount "$TESTPOOL/$TESTFS/deleg_root/child1"
+log_must zfs unmount "$TESTPOOL/$TESTFS/outside"
+
+# Test 1: Rename within subtree (should succeed)
+log_note "Test 1: Renaming within delegation subtree..."
+typeset rename_result
+rename_result=$(run_in_userns "$ZONED_TEST_UID" \
+ rename "$TESTPOOL/$TESTFS/deleg_root/child1" \
+ "$TESTPOOL/$TESTFS/deleg_root/child1_renamed" 2>&1)
+typeset rename_status=$?
+
+if [[ $rename_status -ne 0 ]]; then
+ log_note "Rename output: $rename_result"
+ log_fail "Failed to rename within delegation subtree"
+fi
+
+# Verify old name is gone and new name exists
+if zfs list "$TESTPOOL/$TESTFS/deleg_root/child1" 2>/dev/null; then
+ log_fail "Old dataset name should not exist after rename"
+fi
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child1_renamed"
+log_note "Rename within subtree succeeded"
+
+# Test 2: Rename to a different location within subtree (should succeed)
+log_note "Test 2: Moving dataset within subtree..."
+typeset move_result
+move_result=$(run_in_userns "$ZONED_TEST_UID" \
+ rename "$TESTPOOL/$TESTFS/deleg_root/subdir/nested" \
+ "$TESTPOOL/$TESTFS/deleg_root/nested_moved" 2>&1)
+typeset move_status=$?
+
+if [[ $move_status -ne 0 ]]; then
+ log_note "Move output: $move_result"
+ log_fail "Failed to move dataset within delegation subtree"
+fi
+
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/nested_moved"
+log_note "Move within subtree succeeded"
+
+# Test 3: Attempt to rename outside the subtree (should FAIL)
+log_note "Test 3: Attempting to rename outside subtree (should fail)..."
+typeset escape_result
+escape_result=$(run_in_userns "$ZONED_TEST_UID" \
+ rename "$TESTPOOL/$TESTFS/deleg_root/child1_renamed" \
+ "$TESTPOOL/$TESTFS/outside/escaped" 2>&1)
+typeset escape_status=$?
+
+if [[ $escape_status -eq 0 ]]; then
+ log_fail "Renaming outside delegation subtree should have been denied"
+fi
+
+log_note "Rename outside subtree correctly denied: $escape_result"
+
+# Verify the dataset is still in its original location
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child1_renamed"
+log_note "Dataset remains in delegation subtree"
+
+# Test 4: Attempt to rename from outside into subtree (should FAIL)
+# This tests that we can't "steal" datasets from outside
+log_note "Test 4: Attempting to rename from outside into subtree (should fail)..."
+typeset steal_result
+steal_result=$(run_in_userns "$ZONED_TEST_UID" \
+ rename "$TESTPOOL/$TESTFS/outside" \
+ "$TESTPOOL/$TESTFS/deleg_root/stolen" 2>&1)
+typeset steal_status=$?
+
+if [[ $steal_status -eq 0 ]]; then
+ log_fail "Renaming from outside into subtree should have been denied"
+fi
+
+log_note "Rename from outside correctly denied: $steal_result"
+
+# Verify outside dataset still exists in original location
+log_must zfs list "$TESTPOOL/$TESTFS/outside"
+log_note "Outside dataset remains in place"
+
+log_pass "Authorized user namespace can rename within delegation subtree only"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_010_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_010_pos.ksh
new file mode 100755
index 00000000000..c5f10048be4
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_010_pos.ksh
@@ -0,0 +1,157 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that an authorized user namespace can set properties on
+# datasets within the delegation subtree.
+#
+# STRATEGY:
+# 1. Create a delegation root with zoned_uid set
+# 2. Create a child dataset
+# 3. Enter user namespace and set various properties
+# 4. Verify properties were set correctly
+# 5. Test setting properties on delegation root itself
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Authorized user namespace can set properties on delegated datasets"
+log_onexit cleanup
+
+# Create delegation root with child
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ quota,compression,atime,userprop
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+
+log_note "Created delegation root with child dataset"
+
+# Test 1: Set quota on child dataset
+log_note "Test 1: Setting quota from user namespace..."
+typeset quota_result
+quota_result=$(run_in_userns "$ZONED_TEST_UID" \
+ set quota=100M "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+quota_status=$?
+
+if [[ $quota_status -ne 0 ]]; then
+ log_note "Set quota output: $quota_result"
+ log_fail "Failed to set quota from user namespace"
+fi
+
+# Verify quota was set
+typeset actual_quota
+actual_quota=$(zfs get -H -o value quota "$TESTPOOL/$TESTFS/deleg_root/child")
+if [[ "$actual_quota" != "100M" ]]; then
+ log_fail "Quota not set correctly: expected 100M, got $actual_quota"
+fi
+log_note "Quota set successfully to 100M"
+
+# Test 2: Set compression on child dataset
+log_note "Test 2: Setting compression from user namespace..."
+typeset comp_result
+comp_result=$(run_in_userns "$ZONED_TEST_UID" \
+ set compression=lz4 "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+comp_status=$?
+
+if [[ $comp_status -ne 0 ]]; then
+ log_note "Set compression output: $comp_result"
+ log_fail "Failed to set compression from user namespace"
+fi
+
+typeset actual_comp
+actual_comp=$(zfs get -H -o value compression "$TESTPOOL/$TESTFS/deleg_root/child")
+if [[ "$actual_comp" != "lz4" ]]; then
+ log_fail "Compression not set correctly: expected lz4, got $actual_comp"
+fi
+log_note "Compression set successfully to lz4"
+
+# Test 3: Set atime on delegation root
+# Unmount delegation root first — setting atime triggers a remount, and
+# inherited mounts are MNT_LOCKED (cannot be remounted from a child mount
+# namespace).
+log_must zfs unmount "$TESTPOOL/$TESTFS/deleg_root"
+log_note "Test 3: Setting atime on delegation root..."
+typeset atime_result
+atime_result=$(run_in_userns "$ZONED_TEST_UID" \
+ set atime=off "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+atime_status=$?
+
+if [[ $atime_status -ne 0 ]]; then
+ log_note "Set atime output: $atime_result"
+ log_fail "Failed to set atime on delegation root"
+fi
+
+typeset actual_atime
+actual_atime=$(zfs get -H -o value atime "$TESTPOOL/$TESTFS/deleg_root")
+if [[ "$actual_atime" != "off" ]]; then
+ log_fail "Atime not set correctly: expected off, got $actual_atime"
+fi
+log_note "Atime set successfully on delegation root"
+
+# Test 4: Set a user property
+log_note "Test 4: Setting user property from user namespace..."
+typeset userprop_result
+userprop_result=$(run_in_userns "$ZONED_TEST_UID" \
+ set com.example:testprop=testvalue "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+userprop_status=$?
+
+if [[ $userprop_status -ne 0 ]]; then
+ log_note "Set user property output: $userprop_result"
+ log_fail "Failed to set user property from user namespace"
+fi
+
+typeset actual_userprop
+actual_userprop=$(zfs get -H -o value com.example:testprop "$TESTPOOL/$TESTFS/deleg_root/child")
+if [[ "$actual_userprop" != "testvalue" ]]; then
+ log_fail "User property not set correctly: expected testvalue, got $actual_userprop"
+fi
+log_note "User property set successfully"
+
+# Test 5: Verify properties are visible from user namespace
+log_note "Test 5: Verifying properties visible from user namespace..."
+typeset get_result
+get_result=$(run_in_userns "$ZONED_TEST_UID" \
+ get quota,compression "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+get_status=$?
+
+if [[ $get_status -ne 0 ]]; then
+ log_note "Get properties output: $get_result"
+ log_fail "Failed to get properties from user namespace"
+fi
+
+log_note "Properties visible from user namespace"
+
+log_pass "Authorized user namespace can set properties on delegated datasets"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_011_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_011_neg.ksh
new file mode 100755
index 00000000000..bc2bbe4a8dd
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_011_neg.ksh
@@ -0,0 +1,153 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that a user namespace with a non-matching UID cannot perform
+# write operations on datasets delegated to a different UID.
+#
+# STRATEGY:
+# 1. Create a delegation root with zoned_uid set to ZONED_TEST_UID
+# 2. Enter a user namespace owned by ZONED_OTHER_UID (different)
+# 3. Verify dataset is visible (read-only path visibility)
+# 4. Attempt to create child dataset (should fail)
+# 5. Attempt to create snapshot (should fail)
+# 6. Attempt to set property (should fail)
+# 7. Attempt to destroy (should fail)
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Unauthorized user namespace cannot perform write operations"
+log_onexit cleanup
+
+# Create delegation root owned by ZONED_TEST_UID
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+
+log_note "Created delegation root with zoned_uid=$ZONED_TEST_UID"
+log_note "Will test access from user namespace owned by $ZONED_OTHER_UID"
+
+# Test 1: Verify dataset visibility (should be visible via parent path)
+# Note: The dataset may or may not be visible depending on implementation
+# The key test is that write operations fail
+log_note "Test 1: Checking visibility from wrong user namespace..."
+typeset list_result
+list_result=$(run_in_userns "$ZONED_OTHER_UID" \
+ list "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+list_status=$?
+log_note "List result (status=$list_status): $list_result"
+
+# Test 2: Attempt to create child dataset (should FAIL)
+log_note "Test 2: Attempting to create child from wrong namespace (should fail)..."
+typeset create_result
+create_result=$(run_in_userns "$ZONED_OTHER_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/unauthorized_child" 2>&1)
+create_status=$?
+
+if [[ $create_status -eq 0 ]]; then
+ log_fail "Creating child from unauthorized namespace should have been denied"
+fi
+log_note "Create correctly denied: $create_result"
+
+# Verify the unauthorized child was not created
+if zfs list "$TESTPOOL/$TESTFS/deleg_root/unauthorized_child" 2>/dev/null; then
+ log_fail "Unauthorized child dataset should not exist"
+fi
+
+# Test 3: Attempt to create snapshot (should FAIL)
+log_note "Test 3: Attempting to create snapshot from wrong namespace (should fail)..."
+typeset snap_result
+snap_result=$(run_in_userns "$ZONED_OTHER_UID" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root/child@unauthorized" 2>&1)
+snap_status=$?
+
+if [[ $snap_status -eq 0 ]]; then
+ log_fail "Creating snapshot from unauthorized namespace should have been denied"
+fi
+log_note "Snapshot correctly denied: $snap_result"
+
+# Test 4: Attempt to set property (should FAIL)
+log_note "Test 4: Attempting to set property from wrong namespace (should fail)..."
+typeset prop_result
+prop_result=$(run_in_userns "$ZONED_OTHER_UID" \
+ set quota=1G "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+prop_status=$?
+
+if [[ $prop_status -eq 0 ]]; then
+ log_fail "Setting property from unauthorized namespace should have been denied"
+fi
+log_note "Set property correctly denied: $prop_result"
+
+# Verify quota was not changed
+typeset actual_quota
+actual_quota=$(zfs get -H -o value quota "$TESTPOOL/$TESTFS/deleg_root/child")
+if [[ "$actual_quota" == "1G" ]]; then
+ log_fail "Quota should not have been changed by unauthorized namespace"
+fi
+
+# Test 5: Attempt to destroy (should FAIL)
+log_note "Test 5: Attempting to destroy from wrong namespace (should fail)..."
+typeset destroy_result
+destroy_result=$(run_in_userns "$ZONED_OTHER_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+destroy_status=$?
+
+if [[ $destroy_status -eq 0 ]]; then
+ log_fail "Destroying from unauthorized namespace should have been denied"
+fi
+log_note "Destroy correctly denied: $destroy_result"
+
+# Verify child still exists
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child"
+log_note "Child dataset still exists (protected from unauthorized access)"
+
+# Test 6: Attempt to rename (should FAIL)
+log_note "Test 6: Attempting to rename from wrong namespace (should fail)..."
+typeset rename_result
+rename_result=$(run_in_userns "$ZONED_OTHER_UID" \
+ rename "$TESTPOOL/$TESTFS/deleg_root/child" \
+ "$TESTPOOL/$TESTFS/deleg_root/child_renamed" 2>&1)
+rename_status=$?
+
+if [[ $rename_status -eq 0 ]]; then
+ log_fail "Renaming from unauthorized namespace should have been denied"
+fi
+log_note "Rename correctly denied: $rename_result"
+
+# Verify child still has original name
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child"
+
+log_pass "Unauthorized user namespace cannot perform write operations"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_012_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_012_pos.ksh
new file mode 100755
index 00000000000..db90ff1bade
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_012_pos.ksh
@@ -0,0 +1,120 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that an authorized user namespace can inherit properties
+# on datasets within the delegation subtree.
+#
+# STRATEGY:
+# 1. Create a delegation root with zoned_uid set
+# 2. Create a child dataset
+# 3. Set properties on the child, then inherit them from user namespace
+# 4. Verify properties were inherited correctly
+# 5. Test inheriting both native and user properties
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Authorized user namespace can inherit properties on delegated datasets"
+log_onexit cleanup
+
+# Create delegation root with child
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ userprop,compression
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+
+log_note "Created delegation root with child dataset"
+
+# Set a native property on child that we will then inherit
+log_must zfs set compression=lz4 "$TESTPOOL/$TESTFS/deleg_root/child"
+
+typeset actual_comp
+actual_comp=$(zfs get -H -o value compression "$TESTPOOL/$TESTFS/deleg_root/child")
+if [[ "$actual_comp" != "lz4" ]]; then
+ log_fail "Failed to set compression: expected lz4, got $actual_comp"
+fi
+
+# Set a user property on child that we will then inherit
+log_must zfs set com.example:testprop=localvalue "$TESTPOOL/$TESTFS/deleg_root/child"
+
+typeset actual_userprop
+actual_userprop=$(zfs get -H -o value com.example:testprop "$TESTPOOL/$TESTFS/deleg_root/child")
+if [[ "$actual_userprop" != "localvalue" ]]; then
+ log_fail "Failed to set user property: expected localvalue, got $actual_userprop"
+fi
+
+# Test 1: Inherit native property from user namespace
+log_note "Test 1: Inheriting native property from user namespace..."
+typeset inherit_result
+inherit_result=$(run_in_userns "$ZONED_TEST_UID" \
+ inherit compression "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+inherit_status=$?
+
+if [[ $inherit_status -ne 0 ]]; then
+ log_note "Inherit compression output: $inherit_result"
+ log_fail "Failed to inherit compression from user namespace"
+fi
+
+# Verify compression was inherited (should match parent's value)
+actual_comp=$(zfs get -H -o value compression "$TESTPOOL/$TESTFS/deleg_root/child")
+typeset comp_source
+comp_source=$(zfs get -H -o source compression "$TESTPOOL/$TESTFS/deleg_root/child")
+if [[ "$comp_source" == "local" ]]; then
+ log_fail "Compression still local after inherit: $actual_comp (source=$comp_source)"
+fi
+log_note "Compression inherited successfully (value=$actual_comp, source=$comp_source)"
+
+# Test 2: Inherit user property from user namespace
+log_note "Test 2: Inheriting user property from user namespace..."
+typeset inherit_userprop_result
+inherit_userprop_result=$(run_in_userns "$ZONED_TEST_UID" \
+ inherit com.example:testprop "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+inherit_userprop_status=$?
+
+if [[ $inherit_userprop_status -ne 0 ]]; then
+ log_note "Inherit user property output: $inherit_userprop_result"
+ log_fail "Failed to inherit user property from user namespace"
+fi
+
+# Verify user property was removed (inherited means no local value)
+actual_userprop=$(zfs get -H -o value com.example:testprop "$TESTPOOL/$TESTFS/deleg_root/child")
+if [[ "$actual_userprop" == "localvalue" ]]; then
+ log_fail "User property still has local value after inherit"
+fi
+log_note "User property inherited successfully (value=$actual_userprop)"
+
+log_pass "Authorized user namespace can inherit properties on delegated datasets"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_013_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_013_pos.ksh
new file mode 100755
index 00000000000..c5a8bfe598b
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_013_pos.ksh
@@ -0,0 +1,122 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+. $STF_SUITE/include/math.shlib
+
+#
+# DESCRIPTION:
+# Verify that an authorized user namespace can set userquota
+# and groupquota properties on delegated datasets.
+#
+# STRATEGY:
+# 1. Create a delegation root with zoned_uid set
+# 2. Create a child dataset
+# 3. Enter user namespace and set userquota on child
+# 4. Set groupquota on child
+# 5. Verify quotas were applied correctly
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Authorized user namespace can set userquota/groupquota on delegated datasets"
+log_onexit cleanup
+
+# Create delegation root with child
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ userquota,groupquota
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+
+log_note "Created delegation root with child dataset"
+
+# Test 1: Set userquota from user namespace
+log_note "Test 1: Setting userquota from user namespace..."
+typeset uq_result
+uq_result=$(run_in_userns "$ZONED_TEST_UID" \
+ set userquota@0=50M "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+typeset uq_status=$?
+
+if [[ $uq_status -ne 0 ]]; then
+ log_note "Set userquota output: $uq_result"
+ log_fail "Failed to set userquota from user namespace"
+fi
+
+# Verify userquota was set (use -p for parseable/raw bytes)
+typeset actual_uq
+actual_uq=$(zfs get -Hp -o value userquota@0 "$TESTPOOL/$TESTFS/deleg_root/child")
+if ! within_percent "$actual_uq" $((50 * 1048576)) 99; then
+ log_fail "Userquota not set correctly: expected ~50M, got $actual_uq"
+fi
+log_note "Userquota set successfully ($actual_uq bytes)"
+
+# Test 2: Set groupquota from user namespace
+log_note "Test 2: Setting groupquota from user namespace..."
+typeset gq_result
+gq_result=$(run_in_userns "$ZONED_TEST_UID" \
+ set groupquota@0=100M "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+typeset gq_status=$?
+
+if [[ $gq_status -ne 0 ]]; then
+ log_note "Set groupquota output: $gq_result"
+ log_fail "Failed to set groupquota from user namespace"
+fi
+
+# Verify groupquota was set (use -p for parseable/raw bytes)
+typeset actual_gq
+actual_gq=$(zfs get -Hp -o value groupquota@0 "$TESTPOOL/$TESTFS/deleg_root/child")
+if ! within_percent "$actual_gq" $((100 * 1048576)) 99; then
+ log_fail "Groupquota not set correctly: expected ~100M, got $actual_gq"
+fi
+log_note "Groupquota set successfully ($actual_gq bytes)"
+
+# Test 3: Set userquota on delegation root itself
+log_note "Test 3: Setting userquota on delegation root..."
+typeset root_uq_result
+root_uq_result=$(run_in_userns "$ZONED_TEST_UID" \
+ set userquota@0=200M "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+typeset root_uq_status=$?
+
+if [[ $root_uq_status -ne 0 ]]; then
+ log_note "Set userquota on root output: $root_uq_result"
+ log_fail "Failed to set userquota on delegation root"
+fi
+
+typeset actual_root_uq
+actual_root_uq=$(zfs get -Hp -o value userquota@0 "$TESTPOOL/$TESTFS/deleg_root")
+if ! within_percent "$actual_root_uq" $((200 * 1048576)) 99; then
+ log_fail "Root userquota not set correctly: expected ~200M, got $actual_root_uq"
+fi
+log_note "Delegation root userquota set successfully ($actual_root_uq bytes)"
+
+log_pass "Authorized user namespace can set userquota/groupquota on delegated datasets"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_014_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_014_pos.ksh
new file mode 100755
index 00000000000..131addf6aa9
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_014_pos.ksh
@@ -0,0 +1,116 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that an authorized user namespace can create sub-datasets
+# (grandchildren) under a delegation root. The zoned_uid property
+# must inherit so that children of children are also authorized.
+#
+# STRATEGY:
+# 1. Create a delegation root and set zoned_uid
+# 2. From user namespace, create a child dataset
+# 3. From user namespace, create a grandchild under the child
+# 4. Verify the grandchild exists and is visible from the namespace
+# 5. Verify zoned_uid inherited to both child and grandchild
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Authorized user namespace can create sub-datasets (grandchildren)"
+log_onexit cleanup
+
+# Create delegation root and set zoned_uid
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ create,mount
+log_note "Delegation root created with zoned_uid=$ZONED_TEST_UID"
+
+# Step 1: Create child from user namespace
+typeset create_result
+create_result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+create_status=$?
+
+if [[ $create_status -ne 0 ]]; then
+ log_note "Create child output: $create_result"
+ log_fail "Failed to create child dataset (status=$create_status)"
+fi
+log_note "Child dataset created successfully"
+
+# Step 2: Create grandchild from user namespace
+typeset grandchild_result
+grandchild_result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/child/grandchild" 2>&1)
+grandchild_status=$?
+
+if [[ $grandchild_status -ne 0 ]]; then
+ log_note "Create grandchild output: $grandchild_result"
+ log_fail "Failed to create grandchild dataset (status=$grandchild_status)"
+fi
+log_note "Grandchild dataset created successfully"
+
+# Step 3: Verify both exist from global zone
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child"
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child/grandchild"
+log_note "Both datasets verified from global zone"
+
+# Step 4: Verify grandchild is visible from user namespace
+typeset list_result
+list_result=$(run_in_userns "$ZONED_TEST_UID" \
+ list "$TESTPOOL/$TESTFS/deleg_root/child/grandchild" 2>&1)
+list_status=$?
+
+if [[ $list_status -ne 0 ]]; then
+ log_note "List output: $list_result"
+ log_fail "Grandchild not visible from user namespace"
+fi
+log_note "Grandchild visible from user namespace"
+
+# Step 5: Verify zoned_uid inherited to child and grandchild
+typeset child_uid
+child_uid=$(get_zoned_uid "$TESTPOOL/$TESTFS/deleg_root/child")
+typeset grandchild_uid
+grandchild_uid=$(get_zoned_uid "$TESTPOOL/$TESTFS/deleg_root/child/grandchild")
+
+if [[ "$child_uid" != "$ZONED_TEST_UID" ]]; then
+ log_fail "zoned_uid not inherited to child: expected $ZONED_TEST_UID, got $child_uid"
+fi
+if [[ "$grandchild_uid" != "$ZONED_TEST_UID" ]]; then
+ log_fail "zoned_uid not inherited to grandchild: expected $ZONED_TEST_UID, got $grandchild_uid"
+fi
+log_note "zoned_uid correctly inherited to child ($child_uid) and grandchild ($grandchild_uid)"
+
+log_pass "Authorized user namespace can create sub-datasets (grandchildren)"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_015_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_015_pos.ksh
new file mode 100755
index 00000000000..9c5ad675e76
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_015_pos.ksh
@@ -0,0 +1,114 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that destroying and recreating a pool with zoned_uid works
+# without stale kernel state. Exercises the spa_export_os() cleanup
+# path that must detach zone_uid_datasets entries on pool destroy.
+#
+# STRATEGY:
+# 1. Create a delegation root with zoned_uid set
+# 2. Create child datasets with inherited zoned_uid
+# 3. Verify delegation works (create from namespace)
+# 4. Destroy the pool
+# 5. Recreate the pool with same zoned_uid
+# 6. Verify delegation works again on the new pool
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ if poolexists "$TESTPOOL"; then
+ # Ensure pool is in a clean state
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+ else
+ # Pool was destroyed by test; recreate it for the framework
+ DISK=${DISKS%% *}
+ default_setup_noexit "$DISK"
+ fi
+}
+
+log_assert "Pool destroy/recreate with zoned_uid works without stale state"
+log_onexit cleanup
+
+# Step 1-2: Create delegation root with children
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ create,mount
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child1"
+
+log_note "Created delegation root with child, zoned_uid=$ZONED_TEST_UID"
+
+# Step 3: Verify delegation works
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/ns_child" 2>&1)
+typeset status=$?
+
+if [[ $status -ne 0 ]]; then
+ log_note "Create output: $result"
+ log_fail "Initial delegation failed (status=$status)"
+fi
+log_note "Initial delegation works: created ns_child from namespace"
+
+# Step 4: Destroy the pool
+log_must zpool destroy "$TESTPOOL"
+
+log_note "Pool destroyed"
+
+# Step 5: Recreate the pool with same zoned_uid
+DISK=${DISKS%% *}
+log_must zpool create -f "$TESTPOOL" "$DISK"
+log_must zfs create "$TESTPOOL/$TESTFS"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ create,mount
+
+log_note "Pool recreated with zoned_uid=$ZONED_TEST_UID"
+
+# Step 6: Verify delegation works again on the new pool
+typeset result2
+result2=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/ns_child2" 2>&1)
+typeset status2=$?
+
+if [[ $status2 -ne 0 ]]; then
+ log_note "Create output after recreate: $result2"
+ log_fail "Delegation failed after pool destroy/recreate (status=$status2)"
+fi
+
+# Verify the dataset exists
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/ns_child2"
+log_note "Delegation works after pool destroy/recreate"
+
+log_pass "Pool destroy/recreate with zoned_uid works without stale state"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_016_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_016_pos.ksh
new file mode 100755
index 00000000000..aeb97e20d58
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_016_pos.ksh
@@ -0,0 +1,132 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that snapshots can be individually destroyed from within a
+# delegated user namespace. Covers the zone_dataset_check_list()
+# visibility fix for '@' separator.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid
+# 2. From namespace: create child, create snapshot on child
+# 3. From namespace: verify snapshot is visible via zfs list -t snapshot
+# 4. From namespace: destroy snapshot individually (zfs destroy ds@snap)
+# 5. Verify snapshot is gone
+# 6. From namespace: create snapshot on delegation root itself
+# 7. From namespace: destroy that snapshot individually
+# 8. Verify snapshot is gone
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Individual snapshot destroy works from delegated user namespace"
+log_onexit cleanup
+
+# Step 1: Create delegation root
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ create,mount,snapshot,destroy
+
+# Step 2: Create child and snapshot from namespace
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/child1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create child output: $result"
+ log_fail "Failed to create child from namespace"
+fi
+
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root/child1@snap1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create snapshot output: $result"
+ log_fail "Failed to create snapshot from namespace"
+fi
+
+log_note "Created child1@snap1 from namespace"
+
+# Step 3: Verify snapshot is visible from namespace
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ list -t snapshot "$TESTPOOL/$TESTFS/deleg_root/child1@snap1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "List snapshot output: $result"
+ log_fail "Snapshot not visible from namespace"
+fi
+log_note "Snapshot visible from namespace"
+
+# Step 4: Destroy snapshot individually from namespace
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/child1@snap1" 2>&1)
+typeset status=$?
+
+if [[ $status -ne 0 ]]; then
+ log_note "Destroy snapshot output: $result"
+ log_fail "Failed to destroy individual snapshot from namespace (status=$status)"
+fi
+
+# Step 5: Verify snapshot is gone
+if zfs list -t snapshot "$TESTPOOL/$TESTFS/deleg_root/child1@snap1" 2>/dev/null; then
+ log_fail "Snapshot child1@snap1 should have been destroyed"
+fi
+log_note "child1@snap1 destroyed successfully from namespace"
+
+# Step 6: Create snapshot on delegation root itself, then destroy it
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root@rootsnap" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create root snapshot output: $result"
+ log_fail "Failed to create snapshot on delegation root"
+fi
+
+log_note "Created deleg_root@rootsnap from namespace"
+
+# Step 7: Destroy the root snapshot individually from namespace
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root@rootsnap" 2>&1)
+status=$?
+
+if [[ $status -ne 0 ]]; then
+ log_note "Destroy root snapshot output: $result"
+ log_fail "Failed to destroy root snapshot from namespace (status=$status)"
+fi
+
+# Step 8: Verify root snapshot is gone
+if zfs list -t snapshot "$TESTPOOL/$TESTFS/deleg_root@rootsnap" 2>/dev/null; then
+ log_fail "Snapshot deleg_root@rootsnap should have been destroyed"
+fi
+log_note "deleg_root@rootsnap destroyed successfully from namespace"
+
+log_pass "Individual snapshot destroy works from delegated user namespace"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_017_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_017_neg.ksh
new file mode 100755
index 00000000000..40c314dcd98
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_017_neg.ksh
@@ -0,0 +1,125 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that a namespace user cannot modify the zoned_uid property,
+# even on datasets they have delegation over. Only the global zone
+# admin should be able to manage delegation assignments.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid=$ZONED_TEST_UID
+# 2. Create child dataset (inherits zoned_uid)
+# 3. From namespace: attempt zfs set zoned_uid=none on child (should FAIL)
+# 4. Verify zoned_uid still inherited on child
+# 5. From namespace: attempt zfs set zoned_uid=$ZONED_OTHER_UID (should FAIL)
+# 6. From namespace: attempt zfs set zoned_uid=$ZONED_TEST_UID (should FAIL)
+# 7. From namespace: attempt zfs set zoned_uid=none on root (should FAIL)
+# 8. Verify delegation root still has original zoned_uid
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Namespace user cannot modify zoned_uid property"
+log_onexit cleanup
+
+# Step 1-2: Create delegation root and child
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+
+log_note "Created delegation root and child with zoned_uid=$ZONED_TEST_UID"
+
+# Step 3: Attempt to clear zoned_uid on child from namespace (should FAIL)
+log_note "Test 1: Attempting zfs set zoned_uid=none on child from namespace..."
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set zoned_uid=none "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+typeset status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "Setting zoned_uid=none on child should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Step 4: Verify zoned_uid still inherited on child
+typeset child_uid
+child_uid=$(get_zoned_uid "$TESTPOOL/$TESTFS/deleg_root/child")
+if [[ "$child_uid" != "$ZONED_TEST_UID" ]]; then
+ log_fail "Child zoned_uid changed to '$child_uid', expected '$ZONED_TEST_UID'"
+fi
+log_note "Child zoned_uid still $ZONED_TEST_UID (inherited)"
+
+# Step 5: Attempt to change zoned_uid to different UID (should FAIL)
+log_note "Test 2: Attempting zfs set zoned_uid=$ZONED_OTHER_UID on child..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set "zoned_uid=$ZONED_OTHER_UID" "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "Setting zoned_uid to different UID should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Step 6: Attempt to set zoned_uid to same UID (should still FAIL)
+log_note "Test 3: Attempting zfs set zoned_uid=$ZONED_TEST_UID on child..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set "zoned_uid=$ZONED_TEST_UID" "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "Setting zoned_uid (even to same value) should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Step 7: Attempt to clear zoned_uid on delegation root (should FAIL)
+log_note "Test 4: Attempting zfs set zoned_uid=none on delegation root..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set zoned_uid=none "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "Setting zoned_uid=none on delegation root should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Step 8: Verify delegation root still has original zoned_uid
+typeset root_uid
+root_uid=$(get_zoned_uid "$TESTPOOL/$TESTFS/deleg_root")
+if [[ "$root_uid" != "$ZONED_TEST_UID" ]]; then
+ log_fail "Root zoned_uid changed to '$root_uid', expected '$ZONED_TEST_UID'"
+fi
+log_note "Delegation root zoned_uid still $ZONED_TEST_UID"
+
+log_pass "Namespace user cannot modify zoned_uid property"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_018_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_018_pos.ksh
new file mode 100755
index 00000000000..770b6bbcabc
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_018_pos.ksh
@@ -0,0 +1,129 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that clone operations work from within a delegated user
+# namespace, and that cloning outside the subtree is denied.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid
+# 2. From namespace: create child dataset
+# 3. From namespace: create snapshot on child
+# 4. From namespace: clone the snapshot to a new dataset within subtree
+# 5. Verify clone exists and is writable
+# 6. From namespace: attempt to clone outside the subtree (should FAIL)
+# 7. Verify the failed clone doesn't exist
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+ zfs destroy -rf "$TESTPOOL/$TESTFS/outside_clone" 2>/dev/null
+}
+
+log_assert "Clone operations work from delegated user namespace"
+log_onexit cleanup
+
+# Step 1: Create delegation root
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ create,mount,snapshot,clone
+
+# Step 2: Create child from namespace
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create child output: $result"
+ log_fail "Failed to create child from namespace"
+fi
+
+# Step 3: Create snapshot from namespace
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root/child@snap1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create snapshot output: $result"
+ log_fail "Failed to create snapshot from namespace"
+fi
+
+log_note "Created child@snap1 from namespace"
+
+# Step 4: Clone snapshot to new dataset within subtree
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ clone "$TESTPOOL/$TESTFS/deleg_root/child@snap1" \
+ "$TESTPOOL/$TESTFS/deleg_root/myclone" 2>&1)
+typeset status=$?
+
+if [[ $status -ne 0 ]]; then
+ log_note "Clone output: $result"
+ log_fail "Failed to clone within subtree from namespace (status=$status)"
+fi
+
+# Step 5: Verify clone exists and is writable
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/myclone"
+
+typeset origin
+origin=$(zfs get -H -o value origin "$TESTPOOL/$TESTFS/deleg_root/myclone")
+if [[ "$origin" != "$TESTPOOL/$TESTFS/deleg_root/child@snap1" ]]; then
+ log_fail "Clone origin should be child@snap1, got: $origin"
+fi
+log_note "Clone exists with correct origin"
+
+# Verify writable: create a child under the clone from namespace
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/myclone/subchild" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create under clone output: $result"
+ log_fail "Clone is not writable from namespace"
+fi
+log_note "Clone is writable from namespace"
+
+# Step 6: Attempt to clone outside the subtree (should FAIL)
+log_note "Attempting clone to outside subtree..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ clone "$TESTPOOL/$TESTFS/deleg_root/child@snap1" \
+ "$TESTPOOL/$TESTFS/outside_clone" 2>&1)
+status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "Clone to outside subtree should have been denied"
+fi
+log_note "Correctly denied clone to outside subtree: $result"
+
+# Step 7: Verify the failed clone doesn't exist
+if datasetexists "$TESTPOOL/$TESTFS/outside_clone"; then
+ log_fail "Outside clone should not exist"
+fi
+log_note "Outside clone correctly does not exist"
+
+log_pass "Clone operations work from delegated user namespace"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_019_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_019_neg.ksh
new file mode 100755
index 00000000000..60393758357
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_019_neg.ksh
@@ -0,0 +1,141 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that two different UIDs with sibling delegations cannot
+# access each other's subtrees (multi-UID isolation).
+#
+# STRATEGY:
+# 1. Create two sibling delegation roots with different zoned_uids
+# 2. Create a child under each from global zone
+# 3. From UID A's namespace: verify can create under deleg_root_a
+# 4. From UID A's namespace: attempt create under deleg_root_b (FAIL)
+# 5. From UID A's namespace: attempt destroy child under deleg_root_b (FAIL)
+# 6. From UID A's namespace: attempt set property on deleg_root_b/child (FAIL)
+# 7. From UID B's namespace: verify can create under deleg_root_b
+# 8. From UID B's namespace: attempt create under deleg_root_a (FAIL)
+# 9. Verify both subtrees remain intact
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root_a" 2>/dev/null
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root_b" 2>/dev/null
+}
+
+log_assert "Multi-UID isolation: sibling delegations cannot cross boundaries"
+log_onexit cleanup
+
+# Step 1: Create two sibling delegation roots
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root_a"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root_a" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root_a" "$ZONED_TEST_UID" \
+ create,mount
+
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root_b"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root_b" "$ZONED_OTHER_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root_b" "$ZONED_OTHER_UID" \
+ create,mount
+
+# Step 2: Create a child under each from global zone
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root_a/child_a"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root_b/child_b"
+
+log_note "Created two delegation roots: A(uid=$ZONED_TEST_UID) B(uid=$ZONED_OTHER_UID)"
+
+# Step 3: UID A can create under its own subtree
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root_a/ns_child_a" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create output: $result"
+ log_fail "UID A should be able to create under deleg_root_a"
+fi
+log_note "UID A can create under its own subtree"
+
+# Step 4: UID A cannot create under UID B's subtree
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root_b/intruder_a" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "UID A should NOT be able to create under deleg_root_b"
+fi
+log_note "UID A correctly denied create under deleg_root_b"
+
+# Step 5: UID A cannot destroy child under UID B's subtree
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root_b/child_b" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "UID A should NOT be able to destroy under deleg_root_b"
+fi
+log_note "UID A correctly denied destroy under deleg_root_b"
+
+# Step 6: UID A cannot set property on UID B's subtree
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set mountpoint=none "$TESTPOOL/$TESTFS/deleg_root_b/child_b" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "UID A should NOT be able to set properties on deleg_root_b"
+fi
+log_note "UID A correctly denied setprop on deleg_root_b"
+
+# Step 7: UID B can create under its own subtree
+result=$(run_in_userns "$ZONED_OTHER_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root_b/ns_child_b" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create output: $result"
+ log_fail "UID B should be able to create under deleg_root_b"
+fi
+log_note "UID B can create under its own subtree"
+
+# Step 8: UID B cannot create under UID A's subtree
+result=$(run_in_userns "$ZONED_OTHER_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root_a/intruder_b" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "UID B should NOT be able to create under deleg_root_a"
+fi
+log_note "UID B correctly denied create under deleg_root_a"
+
+# Step 9: Verify both subtrees remain intact
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root_a/child_a"
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root_a/ns_child_a"
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root_b/child_b"
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root_b/ns_child_b"
+
+# Verify intruder datasets don't exist
+if datasetexists "$TESTPOOL/$TESTFS/deleg_root_b/intruder_a"; then
+ log_fail "Intruder dataset from UID A should not exist"
+fi
+if datasetexists "$TESTPOOL/$TESTFS/deleg_root_a/intruder_b"; then
+ log_fail "Intruder dataset from UID B should not exist"
+fi
+log_note "Both subtrees intact, no cross-contamination"
+
+log_pass "Multi-UID isolation: sibling delegations cannot cross boundaries"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_020_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_020_neg.ksh
new file mode 100755
index 00000000000..4de33b30e54
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_020_neg.ksh
@@ -0,0 +1,171 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that operations without zone_dataset_admin_check() integration
+# are denied from a delegated namespace. These operations go through
+# zfs_dozonecheck_impl() which requires zoned=on (not set in the
+# zoned_uid-only flow), so they should all fail with EPERM.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid, create child, create snapshot
+# 2. From namespace: attempt zfs send (should FAIL)
+# 3. From namespace: attempt zfs rollback (should FAIL)
+# 4. From namespace: attempt zfs hold (should FAIL)
+# 5. From namespace: attempt zfs bookmark (should FAIL)
+# 6. From namespace: attempt zfs allow (should FAIL)
+# 7. From namespace: attempt zfs promote on a clone (should FAIL)
+# 8. Verify dataset state unchanged
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Operations without admin_check integration are denied from namespace"
+log_onexit cleanup
+
+# Step 1: Setup — create delegation root, child, snapshot, and clone
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ create,mount,snapshot
+
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create child output: $result"
+ log_fail "Failed to create child from namespace"
+fi
+
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root/child@snap1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create snapshot output: $result"
+ log_fail "Failed to create snapshot from namespace"
+fi
+
+# Create a clone from global zone for promote test
+log_must zfs clone "$TESTPOOL/$TESTFS/deleg_root/child@snap1" \
+ "$TESTPOOL/$TESTFS/deleg_root/myclone"
+
+log_note "Setup complete: child, child@snap1, myclone"
+
+# Step 2: Attempt zfs send (should FAIL)
+log_note "Test 1: zfs send from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ send "$TESTPOOL/$TESTFS/deleg_root/child@snap1" 2>&1)
+typeset status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "zfs send should have been denied from namespace"
+fi
+log_note "Correctly denied: zfs send (status=$status)"
+
+# Step 3: Attempt zfs rollback (should FAIL)
+log_note "Test 2: zfs rollback from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ rollback "$TESTPOOL/$TESTFS/deleg_root/child@snap1" 2>&1)
+status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "zfs rollback should have been denied from namespace"
+fi
+log_note "Correctly denied: zfs rollback (status=$status)"
+
+# Step 4: Attempt zfs hold (should FAIL)
+log_note "Test 3: zfs hold from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ hold mytag "$TESTPOOL/$TESTFS/deleg_root/child@snap1" 2>&1)
+status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "zfs hold should have been denied from namespace"
+fi
+log_note "Correctly denied: zfs hold (status=$status)"
+
+# Step 5: Attempt zfs bookmark (should FAIL)
+log_note "Test 4: zfs bookmark from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ bookmark "$TESTPOOL/$TESTFS/deleg_root/child@snap1" \
+ "$TESTPOOL/$TESTFS/deleg_root/child#bmark1" 2>&1)
+status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "zfs bookmark should have been denied from namespace"
+fi
+log_note "Correctly denied: zfs bookmark (status=$status)"
+
+# Step 6: Attempt zfs allow (should FAIL)
+log_note "Test 5: zfs allow from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ allow -e create "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "zfs allow should have been denied from namespace"
+fi
+log_note "Correctly denied: zfs allow (status=$status)"
+
+# Step 7: Attempt zfs promote (should FAIL)
+log_note "Test 6: zfs promote from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ promote "$TESTPOOL/$TESTFS/deleg_root/myclone" 2>&1)
+status=$?
+
+if [[ $status -eq 0 ]]; then
+ log_fail "zfs promote should have been denied from namespace"
+fi
+log_note "Correctly denied: zfs promote (status=$status)"
+
+# Step 8: Verify dataset state unchanged
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child"
+log_must zfs list -t snapshot "$TESTPOOL/$TESTFS/deleg_root/child@snap1"
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/myclone"
+
+# Verify no holds were placed
+typeset holds
+holds=$(zfs holds "$TESTPOOL/$TESTFS/deleg_root/child@snap1" 2>&1 | wc -l)
+if [[ $holds -gt 1 ]]; then
+ log_fail "Unexpected holds found on snapshot"
+fi
+
+# Verify no bookmarks were created
+if zfs list -t bookmark "$TESTPOOL/$TESTFS/deleg_root/child#bmark1" 2>/dev/null; then
+ log_fail "Bookmark should not exist"
+fi
+
+log_note "All datasets unchanged after denied operations"
+
+log_pass "Operations without admin_check integration are denied from namespace"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_021_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_021_neg.ksh
new file mode 100755
index 00000000000..6a3a7c3030c
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_021_neg.ksh
@@ -0,0 +1,109 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that the 'zoned' property cannot be modified from within
+# a delegated namespace. The ZFS_PROP_ZONED case blocks this via
+# !INGLOBALZONE(curproc), but this is never tested in the zoned_uid
+# delegation context.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid
+# 2. Create child dataset
+# 3. From namespace: attempt zfs set zoned=on on child (should FAIL)
+# 4. From namespace: attempt zfs set zoned=off on child (should FAIL)
+# 5. From namespace: attempt zfs set zoned=on on delegation root (FAIL)
+# 6. Verify zoned property unchanged on all datasets
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Cannot set 'zoned' property from delegated namespace"
+log_onexit cleanup
+
+# Step 1-2: Create delegation root and child
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+
+log_note "Created delegation root and child"
+
+# Record original zoned values
+typeset orig_root_zoned orig_child_zoned
+orig_root_zoned=$(zfs get -H -o value zoned "$TESTPOOL/$TESTFS/deleg_root")
+orig_child_zoned=$(zfs get -H -o value zoned "$TESTPOOL/$TESTFS/deleg_root/child")
+
+# Step 3: Attempt zfs set zoned=on on child (should FAIL)
+log_note "Test 1: zfs set zoned=on on child from namespace..."
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set zoned=on "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "Setting zoned=on on child should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Step 4: Attempt zfs set zoned=off on child (should FAIL)
+log_note "Test 2: zfs set zoned=off on child from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set zoned=off "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "Setting zoned=off on child should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Step 5: Attempt zfs set zoned=on on delegation root (should FAIL)
+log_note "Test 3: zfs set zoned=on on delegation root from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set zoned=on "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "Setting zoned=on on delegation root should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Step 6: Verify zoned property unchanged on all datasets
+typeset cur_root_zoned cur_child_zoned
+cur_root_zoned=$(zfs get -H -o value zoned "$TESTPOOL/$TESTFS/deleg_root")
+cur_child_zoned=$(zfs get -H -o value zoned "$TESTPOOL/$TESTFS/deleg_root/child")
+
+if [[ "$cur_root_zoned" != "$orig_root_zoned" ]]; then
+ log_fail "Root zoned changed from '$orig_root_zoned' to '$cur_root_zoned'"
+fi
+if [[ "$cur_child_zoned" != "$orig_child_zoned" ]]; then
+ log_fail "Child zoned changed from '$orig_child_zoned' to '$cur_child_zoned'"
+fi
+log_note "zoned property unchanged on all datasets"
+
+log_pass "Cannot set 'zoned' property from delegated namespace"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_022_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_022_neg.ksh
new file mode 100755
index 00000000000..cf1775e5dbb
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_022_neg.ksh
@@ -0,0 +1,154 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that delegated users cannot override filesystem_limit and
+# snapshot_limit set by the global admin on the delegation root.
+# Delegated users CAN set tighter sub-limits on child datasets.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid
+# 2. Global admin: set filesystem_limit=10, snapshot_limit=5
+# 3. From namespace: attempt filesystem_limit=none on root (FAIL)
+# 4. From namespace: attempt snapshot_limit=none on root (FAIL)
+# 5. Verify limits unchanged on delegation root
+# 6. From namespace: create child dataset
+# 7. From namespace: set filesystem_limit=3 on child (SUCCEED)
+# 8. From namespace: set snapshot_limit=2 on child (SUCCEED)
+# 9. Verify child has the sub-limits set
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Delegated user cannot override admin limits on delegation root"
+log_onexit cleanup
+
+# Step 1: Create delegation root
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ create,mount,filesystem_limit,snapshot_limit
+
+# Step 2: Global admin sets limits
+log_must zfs set filesystem_limit=10 "$TESTPOOL/$TESTFS/deleg_root"
+log_must zfs set snapshot_limit=5 "$TESTPOOL/$TESTFS/deleg_root"
+
+log_note "Admin set filesystem_limit=10, snapshot_limit=5 on delegation root"
+
+# Step 3: Attempt to remove filesystem_limit from namespace (should FAIL)
+log_note "Test 1: filesystem_limit=none on root from namespace..."
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set filesystem_limit=none "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "Removing filesystem_limit on root should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Also try raising the limit
+log_note "Test 2: filesystem_limit=100 on root from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set filesystem_limit=100 "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "Raising filesystem_limit on root should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Step 4: Attempt to remove snapshot_limit from namespace (should FAIL)
+log_note "Test 3: snapshot_limit=none on root from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set snapshot_limit=none "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "Removing snapshot_limit on root should have been denied"
+fi
+log_note "Correctly denied: $result"
+
+# Step 5: Verify limits unchanged
+typeset fs_limit snap_limit
+fs_limit=$(get_prop filesystem_limit "$TESTPOOL/$TESTFS/deleg_root")
+snap_limit=$(get_prop snapshot_limit "$TESTPOOL/$TESTFS/deleg_root")
+
+if [[ "$fs_limit" != "10" ]]; then
+ log_fail "filesystem_limit changed to '$fs_limit', expected '10'"
+fi
+if [[ "$snap_limit" != "5" ]]; then
+ log_fail "snapshot_limit changed to '$snap_limit', expected '5'"
+fi
+log_note "Admin limits unchanged on delegation root"
+
+# Step 6: Create child from namespace
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Create child output: $result"
+ log_fail "Failed to create child from namespace"
+fi
+
+# Step 7: Set filesystem_limit on child (should SUCCEED - tighter sub-limit)
+log_note "Test 4: filesystem_limit=3 on child from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set filesystem_limit=3 "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+typeset status=$?
+if [[ $status -ne 0 ]]; then
+ log_note "Set filesystem_limit on child output: $result"
+ log_fail "Setting filesystem_limit on child should succeed (status=$status)"
+fi
+
+# Step 8: Set snapshot_limit on child (should SUCCEED)
+log_note "Test 5: snapshot_limit=2 on child from namespace..."
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ set snapshot_limit=2 "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+status=$?
+if [[ $status -ne 0 ]]; then
+ log_note "Set snapshot_limit on child output: $result"
+ log_fail "Setting snapshot_limit on child should succeed (status=$status)"
+fi
+
+# Step 9: Verify child has the sub-limits
+typeset child_fs_limit child_snap_limit
+child_fs_limit=$(get_prop filesystem_limit \
+ "$TESTPOOL/$TESTFS/deleg_root/child")
+child_snap_limit=$(get_prop snapshot_limit \
+ "$TESTPOOL/$TESTFS/deleg_root/child")
+
+if [[ "$child_fs_limit" != "3" ]]; then
+ log_fail "Child filesystem_limit should be 3, got: $child_fs_limit"
+fi
+if [[ "$child_snap_limit" != "2" ]]; then
+ log_fail "Child snapshot_limit should be 2, got: $child_snap_limit"
+fi
+log_note "Child has correct sub-limits: filesystem_limit=3, snapshot_limit=2"
+
+log_pass "Delegated user cannot override admin limits on delegation root"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_023_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_023_pos.ksh
new file mode 100755
index 00000000000..9cdc73aa72d
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_023_pos.ksh
@@ -0,0 +1,131 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Additive least privilege: non-destructive operations (create, snapshot,
+# setprop) succeed only when BOTH dsl_deleg grants the permission AND
+# the namespace has at least CAP_FOWNER.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid
+# 2. Grant create,snapshot,mount via zfs allow
+# 3. With CAP_FOWNER: create succeeds (L1 yes + L2 yes)
+# 4. With no caps: create fails (L1 yes, L2 no)
+# 5. Without zfs allow grant: create fails even with CAP_FOWNER (L1 no)
+# 6. With CAP_FOWNER + snapshot grant: snapshot succeeds
+# 7. With CAP_FOWNER + create grant only: snapshot fails (wrong perm)
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Additive L1+L2: non-destructive ops need dsl_deleg AND CAP_FOWNER"
+log_onexit cleanup
+
+# Step 1: Create delegation root.
+# Use mountpoint=none so create/snapshot from the namespace don't
+# trigger mount operations that would fail without CAP_SYS_ADMIN.
+log_must zfs create -o mountpoint=none "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+
+# Step 2: Grant create,snapshot,mount
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ "create,snapshot,mount"
+
+# ADD-1: L1 grants create + L2 has CAP_FOWNER → allowed
+log_note "Test ADD-1: create with dsl_deleg + CAP_FOWNER"
+typeset result
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "drop_sys_admin" \
+ create "$TESTPOOL/$TESTFS/deleg_root/add1_child" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "ADD-1: create should succeed with dsl_deleg + CAP_FOWNER"
+fi
+log_note "ADD-1 passed: create allowed"
+
+# ADD-2: L1 grants create + L2 has no caps → denied
+log_note "Test ADD-2: create with dsl_deleg + no caps"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "none" \
+ create "$TESTPOOL/$TESTFS/deleg_root/add2_child" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "ADD-2: create should fail without capabilities"
+fi
+log_note "ADD-2 passed: create denied without caps"
+
+# Verify the dataset was NOT created
+if datasetexists "$TESTPOOL/$TESTFS/deleg_root/add2_child"; then
+ log_fail "ADD-2: dataset should not exist"
+fi
+
+# ADD-3: No dsl_deleg grant + CAP_FOWNER → denied
+log_note "Test ADD-3: create without dsl_deleg grant"
+log_must revoke_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "drop_sys_admin" \
+ create "$TESTPOOL/$TESTFS/deleg_root/add3_child" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "ADD-3: create should fail without dsl_deleg grant"
+fi
+log_note "ADD-3 passed: create denied without dsl_deleg"
+
+if datasetexists "$TESTPOOL/$TESTFS/deleg_root/add3_child"; then
+ log_fail "ADD-3: dataset should not exist"
+fi
+
+# ADD-5: Restore grants, test snapshot with CAP_FOWNER
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ "create,snapshot,mount"
+
+log_note "Test ADD-5: snapshot with dsl_deleg + CAP_FOWNER"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "drop_sys_admin" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root/add1_child@snap1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "ADD-5: snapshot should succeed with dsl_deleg + CAP_FOWNER"
+fi
+log_note "ADD-5 passed: snapshot allowed"
+
+# ADD-6: create grant only, snapshot should fail (wrong perm)
+log_must revoke_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ "create,mount"
+
+log_note "Test ADD-6: snapshot with create-only dsl_deleg"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "drop_sys_admin" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root/add1_child@snap_bad" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "ADD-6: snapshot should fail with create-only dsl_deleg"
+fi
+log_note "ADD-6 passed: snapshot denied (wrong perm in dsl_deleg)"
+
+log_pass "Additive L1+L2: non-destructive ops need dsl_deleg AND CAP_FOWNER"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_024_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_024_neg.ksh
new file mode 100755
index 00000000000..487b3baa99c
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_024_neg.ksh
@@ -0,0 +1,144 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Additive least privilege: destructive operations (destroy, rename,
+# clone) require BOTH dsl_deleg grant AND CAP_SYS_ADMIN.
+# CAP_FOWNER alone is insufficient for destructive operations.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid and child datasets
+# 2. Grant destroy,rename,clone,mount,create via zfs allow
+# 3. With CAP_SYS_ADMIN + destroy grant: destroy succeeds
+# 4. With CAP_FOWNER + destroy grant: destroy fails (wrong cap tier)
+# 5. With CAP_SYS_ADMIN but no destroy grant: destroy fails (L1 no)
+# 6. With no caps + destroy grant: destroy fails (L2 no)
+# 7. Test rename similarly: needs SYS_ADMIN + rename grant
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Additive L1+L2: destructive ops need dsl_deleg AND CAP_SYS_ADMIN"
+log_onexit cleanup
+
+# Setup: delegation root with children.
+# Use mountpoint=none so datasets aren't mounted in the host namespace;
+# otherwise destroy from a user namespace fails because mount-locked
+# mounts (created by the host) cannot be unmounted from a child namespace.
+log_must zfs create -o mountpoint=none "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/victim1"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/victim2"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/victim3"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/victim4"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/rename_src"
+
+# Grant destructive permissions
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ "create,destroy,rename,clone,mount,snapshot"
+
+# ADD-8: destroy with dsl_deleg + CAP_SYS_ADMIN → allowed
+log_note "Test ADD-8: destroy with dsl_deleg + CAP_SYS_ADMIN"
+typeset result
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "all" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/victim1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "ADD-8: destroy should succeed with dsl_deleg + CAP_SYS_ADMIN"
+fi
+if datasetexists "$TESTPOOL/$TESTFS/deleg_root/victim1"; then
+ log_fail "ADD-8: victim1 should not exist after destroy"
+fi
+log_note "ADD-8 passed: destroy allowed with SYS_ADMIN"
+
+# ADD-9: destroy with dsl_deleg + CAP_FOWNER → denied (wrong tier)
+log_note "Test ADD-9: destroy with dsl_deleg + CAP_FOWNER only"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "drop_sys_admin" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/victim2" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "ADD-9: destroy should fail with CAP_FOWNER (needs SYS_ADMIN)"
+fi
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/victim2"
+log_note "ADD-9 passed: destroy denied with CAP_FOWNER only"
+
+# ADD-10: destroy with dsl_deleg + no caps → denied
+log_note "Test ADD-10: destroy with dsl_deleg + no caps"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "none" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/victim3" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "ADD-10: destroy should fail without any capabilities"
+fi
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/victim3"
+log_note "ADD-10 passed: destroy denied without caps"
+
+# ADD-11: destroy with CAP_SYS_ADMIN but NO dsl_deleg grant → denied
+log_note "Test ADD-11: destroy without dsl_deleg grant"
+log_must revoke_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "all" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/victim4" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "ADD-11: destroy should fail without dsl_deleg grant"
+fi
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/victim4"
+log_note "ADD-11 passed: destroy denied without dsl_deleg"
+
+# ADD-12: rename with dsl_deleg + CAP_SYS_ADMIN → allowed
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ "create,destroy,rename,clone,mount,snapshot"
+
+log_note "Test ADD-12: rename with dsl_deleg + CAP_SYS_ADMIN"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "all" \
+ rename "$TESTPOOL/$TESTFS/deleg_root/rename_src" \
+ "$TESTPOOL/$TESTFS/deleg_root/rename_dst" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "ADD-12: rename should succeed with dsl_deleg + CAP_SYS_ADMIN"
+fi
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/rename_dst"
+log_note "ADD-12 passed: rename allowed with SYS_ADMIN"
+
+# ADD-13: clone with dsl_deleg + CAP_FOWNER → denied (destructive tier)
+log_note "Test ADD-13: clone with dsl_deleg + CAP_FOWNER"
+# Create a snapshot to clone from
+log_must zfs snapshot "$TESTPOOL/$TESTFS/deleg_root/victim2@snap"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "drop_sys_admin" \
+ clone "$TESTPOOL/$TESTFS/deleg_root/victim2@snap" \
+ "$TESTPOOL/$TESTFS/deleg_root/clone_dst" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "ADD-13: clone should fail with CAP_FOWNER (needs SYS_ADMIN)"
+fi
+log_note "ADD-13 passed: clone denied with CAP_FOWNER only"
+
+log_pass "Additive L1+L2: destructive ops need dsl_deleg AND CAP_SYS_ADMIN"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_025_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_025_pos.ksh
new file mode 100755
index 00000000000..77ecd631631
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_025_pos.ksh
@@ -0,0 +1,102 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Read-only operations (list, get properties) require no capabilities
+# and no dsl_deleg grants. Visibility is controlled solely by the
+# zoned_uid delegation scoping.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid and a child
+# 2. No dsl_deleg grants, no capabilities
+# 3. From namespace: zfs list succeeds for delegated dataset
+# 4. From namespace: zfs get succeeds for delegated dataset
+# 5. From namespace: zfs list fails for non-delegated dataset
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Read-only operations need no caps and no dsl_deleg"
+log_onexit cleanup
+
+# Setup: delegation root with a child, NO zfs allow grants.
+# Use mountpoint=none to avoid mount-lock issues in user namespaces.
+log_must zfs create -o mountpoint=none "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+
+# ADD-14: list with no caps, no dsl_deleg → allowed (read-only)
+log_note "Test ADD-14: list delegated dataset with no caps"
+typeset result
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "none" \
+ list "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "ADD-14: list should succeed with no caps (read-only)"
+fi
+log_note "ADD-14 passed: list allowed"
+
+# Get properties with no caps → should work
+log_note "Test: get properties with no caps"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "none" \
+ get zoned_uid "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "get properties should succeed with no caps (read-only)"
+fi
+log_note "Get properties passed"
+
+# List child dataset with no caps → should work (child of delegation)
+log_note "Test: list child dataset with no caps"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "none" \
+ list "$TESTPOOL/$TESTFS/deleg_root/child" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "list child should succeed with no caps"
+fi
+log_note "List child passed"
+
+# Non-delegated dataset should NOT be visible
+log_note "Test: list non-delegated dataset from namespace"
+log_must zfs create "$TESTPOOL/$TESTFS/other_ds"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "none" \
+ list "$TESTPOOL/$TESTFS/other_ds" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "Non-delegated dataset should not be visible"
+fi
+log_note "Non-delegated dataset correctly not visible"
+log_must zfs destroy "$TESTPOOL/$TESTFS/other_ds"
+
+log_pass "Read-only operations need no caps and no dsl_deleg"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_026_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_026_pos.ksh
new file mode 100755
index 00000000000..10913fbdb4a
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_026_pos.ksh
@@ -0,0 +1,112 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# When pool delegation is disabled (zpool set delegation=off),
+# ALL zoned_uid write operations are denied regardless of
+# capabilities. Delegation OFF means the pool admin has opted
+# out of delegating access entirely (POLP).
+# Read-only operations (list, get) still succeed.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid
+# 2. Disable delegation on the pool
+# 3. DOFF-1: create with CAP_FOWNER → denied (delegation off)
+# 4. DOFF-2: destroy with CAP_SYS_ADMIN → denied (delegation off)
+# 5. DOFF-3: create with all caps → denied (delegation off)
+# 6. DOFF-4: list with no caps → allowed (read-only)
+# 7. Re-enable delegation
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ # Always re-enable delegation
+ log_must zpool set delegation=on "$TESTPOOL"
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Delegation OFF: all zoned_uid writes denied"
+log_onexit cleanup
+
+# Setup.
+# Use mountpoint=none to avoid mount-lock issues in user namespaces.
+log_must zfs create -o mountpoint=none "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/victim"
+
+# Disable delegation on pool
+log_must zpool set delegation=off "$TESTPOOL"
+log_note "Pool delegation disabled"
+
+# DOFF-1: create with CAP_FOWNER → denied (delegation off overrides caps)
+log_note "Test DOFF-1: create with CAP_FOWNER (delegation off)"
+typeset result
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "drop_sys_admin" \
+ create "$TESTPOOL/$TESTFS/deleg_root/doff1_child" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "DOFF-1: create should fail when delegation is off"
+fi
+log_note "DOFF-1 passed: create denied (delegation off)"
+
+# DOFF-2: destroy with CAP_SYS_ADMIN → denied (delegation off)
+log_note "Test DOFF-2: destroy with CAP_SYS_ADMIN (delegation off)"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "all" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/victim" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "DOFF-2: destroy should fail when delegation is off"
+fi
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/victim"
+log_note "DOFF-2 passed: destroy denied (delegation off)"
+
+# DOFF-3: create with all caps → denied (delegation off)
+log_note "Test DOFF-3: create with all caps (delegation off)"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "all" \
+ create "$TESTPOOL/$TESTFS/deleg_root/doff3_child" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "DOFF-3: create should fail when delegation is off"
+fi
+log_note "DOFF-3 passed: create denied (delegation off)"
+
+# DOFF-4: list with no caps → allowed (read-only)
+log_note "Test DOFF-4: list with no caps (delegation off)"
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "none" \
+ list "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "DOFF-4: list should succeed with no caps (read-only)"
+fi
+log_note "DOFF-4 passed"
+
+# Re-enable delegation
+log_must zpool set delegation=on "$TESTPOOL"
+
+log_pass "Delegation OFF: all zoned_uid writes denied"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_027_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_027_pos.ksh
new file mode 100755
index 00000000000..c753145f154
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_027_pos.ksh
@@ -0,0 +1,103 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# CAP_SYS_ADMIN satisfies the L2 requirement for ALL operation tiers
+# (both non-destructive and destructive). This verifies that
+# SYS_ADMIN is a superset of FOWNER for L2 purposes.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid
+# 2. Grant all permissions via zfs allow
+# 3. With CAP_SYS_ADMIN: create succeeds (SYS_ADMIN covers FOWNER tier)
+# 4. With CAP_SYS_ADMIN: snapshot succeeds
+# 5. With CAP_SYS_ADMIN: destroy succeeds
+# 6. Verify CAP_SYS_ADMIN is a complete L2 pass for all ops
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "CAP_SYS_ADMIN satisfies L2 for all operation tiers"
+log_onexit cleanup
+
+# Setup
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ "create,destroy,snapshot,rename,clone,mount"
+
+# ADD-7: create with dsl_deleg + CAP_SYS_ADMIN → allowed
+log_note "Test ADD-7: create with CAP_SYS_ADMIN"
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ create "$TESTPOOL/$TESTFS/deleg_root/child1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "ADD-7: create should succeed with SYS_ADMIN"
+fi
+log_note "ADD-7 passed: create allowed with SYS_ADMIN"
+
+# Snapshot with CAP_SYS_ADMIN
+log_note "Test: snapshot with CAP_SYS_ADMIN"
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ snapshot "$TESTPOOL/$TESTFS/deleg_root/child1@snap1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "snapshot should succeed with SYS_ADMIN"
+fi
+log_note "Snapshot passed with SYS_ADMIN"
+
+# Clone with CAP_SYS_ADMIN (destructive tier)
+log_note "Test: clone with CAP_SYS_ADMIN"
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ clone "$TESTPOOL/$TESTFS/deleg_root/child1@snap1" \
+ "$TESTPOOL/$TESTFS/deleg_root/clone1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "clone should succeed with SYS_ADMIN"
+fi
+log_note "Clone passed with SYS_ADMIN"
+
+# Destroy with CAP_SYS_ADMIN (destructive tier)
+log_note "Test: destroy with CAP_SYS_ADMIN"
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/clone1" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "destroy should succeed with SYS_ADMIN"
+fi
+log_note "Destroy passed with SYS_ADMIN"
+
+log_pass "CAP_SYS_ADMIN satisfies L2 for all operation tiers"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_028_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_028_neg.ksh
new file mode 100755
index 00000000000..f1dbed22d35
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_028_neg.ksh
@@ -0,0 +1,103 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that the additive model does not affect non-zoned datasets.
+# Standard ZFS permission checks (secpolicy_zfs → dsl_deleg) continue
+# to work unchanged when zone_dataset_admin_check returns NOT_APPLICABLE.
+#
+# STRATEGY:
+# 1. Create a dataset WITHOUT zoned_uid
+# 2. From global zone as root: all operations succeed (existing behavior)
+# 3. Grant permissions to a non-root user via zfs allow
+# 4. As non-root user (not in namespace): operations succeed via dsl_deleg
+# 5. Without zfs allow grant: operations fail
+# 6. Verify zoned_uid model doesn't interfere
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/normal_ds" 2>/dev/null
+}
+
+log_assert "Non-zoned datasets use standard permission model unchanged"
+log_onexit cleanup
+
+# Create a normal dataset (no zoned_uid)
+log_must zfs create "$TESTPOOL/$TESTFS/normal_ds"
+
+# Verify zoned_uid is 0 (unset)
+typeset val
+val=$(get_zoned_uid "$TESTPOOL/$TESTFS/normal_ds")
+if [[ "$val" != "0" ]]; then
+ log_fail "Default zoned_uid should be 0, got: $val"
+fi
+
+# EXIST-1: Root in global zone can do everything (existing behavior)
+log_note "Test EXIST-1: root in global zone"
+log_must zfs create "$TESTPOOL/$TESTFS/normal_ds/child"
+log_must zfs snapshot "$TESTPOOL/$TESTFS/normal_ds/child@snap"
+log_must zfs destroy "$TESTPOOL/$TESTFS/normal_ds/child@snap"
+log_must zfs destroy "$TESTPOOL/$TESTFS/normal_ds/child"
+log_note "EXIST-1 passed: root can do everything"
+
+# EXIST-2: Non-root with zfs allow can perform delegated operations
+log_note "Test EXIST-2: non-root with zfs allow"
+log_must grant_deleg "$TESTPOOL/$TESTFS/normal_ds" "$ZONED_TEST_UID" \
+ "create,snapshot,mount,destroy"
+
+# Run as the test user (NOT in a namespace, just sudo -u)
+typeset zfs_cmd result
+zfs_cmd="$(which zfs)"
+result=$(sudo -u \#"$ZONED_TEST_UID" "$zfs_cmd" \
+ create "$TESTPOOL/$TESTFS/normal_ds/deleg_child" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "EXIST-2: non-root with zfs allow should be able to create"
+fi
+log_note "EXIST-2 passed: dsl_deleg works for non-root"
+
+# EXIST-3: Non-root WITHOUT zfs allow is denied
+log_note "Test EXIST-3: non-root without zfs allow"
+log_must revoke_deleg "$TESTPOOL/$TESTFS/normal_ds" "$ZONED_TEST_UID"
+
+result=$(sudo -u \#"$ZONED_TEST_UID" "$zfs_cmd" \
+ create "$TESTPOOL/$TESTFS/normal_ds/denied_child" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "EXIST-3: non-root without zfs allow should be denied"
+fi
+log_note "EXIST-3 passed: denied without dsl_deleg"
+
+# Cleanup the child we created
+log_must zfs destroy "$TESTPOOL/$TESTFS/normal_ds/deleg_child"
+
+log_pass "Non-zoned datasets use standard permission model unchanged"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_029_neg.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_029_neg.ksh
new file mode 100755
index 00000000000..fa6ec3ce431
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_029_neg.ksh
@@ -0,0 +1,120 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Cross-cutting constraints still apply under the additive model.
+# Even with full dsl_deleg grants AND CAP_SYS_ADMIN, certain
+# operations are always denied to protect the delegation boundary.
+#
+# STRATEGY:
+# 1. Create delegation root with full grants + CAP_SYS_ADMIN
+# 2. CROSS-1: Cannot destroy delegation root itself
+# 3. CROSS-2: Cannot rename dataset outside delegation subtree
+# 4. CROSS-3: Cannot modify zoned_uid property from namespace
+# 5. CROSS-4: Cannot override admin-set limits on delegation root
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+ zfs destroy -rf "$TESTPOOL/$TESTFS/outside" 2>/dev/null
+}
+
+log_assert "Cross-cutting constraints enforced under additive model"
+log_onexit cleanup
+
+# Setup: full permissions
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+log_must zfs create "$TESTPOOL/$TESTFS/outside"
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ "create,destroy,snapshot,rename,clone,mount"
+log_must zfs set filesystem_limit=10 "$TESTPOOL/$TESTFS/deleg_root"
+log_must zfs set snapshot_limit=5 "$TESTPOOL/$TESTFS/deleg_root"
+
+# CROSS-1: Cannot destroy the delegation root itself
+log_note "Test CROSS-1: destroy delegation root"
+run_in_userns "$ZONED_TEST_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root" >/dev/null 2>&1
+if [[ $? -eq 0 ]]; then
+ log_fail "CROSS-1: should not be able to destroy delegation root"
+fi
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root"
+log_note "CROSS-1 passed: delegation root protected"
+
+# CROSS-2: Cannot rename outside delegation subtree
+log_note "Test CROSS-2: rename outside subtree"
+run_in_userns "$ZONED_TEST_UID" \
+ rename "$TESTPOOL/$TESTFS/deleg_root/child" \
+ "$TESTPOOL/$TESTFS/outside/escaped" >/dev/null 2>&1
+if [[ $? -eq 0 ]]; then
+ log_fail "CROSS-2: should not be able to rename outside subtree"
+fi
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child"
+log_note "CROSS-2 passed: cannot escape delegation"
+
+# CROSS-3: Cannot modify zoned_uid from namespace
+log_note "Test CROSS-3: set zoned_uid from namespace"
+run_in_userns "$ZONED_TEST_UID" \
+ set zoned_uid=0 "$TESTPOOL/$TESTFS/deleg_root" >/dev/null 2>&1
+if [[ $? -eq 0 ]]; then
+ log_fail "CROSS-3: should not be able to modify zoned_uid"
+fi
+typeset val
+val=$(get_zoned_uid "$TESTPOOL/$TESTFS/deleg_root")
+if [[ "$val" != "$ZONED_TEST_UID" ]]; then
+ log_fail "CROSS-3: zoned_uid changed from $ZONED_TEST_UID to $val"
+fi
+log_note "CROSS-3 passed: zoned_uid protected"
+
+# CROSS-4: Cannot override admin limits on delegation root
+log_note "Test CROSS-4: override filesystem_limit on root"
+run_in_userns "$ZONED_TEST_UID" \
+ set filesystem_limit=none "$TESTPOOL/$TESTFS/deleg_root" >/dev/null 2>&1
+if [[ $? -eq 0 ]]; then
+ log_fail "CROSS-4: should not be able to remove admin limits"
+fi
+typeset fs_limit
+fs_limit=$(get_prop filesystem_limit "$TESTPOOL/$TESTFS/deleg_root")
+if [[ "$fs_limit" != "10" ]]; then
+ log_fail "CROSS-4: filesystem_limit changed to $fs_limit"
+fi
+
+run_in_userns "$ZONED_TEST_UID" \
+ set snapshot_limit=none "$TESTPOOL/$TESTFS/deleg_root" >/dev/null 2>&1
+if [[ $? -eq 0 ]]; then
+ log_fail "CROSS-4: should not be able to remove snapshot_limit"
+fi
+log_note "CROSS-4 passed: admin limits protected"
+
+log_pass "Cross-cutting constraints enforced under additive model"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_030_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_030_pos.ksh
new file mode 100755
index 00000000000..8536b36e294
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_030_pos.ksh
@@ -0,0 +1,183 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Validate that the capability control mechanism (capsh --drop within
+# unshare --user --map-root-user) works correctly. This is a
+# prerequisite for all L2 capability-tier tests (023-027).
+#
+# The kernel's ns_capable() checks the effective capability set
+# within the user namespace. capsh --drop removes capabilities
+# from the bounding set, and the exec'd child process inherits
+# the restricted set.
+#
+# STRATEGY:
+# 1. Verify capsh is available
+# 2. Full caps (all): CAP_SYS_ADMIN present, CAP_FOWNER present
+# 3. Drop SYS_ADMIN only: CAP_SYS_ADMIN absent, CAP_FOWNER present
+# 4. Drop all: CAP_SYS_ADMIN absent, CAP_FOWNER absent
+# 5. Verify /proc/self/status CapEff reflects the drops
+# 6. Verify drops work under sudo -u (as test UID)
+#
+
+verify_runnable "global"
+
+log_assert "Capability control via capsh works in user namespaces"
+
+typeset capsh_cmd
+capsh_cmd="$(which capsh)"
+if [[ -z "$capsh_cmd" ]]; then
+ log_unsupported "capsh not found (install libcap)"
+fi
+
+# Helper: check a capability in a namespace
+function check_cap_in_ns
+{
+ typeset drop_arg=$1
+ typeset cap_to_check=$2
+ typeset expect=$3 # "yes" or "no"
+
+ typeset result cmd_args
+ if [[ "$drop_arg" == "none" ]]; then
+ cmd_args=""
+ else
+ cmd_args="$drop_arg"
+ fi
+
+ if [[ -z "$cmd_args" ]]; then
+ result=$(unshare --user --map-root-user \
+ "$capsh_cmd" --has-p="$cap_to_check" 2>&1 \
+ && echo "YES" || echo "NO")
+ else
+ # shellcheck disable=SC2086
+ result=$(unshare --user --map-root-user \
+ "$capsh_cmd" $cmd_args -- \
+ -c "$capsh_cmd --has-p=$cap_to_check 2>&1 && echo YES || echo NO")
+ fi
+
+ if [[ "$expect" == "yes" && "$result" != *"YES"* ]]; then
+ log_fail "Expected $cap_to_check to be present ($drop_arg), got: $result"
+ fi
+ if [[ "$expect" == "no" && "$result" != *"NO"* ]]; then
+ log_fail "Expected $cap_to_check to be absent ($drop_arg), got: $result"
+ fi
+}
+
+# Test 1: Full caps — both present
+log_note "Test 1: full caps in namespace"
+check_cap_in_ns "none" "cap_sys_admin" "yes"
+check_cap_in_ns "none" "cap_fowner" "yes"
+log_note "Test 1 passed"
+
+# Test 2: Drop SYS_ADMIN — SYS_ADMIN absent, FOWNER present
+log_note "Test 2: drop cap_sys_admin"
+check_cap_in_ns "--drop=cap_sys_admin" "cap_sys_admin" "no"
+check_cap_in_ns "--drop=cap_sys_admin" "cap_fowner" "yes"
+log_note "Test 2 passed"
+
+# Test 3: Drop all — both absent
+log_note "Test 3: drop all caps"
+check_cap_in_ns "--drop=all" "cap_sys_admin" "no"
+check_cap_in_ns "--drop=all" "cap_fowner" "no"
+log_note "Test 3 passed"
+
+# Test 4: Verify via /proc/self/status CapEff bitmask
+log_note "Test 4: verify CapEff bitmask"
+typeset full_eff drop_eff
+full_eff=$(unshare --user --map-root-user \
+ grep CapEff /proc/self/status 2>&1 | awk '{print $2}')
+drop_eff=$(unshare --user --map-root-user \
+ "$capsh_cmd" --drop=cap_sys_admin -- \
+ -c 'grep CapEff /proc/self/status' 2>&1 | awk '{print $2}')
+
+if [[ "$full_eff" == "$drop_eff" ]]; then
+ log_fail "CapEff should differ after dropping cap_sys_admin"
+fi
+log_note "CapEff full=$full_eff drop_sys_admin=$drop_eff"
+
+# CAP_SYS_ADMIN is bit 21 = 0x200000
+# The difference should be exactly this bit
+typeset diff
+diff=$(printf "0x%x" $(( 16#${full_eff} - 16#${drop_eff} )))
+if [[ "$diff" != "0x200000" ]]; then
+ log_note "Expected diff 0x200000 (CAP_SYS_ADMIN), got $diff"
+ log_note "This may indicate kernel cap numbering differs; non-fatal"
+fi
+log_note "Test 4 passed"
+
+# Test 5: Works under sudo -u (as test UID)
+log_note "Test 5: capability drops work under sudo -u"
+typeset result
+result=$(sudo -u \#"$ZONED_TEST_UID" unshare --user --map-root-user \
+ "$capsh_cmd" --drop=cap_sys_admin -- \
+ -c "$capsh_cmd --has-p=cap_sys_admin 2>&1 && echo YES || echo NO" 2>&1)
+if [[ "$result" != *"NO"* ]]; then
+ log_fail "cap_sys_admin should be absent under sudo -u, got: $result"
+fi
+
+result=$(sudo -u \#"$ZONED_TEST_UID" unshare --user --map-root-user \
+ "$capsh_cmd" --drop=cap_sys_admin -- \
+ -c "$capsh_cmd --has-p=cap_fowner 2>&1 && echo YES || echo NO" 2>&1)
+if [[ "$result" != *"YES"* ]]; then
+ log_fail "cap_fowner should be present under sudo -u, got: $result"
+fi
+log_note "Test 5 passed"
+
+# Test 6: Verify run_in_userns_caps helper modes work
+log_note "Test 6: run_in_userns_caps helper verification"
+
+# "all" mode — should have SYS_ADMIN
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "all" \
+ version 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "run_in_userns_caps 'all' should work"
+fi
+log_note "'all' mode works"
+
+# "drop_sys_admin" mode — zfs version should still work (read-only)
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "drop_sys_admin" \
+ version 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "run_in_userns_caps 'drop_sys_admin' should work for read-only"
+fi
+log_note "'drop_sys_admin' mode works"
+
+# "none" mode — zfs version should still work (read-only)
+result=$(run_in_userns_caps "$ZONED_TEST_UID" "none" \
+ version 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "run_in_userns_caps 'none' should work for read-only"
+fi
+log_note "'none' mode works"
+
+log_pass "Capability control via capsh works in user namespaces"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_031_pos.ksh b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_031_pos.ksh
new file mode 100755
index 00000000000..fe0fb2ab024
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_031_pos.ksh
@@ -0,0 +1,110 @@
+#!/bin/ksh -p
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid_common.kshlib
+
+#
+# DESCRIPTION:
+# Verify that namespace-initiated rename+destroy properly cleans up
+# kernel-side zone tracking entries. When a namespace user renames
+# a dataset, a tracking entry is created for the new name. When
+# the renamed dataset is subsequently destroyed, that tracking entry
+# must be removed. If it persists (stale), the delegation root
+# remains visible as a parent dataset even after the admin removes
+# the zoned_uid delegation — an information leak.
+#
+# STRATEGY:
+# 1. Create delegation root with zoned_uid, grant permissions
+# 2. From namespace: rename child → child2 (creates tracking entry)
+# 3. From namespace: destroy child2 (should clean up tracking entry)
+# 4. Admin removes zoned_uid delegation (zfs set zoned_uid=0)
+# 5. Verify delegation root is NOT visible from the old namespace
+# (if stale tracking persists, it would still be visible)
+#
+
+verify_runnable "global"
+
+function cleanup
+{
+ zfs destroy -rf "$TESTPOOL/$TESTFS/deleg_root" 2>/dev/null
+}
+
+log_assert "Zone tracking cleanup after namespace rename+destroy"
+log_onexit cleanup
+
+# Setup: delegation root with child, mountpoint=none to avoid mount-lock issues
+log_must zfs create -o mountpoint=none "$TESTPOOL/$TESTFS/deleg_root"
+log_must set_zoned_uid "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID"
+log_must zfs create "$TESTPOOL/$TESTFS/deleg_root/child"
+
+# Grant all needed permissions
+log_must grant_deleg "$TESTPOOL/$TESTFS/deleg_root" "$ZONED_TEST_UID" \
+ "create,destroy,rename,mount"
+
+# Step 1: From namespace, rename child → child2
+# This internally calls zone_dataset_attach_uid for the new name
+log_note "Test: rename child from namespace"
+typeset result
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ rename "$TESTPOOL/$TESTFS/deleg_root/child" \
+ "$TESTPOOL/$TESTFS/deleg_root/child2" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "rename should succeed from namespace"
+fi
+log_must zfs list "$TESTPOOL/$TESTFS/deleg_root/child2"
+log_note "Rename succeeded"
+
+# Step 2: From namespace, destroy child2
+# This should clean up the tracking entry created by rename
+log_note "Test: destroy renamed child from namespace"
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ destroy "$TESTPOOL/$TESTFS/deleg_root/child2" 2>&1)
+if [[ $? -ne 0 ]]; then
+ log_note "Output: $result"
+ log_fail "destroy should succeed from namespace"
+fi
+log_note "Destroy succeeded"
+
+# Step 3: Admin removes the zoned_uid delegation
+log_note "Test: admin removes zoned_uid delegation"
+log_must zfs set zoned_uid=0 "$TESTPOOL/$TESTFS/deleg_root"
+
+# Step 4: Verify the delegation root is NOT visible from the old namespace.
+# If the tracking entry from the rename was not cleaned up (stale),
+# the delegation root would still be visible as a parent of the stale
+# entry, leaking its existence after delegation was revoked.
+log_note "Test: verify no stale visibility after delegation removal"
+result=$(run_in_userns "$ZONED_TEST_UID" \
+ list "$TESTPOOL/$TESTFS/deleg_root" 2>&1)
+if [[ $? -eq 0 ]]; then
+ log_fail "Delegation root should NOT be visible after " \
+ "zoned_uid=0 (stale tracking entry detected)"
+fi
+log_note "No stale visibility: delegation root correctly hidden"
+
+log_pass "Zone tracking cleanup after namespace rename+destroy"
diff --git a/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_common.kshlib b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_common.kshlib
new file mode 100644
index 00000000000..a44a3f0cc7a
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/zoned_uid/zoned_uid_common.kshlib
@@ -0,0 +1,237 @@
+# SPDX-License-Identifier: CDDL-1.0
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2026 Colin K. Williams / LINK ORG LLC / LI-NK.SOCIAL. All rights reserved.
+#
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/zoned_uid/zoned_uid.cfg
+
+#
+# Check if the kernel supports zoned_uid property
+#
+function zoned_uid_supported
+{
+ zfs get zoned_uid "$TESTPOOL" >/dev/null 2>&1
+ return $?
+}
+
+#
+# Get the zoned_uid property value for a dataset
+# Use -p for parseable (raw numeric) output
+#
+function get_zoned_uid
+{
+ typeset dataset=$1
+ get_prop zoned_uid "$dataset"
+}
+
+#
+# Set the zoned_uid property on a dataset
+#
+function set_zoned_uid
+{
+ typeset dataset=$1
+ typeset uid=$2
+ zfs set zoned_uid="$uid" "$dataset"
+}
+
+#
+# Clear the zoned_uid property (set to 0/none)
+#
+function clear_zoned_uid
+{
+ typeset dataset=$1
+ zfs set zoned_uid=0 "$dataset"
+}
+
+#
+# Run a ZFS command inside a user namespace owned by the given UID.
+# Uses absolute path to zfs so the binary is found regardless of the
+# target user's PATH (e.g. when running from a source build).
+#
+# The namespace gets CAP_SYS_ADMIN via --map-root-user (default behavior,
+# equivalent to a container launched with --cap-add SYS_ADMIN).
+#
+# Usage: run_in_userns
+# Output is captured to stdout/stderr; return code is preserved.
+#
+function run_in_userns
+{
+ typeset uid=$1
+ shift
+ typeset zfs_cmd
+ zfs_cmd="$(which zfs)"
+
+ sudo -u \#"${uid}" unshare --user --mount --map-root-user \
+ "$zfs_cmd" "$@"
+}
+
+#
+# Run a ZFS command inside a user namespace with specific capabilities.
+# Uses capsh --drop to remove capabilities from the bounding set after
+# creating the namespace via unshare --map-root-user. The exec'd shell
+# (via capsh -- -c) inherits the restricted bounding set, so the ZFS
+# binary sees only the kept capabilities in effective/permitted.
+#
+# Usage: run_in_userns_caps
+# cap_spec:
+# "all" — keep all caps (same as run_in_userns)
+# "none" — drop all capabilities
+# "drop_sys_admin" — drop only CAP_SYS_ADMIN (keep FOWNER etc.)
+# "cap_fowner" — keep only CAP_FOWNER (drop everything else)
+#
+function run_in_userns_caps
+{
+ typeset uid=$1
+ typeset cap_spec=$2
+ shift 2
+ typeset zfs_cmd capsh_cmd
+ zfs_cmd="$(which zfs)"
+ capsh_cmd="$(which capsh)"
+
+ if [[ "$cap_spec" == "all" ]]; then
+ sudo -u \#"${uid}" unshare --user --mount --map-root-user \
+ "$zfs_cmd" "$@"
+ return $?
+ fi
+
+ if [[ "$cap_spec" == "none" ]]; then
+ # Drop every capability from the bounding set
+ sudo -u \#"${uid}" unshare --user --mount --map-root-user \
+ "$capsh_cmd" --drop=all -- -c "$zfs_cmd $*"
+ return $?
+ fi
+
+ if [[ "$cap_spec" == "drop_sys_admin" ]]; then
+ # Drop only CAP_SYS_ADMIN; all other caps (including
+ # CAP_FOWNER) remain. This simulates a default Podman
+ # container (default caps, no --cap-add SYS_ADMIN).
+ sudo -u \#"${uid}" unshare --user --mount --map-root-user \
+ "$capsh_cmd" --drop=cap_sys_admin -- -c "$zfs_cmd $*"
+ return $?
+ fi
+
+ # Generic: drop all caps except the ones listed.
+ # Build the drop list by enumerating all caps and excluding those
+ # the caller wants to keep.
+ typeset all_caps drop_list=""
+ all_caps=$("$capsh_cmd" --print 2>/dev/null | \
+ grep "^Bounding set" | sed 's/.*=//;s/,/ /g')
+ if [[ -z "$all_caps" ]]; then
+ log_fail "capsh --print failed to enumerate capabilities"
+ fi
+ for cap in $all_caps; do
+ typeset keep=false
+ typeset IFS=","
+ for want in $cap_spec; do
+ if [[ "$cap" == "$want" ]]; then
+ keep=true
+ break
+ fi
+ done
+ unset IFS
+ if [[ "$keep" == "false" ]]; then
+ drop_list="$drop_list --drop=$cap"
+ fi
+ done
+
+ # shellcheck disable=SC2086
+ sudo -u \#"${uid}" unshare --user --mount --map-root-user \
+ "$capsh_cmd" $drop_list -- -c "$zfs_cmd $*"
+}
+
+#
+# Verify that capability control via capsh works in user namespaces.
+# Returns 0 if the mechanism is functional, non-zero otherwise.
+# This should be called in setup.ksh to skip tests if capsh is broken.
+#
+function verify_capsh_works
+{
+ typeset capsh_cmd
+ capsh_cmd="$(which capsh)"
+ if [[ -z "$capsh_cmd" ]]; then
+ return 1
+ fi
+
+ # Test 1: after --drop=cap_sys_admin, cap should be absent
+ typeset result
+ result=$(unshare --user --map-root-user \
+ "$capsh_cmd" --drop=cap_sys_admin -- \
+ -c "$capsh_cmd --has-p=cap_sys_admin 2>&1 && echo YES || echo NO")
+ if [[ "$result" != *"NO"* ]]; then
+ return 1
+ fi
+
+ # Test 2: cap_fowner should still be present
+ result=$(unshare --user --map-root-user \
+ "$capsh_cmd" --drop=cap_sys_admin -- \
+ -c "$capsh_cmd --has-p=cap_fowner 2>&1 && echo YES || echo NO")
+ if [[ "$result" != *"YES"* ]]; then
+ return 1
+ fi
+
+ # Test 3: --drop=all should remove everything
+ result=$(unshare --user --map-root-user \
+ "$capsh_cmd" --drop=all -- \
+ -c "$capsh_cmd --has-p=cap_fowner 2>&1 && echo YES || echo NO")
+ if [[ "$result" != *"NO"* ]]; then
+ return 1
+ fi
+
+ return 0
+}
+
+#
+# Grant delegated permissions to a user on a dataset.
+# Wrapper around zfs allow.
+#
+# Usage: grant_deleg
+# perms: comma-separated list, e.g. "create,snapshot,mount"
+#
+function grant_deleg
+{
+ typeset dataset=$1
+ typeset uid=$2
+ typeset perms=$3
+ zfs allow -u "$uid" "$perms" "$dataset"
+}
+
+#
+# Revoke delegated permissions from a user on a dataset.
+# Wrapper around zfs unallow.
+#
+# Usage: revoke_deleg [perms]
+# perms: optional comma-separated list; if omitted, revokes all
+#
+function revoke_deleg
+{
+ typeset dataset=$1
+ typeset uid=$2
+ typeset perms=${3:-}
+ if [[ -n "$perms" ]]; then
+ zfs unallow -u "$uid" "$perms" "$dataset"
+ else
+ zfs unallow -u "$uid" "$dataset"
+ fi
+}