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

netmap_mem_get_info() failure paths unbalance the adapter refcount: premature free/UAF (NIOCREGIF) and pinned adapters (NIOCGINFO)

Summary

After a SUCCESSFUL registration, NIOCREGIF's netmap_mem_get_info() failure path calls netmap_adapter_put(na) on a reference that netmap_do_regif already transferred to priv->np_na - one drop too many, so netmap_dtor's later put frees the adapter while WNA(ifp) and the fd still reference it (kernel UAF). Mirror-image leak in NIOCGINFO: the same failure breaks WITHOUT putting the get_na reference - one pinned adapter per call, and with create=1 each new ifname attaches a generic adapter whose if_input interception can never be removed. Trigger gated on a sticky allocator lasterr (root-writable sysctls dev.netmap.* or ENOMEM during first finalize) - not deterministically unpriv-forceable, hence Low/likely. Line-verified (Phase V skipped: Low, latent, subsystem unbuildable). Fix: rebalance both paths (drop the get_na ref in NIOCGINFO; remove the extra put in NIOCREGIF - the registration stays valid).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3334 Β· 3 files
FileTypeDescriptionSize
README.md doc finding summary + trigger gating 2.3 KB ↓ raw
VERDICT.md verdict per-path refcount trace 2.8 KB ↓ raw
fix.diff suggested-fix two-hunk refcount rebalance 909 B view raw
README.md doc finding summary + trigger gating
↓ download raw

DF-3334 β€” netmap_mem_get_info() failure paths unbalance the adapter refcount

Target: sys/net/netmap/netmap.c:1331-1333 (NIOCGINFO, reference LEAK) and sys/net/netmap/netmap.c:1398-1402 (NIOCREGIF, premature netmap_adapter_put β†’ refcount underflow β†’ UAF).

NIOCREGIF (the dangerous one)

netmap_get_na() (netmap.c:1377) acquires a reference that netmap_do_regif() then takes ownership of (priv->np_na = na, netmap.c:1209; the dtor drops it once at netmap.c:657). If netmap_mem_get_info() fails right after a successful registration, the error path calls netmap_adapter_put(na) again β€” one drop too many. On fd close, netmap_dtor() drops it a second time β†’ refcount hits 0 while WNA(ifp) still points at the adapter β†’ use-after-free for the whole interface (next NIOCGINFO/NIOCREGIF/ifunit traffic on that ifp derefs freed memory; driver detach double-frees).

NIOCGINFO (the leak)

Mirror image: on the same netmap_mem_get_info() failure after a successful netmap_get_na(create=1), the code breaks without any netmap_adapter_put(na) β†’ one pinned reference per call (and each call with a fresh interface name attaches a new generic adapter whose if_input hook can then never be removed).

Trigger gating (why Low/likely)

netmap_mem_get_info() (netmap_mem2.c:198-219) fails when nmd->config(nmd) returns nonzero β€” netmap_mem_global_config() (netmap_mem2.c:798-828) returns a sticky nmd->lasterr that is set by any earlier failed netmap_config_obj_allocator() / netmap_finalize_obj_allocator() (ENOMEM under memory pressure), or directly by root via the RW sysctls dev.netmap.{if,ring,buf}_{size,num} (netmap_mem2.c:146-159). Unprivileged reachability therefore needs a prior allocator failure (plausible under memory pressure, not deterministic); with the sysctl it is trivially root-triggered. Additionally, on current DragonFly the whole netmap subsystem does not compile (see DF-3332/env.txt), so the bug is latent until the subsystem is resurrected.

Verification

Phase V skipped (Low severity, latent trigger, subsystem does not build on this tree β€” see verdict.json). Analysis is line-verified: references are balanced on every other error path of both ioctls (checked line-by-line in VERDICT.md).

VERDICT.md verdict per-path refcount trace
↓ download raw

DF-3334 β€” VERDICT (analysis-verified; no runtime PoC β€” see status)

Reference accounting (traced line-by-line)

