β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0700

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(&centre_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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0700 Β· 11 files
FileTypeDescriptionSize
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
README.md readme human reproduce doc
↓ download 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.

VERDICT.md verdict source trace + dead-code proof + fix
↓ download raw

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.c is 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(..., &centre_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.diff applies 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.c compiles into ipfw3_basic.ko with -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)

Fix verification

not_testable

compile 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.