User-controlled edge count not bounded by MAX_EDGES β BSS OOB array write/read on centre_socks[]
Summary
LATENT: ip_fw3_ctl_sync_ptr never assigned (dead code) but file compiled into module. ip_fw3_ctl_sync_centre_conf :137-153: ioc_centre->count from user sockopt NO check vs MAX_EDGES(10). Stored as fw3_sync_ctx.count :150. centre_start :279-280 for(i=0;i<count;i++) socreate(¢re_socks[i]) i=10 writes past struct socket *[MAX_EDGES=10] into adjacent BSS. Same OOB at centre_test :340 centre_stop :368 send_state :459 MOD_UNLOAD :488. Written value is kernel heap ptr (socreate result). Attacker: raw-socket-capable (root). Fix: if(count<0||count>MAX_EDGES) return EINVAL.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0700 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ipfw3_sync_deadpath.c | trigger-source | empirical dead-path demo (sockopt returns ENOPROTOOPT) | 4.0 KB | view raw |
| build.sh | build-script | cc -o ipfw3_sync_deadpath ... | 292 B | view raw |
| run.sh | run-script | issue the SYNC_CENTRE_CONF sockopt | 391 B | view raw |
| VERDICT.md | verdict | source trace + dead-code proof + fix | 7.5 KB | β raw |
| fix.diff | suggested-fix | MAX_EDGES bounds check in centre_conf | 680 B | view raw |
| run.log | run-log | dead-path demo output (ENOPROTOOPT) | 399 B | view raw |
| env.txt | environment | uname, cc version | 365 B | view raw |
| README.md | readme | human reproduce doc | 197 B | β 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 |
| live_reachability_check.txt | reachability-test | Live ipfw3_sync reachability evidence - dead code (ptr=NULL) | 1.2 KB | view raw |
DF-0700 β PoC evidence pack
See VERDICT.md for the full analysis (verdict, mechanism, fix, fix-validation).
Reproduce: ./build.sh && ./run.sh. Machine-readable catalog: manifest.json.
DF-0700 β User-controlled edge count not bounded by MAX_EDGES β BSS OOB array write on centre_socks[]
Note on the run brief: the task brief labels DF-0700βDF-0703 as "Bluetooth BNEP." They are not β the cited file
sys/net/ipfw3_basic/ip_fw3_sync.cis the ipfw3 firewall state-synchronization sub-component (nothing to do with Bluetooth). The analysis below is for the actual ipfw3_sync code.
Verdict
NOT REPRODUCED β LATENT dead code. The bug is real and unambiguous at the
source level, but the vulnerable function is unreachable from userspace on
this kernel because the sockopt dispatcher that would call it is never wired up
(ip_fw3_ctl_sync_ptr is NULL and never assigned anywhere in the tree). This is
a valid hard blocker (dead/unreachable at runtime); the fix is defense-in-depth
for when the code is eventually wired up.
The bug (source-level trace)
ip_fw3_ctl_sync_centre_conf() (sys/net/ipfw3_basic/ip_fw3_sync.c:137-154) takes
ioc_centre->count straight from the user sockopt value with no bounds check
against MAX_EDGES (10):
ip_fw3_ctl_sync_centre_conf(struct sockopt *sopt)
{
struct ipfw3_ioc_sync_centre *ioc_centre;
int size;
ioc_centre = sopt->sopt_val;
size = ioc_centre->count * LEN_SYNC_EDGE; /* :143 β no bound */
...
fw3_sync_ctx.count = ioc_centre->count; /* :150 β stored unvalidated */
bcopy(ioc_centre->edges, fw3_sync_ctx.edges, ...); /* :151 */
}
fw3_sync_ctx.count then drives array indexing over
fw3_sync_ctx.centre_socks[MAX_EDGES] (ip_fw3_sync.h:77, a struct socket *[10]
that is the last member of struct ipfw3_sync_context), in five places, all
with for (i = 0; i < fw3_sync_ctx.count; i++) and no per-index bound:
| site | what it writes past centre_socks[10] |
line |
|---|---|---|
ip_fw3_ctl_sync_centre_start |
socreate(..., ¢re_socks[i], ...) β a kernel heap pointer (a new struct socket *) |
ip_fw3_sync.c:279-286 |
ip_fw3_ctl_sync_centre_test |
reads centre_socks[i] to sosend |
:339-345 |
ip_fw3_ctl_sync_centre_stop |
soclose(centre_socks[i]) |
:367-369 |
ip_fw3_sync_send_state |
so_pru_sosend(centre_socks[i], ...) |
:458-464 |
ip_fw3_sync_modevent (MOD_UNLOAD) |
soclose(centre_socks[i]) |
:487-489 |
With count > 10, centre_start writes socreate's socket-pointer result past the
end of centre_socks[] β i.e. past the end of the global fw3_sync_ctx BSS symbol
into whatever follows it in BSS. That is an attacker-controlled-count, fixed-stride
OOB write of a kernel heap pointer (CVSS C:N/I:H/A:H). The attacker can shape count
to target a specific BSS offset.
Why it is unreachable on this kernel (dead-code proof)
The whole centre_* sockopt-handler family is only callable through the dispatcher
ip_fw3_ctl_sync_sockopt() (ip_fw3_sync.c:390-435). That dispatcher is itself only
reachable through the function pointer ip_fw3_ctl_sync_ptr, dispatched in
ip_fw3_ctl():
case IP_FW_SYNC_CENTRE_CONF: /* sys/net/ipfw3/ip_fw3.c:1120 */
...
case IP_FW_SYNC_CENTRE_CLEAR: /* :1124 */
if (ip_fw3_ctl_sync_ptr != NULL) { /* :1125 */
error = ip_fw3_ctl_sync_ptr(sopt); /* :1126 */
}
break;
ip_fw3_ctl_sync_ptr is initialized to NULL (ip_fw3.c:133) and is never
assigned anywhere in the entire sys/ tree (grep-verified:
$ 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; sys/net/ipfw3/ip_fw3.c:1125: if (ip_fw3_ctl_sync_ptr != NULL) { sys/net/ipfw3/ip_fw3.c:1126: error = ip_fw3_ctl_sync_ptr(sopt);
β only the NULL init and the NULL-check; no assignment). So the dispatch is always
skipped; every IP_FW_SYNC_* sockopt is a silent no-op. centre_conf (and thus
the OOB) is never called.
There is also a second layer of unreachability: the base kernel ships only the ipfw3
stub ip_fw3_glue.c (ip_fw3_sockopt returns ENOPROTOOPT until
kldload ipfw3). The full ip_fw3.c (with ip_fw3_ctl) and ip_fw3_sync.c (the
buggy file) live in loadable modules that are not loaded by default
(kldstat | grep ipfw is empty). So even reaching the IP_FW_SYNC_* cases needs a
root kldload.
Empirical confirmation (ipfw3_sync_deadpath):
setsockopt(IPPROTO_IP, IP_FW_X, {SYNC_CENTRE_CONF, count=9999}) = -1
errno=42 (Protocol not available) # ENOPROTOOPT β module not loaded
Issuing the sockopt that would trigger the bug returns ENOPROTOOPT on the default
kernel; the SYNC_* dispatch is never entered. (And even after kldload ipfw3,
ip_fw3_ctl_sync_ptr stays NULL, so the dispatch is still skipped.)
To make the bug live would require a source change assigning
ip_fw3_ctl_sync_ptr = ip_fw3_ctl_sync_sockopt in the module's MOD_LOAD β not an
attacker action. This is valid hard blocker (d): dead/unreachable at runtime on this
guest, with no harness exercisable by an unprivileged user (a harness needs
kldload, which is root β root:kernel, not unprivβroot).
Fix (defense-in-depth)
Validate ioc_centre->count against MAX_EDGES in centre_conf, so the OOB
array indexing in centre_start/test/stop/send_state/MOD_UNLOAD becomes impossible
whenever the code is wired up. See fix.diff:
ioc_centre = sopt->sopt_val;
if (ioc_centre->count < 0 || ioc_centre->count > MAX_EDGES) /* +DF-0700 */
return EINVAL;
size = ioc_centre->count * LEN_SYNC_EDGE;
The root fix is for the maintainer to decide: either wire up
ip_fw3_ctl_sync_ptr (and then this bounds check is mandatory) or delete the dead
sync code. Either way, this fix.diff makes the function safe.
Fix validation
fix.diffapplies cleanly to the original source (all hunks succeed).- Compile-validated: applied alongside DF-0701/0702/0703's fixes (DF-0701 supersedes
this check for the combined build),
make -j6 nativekernel KERNCONF=X86_64_GENERICβ rc=0,ip_fw3_sync.ccompiles intoipfw3_basic.kowith-Werror. fix_status: not_testableβ the buggy path is dead code; the fix cannot be exercised from userspace. Validated applies + compiles + traced that it closes the unbounded-index path.
Files
| file | desc |
|---|---|
ipfw3_sync_deadpath.c |
empirical dead-path demo (issues the sockopt, shows ENOPROTOOPT) |
fix.diff |
MAX_EDGES bounds check in centre_conf |
run.log |
dead-path demo output (ENOPROTOOPT) |
env.txt |
guest environment |
Kernel references (confirmed)
sys/net/ipfw3_basic/ip_fw3_sync.c:137-154βip_fw3_ctl_sync_centre_conf(no bounds check)sys/net/ipfw3_basic/ip_fw3_sync.c:150βfw3_sync_ctx.count = ioc_centre->count(stored unvalidated)sys/net/ipfw3_basic/ip_fw3_sync.c:279-286βcentre_startOOBsocreateintocentre_socks[i]sys/net/ipfw3_basic/ip_fw3_sync.c:339-345βcentre_testOOB read ofcentre_socks[i]sys/net/ipfw3_basic/ip_fw3_sync.c:367-369βcentre_stopOOBsoclosesys/net/ipfw3_basic/ip_fw3_sync.c:458-464βsend_stateOOBso_pru_sosendsys/net/ipfw3_basic/ip_fw3_sync.c:487-489βMOD_UNLOADOOBsoclosesys/net/ipfw3_basic/ip_fw3_sync.h:77βcentre_socks[MAX_EDGES]is last field of the structsys/net/ipfw3/ip_fw3.c:133βip_fw3_ctl_sync_ptr = NULL(never assigned β root cause of dead path)sys/net/ipfw3/ip_fw3.c:1125-1126β dead dispatchsys/net/ipfw3/ip_fw3_glue.c:51-62β base-kernel stub returns ENOPROTOOPT until kldload
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
Detail
Exploit chain
none β dead code. The dispatch pointer is NULL and the functions aren't compiled into the module.
Evidence (decisive lines)
ip_fw3_ctl_sync_ptr: 00000000000028f8 B ip_fw3_ctl_sync_ptr (BSS, NULL) centre_conf/centre_start/centre_test in ipfw3.ko: NOT FOUND dispatch check: if (ip_fw3_ctl_sync_ptr != NULL) -> always FALSE
PoC changes
Added live_reachability_check.txt with nm evidence.
Verified recommended fix
fix.diff validates ioc_centre->count against MAX_EDGES(10). If the sync module were ever activated, this fix should be applied.
Verdict
NOT REPRODUCED (dead code). Live verification: ip_fw3_ctl_sync_ptr is in ipfw3.ko BSS at offset 0x28f8, initialized to NULL (type B = zero-initialized). It is NEVER assigned anywhere in sys/ (grep for non-NULL assignment returned 0 results). The dispatch check at ip_fw3.c:1125 (if (ip_fw3_ctl_sync_ptr != NULL)) always evaluates FALSE. The sync functions (centre_conf, centre_start, centre_test, etc.) are NOT in the compiled ipfw3.ko module symbols (nm returned 0 matches for these functions). The sockopt IP_FW_SYNC_CENTRE_CONF is never dispatched.
No comments yet.