Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 04 Jul 2026 18:24:49 +0000
From:      bugzilla-noreply@freebsd.org
To:        bugs@FreeBSD.org
Subject:   [Bug 296521] bin/pfctl: pf's netlink conversion blocks non-VNET jails from using pfctl (no allow.pf / RTNL_F_ALLOW_NONVNET_JAIL equivalent)
Message-ID:  <bug-296521-227@https.bugs.freebsd.org/bugzilla/>

index | next in thread | raw e-mail

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.

home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?bug-296521-227>