Success path NIOCREGIF: netmap_get_na() β†’ +1 (netmap.c:1085 via netmap_get_hw_na/attach), ownership transferred to priv->np_na inside netmap_do_regif() (netmap.c:1209); dtor drops it once (netmap.c:656-659). Balanced.

Error paths of NIOCREGIF: * netmap_get_na fails (netmap.c:1378-1379): nothing held β†’ break. OK. * nifp == NULL (netmap.c:1387-1391): netmap_do_regif cleared priv->np_na on its own error-out (netmap.c:1251), so the caller's netmap_adapter_put(na) drops the one-and-only ref. OK. * netmap_mem_get_info() fails (netmap.c:1398-1402): registration SUCCEEDED (priv->np_na == na, np_nifp set, active_fds incremented, adapter in netmap mode) β€” but the path still does netmap_adapter_put(na). That reference is not the caller's to drop anymore. One drop too many β‡’ at fd close netmap_dtor() drops again β‡’ refcount 0 while WNA(ifp) still points at the adapter (netmap_detach_common cleared WNA only inside that first free, netmap.c:1867-1868 β€” wait: the first premature release frees the adapter via netmap_adapter_put β†’ netmap_detach_common β†’ WNA cleared there; the dtor's second put then decrements freed memory (refcount UAF) and any concurrent NA(ifp) user between the two releases is a plain use-after-free). β‡’ UAF, class memcorrupt, trigger-gated.

Error paths of NIOCGINFO: * netmap_get_na fails: break. OK. * netmap_mem_get_info() fails (netmap.c:1331-1333): break without netmap_adapter_put(na) β‡’ leaked reference per call; with create=1 each new nr_name attaches a generic adapter that is pinned forever (if_input interception never removed). Resource-exhaustion DoS, same gating.

Why no runtime PoC

  1. The netmap subsystem does not compile on this DragonFly tree (if_unused7 bit-rot β€” DF-3332/env.txt); no ioctl path exists at runtime.
  2. Even on a compiling system, forcing netmap_mem_get_info() to fail requires a sticky allocator error (nmd->lasterr != 0, netmap_mem2.c:826/692) β€” root-writable sysctl dev.netmap.buf_size (netmap_mem2.c:148) or genuine ENOMEM during a previous finalize (netmap_mem2.c:671). Not deterministically unprivileged-triggerable, hence Low severity and skipped Phase V.

Fix

fix.diff (two hunks): NIOCREGIF must not drop the reference it no longer owns; NIOCGINFO must drop it on failure. Both restore exact balance on every path.

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 no→ patch + rebuild →patched clean

not testable: netmap subsystem does not compile on this tree (if_unused7 bit-rot)

fix.diff (line-exact, git-apply-able)
↓ fix.diffper-fix-DF-3334

Confirmed kernel references

Detail

Evidence (decisive lines)

['VERDICT.md: full per-path reference-accounting trace', 'fix.diff: two-hunk rebalance']

PoC changes

not built (Low, latent trigger, subsystem dead on this tree)

Verified recommended fix

NIOCREGIF: on get_info failure do NOT netmap_adapter_put (ref belongs to priv->np_na); NIOCGINFO: netmap_adapter_put(na) before break on get_info failure

Verdict

Error-path refcount imbalance around netmap_mem_get_info(): NIOCREGIF (netmap.c:1398-1402) drops the adapter reference that netmap_do_regif already transferred to priv->np_na, so the dtor's later drop underflows the count -> adapter freed while WNA(ifp)/priv still point at it (UAF, memcorrupt class); NIOCGINFO (netmap.c:1331-1333) leaks one reference per failing call (generic adapters pinned forever with if_input hooked). Trigger gated on netmap_mem_get_info() failing, i.e. a sticky allocator lasterr (root sysctl dev.netmap.buf_size, or ENOMEM during a prior finalize) -- not deterministically unprivileged-reachable, hence Low/likely. Line-verified against every other error path of both ioctls (all balanced). No runtime PoC: the netmap subsystem does not compile on current DragonFly (if_unused7 bit-rot) and the failure precondition is not deterministically forceable unprivileged.