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

accf_http soparsehttpvers() restarts its scan at ssb_mb on every data arrival with no retained parse state β€” O(n^2) per connection, all on the netisr protocol thread; unauthenticated remote CPU/netisr exhaustion for any httpready-configured listener

Field Value
ID DF-2977
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
CWE CWE-405
File sys/net/accf_http/accf_http.c
Lines 237-287 (found in uipc_accf.c pass-2 consumer sweep)
Area net
Confidence certain
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket base:net? (see bucket field)
Reported pending
Known CVE none
CVE match novel

Summary

The httpready filter's version parser walks the ENTIRE accumulated receive buffer from byte 0 on every upcall and, when the request is still incomplete, re-arms itself for the next arrival without remembering how far it already scanned — the accf_create/arg state mechanism that would hold the parse position is never used. A remote unauthenticated client sending 'GET /aaaa...' with exactly one space, no newline, one byte per TCP segment keeps the filter in readmore forever: b buffered bytes cost Σi byte-visits plus b upcalls, quadratic, all executed synchronously in tcp_input→sowakeup on the protocol thread that also serves other sockets hashed to that CPU. Measured on the guest: 4096/8192/16384 dribbled bytes cost 107765/315302/1153292 ¡s of kernel system time per 4 connections (2.9×→3.7× per doubling → O(n²)), ~18,000 CPU-¡s per attacker byte vs ~0 for the same bytes sent bulk. Low severity because accf_http is an opt-in kld absent from GENERIC. Fix: persist per-socket parse state (implement accf_create/destroy returning a scan-position struct); interim cap the rescan or fall out to soisconnected() after 64 KB.

Timeline

  • 2026-09-02 Discovered during pass-2 audit of uipc_accf.c (GLM 5.3); O(nΒ²) measured on guest.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2977 Β· 9 files
FileTypeDescriptionSize
bench.c β€” 2.7 KB view raw
srv.c β€” 1.5 KB view raw
build.sh β€” 71 B view raw
run.sh β€” 537 B view raw
build.log β€” 150 B view raw
run.log β€” 698 B view raw
run.2.log β€” 785 B view raw
env.txt β€” 224 B view raw
VERDICT.md β€” 2.8 KB ↓ raw
VERDICT.md
↓ download raw

DF-2977 β€” VERDICT

status: reproduced / impact: dos (unauthenticated remote CPU/netisr exhaustion, conditional on accf_http deployment) / confidence: certain

What was run

srv.c (httpready-filtered listener, port 19000) + bench.c on the DF 6.5-DEVELOPMENT guest (stock INVARIANTS kernel), accf_http.ko loaded by root, client and server both guest-local on 127.0.0.1 (the measurement is of kernel protocol-thread work, which is identical for a remote attacker).

Results (kern.cp_time sys-delta, microseconds, 4 connections each)

request bytes bulk (control) byte-at-a-time per-2x growth
4096 ~0 107,765 β€”
8192 ~0 315,302 2.9x
16384 ~0 1,153,292 3.7x

Quadratic behavior confirmed (approaching the 4x O(nΒ²) asymptote as fixed overheads wash out). Wall-clock: 0.396 s vs 0.021 s for 32 KB dribbled vs bulk. ~0.29 CPU-seconds of system time per single 16 KB connection; the work runs in tcp_input β†’ sorwakeup β†’ sowakeup (uipc_socket2.c:603-604) β†’ soparsehttpvers on the netisr protocol thread.

Why it is a bug (path:line)

  • sys/net/accf_http/accf_http.c:237-278 β€” soparsehttpvers() restarts the scan at so->so_rcv.ssb_mb on every upcall; readmore (line 279-287) re-arms the same full-buffer parser.
  • sys/net/accf_http/accf_http.c:225-235 β€” no parse position/state is kept anywhere (the filter gets void *arg it never uses to store progress).
  • The dispatch is per-arrival: every delivered segment triggers the upcall (uipc_socket2.c:603-604).

An unauthenticated remote client choosing "one byte per TCP segment, one space total, never a newline" converts 16 KB of traffic into ~0.3 CPU-s of protocol-thread work per connection; N parallel connections pin the netisr threads that service other sockets hashed to the same CPUs.

Scope / severity rationale

accf_http is not in GENERIC (no device accf_* in sys/config/GENERIC) β€” an admin must kldload it and an application must opt in via setsockopt(SO_ACCEPTFILTER). Within that (intended, documented http-accelerator) deployment the trigger is fully unauthenticated. Low severity because of the opt-in precondition; inherited unchanged from the FreeBSD ancestry of this file.

Exploit chain

none β€” pure CPU/resource exhaustion.

Fix direction (not patch-validated; redesign required)

Keep per-socket parse state (bytes consumed, spaces seen) in a filter-private struct allocated by accf_create() and passed back through arg, so each upcall resumes where the previous one stopped (O(n) total). Interim cheap mitigation: cap the rescan length (e.g. skip the first parsed_upto bytes) or cap parse_http_version scanning at the first 64 KB and fall out to soisconnected().

Fix verification

not_testable
per-fix-DF-2977

Confirmed kernel references

Detail

Evidence (decisive lines)

['run.log: byte-at-a-time 8192x4conns = 387259us sys vs bulk = 0us (same bytes)', 'run.2.log: 4096->8192->16384 = 107765->315302->1153292 us (quadratic sweep)', 'VERDICT.md: soparsehttpvers restarts at ssb_mb every upcall, readmore re-arms full scan']

PoC changes

removed duplicate struct accept_filter_arg (guest exports it); kern.cp_time units are microseconds on DF, not ticks.

Verified recommended fix

Persist per-socket parse state (via accf_create-provided arg) so each upcall resumes scanning where the last stopped, making the total work O(n).

Verdict

Quadratic re-parse reproduced: an httpready-filtered socket fed 4096/8192/16384 bytes one TCP segment at a time (one space, no newline) consumed 107765/315302/1153292 us of kernel system time on the netisr protocol thread (bulk control: ~0), growth 2.9x-3.7x per doubling of request size -> O(n^2). ~0.29 CPU-s per 16KB connection, ~18000 CPU-us per attacker byte; unauthenticated remote CPU/netisr exhaustion for any server opting into accf_http.