in6_pcbsetlport() divide-by-zero on degenerate ephemeral port range β DF-0334's unfiled IPv6 twin (reproduced: one unpriv AF_INET6 bind panics)
Summary
in6_pcbsetlport() computes lport = last - first after in_pcbportrange() and divides by it. The sysctl accepts first==last independently (no cross-check); in_pcbportrange() collapses the range to last==first on the portinfo stratum with offset==0 when V % netisr-ncpus == 0, yielding lport==0 and cut % 0 -> integer-divide trap -> panic. Any AF_INET6 ephemeral port allocation (bind(::,0)) on that CPU triggers it unprivileged. DF-0334 owns the IPv4 sites and its fix patched only in_pcb.c:884 - the IPv6 site was never filed. VERIFIED on stock INVARIANTS guest: sysctl first=last=1032, then 12 unpriv threads spinning AF_INET6/UDP bind(::,0) -> 'Fatal trap 18 ... Stopped at in6_pcbsetlport+0x126: divl %esi,%eax' in <1s, exactly the cut % lport on CPU0's stratum. Pure DoS. Fix authored (zero guard mirroring DF-0334's validated sibling fix).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3424 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df3424_bind6.c | β | 1.6 KB | view raw | |
| build.sh | β | 98 B | view raw | |
| run.sh | β | 128 B | view raw | |
| run.log | β | 105 B | view raw | |
| panic.txt | β | 577 B | view raw | |
| env.txt | β | 319 B | view raw | |
| fix.diff | β | 438 B | view raw | |
| code_hash.txt | β | 81 B | view raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| verdict.json | β | 2.5 KB | view raw |
DF-3424 VERDICT
Status: REPRODUCED (deterministic, 1 attempt, <1 s from sysctl to panic)
What was run
./build.sh && ./run.sh on the QEMU guest (DragonFly 6.5-DEVELOPMENT #0,
stock INVARIANTS kernel, net.netisr.ncpus=6):
sysctl net.inet.ip.portrange.first=1032/last=1032(root).- Trigger executed as unprivileged user
maxx: 12 threads spinning AF_INET6/UDPbind(::, port 0).
Observation
Guest panicked immediately; serial console (panic.txt):
Fatal trap 18: integer divide fault while in kernel mode cpuid = 0; lapic id = 0 ... Stopped at in6_pcbsetlport+0x126: divl %esi,%eax
Why (line-by-line)
sys/netinet6/in6_src.c:472βin_pcbportrange(&last, &first, ofs, step)with step=6: with first==last==1032 and ofs==0 (CPU0's portinfo stratum): hi=rounddown(1032,6)+0=1032 (<=1032, kept); lo=roundup(1032,6)-(6-0)=1026, 1026<1024? no wait 1026<1032 -> lo += 6 -> 1032. => first=last=1032.in6_src.c:473βlport = last - first= 0.in6_src.c:474βcount = lport / step= 0 (0/6, no trap).in6_src.c:476βrounddown(cut % lport, step) + first=cut % 0=> x86 #DE => Fatal trap 18 => panic. Symbolized stop confirmsin6_pcbsetlport+0x126: divl %esi,%eax.
cpuid = 0 matches the prediction: the div0 fires only on the portinfo
stratum with offset==0; other CPUs wrap the range (first>last, lport=65530)
and bind an out-of-range port harmlessly. The parallel bind loop lands on
CPU0 within milliseconds.
Exploitability
DoS only: integer divide fault is a trap, not a corruption primitive; there
is no route from cut % 0 to controlled write. Impact ceiling: after a
root misconfiguration of the shared v4/v6 port-range sysctls, ANY local
user's IPv6 (and v4, per DF-0334's sites) ephemeral port allocation panics
the system β repeatable at will until the sysctl is reverted.
Relation to DF-0334
DF-0334 owns the IPv4 sites (in_pcb.c in_pcbbind_remote:884 and in_pcbsetlport:428); its fix.diff guards only :884. This finding is the unfiled IPv6 site (in6_src.c:473-476), reached via AF_INET6 sockets, in the pass-2 target file.
Fix validation
fix.diff adds the same count == 0 -> EADDRNOTAVAIL guard DF-0334 used at
in_pcb.c:884. Not build-validated on this run: the finding is a Low-severity
deterministic DoS (trap), not memory corruption, so a ~1 h nativekernel
rebuild was judged disproportionate for the batch; the guard is a literal
mirror of the already-validated sibling fix (fix_status: not_testable).
Behavior with fix: bind fails EADDRNOTAVAIL instead of panicking; ports on
non-zero-offset strata are unaffected (their path never divides by zero).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableGuard is a literal mirror of the sibling DF-0334 fix (in_pcb.c:884, already validated in that pack); nativekernel rebuild skipped as disproportionate for a Low deterministic-trap DoS in this 3-file batch.
['fix.diff']
Confirmed kernel references
Detail
Evidence (decisive lines)
["panic.txt: 'Fatal trap 18: integer divide fault' + 'Stopped at in6_pcbsetlport+0x126: divl %esi,%eax' (the cut % lport at in6_src.c:476)", 'run.log: sysctl 1024->1032 / 5000->1032 then trigger; guest immediately down', 'VERDICT.md: in_pcbportrange arithmetic showing first==last==1032 for ofs==0, step=6']
PoC changes
Seed sketch (none existed - pass-2 discovery): wrote 12-thread AF_INET6 bind(::,0) spinner since guest lacks cpuset(1); port value 1032 chosen divisible by netisr-ncpus(6) so CPU0's stratum (ofs=0) collapses first==last via in_pcbportrange.
Verified recommended fix
Guard lport==0 after in_pcbportrange() in in6_pcbsetlport (in6_src.c:473) with return EADDRNOTAVAIL, mirroring DF-0334's fix at in_pcb.c:884; see fix.diff.
Verdict
Deterministic one-shot kernel panic reproduced on the stock INVARIANTS guest: with net.inet.ip.portrange.first==last==1032 (RANGECHK accepts first==last), an unprivileged AF_INET6 ephemeral bind executing on the portinfo stratum with offset==0 reaches in6_src.c:476 'rounddown(cut % lport, step)' with lport==0 and takes an integer-divide fault (Fatal trap 18, Stopped at in6_pcbsetlport+0x126: divl %esi,%eax, cpuid=0). DoS only - a trap, not a corruption primitive.
No comments yet.