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

ng_generic_msg() leaks the entire control message on NGM_MKPEER/NGM_CONNECT/NGM_NAME/NGM_RMHOOK arglen mismatch: permanent, unbounded kernel heap leak per failing generic control send (v1 sibling of netgraph7's DF-3474)

Summary

The four argument-carrying generic commands validate msg->header.arglen first and on mismatch do 'TRAP_ERROR; return (EINVAL);' without kfree(msg) - violating the handler contract ('It is up to the message handler to free the message'), while every other exit path frees and neither caller frees on error. The leaked allocation is the buffer ngc_send kmalloc'd for the user's sendto payload (user-chosen size). Root-gated (SYSCAP_RESTRICTEDROOT). REPRODUCED on guest with exact size correlation: 50k bad MKPEER sends leaked 1935/1861 free pages (predicted 1953 = 50000x160B zone), 50k bad NGM_NAME sends leaked 1122/1154 (predicted 1172 = 50000x96B), control phase flat; 200k sends ~30MB permanently. Root (or a compromised root daemon such as an MPD-style controller) permanently exhausts the kernel heap. Fix validated (kfree at the four early returns): patched netgraph.ko shows noise-level deltas on the identical test.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3483 Β· 10 files
FileTypeDescriptionSize
arglenleak.c β€” 4.6 KB view raw
build.sh β€” 65 B view raw
run.sh β€” 95 B view raw
run.log β€” 497 B view raw
run.2.log β€” 997 B view raw
fix_run.log β€” 954 B view raw
fix.diff β€” 1.2 KB view raw
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 2.8 KB ↓ raw
verdict.json β€” 4.1 KB view raw

DF-3483 β€” v1 ng_base.c ng_generic_msg() control-message leak (4 sites)

What this is

NGM_MKPEER / NGM_CONNECT / NGM_NAME / NGM_RMHOOK arglen-mismatch error paths in ng_generic_msg() return (EINVAL) WITHOUT kfree(msg) (ng_base.c:1263, 1279, 1296, 1309), violating the handler contract ("It is up to the message handler to free the message", ng_base.c:1177-1179); neither caller frees on error (ng_send_msg:1226, ngintr:2062). Every failing control message leaks its entire user-sized struct ng_mesg allocation (ngc_send allocates it with the sendto payload length β€” attacker-chosen). Root-gated (control socket is SYSCAP_RESTRICTEDROOT, ng_socket.c:172), same privilege frame as netgraph7's DF-3474. This is the direct answer to the pass-2 question "does v1 share DF-3474's item-leak family?": v1 has no NG_FREE_ITEM/item abstraction at all (its queue entries are really freed), but the control message leak on generic-handler error paths is the v1 sibling.

Verification: REPRODUCED (A/B with exact size correlation)

arglenleak.c sends, on one control socket: good : 50k MKPEER with CORRECT arglen to a bogus path (fails in ng_path2node; ng_send_msg kfrees at :1218) -> flat bad : 50k MKPEER with arglen sizeof-1 to "." (handler early-return) -> leaks bad2 : 50k NGM_NAME with arglen sizeof-1 -> leaks

Baseline (run.log, run.2.log): bad delta 1935 / 1861 pages per 50k (predicted 50000160B = 1953) bad2 delta 1122 / 1154 pages per 50k (predicted 5000096B = 1172) good delta 104 / -2 pages (noise) Patched netgraph.ko (fix.diff: kfree before each early return), fix_run.log: bad delta 11 / 1 pages; bad2 delta 0 / 0 pages -> LEAK GONE

Files

  • arglenleak.c β€” A/B/C trigger
  • build.sh / run.sh
  • run.log, run.2.log β€” baseline (stock modules)
  • fix_run.log β€” patched netgraph.ko
  • fix.diff β€” 4x kfree(msg) before return (EINVAL)
  • manifest.json / verdict.json

Build / run (guest, root)

cc -O2 -w -o arglenleak arglenleak.c
kldload ng_socket       # pulls in netgraph.ko
./arglenleak 50000
VERDICT.md
↓ download raw

DF-3483 VERDICT β€” ng_generic_msg arglen-mismatch leaks the control message

Status: reproduced (kernel memory leak, permanent, unbounded; root-gated)

Root cause

ng_generic_msg() implements the four argument-carrying generic commands. Each first validates msg->header.arglen and on mismatch does TRAP_ERROR; return (EINVAL); β€” WITHOUT freeing msg:

  • NGM_MKPEER ng_base.c:1263-1266
  • NGM_CONNECT ng_base.c:1279-1282
  • NGM_NAME ng_base.c:1296-1299
  • NGM_RMHOOK ng_base.c:1309-1312

The function's contract (and every other exit path) frees the message: the cookie-mismatch path kfrees (1252-1253), the default case falls through to kfree(msg) (1669), and successful handlers pass ownership onward. Neither caller frees on error: ng_send_msg() (1207-1239) only kfrees the retaddr, and ngintr() (2062) assumes the handler freed it. The leaked allocation is the buffer ngc_send kmalloc'd for the user's sendto payload (user-chosen length; 149-249 bytes in the PoC) β€” permanent, unbounded, reclaimable only by reboot. This is the v1 sibling of netgraph7's DF-3474: v1 has no NG_FREE_ITEM/item abstraction (its ng_queue_entry objects are genuinely freed), but generic-handler error paths leak the control message itself.

Reachability / threat

Control sockets are SYSCAP_RESTRICTEDROOT-gated (ng_socket.c:172-176), so the direct trigger is root (ngctl-equivalent traffic, long-running root daemons driving netgraph). A root loop leaks kernel heap without bound: 200k bad sends in the PoC leaked ~30 MB. Impact: local kernel-memory exhaustion DoS (kldload/fork/socket allocations degrade); no info leak, no corruption. Non-generic node types' rcvmsg handlers with the same early-return pattern are separately auditable (out of this file's scope).

