Skip to content
FreeBSDDeep Dive Published Updated 8 min readViews unavailable

FreeBSD ZFS Delegated Permissions: Scope, Inheritance, and Revocation

Grant ZFS administration rights on FreeBSD by dataset, descendant scope, and permission set, then audit effective grants and revoke without assuming a deny rule.

zfs allow lets a storage administrator delegate selected ZFS operations without handing an account the pool’s root credentials. The important boundary is the dataset tree: a grant can apply to one dataset, only its descendants, or both. A later zfs unallow removes a grant; it does not create a deny rule that overrides another grant higher in the tree.

That makes delegation useful for tenant datasets, team-owned snapshots, and quota management, but it is not a general-purpose permission system. ZFS delegation answers whether an identity may perform a ZFS subcommand or change a ZFS property. It does not automatically grant ordinary filesystem access to a mountpoint or undo operations the user already performed.

Treat a grant as a small administrative capability

The permission names largely follow zfs subcommands and properties. A user granted snapshot can create snapshots, while quota or refquota controls the ability to change those properties. Some actions have prerequisites: the FreeBSD 15.1 manual says create also requires mount, and destroy and snapshot likewise require mount. The allow permission itself is bounded: the delegator must also possess the permission being delegated. A grant to delegate permissions therefore does not let a user invent authority they do not have.

Start with the operation the account needs, not with a broad set such as all. A snapshot operator may need snapshot and its required mount permission; they do not automatically need destroy, rollback, change-key, receive, or permission to manage other users’ grants. A quota operator can often be limited to the properties that team policy permits changing. Treat each addition as a new ability to consume capacity or affect recoverability, then validate the prerequisites against the manual page for the installed OpenZFS/FreeBSD version.

ZFS permissions and filesystem/OS mount checks are separate. The FreeBSD Handbook adds two requirements that a zfs allow listing cannot satisfy: creating a child dataset involves mounting it, so non-root mounts require vfs.usermount=1, and a non-root user must own the mountpoint where the filesystem will be mounted. vfs.usermount is an operating-system setting, not a per-dataset grant. Treat changing it as a host-wide security decision; a ZFS grant does not make that change for you. A zfs allow success is not evidence that the same account can traverse a mountpoint, read files, or satisfy every kernel-level mount restriction.

Choose the dataset scope deliberately

Without -d or -l, zfs allow grants permissions on the named filesystem or volume and all descendants. -l means the named dataset only; -d means descendants only. Supplying both has the same local-plus-descendant scope as omitting them. That distinction is easy to lose in a tree such as:

LABPOOL/
└── teams/
    ├── alice/
    └── bob/

A local grant on LABPOOL/teams does not mean “only Alice’s child.” A descendant-only grant at that parent potentially reaches both alice and bob and future descendants. If two teams have different trust boundaries, put the grant at the narrowest dataset node that matches the intended scope; do not rely on a name convention to narrow an ancestor grant.

The -c form handles a different case: it grants a permission locally to the creator of each newly created descendant filesystem. The documented pattern lets members of a group create and mount their own filesystems, then destroy the filesystems they create without gaining the ability to destroy a colleague’s dataset. This is a useful distinction between “may manage this tree” and “may manage the descendant I created.” It does not make the parent dataset disposable or make snapshot and mountpoint policies irrelevant.

Permission sets help keep repeated grants readable. Define a set with zfs allow -s @name ...; grant @name to a user or group; and update the set when policy changes. The manual says sets are evaluated dynamically, so changing the set immediately changes the permissions granted through it. They are not immutable snapshots of policy. Review every existing grant that references a set before editing it.

Build a disposable delegation lab

The following sequence assumes LABPOOL is already a disposable test pool and LAB_USER is a test account provisioned for that lab. It does not create a pool, change a production dataset, or modify the host’s pool. Do not substitute an important pool name. First inspect the exact tree and confirm the test target:

zpool list LABPOOL
zfs list -r -o name,mountpoint LABPOOL

As the lab administrator, create a test parent and child mountpoint. The child gives the test user an owned directory beneath which a further dataset can be mounted. Resolve the actual mountpoint before changing its ownership; do not assume a pool’s default path:

