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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5711 Β· 7 files
FileTypeDescriptionSize
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 per netmap_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.

VERDICT.md
↓ download raw

DF-5711 VERDICT β€” reproduced (non-termination); impact none (hardening)

From the pass-2 consumer census of the sys/libkern/strncpy.c audit.

sys/net/netmap/netmap_vale.c:

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_testable
baseline reproduced→ patch + rebuild →patched clean

Info/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).
↓ fix.diffper-fix-DF-5711

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.