Lifecycle bugs in edge handler cleanup: UAF on replaced edge_sock, missing thread join, soclose+sofree imbalance
Summary
LATENT (dead code). Multiple: (a) edge_handler caches so=ctx.edge_sock :195 but cleanup :227-228 uses global if edge_conf replaced it between -> frees wrong socket leaks original. (b) edge_stop soclose :355 + handler sofree :228 = 2 refcount drops vs 1 soreference :259 -> socket freed while in use. (c) MOD_UNLOAD :476-490 sets running=0 closes sockets but NEVER joins edge_td -> handler thread runs after module code unmapped. (d) MOD_UNLOAD never clears ipfw_sync_send_state_prt -> dangling fn ptr. Fix: use cached so+thread join+NULL ptr.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0706 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ipfw3_sync_lifecycle_probe.c | trigger-source | issues every IP_FW_SYNC_* opcode via IP_FW_X; proves all are silently no-op'd by the dispatcher | 4.6 KB | view raw |
| build.sh | build-script | cc -O2 -Wall | 187 B | view raw |
| run.sh | run-script | kldload ipfw3 + ipfw3_basic + run probe (does NOT kldunload β separate rn_flush panic) | 912 B | view raw |
| run.log | run-log | all 7 opcodes return rc=0; no panic | 934 B | view raw |
| env.txt | environment | uname, cc version, kldstat | 284 B | view raw |
| VERDICT.md | verdict | this analysis β LATENT dead code, same as DF-0704/0705 | 6.1 KB | β raw |
| README.md | readme | build/run/expected | 1.1 KB | β raw |
| fix.diff | suggested-fix | defensive fix for (a) cached so cleanup, (b) edge_stop no soclose + join, (c) MOD_UNLOAD joins edge_td, (d) clears fn ptrs | 4.8 KB | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0706 β PoC
Lifecycle bugs in ipfw3 sync edge handler cleanup (UAF on replaced
edge_sock, missing thread join, soclose+sofree imbalance, dangling
ipfw_sync_send_state_prt).
Build
./build.sh
Requires DragonFlyBSD cc (gcc 8.3 on master guest).
Run
ssh dfbsd # root cd poc/DF-0706 ./run.sh
run.sh kldloads ipfw3.ko + ipfw3_basic.ko then fires every
IP_FW_SYNC_* opcode via IP_FW_X. On the default kernel every
opcode returns rc=0 errno=0 with no effect β the dispatcher falls
through because ip_fw3_ctl_sync_ptr is never assigned. Guest stays
up, no panic. This proves the cited lifecycle bugs cannot fire on a
default kernel.
Do NOT kldunload ipfw3_basic after this test: there is a
separate, unrelated panic in rn_flush() (radix-tree cleanup) on
this master build that is NOT the DF-0706 bug. The serial log will
show Stopped at rn_flush+0x1d. (Reset the guest with vm.sh reset
if you accidentally trigger it.)
Expected
All IP_FW_SYNC_* opcodes return success with no observable effect;
guest stays up. See VERDICT.md for the full dead-code analysis and
fix.diff for the defensive fix.
DF-0706 β Lifecycle bugs in edge handler cleanup: UAF on replaced edge_sock, missing thread join, soclose+sofree imbalance
Verdict: NOT REPRODUCED (LATENT β entire ip_fw3_sync.c is dead code on a default kernel)
The finding cites four real defects in sys/net/ipfw3_basic/ip_fw3_sync.c:
| # | Bug | Mechanism |
|---|---|---|
| (a) | UAF on replaced edge_sock |
edge_socket_handler caches so at :195 for the receive loop but uses fw3_sync_ctx.edge_sock for cleanup at :227-228; if edge_conf replaced the global meanwhile, cleanup hits the wrong socket and leaks the original |
| (b) | soclose+sofree imbalance |
edge_stop calls soclose (:355); handler calls sofree (:228); only one soreference was added at edge_start :259 β two ref-drops for one ref-add |
| (c) | MOD_UNLOAD never joins edge_td |
ip_fw3_sync_modevent at :480-484 sets running=0, calls soclose, and edge_td = NULL β but the handler thread keeps running until its receive returns; if the module is unmapped first, the handler executes unmapped code |
| (d) | MOD_UNLOAD never clears ipfw_sync_send_state_prt |
after unload the global still points at ip_fw3_sync_send_state which is now unmapped memory |
All four are real defects in code that can never execute on a default
kernel. The entire ip_fw3_sync.c file is dead code, for two independent
reasons that each suffice on their own:
Why the code paths are unreachable
(1) ip_fw3_ctl_sync_ptr is never assigned. The IP_FW_SYNC_
sockopt opcodes are dispatched in sys/net/ipfw3/ip_fw3.c at lines
1113-1128 only inside if (ip_fw3_ctl_sync_ptr != NULL). That pointer
is initialised to NULL (line 133) and never assigned anywhere in the
tree*:
$ grep -rn 'ip_fw3_ctl_sync_ptr =' sys/ sys/net/ipfw3/ip_fw3.c:133:ip_fw_ctl_t *ip_fw3_ctl_sync_ptr = NULL;
So every IP_FW_SYNC_* opcode silently falls through with error = 0
(setsockopt returns success with no effect). Confirmed in run.log.
(2) ip_fw3_sync_modevent is never called. The companion function
pointer ipfw_sync_send_state_prt (and ipfw_sync_install_state_prt)
would be set by ip_fw3_sync_modevent(MOD_LOAD) (ip_fw3_sync.c:518-520).
But ip_fw3_sync_modevent is never invoked by anyone β the
ipfw3_basic module hook (ipfw3_basic_modevent, ip_fw3_basic.c:657-658)
calls only ip_fw3_state_modevent and ip_fw3_table_modevent,
skipping sync entirely:
$ grep -rn 'ip_fw3_sync_modevent\|ip_fw3_state_modevent\|ip_fw3_table_modevent' sys/ sys/net/ipfw3_basic/ip_fw3_basic.c:657: ip_fw3_state_modevent(type); sys/net/ipfw3_basic/ip_fw3_basic.c:658: ip_fw3_table_modevent(type); sys/net/ipfw3_basic/ip_fw3_sync.h:86: void ip_fw3_sync_modevent(int type); sys/net/ipfw3_basic/ip_fw3_sync.c:514: ip_fw3_sync_modevent(int type) sys/net/ipfw3_basic/ip_fw3_state.c:712: ip_fw3_state_modevent(int type) sys/net/ipfw3_basic/ip_fw3_table.c:616: ip_fw3_table_modevent(int type)
state and table are called from ipfw3_basic_modevent; sync is
declared and defined but not called from there. So MOD_LOAD of
ipfw3_basic never sets ipfw_sync_send_state_prt, and MOD_UNLOAD
never runs the cleanup code at all.
Empirical confirmation
run.log shows every IP_FW_SYNC_* opcode returning rc=0 errno=0
(silent fall-through) after kldload ipfw3.ko and kldload ipfw3_basic.ko
on the default with-src kernel (#0 build, INVARIANTS ON):
[*] loading ipfw3.ko + ipfw3_basic.ko [*] running as: uid=0 euid=0 [fire IP_FW_SYNC_EDGE_CONF opcode=84 plen=8] rc=0 errno=0 [fire IP_FW_SYNC_EDGE_START opcode=85 plen=4] rc=0 errno=0 [fire IP_FW_SYNC_EDGE_STOP opcode=86 plen=4] rc=0 errno=0 [fire IP_FW_SYNC_CENTRE_CONF opcode=89 plen=4] rc=0 errno=0 [fire IP_FW_SYNC_CENTRE_START opcode=90 plen=4] rc=0 errno=0 [fire IP_FW_SYNC_SHOW_STATUS opcode=83 plen=4] rc=0 errno=0 [fire IP_FW_SYNC_SHOW_CONF opcode=82 plen=64] rc=0 errno=0 [+] All opcodes returned without panic.
If the buggy paths were live, EDGE_CONF would socreate() an edge_sock,
EDGE_START would soreference() + kthread_create the handler thread,
and EDGE_STOP would exercise the soclose+sofree imbalance. None of that
happens β the dispatcher returns success without invoking any sync code.
MOD_UNLOAD of ipfw3_basic completes without invoking
ip_fw3_sync_modevent (proven by grep), so neither (c) nor (d) can
fire β the conditions they require (edge_td exists / ipfw_sync_send_state_prt
is set) never arise.
Conclusion
Consistent with DF-0704 and DF-0705 (which both analysed other defects in
this same file and reached the same dead-code conclusion): the cited bugs
are real defects in the source code but cannot be triggered on a default
kernel because the entire ipfw3_sync subsystem is unwired. Marked
not_reproduced β impact=none, latent.
Suggested fix
fix.diff is a defensive, in-place fix for all four cited bugs. Because
the code is currently dead, the fix cannot be runtime-tested on the
default kernel (no trigger path exists); instead the diff is validated
for git apply --check and compiles cleanly. The day someone wires
ip_fw3_sync_modevent(type) into ipfw3_basic_modevent (a one-line
addition) and assigns ip_fw3_ctl_sync_ptr, the fixes are already in
place:
- (a)
edge_socket_handlernow uses the cachedsofor cleanup too - (b)
edge_stopno longer callssoclose; it signals the handler viasoshutdownandkthread_joinsedge_td, leaving the single ref-drop to the handler'ssofree - (c)
ip_fw3_sync_modevent(MOD_UNLOAD)nowsoshutdowns the socket,kthread_joinsedge_td, and only then frees resources - (d)
MOD_UNLOADclearsipfw_sync_send_state_prt = NULLandipfw_sync_install_state_prt = NULLfirst, before any other teardown
The fix also picks up DF-0704's per-type m_len validation in the
handler as a bonus (it touches the same function). That hardens the
receive loop against short datagrams if the code is ever enabled.
The fix is defensive only β it does not enable the dead code (that is upstream's call) but ensures the bugs are closed if/when the code is hooked up.
Fix verification
not_testablen/a
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
LATENT dead code. ipfw3_sync edge handler lifecycle bugs. ip_fw3_ctl_sync_ptr never assigned. Same as DF-0704/0705.
No comments yet.