zfs create LABPOOL/teams
zfs create LABPOOL/teams/alice
ALICE_MOUNT=$(zfs get -H -o value mountpoint LABPOOL/teams/alice)
printf 'Review this disposable mountpoint before chown: %s\n' "$ALICE_MOUNT"

In the same root shell, after confirming that ALICE_MOUNT is the intended path in this disposable lab (and is neither none nor legacy), set its owner and grant only the needed capabilities. The dataset and mountpoint are new and empty; do not use this ownership change on an existing data directory:

chown "$LAB_USER" "$ALICE_MOUNT"
zfs allow -s @quota_ops quota,refquota,reservation LABPOOL/teams
zfs allow -l -u "$LAB_USER" create,mount LABPOOL/teams/alice
zfs allow -d -u "$LAB_USER" @quota_ops LABPOOL/teams
zfs allow LABPOOL/teams/alice

This grants the test user local create and mount on LABPOOL/teams/alice, while the quota set is limited to descendants of LABPOOL/teams. Before testing, record the existing kernel setting and decide whether this isolated lab may enable non-root mounts:

USERMOUNT_BEFORE=$(sysctl -n vfs.usermount)
printf 'Original vfs.usermount=%s\n' "$USERMOUNT_BEFORE"
# Only on an isolated test host, after reviewing the security impact:
sysctl vfs.usermount=1

The Handbook’s requirement applies in addition to ZFS delegation and mountpoint ownership. Do not enable this on a shared or production host merely to make the example pass; if the setting is changed for the lab, restore the recorded value after testing with sysctl vfs.usermount="$USERMOUNT_BEFORE" from the same root shell. Do not persist the change in /etc/sysctl.conf for this temporary test. Switch to the test account only inside the disposable lab and create one child dataset with a small quota, for example zfs create LABPOOL/teams/alice/home followed by zfs set refquota=1G LABPOOL/teams/alice/home. Do not test by granting destroy or rollback on a real tree.

For an audit, inspect the parent and each relevant descendant with zfs allow DATASET. Keep the observed listing with the change record: it shows the permission entries that explain a later success or denial. In the example, check LABPOOL/teams and LABPOOL/teams/alice; do not infer a child’s effective access from the parent’s command line alone. Also inspect which account or group name the command resolves to. -u and -g remove ambiguity when a user and group share a name, or when a name collides with the special keyword everyone.

Revoke the grant without mistaking it for rollback

In the lab, remove the local creation grant with the matching identity and scope:

zfs unallow -l -u "$LAB_USER" create,mount LABPOOL/teams/alice

If the quota set was granted to descendants in this lab, remove that descendant-scope grant from the tree with zfs unallow -d -r -u "$LAB_USER" @quota_ops LABPOOL/teams, then inspect the parent and children again with zfs allow. Use -d -r only when recursive descendant removal is intended; it modifies grant state on the named dataset’s descendants. The earlier sysctl block restores the recorded value; do not assume it was originally 0. Removing a permission does not deny the same permission if an ancestor, another group, everyone, or another local grant still supplies it. The manual explicitly describes zfs unallow as removal rather than denial.

Revocation also does not reverse the user’s prior work. It does not restore a changed quota, recreate a destroyed dataset, or reclaim space already written. A permission set edit can immediately affect users who hold that set, while unallow changes the grant references you target. After either kind of policy change, query the tree again, check the specific account and groups involved, and independently review data and property changes.

Failure triage

When an operation fails, first compare the requested subcommand/property with zfs allow output at the exact target dataset. Then check whether the grant is local or inherited from an ancestor, whether the user receives another grant through group membership, and whether a prerequisite such as mount is missing. On FreeBSD, separately inspect vfs.usermount and the owner of the intended mountpoint before changing the ZFS grant. Finally distinguish a ZFS authorization error from a mountpoint ACL or operating-system restriction. Increasing the grant to everyone is not a useful diagnostic shortcut because it expands the blast radius without identifying which layer denied access.

Keep the scope version-specific. The linked FreeBSD 15.1 manual is the basis for the examples here; the command page itself notes platform differences in OpenZFS delegation, including unavailable mount-related grants on Linux. Read zfs-allow(8) on the actual FreeBSD release before applying a policy to another release or OpenZFS port.

Related:

Sources:

Comments