[Bug 296521] bin/pfctl: pf's netlink conversion blocks non-VNET jails from using pfctl (no allow.pf / RTNL_F_ALLOW_NONVNET_JAIL equivalent)
Date: Sat, 04 Jul 2026 18:24:49 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=296521
Bug ID: 296521
Summary: bin/pfctl: pf's netlink conversion blocks non-VNET
jails from using pfctl (no allow.pf /
RTNL_F_ALLOW_NONVNET_JAIL equivalent)
Product: Base System
Version: Unspecified
Hardware: Any
OS: Any
Status: New
Severity: Affects Some People
Priority: ---
Component: kern
Assignee: bugs@FreeBSD.org
Reporter: laszlo-rdr-keethu9thi@karolyi.hu
I've upgraded from 14.4 to 15.1 and hit this. After a couple hours of
investigating an interacting on the #freebsd IRC channel, I was asked to make a
summary of what I've bumped into. This is the condensed summary of it.
Environment:
Introduced in 15.0-RELEASE, unchanged through 15.1-RELEASE and head (2026-07).
sys/netpfil/pf/pf_nl.c does not exist in 14.4; it exists in 15.0 already with
the restriction described below, so this is a 15.0 regression, not 15.1.
(Distinct from the securelevel/EPERM issue some hit after upgrading jails to
15.1 -- commit 47c12f20bf58/D56390, added between 15.0 and 15.1. That gates
on securelevel and affects VNET jails too. This report is about a separate,
unconditional gate that blocks non-VNET jails regardless of securelevel.)
Description:
15.0 converted pfctl/libpfctl from ioctl(/dev/pf) to netlink(4) (generic
netlink family "pf", sys/netpfil/pf/pf_nl.c). Every command in pf_cmds[]
sets `.cmd_priv = PRIV_NETINET_PF`, enforced unconditionally in
genl_handle_message() (sys/netlink/netlink_generic.c):
if (cmd->cmd_priv != 0 && !nlp_has_priv(nlp, cmd->cmd_priv))
return (EPERM);
prison_priv_check() (sys/kern/kern_jail.c) only grants PRIV_NETINET_PF to
jails with their own vnet (`pr_flags & PR_VNET`); there is no jail
permission that grants it to a non-VNET jail. Result: pfctl inside any
non-VNET jail now fails (EPERM) on essentially all operations -- rule
load, start/stop, tables, kill-states -- even with /dev/pf exposed via
devfs_ruleset, securelevel 0, and jailed root.
The old ioctl(/dev/pf) path (pf_ioctl.c) never called priv_check() at all;
access was gated purely by opening /dev/pf. That's why this worked
through 14.x for non-VNET jails, and why it's a regression rather than a
pre-existing restriction.
This is the same VNET-only restriction that routing sockets hit for
PRIV_NET_ROUTE -- and which *was* given a fix: commit 3a53fe2cc4b7
("jail: add allow.routing jail permission", D49843) added a
PR_ALLOW_ROUTING permission plus an RTNL_F_ALLOW_NONVNET_JAIL command
flag, checked in rtnl_handle_message() (sys/netlink/netlink_route.c), so
non-VNET jails can be opted in per command. pf's genl_cmd struct has no
equivalent flag, and no PR_ALLOW_PF exists.
How-To-Repeat:
1. Non-VNET jail, /dev/pf exposed via devfs_ruleset.
2. Inside jail: pfctl -e (or pfctl -f pf.conf)
3. Get: pfctl: DIOCSTART (or DIOCADDRULE, etc.): Operation not permitted.
4. Same command from host, or from a VNET jail: succeeds.
Suggested Fix (modeled on D49843):
- Add PR_ALLOW_PF jail permission (sys/sys/jail.h, jail.conf(5)).
- Add a command flag to struct genl_cmd (sys/netlink/netlink_ctl.h),
checked in genl_handle_message() alongside cmd_priv/cmd_securelevel, to
let PR_ALLOW_PF satisfy cmd_priv for non-VNET jails.
- Tag the relevant pf_cmds[] entries in pf_nl.c with it -- likely
everything currently gated by PRIV_NETINET_PF, restoring what non-VNET
jails could already do via the old ioctl path pre-15.0.
- Document clearly: since non-VNET jails share the host's vnet0, this
permission lets the jail rewrite the *host's* actual pf ruleset/state/
tables, not an isolated copy -- same caveat as allow.routing, worth
flagging given pf is often used for jail-facing security tooling
(e.g. fail2ban-style jails reacting to hostile input).
Workarounds in the meantime:
- Convert the jail to VNET (isolated pf instance, no gap, but requires
epair/bridge rearchitecting).
- Manage the jail's pf rules from the host instead (anchor + `pfctl -a
jailname`, or a host-side proxy acting on the jail's behalf).
- Keeping an old (14.4) pfctl binary in the jail to keep using the ioctl
path works but is unsupported/fragile and re-opens the same privilege
gap for jails handling untrusted input -- not recommended.
--
You are receiving this mail because:
You are the assignee for the bug.