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

hw.oceN.firmware_version served via unbounded strlen over a possibly non-NUL-terminated device string β€” adjacent softc bytes (incl. kernel pointers) leak to unprivileged readers

Summary

oce_get_fw_version bcopy's exactly 32 device bytes into fw_version[32] with no NUL guarantee; the string-sysctl handler serves strlen+1 bytes with strlen NOT bounded. A device returning 32 non-NUL bytes makes the read walk into macaddr and DMA pointers and copy those bytes to any unprivileged sysctl reader. Guest guarded replica: strlen walked 87 bytes (+55 past the buffer). Terminate-the-string fix (1 hunk). Also flags the repo-wide unbounded-strlen string-sysctl pattern.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4386 Β· 12 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 2.6 KB ↓ raw
trigger.c β€” 2.0 KB view raw
build.sh β€” 65 B view raw
run.sh β€” 89 B view raw
run.log β€” 34 B view raw
t6_guarded_run.log β€” 220 B view raw
fix.diff β€” 451 B view raw
env.txt β€” 478 B view raw
manifest.json β€” 734 B view raw
verdict.json β€” 2.1 KB view raw
trigger_replica.log β€” 654 B view raw

DF-4386 β€” hw.oceN.firmware_version can serve an unbounded strlen() over a non-NUL-terminated device string (kernel heap bytes to unprivileged readers)

If all 32 bytes are non-NUL, strlen walks out of fw_version into the rest of struct oce_softc (macaddr, then OCE_DMA_MEM bsmbx β€” kernel virtual pointers), and SYSCTL_OUT copies strlen+1 bytes to any unprivileged user running sysctl hw.oceN.firmware_version. Reading sysctls needs no privilege on DragonFly.

Build / run

./run.sh β€” hardware gate (no oce NIC on this guest) + guarded replica of the strlen walk: 32 non-NUL device bytes followed by non-zero adjacent fields β†’ strlen walks +55 bytes past the buffer.

Expected

t6-style output: strlen() walked: 87 bytes (+55 past the buffer), RESULT: strlen OOB confirmed. Kernel-path confirmation needs an Emulex adapter whose GET_FW_VERSION response fills all 32 bytes.

VERDICT.md
↓ download raw

DF-4386 VERDICT

Trace

  1. oce_get_fw_version() (sys/dev/netif/oce/oce_mbox.c:260-292) issues OPCODE_COMMON_GET_FW_VERSION; the response payload fwcmd->params.rsp.fw_ver_str (uint8_t [32], oce_hw.h:1509) is device-DMA'd. :289 bcopy(fw_ver_str, sc->fw_version, 32) copies exactly sizeof(sc->fw_version) bytes β€” a full-width copy overwrites any NUL, so termination depends entirely on device bytes.
  2. oce_add_sysctls() (sys/dev/netif/oce/oce_sysctl.c:84-89) publishes the buffer as CTLFLAG_RD SYSCTL_ADD_STRING.
  3. On read, sysctl_handle_string() (sys/kern/kern_sysctl.c:1171) does SYSCTL_OUT(req, arg1, strlen((char *)arg1)+1) β€” strlen is unbounded by the oid's arg2. (Contrast the write path at :1176-1181, which is bounded by arg2.)
  4. Layout: fw_version (oce_if.h:835) is followed by struct mac_address_format macaddr and OCE_DMA_MEM bsmbx (bus_dma_tag_t/vaddr/paddr kernel pointers). The leaked window runs until the first zero byte; kernel pointers contain zero bytes so the walk stops within tens of bytes β€” but those tens of bytes (mac, DMA pointers) are copied to userspace verbatim.

Threat model: requires a device (or device firmware compromise) returning a non-NUL-terminated version string β€” malicious-peripheral class. The reader needs no privilege: sysctl hw.oceN.firmware_version is world-readable.

Verification (Phase V)

  • Kernel path hardware-gated: no 19a2: PCI device, no hw.oce sysctl tree on the guest (run.log).
  • trigger.c guarded replica on the guest (t6_guarded_run.log): 32 non-NUL bytes + adjacent non-zero fields β†’ strlen() walked: 87 bytes (+55 past the buffer) β€” the exact sink behavior at kern_sysctl.c:1171.
  • fix.diff (ocean_mbox.c): force sc->fw_version[31] = '\0' after the bcopy; git apply --check OK; patched oce_mbox.c syntax-validated on the guest kernel toolchain (0 errors).

Bottom line

Certain code-wise, device-gated exploit-wise: Low-severity kernel info leak (adjacent softc bytes incl. kernel pointers) to unprivileged readers. The same unbounded-strlen string-sysctl pattern should be audited repo-wide (kern_sysctl.c:1171 is the sink every CTLFLAG_RD string oid relies on).

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 at runtime (no oce NIC); fix.diff git-apply OK, patched oce_mbox.c syntax-validated with guest kernel toolchain (0 errors).

fix.diff; guest cc -fsyntax-only of patched oce_mbox.c: 0 errors.
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64

Confirmed kernel references

Detail

Evidence (decisive lines)

t6_guarded_run.log (strlen walked 87 bytes, +55 OOB), VERDICT.md trace, fix.diff (git apply --check OK, syntax-validated)

PoC changes

n/a - pass-2 finding authored fresh; first replica hit gcc constant-folding, rewritten with volatile+guard-page model

Verified recommended fix

terminate: sc->fw_version[sizeof(sc->fw_version)-1] = 0 after the bcopy (fix.diff)

Verdict

oce_get_fw_version (oce_mbox.c:289) bcopy's exactly 32 device bytes into sc->fw_version[32] with no NUL guarantee; oce_sysctl.c:84-89 publishes it as a read-only string sysctl whose handler (kern_sysctl.c:1171) serves strlen(arg1)+1 bytes UNBOUNDED by arg2. A device returning 32 non-NUL bytes makes strlen walk into adjacent struct oce_softc fields (macaddr, OCE_DMA_MEM kernel pointers) and copy them to any unprivileged sysctl reader. Hardware-gated on guest (no oce NIC); sink behavior proven by guarded replica (+55 bytes past buffer). Low: malicious-peripheral class, defense-in-depth; also flags the unbounded-strlen string-sysctl pattern.