netmap_vale: bridge-name clamp off-by-one (IFNAMSIZ not IFNAMSIZ-1) leaves bdg_basename[IFNAMSIZ] unterminated
Summary
namelen clamps to IFNAMSIZ (16) and strncpy copies exactly that many bytes into bdg_basename[16]: a >=16-non-NUL-char name fills the array with no terminator - the documented strncpy trap at the caller. Reproduced: no NUL in [0,16), strlen reads 47 through the canary. Latent only (all in-tree readers are no-op debug prints; matching uses bdg_namelen+strncmp). force-termination fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5711 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness_netmap_clamp.c | β | 1.7 KB | view raw | |
| fix.diff | β | 520 B | view raw | |
| run.log | β | 411 B | view raw | |
| README.md | β | 1.4 KB | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| manifest.json | β | 663 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-5711 β netmap_vale bridge-name clamp off-by-one leaves bdg_basename unterminated (Info, hardening)
- File (audit target):
sys/libkern/strncpy.c(pass 2, consumer census) - Defect location:
sys/net/netmap/netmap_vale.c:331-332+:352 - Severity: Info β latent non-termination; no live consumer in release
builds (only
ND()/D()debug prints, which are no-ops pernetmap_kern.h:80-81); filed as hardening.
Reproduce
Build with the shared rig (findings/poc/DF-5710/build.sh compiles the kernel
strncpy.c unmodified; this harness is part of it), then:
./harness_netmap_clamp
Expected:
bdg_basename has NUL within [0,16)? -> NO (index -1) strlen(bdg_basename) (what any %s consumer reads): 47
A bridge name of β₯16 chars (clamped namelen = IFNAMSIZ, strncpy of
exactly 16 non-NUL bytes into char bdg_basename[IFNAMSIZ] at
netmap_vale.c:198/352) leaves the array with no NUL: any future consumer
that treats it as a C string reads past the array (demonstrated: strlen 47
through the adjacent canary).
Fix
fix.diff: force b->bdg_basename[IFNAMSIZ - 1] = '\0' after the copy.
Deliberately does NOT change the IFNAMSIZ clamp, because bdg_namelen
feeds the strncmp-based bridge matching (netmap_vale.c:342-344) β
terminating the stored copy fixes the string without altering lookup
semantics. Applies clean to guest /usr/src.
DF-5711 VERDICT β reproduced (non-termination); impact none (hardening)
From the pass-2 consumer census of the sys/libkern/strncpy.c audit.
331: if (namelen >= IFNAMSIZ)
332: namelen = IFNAMSIZ; /* should keep a byte for NUL */
...
352: strncpy(b->bdg_basename, name, namelen);
bdg_basename is char[IFNAMSIZ] (netmap_vale.c:198). With a bridge name
of β₯16 non-NUL chars (fully user-controlled via the netmap/VALE
NIOCTXSYNC/bdg-attach ioctl path β requires /dev/netmap access, i.e.
root/operator), strncpy writes exactly 16 non-NUL bytes and provides no
termination β the documented strncpy trap, at the caller.
Reproduced on the guest with the kernel's own strncpy.c: no NUL exists
within bdg_basename[0..15]; strlen runs to 47 through the adjacent
canary β the exact behavior any future %s/strcmp consumer would hit.
Why Info and not higher: every in-tree read of bdg_basename is inside
ND() (netmap_vale.c:353,482) or D() (:566), and ND is #define
ND(format, ...) β a no-op in release builds (netmap_kern.h:80-81).
Matching uses bdg_namelen + strncmp, never the stored NUL. So today this
is a latent trap, not a live OOB read. Filed as hardening: one line prevents
the next consumer from becoming a bug.
Fix validated: fix.diff (force bdg_basename[IFNAMSIZ-1] = '\0' after the
copy; clamp intentionally untouched to preserve strncmp matching semantics)
applies clean to guest /usr/src (patch -p1 --dry-run: Hunk #1 succeeded
at 350).
kernel_refs: sys/net/netmap/netmap_vale.c:198,331-332,342-344,352-353, sys/net/netmap/netmap_kern.h:80-81, sys/libkern/strncpy.c:43-58.
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_testableInfo/hardening finding: no kernel build/boot validation performed (nothing manifests in release builds). fix.diff applies cleanly and forces NUL termination without altering the bdg_namelen/strncmp matching semantics.
patch -C -p1 --dry-run on guest /usr/src: 'Hunk #1 succeeded at 350' (output captured in findings/poc/DF-5710/fix_run.log).
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log: 'bdg_basename has NUL within [0,16)? -> NO (index -1)' and 'strlen(...): 47'; fix.diff applies clean to guest /usr/src (Hunk #1 succeeded at 350).
PoC changes
Harness written fresh (rig shared with DF-5710; kernel strncpy compiled unmodified from /usr/src).
Verified recommended fix
netmap_vale.c:352: set b->bdg_basename[IFNAMSIZ-1] = '\0' after the strncpy (fix.diff).
Verdict
Consumer-census finding from the sys/libkern/strncpy.c pass-2 audit: netmap_vale.c:331-332 clamps namelen to IFNAMSIZ (not IFNAMSIZ-1) and netmap_vale.c:352 then strncpy's exactly 16 non-NUL bytes into char bdg_basename[IFNAMSIZ] (netmap_vale.c:198) -- the array is left unterminated. Reproduced on the DF 6.5 guest with the kernel's own strncpy: no NUL within [0,16), strlen runs to 47 through the adjacent canary. Impact none today: the only in-tree readers are ND()/D() debug prints and ND is a no-op in release (netmap_kern.h:80-81); bridge matching uses bdg_namelen+strncmp, not the stored NUL. Filed as Info hardening.
No comments yet.