------------------------------------------
Description of issue
------------------------------------------
When pfctl's ruleset optimizer (enabled by default) fails to create an
__automatic_* table — for example because the kernel table limit is
exhausted — the error is printed but execution continues and the
incomplete ruleset is committed to the kernel via DIOCXCOMMIT. This
leaves the firewall in a broken state, silently passing or mishandling
traffic that the intended ruleset would have blocked.

The root cause is in pfctl_load_ruleset() in sbin/pfctl/pfctl.c. The
return value of pfctl_optimize_ruleset() is discarded, so its failure
does not trigger the existing error recovery path (goto error) that
would instead issue DIOCXROLLBACK and abort the load.

Every other fallible call in pfctl_load_ruleset() correctly checks its
return value and branches to the error label on failure. The optimizer
call is the sole exception.

------------------------------------------
Reproduction
------------------------------------------
Configure a pf.conf whose ruleset causes the optimizer to generate
more tables than the kernel table limit allows (default 1000). I was able
reproduce the error condition with a pf.conf file that defines 500 tables
along with one or more rules that result in automatically  generated
tables. Automatically generated tables are observed as the proximate
trigger for the error condition in this case as seen in the verbose log
output. While reloading the ruleset, the default 1000 table limit is exceeded.

Observed output (with instrumented pfctl logging DIOCX* calls):

# /usr/obj/sbin/pfctl/pfctl-debug-trans -vv -f /etc/pf.conf.bad
...
table <blocked_ips> persist file "/etc/pf_table_blocked.conf"
table <__automatic_71e4967e_0> const { 142.251.150.119 142.251.151.119 
142.251.152.119 142.251.153.119 142.251.154.119 142.251.155.119 142.251.156.119 
142.251.157.119 9.9.9.9 }
pfctl-debug-trans: failed to create table __automatic_71e4967e_1 in : Cannot 
allocate memory
[trans] >>> DIOCXCOMMIT: 2 element(s)
[trans]       [0] RULESET anchor="(root)"
[trans]       [1] TABLE   anchor="(root)"
[trans] <<< DIOCXCOMMIT OK

The incomplete ruleset is committed. Rules referencing the missing
__automatic_* table operate incorrectly or not at all, resulting in
traffic bypassing the intended firewall policy.

------------------------------------------
Expected behavior
------------------------------------------
The failure of pfctl_optimize_ruleset() should abort the ruleset load.
The kernel transaction should be rolled back via DIOCXROLLBACK, leaving
the previously active ruleset intact.

------------------------------------------
Patch
------------------------------------------
--- pfctl.c.orig        Fri Mar 13 05:23:23 2026
+++ pfctl.c     Fri Mar 13 05:25:44 2026
@@ -1527,7 +1527,8 @@
        }

        if (pf->optimize)
-               pfctl_optimize_ruleset(pf, rs);
+               if ((error = pfctl_optimize_ruleset(pf, rs)) != 0)
+               goto error;

        while ((r = TAILQ_FIRST(rs->rules.active.ptr)) != NULL) {
                TAILQ_REMOVE(rs->rules.active.ptr, r, entries);

------------------------------------------
Explanation
------------------------------------------
pfctl_optimize_ruleset() is declared as returning int in pfctl_parser.h
and returns non-zero on failure (e.g. from pfctl_define_table() failing
in pfctl_optimize.c). The existing error: label in pfctl_load_ruleset()
propagates the error back to pfctl_rules(), which checks the return
value of pfctl_load_ruleset() and on failure branches to its own _error
label, which explicitly calls pfctl_trans(dev, t, DIOCXROLLBACK, osize)
to roll back the kernel transaction, leaving the previously active
ruleset intact.

------------------------------------------
Patch verification
------------------------------------------
# /usr/obj/sbin/pfctl/pfctl-debug-trans-patched -vv -f /etc/pf.conf.bad
...
table <blocked_ips> persist file "/etc/pf_table_blocked.conf"
table <__automatic_e5eca4d2_0> const { 142.251.150.119 142.251.151.119 
142.251.152.119 142.251.153.119 142.251.154.119 142.251.155.119 142.251.156.119 
142.251.157.119 9.9.9.9 }
pfctl-debug-trans-patched: failed to create table __automatic_e5eca4d2_1 in : 
Cannot allocate memory
pfctl-debug-trans-patched: Unable to load rules into kernel
[trans] >>> DIOCXROLLBACK: 2 element(s)
[trans]       [0] RULESET anchor="(root)"
[trans]       [1] TABLE   anchor="(root)"
[trans] <<< DIOCXROLLBACK OK

The previously active ruleset remains enforced.

Reply via email to