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)
PoC verification
Evidence pack
findings/poc/DF-3334 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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).
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
- The netmap subsystem does not compile on this DragonFly tree
(
if_unused7bit-rot β DF-3332/env.txt); no ioctl path exists at runtime. - 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 sysctldev.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_testablenot testable: netmap subsystem does not compile on this tree (if_unused7 bit-rot)
fix.diff (line-exact, git-apply-able)
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.
No comments yet.