pfr_add_tables() allocates the ktable and bumps rs->tables BEFORE the duplicate check of a DIOCRADDTABLES batch: duplicate element leaks the table and permanently desynchronizes the ruleset count (DIOCRGETTABLES -> ENOTTY until reboot)
Summary
When one DIOCRADDTABLES batch contains the same table twice, pfr_create_ktable() (kmalloc ktable + 2 radix heads + rs->tables++) has already run for the second element when the addq duplicate scan hits 'goto _skip'; the fresh table is neither inserted nor freed - on both the live and DUMMY paths. rs->tables drifts +1 per leak while the RB tree does not, so every later pfr_get_tables() ends with n>0, prints 'pfr_get_tables: corruption detected (2).' and returns ENOTTY. VERIFIED: one ioctl pair leaks ~1.4-2.8KB (kernel complains 'malloc_uninit: 2816 bytes of pfrktable still allocated' at kldunload) and breaks 'pfctl -s Tables' for the ruleset until reboot - persistent admin-plane DoS plus repeatable unbounded leak. Root-only but 100% deterministic. Fix: duplicate scan before create (or unwind on skip).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3339 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| duptable_leak.c | trigger-source | DIOCRADDTABLES with duplicate batch + DIOCRGETTABLES probe | 2.8 KB | view raw |
| run.log | run-log | full deterministic output incl. vmstat + dmesg | 647 B | view raw |
| verdict.json | verdict | machine verdict | 2.3 KB | view raw |
Fix verification
not_testableConfirmed kernel references
Detail
Exploit chain
root sends one DIOCRADDTABLES containing [T,T] -> ktable leak + rs->tables drift -> all table listing ioctls for the ruleset return ENOTTY until reboot (admin-plane DoS) + unbounded repeatable leak.
Evidence (decisive lines)
['run.log: [1] nadd=1, [3] DIOCRGETTABLES rc=-1 errno=25, dmesg corruption line, vmstat 3 allocs', 'kernel source pf_table.c:1170-1182 (create-before-dup-check)']
PoC changes
added #include
Verified recommended fix
In pfr_add_tables(), scan addq for the duplicate before pfr_create_ktable() (or destroy the fresh table and decrement rs->tables on the _skip path).
Verdict
REPRODUCED (deterministic): pfr_add_tables() allocates the ktable and increments rs->tables BEFORE scanning addq for duplicates, then goto _skip discards the pointer on a duplicate batch element. Two identical pfr_table entries in one DIOCRADDTABLES leak ~1.4KB (vmstat: 3 pfrktable allocs for 1 usable table; kldunload: 'malloc_uninit: 2816 bytes of pfrktable still allocated') and permanently drift pf_main_ruleset.tables vs the RB tree, so every subsequent DIOCRGETTABLES prints 'pfr_get_tables: corruption detected (2).' and returns ENOTTY - pfctl -s Tables fails until reboot. Root-only, deterministic; Low severity.
No comments yet.