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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3424 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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):

  1. sysctl net.inet.ip.portrange.first=1032 / last=1032 (root).
  2. Trigger executed as unprivileged user maxx: 12 threads spinning AF_INET6/UDP bind(::, 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 confirms in6_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_testable
baseline reproduced→ patch + rebuild →patched clean

Guard 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']
↓ fix.diffper-fix-DF-3424

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.