Evidence (baseline, stock modules)

run.log / run.2.log β€” 50k-iteration phases, free-page deltas: good 104 / -1 pages (addressing failure path kfrees correctly) bad 1935 / 1861 pages (MKPEER arglen-1; predicted 1953 = 50000 x 160B zone) bad2 1122 / 1154 pages (NAME arglen-1; predicted 1172 = 50000 x 96B zone) The leaked-byte arithmetic matches the allocation zone size exactly for both independent sites β€” the leak is the ng_mesg buffer itself.

Fix validation

fix.diff adds kfree(msg, M_NETGRAPH); before each of the four early returns. Patched netgraph.ko built in-guest (-Werror), loaded on a fresh boot, same PoC (fix_run.log): bad 11 / 1 pages (noise) bad2 0 / 0 pages Leak eliminated; good path unchanged. fix_status: fixed.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Same 3-phase A/B/C test on the patched module: bad 11/1 pages, bad2 0/0 pages (noise) vs baseline 1935/1861 and 1122/1154 β€” leak eliminated, good path unchanged.

findings/poc/DF-3483/fix_run.log
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0: (netgraph.ko rebuilt in-guest with fix.diff, loaded on fresh boot; ng_socket/ng_ppp/ng_ksocket stock)

Confirmed kernel references

Detail

Exploit chain

root (or compromised root daemon) -> 4-arglen-off-by-one control message on an AF_NETGRAPH control socket -> ng_generic_msg early-returns without freeing -> each send permanently leaks kmalloc(payload) -> unbounded kernel heap exhaustion (DoS). No uid=0 route: leak-only.

Evidence (decisive lines)

['findings/poc/DF-3483/run.log β€” baseline: good 104p / bad 1935p / bad2 1122p per 50k', 'findings/poc/DF-3483/run.2.log β€” repeat baseline: good -1p / bad 1861p / bad2 1154p', 'findings/poc/DF-3483/fix_run.log β€” patched netgraph.ko: bad 11p/1p, bad2 0p/0p β€” leak gone', 'findings/poc/DF-3483/arglenleak.c β€” A/B/C trigger with free-page accounting', 'findings/poc/DF-3483/fix.diff β€” 4x kfree(msg) before the early returns']

PoC changes

none of substance; wrote the A/B/C trigger from scratch (no seed PoC). Measured via vm.stats.vm.v_free_count deltas after vmstat -m proved unreliable for module malloc types on this guest (same caveat as DF-3474).

Verified recommended fix

kfree(msg, M_NETGRAPH) before each of the four arglen-mismatch returns in ng_generic_msg() β€” see fix.diff.

Verdict

ng_generic_msg()'s NGM_MKPEER/NGM_CONNECT/NGM_NAME/NGM_RMHOOK arglen-mismatch error paths return EINVAL without kfree(msg) (ng_base.c:1263, 1279, 1296, 1309), violating the handler-frees-message contract (ng_base.c:1177); neither caller frees on error (ng_send_msg:1226, ngintr:2062), so every failing generic control message permanently leaks its user-sized struct ng_mesg allocation. Root-gated like netgraph7's DF-3474 (control socket is SYSCAP_RESTRICTEDROOT, ng_socket.c:172): v1 has no NG_FREE_ITEM/item abstraction to share DF-3474's mark-only semantics (its queue entries are really freed), but this is the same family -- control-message leak on generic error paths. Reproduced with exact size correlation: 50k bad MKPEER sends leaked 1935/1861 free pages vs 1953 predicted (160B zone), 50k bad NAME sends leaked 1122/1154 vs 1172 predicted (96B zone), while the addressing-failure control phase was flat (104/-1 pages). 200k total bad sends leaked ~30MB permanently. Impact ceiling: unbounded kernel heap exhaustion by a root caller (kldload/fork/socket degradation) -- local DoS, no info leak, no corruption. Fix validated: kfree(msg) added at all four sites; patched netgraph.ko shows 11/1 and 0/0 page deltas (noise) on the same test.