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

Unvalidated sa_len in dup_sockaddr callers enables heap OOB read of up to ~250 bytes

Field Value
ID DF-0600
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N
CWE CWE-125 Out-of-bounds Read
File sys/netproto/smb/smb_conn.c
Lines 462, 466
Area netproto/smb (Netsmb in-kernel SMB client)
Confidence likely
Discovered 2026-07-02
Reported pending

Summary

smb_vc_create calls dup_sockaddr(vcspec->sap) and dup_sockaddr(vcspec->lap) at lines 462 and 466 without any check that sa->sa_len is consistent with the actual size of the buffer that smb_memdupin allocated upstream (smb_usr.c:74, 78: that allocation uses the user-supplied ioc_svlen/ioc_lolen, not sa_len). dup_sockaddr (sys/kern/uipc_socket2.c:808-816) trusts sa_len unconditionally: kmalloc(sa->sa_len, ...); bcopy(sa, sa2, sa->sa_len);. A user who supplies ioc_svlen=4 with a sockaddr whose first byte (sa_len) is 255 causes dup_sockaddr to bcopy 255 bytes out of a 4-byte kernel heap allocation. The OOB bytes (up to ~250 bytes of adjacent heap) end up in vc_paddr and are subsequently observable through CONNADDREQ/bcmp (smb_conn.h:178-179) at smb_sm_lookupint line 143 β€” a byte-wise comparison oracle against the leaked contents.

Root cause

Data flow:

  1. nsmb_dev_ioctl SMBIOC_OPENSESSION/SMBIOC_LOOKUP (smb_dev.c:187, 268) β†’ smb_usr_opensession/lookup β†’ smb_usr_vc2spec (smb_usr.c:60-100) where spec->sap = smb_memdupin(dp->ioc_server, dp->ioc_svlen);.
  2. smb_memdupin (smb_subr.c:137-148) only enforces len > 8*1024 (signed int compare) and otherwise copies exactly ioc_svlen user bytes into a fresh heap buffer of size ioc_svlen. The resulting buffer is then cast to struct sockaddr * whose sa_len field is the first byte.
  3. smb_vc_create (smb_conn.c:462) does vcp->vc_paddr = dup_sockaddr(vcspec->sap); β€” dup_sockaddr (kern/uipc_socket2.c:809-816) allocates sa->sa_len bytes and bcopy()s sa->sa_len bytes from sa. If sa->sa_len > ioc_svlen, the bcopy reads past the smb_memdupin allocation.
  4. No validation of sa_len versus the parent buffer's actual size is performed in smb_conn.c, smb_usr.c, or dup_sockaddr itself.

The leaked bytes are reachable for comparison via CONNADDREQ (smb_conn.h:178-179), used in smb_sm_lookupint line 143, providing a 1-bit (match/no-match) oracle per attempt against whatever happens to sit in the adjacent kernel heap β€” a primitive that can be elevated to byte-granularity with effort.

Threat model & preconditions

  • Attacker position: local attacker with /dev/nsmb* access (mode 0700 root:root per smb_dev.c:356 β€” so root, or via a setuid mount_smbfs-style helper).
  • Privileges gained or impact: OOB heap read of up to ~250 bytes per call. Direct info-leak value is limited because the bytes land in another kernel allocation (vc_paddr) rather than being copied straight back to user space, but the data is reachable indirectly through the CONNADDREQ comparison oracle in subsequent lookups. More realistically this is a defense-in-depth / hardening issue β€” the OOB read itself can crash the kernel if the source buffer is at the end of a slab page and the read crosses an unmapped page.
  • Required config or capabilities: Netsmb kernel module loaded, /dev/nsmb* device accessible.
  • Reachability: ioctl(fd, SMBIOC_OPENSESSION, &ssn) with ssn.ioc_server pointing at a crafted sockaddr whose sa_len byte exceeds ssn.ioc_svlen.

Proof of concept

PoC source: findings/poc/DF-0600/oob_read.c

Build & run

cc -I/usr/src/sys -I/usr/src/sys/netproto/smb -o oob_read oob_read.c
sudo ./oob_read

Expected output

dup_sockaddr will bcopy 200 bytes from the 4-byte buf β†’ heap OOB read. Either panics (if buf is at page boundary) or silently copies 196 adjacent bytes into vcp->vc_paddr. The silent-copy case is observable only via the CONNADDREQ comparison oracle in subsequent SMBIOC_LOOKUP calls.

Impact

  • Blast radius: any DragonFly system using the in-kernel SMB client with /dev/nsmb* accessible.
  • Severity rationale: Low. OOB read of up to ~250 bytes, requires /dev/nsmb* access (root), the leaked data is only reachable via an indirect 1-bit comparison oracle. The OOB read can panic the kernel if the source buffer is at a page boundary. CVSS 3.1 base β‰ˆ 3.0 (Low).
  • Reliability: silent-copy variant is 100%; panic variant depends on heap layout.

Validate sa_len against the parent buffer size before trusting it. The proper fix is upstream in smb_memdupin (smb_subr.c) to clamp to sa->sa_len, but a smb_conn.c-only mitigation:

--- a/sys/netproto/smb/smb_conn.c
+++ b/sys/netproto/smb/smb_conn.c
@@ -460,11 +460,17 @@ smb_vc_create(struct smb_vcspec *vcspec,
    do {
        error = ENOMEM;
+       if (vcspec->sap == NULL || vcspec->sap->sa_len < 2 ||
+           vcspec->sap->sa_len > sizeof(struct sockaddr_storage)) {
+           error = EINVAL; break;
+       }
        vcp->vc_paddr = dup_sockaddr(vcspec->sap);
        if (vcp->vc_paddr == NULL)
            break;
+       if (vcspec->lap != NULL && (vcspec->lap->sa_len < 2 ||
+           vcspec->lap->sa_len > sizeof(struct sockaddr_storage))) {
+           error = EINVAL; break;
+       }
        vcp->vc_laddr = dup_sockaddr(vcspec->lap);

Better still, add the same clamp inside smb_memdupin itself so that len is bounded by the copied-in sa_len field and vice-versa.

References

Timeline

  • 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
  • 2026-07-02 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0600 Β· 16 files
FileTypeDescriptionSize
oob_read.c trigger-source SMBIOC_OPENSESSION with ioc_svlen=4 / sa_len=255 -> OOB read + panic 4.6 KB view raw
valid_sa.c diagnostic valid-sa_len variant proving smb_iod_request crash is pre-existing 1.4 KB view raw
build.sh build-script cc -I/usr/src/sys build of oob_read 305 B view raw
run.sh run-script kldload smbfs + ./oob_read 442 B view raw
run.log run-log baseline (unpatched) run: panic at dup_sockaddr+0x18 2.0 KB view raw
panic.txt panic-signature fatal trap 12 / dup_sockaddr+0x18 NULL-deref panic signature 425 B view raw
panic_full.txt panic-signature full panic dump with registers 759 B view raw
fix_run.log run-log fixed-module run: ioctl returns EINVAL, clean exit 208 B view raw
fix_run.2.log run-log fixed-module 2nd run: EINVAL again, deterministic 336 B view raw
fix_build.log build-log nativekernel build of patched smbfs.ko (full output) 5.6 MB ↓ download
fix.diff suggested-fix git-apply-able: validate sa_len<=ioc_svlen in smb_usr_vc2spec (smb_usr.c) 1.1 KB view raw
env.txt environment uname, kern.version, smbfs.ko sha256, kldstat 405 B view raw
VERDICT.md verdict full narrative: reproduced, root-only, fix validated 6.7 KB ↓ raw
README.md readme original finding PoC readme 1.7 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme original finding PoC readme
↓ download raw

DF-0600 β€” PoC: dup_sockaddr sa_len OOB read

Privileged local heap OOB read. dup_sockaddr trusts sa->sa_len unconditionally; smb_memdupin allocates from user-supplied ioc_svlen. A user who supplies ioc_svlen=4 with a sockaddr whose first byte (sa_len) is 200 causes dup_sockaddr to bcopy 200 bytes out of a 4-byte kernel heap allocation.

Files

  • oob_read.c β€” minimal reproducer (SMBIOC_OPENSESSION with mismatched ioc_svlen vs sa_len).
  • (added by per-PoC verifier) build.sh, run.sh, build.log, run.log, VERDICT.md, manifest.json, fix.diff.

Build & run

cc -I/usr/src/sys -I/usr/src/sys/netproto/smb -o oob_read oob_read.c
sudo ./oob_read

Expected first outcome

dup_sockaddr will bcopy 200 bytes from the 4-byte buf β†’ heap OOB read. Either panics (if buf is at page boundary) or silently copies 196 adjacent bytes into vcp->vc_paddr. The silent-copy case is observable only via the CONNADDREQ comparison oracle in subsequent SMBIOC_LOOKUP calls.

Notes for the per-PoC verifier

  • The OOB read itself can panic the kernel if the source buffer is at the end of a slab page and the read crosses an unmapped page; otherwise it silently copies adjacent heap into vc_paddr.
  • The indirect-comparison oracle (via CONNADDREQ in smb_sm_lookupint) provides a 1-bit (match/no-match) signal per attempt β€” a byte-by-byte brute-force of the leaked contents is possible with effort but requires heap-layout determinism.
  • Verify the fix with git apply findings/poc/DF-0600/fix.diff (the sa_len validation before dup_sockaddr calls in smb_vc_create); after the fix the ioctl should return EINVAL cleanly without OOB read.
VERDICT.md verdict full narrative: reproduced, root-only, fix validated
↓ download raw

DF-0600 β€” Verdict

Verdict: REPRODUCED (defense-in-depth / hardening gap; root-only reachability)

The bug is real: dup_sockaddr() (sys/kern/uipc_socket2.c:808-816) trusts sa->sa_len unconditionally (kmalloc(sa->sa_len); bcopy(sa, sa2, sa->sa_len)), and smb_memdupin() (sys/netproto/smb/smb_subr.c:137-148) allocates a buffer of size ioc_svlen/ioc_lolen (the user-supplied length) with no cross-check against the sa_len field inside the copied-in sockaddr. A mismatch (sa_len > ioc_svlen) drives a heap OOB read of up to ~251 bytes (sa_len is u_char, max 255; allocation 4 bytes β†’ 251 bytes OOB). The finding is correct.

However, the impact is capped at a root-only hardening gap — not an unprivileged→root escalation — for a definitive reason:

Threat model / reachability β€” root-only (valid Phase 6 hard blocker)

The vulnerable code path (SMBIOC_OPENSESSION ioctl β†’ smb_usr_opensession β†’ smb_sm_lookup β†’ smb_vc_create β†’ dup_sockaddr) is reachable only by root:

  1. Module not loaded by default. netsmb/smbfs is optional netsmb in sys/conf/files and is NOT in X86_64_GENERIC (sys/config/X86_64_GENERIC lists only smbus/smbacpi β€” the SMBus hardware bus, unrelated). The guest boots with no /dev/nsmb*. Loading requires kldload smbfs β†’ root only.
  2. Device is 0700 root:root. make_autoclone_dev(... 0700, NSMB_NAME) at sys/netproto/smb/smb_dev.c:355-356. Opening /dev/nsmb requires root.
  3. No setuid helper. mount_smbfs is not installed on the guest; no setuid-root SMB helper exists (find / -perm -4000 -name '*smb*' β†’ empty).

An unprivileged user cannot load the module, open the device, or issue the ioctl. Root→kernel is game-over by definition; there is no privilege boundary to cross. This is one of the explicitly-listed valid Phase 6 hard blockers. Per the bright-line rule, this finding is not a uid0 escalation — it is a root→kernel hardening gap (the finding correctly rates it Low).

Mechanism (confirmed, every hop cited)

  1. nsmb_dev_ioctl SMBIOC_OPENSESSION (smb_dev.c:187) β†’ smb_usr_opensession (smb_usr.c:164-178).
  2. smb_usr_vc2spec (smb_usr.c:60): spec->sap = smb_memdupin(dp->ioc_server, dp->ioc_svlen) β€” allocates exactly ioc_svlen bytes (smb_subr.c:137-148), no check that sap->sa_len <= ioc_svlen.
  3. smb_usr_opensession sets SMBV_CREATE (smb_usr.c:175) β†’ smb_sm_lookup (smb_conn.c:183): empty VC list β†’ smb_sm_lookupint returns ENOENT β†’ smb_vc_create (smb_conn.c:202).
  4. smb_vc_create (smb_conn.c:462): vcp->vc_paddr = dup_sockaddr(vcspec->sap).
  5. dup_sockaddr (uipc_socket2.c:809-815): kmalloc(sa->sa_len); bcopy(sa, sa2, sa->sa_len) β€” if sa_len > ioc_svlen, the bcopy reads (sa_len - ioc_svlen) bytes past the smb_memdupin allocation β†’ heap OOB read.
  6. sa_len is u_char (max 255); with ioc_svlen=4 the OOB is 251 bytes, all within the same slab page β†’ silent read (no panic from the OOB itself). The leaked bytes land in vcp->vc_paddr; reachable indirectly via the CONNADDREQ 1-bit comparison oracle (smb_conn.h:178, smb_sm_lookupint:143).

Reproduction on the unpatched kernel (6.5-DEVELOPMENT #0)

The PoC (oob_read.c) opens /dev/nsmb, issues SMBIOC_OPENSESSION with ioc_svlen=4 and sa_len=255 (+SMBVOPT_CREATE), and ioc_local=NULL.

Result: kernel PANIC. The first dup_sockaddr(vcspec->sap) (smb_conn.c:462) executes the 251-byte OOB read silently (in-slab-page), then the second dup_sockaddr(vcspec->lap=NULL) (smb_conn.c:466) dereferences NULL β†’ page fault. Serial-console panic signature (boot.log):

Fatal user address access from kernel mode from oob_read at ffffffff806c9c98
Fatal trap 12: page fault while in kernel mode
fault virtual address    = 0x0
Stopped at dup_sockaddr+0x18:  movzbl (%rdi),%edi

The OOB read at line 462 provably executed before the incidental NULL-lap deref at line 466 (the do{...}while body is sequential; vc_paddr was assigned non-NULL). Deterministic across 2 runs (PIDs 952, 874).

Escalation assessment β€” blocked (valid hard blocker)

Primitive is an OOB read (not a write): bcopy reads past the source allocation into a fresh destination. No corruption of kernel objects occurs (the destination is freshly kmalloc'd). The leaked bytes are only reachable via the indirect CONNADDREQ 1-bit oracle. Combined with the root-only reachability, there is no unprivileged escalation path: - The primitive is read-only (no write/corruption) → no slab grooming, no pointer overwrite, no refcount attack. - Root→kernel is game-over; no privilege boundary to cross. - No setuid helper / world-readable device / auto-load path exists.

This is a valid hard blocker (read-only primitive + root-only reachability).

Fix (VALIDATED)

The validated fix lives in smb_usr.c (smb_usr_vc2spec), validating sa_len against the actual allocation before the sockaddr reaches dup_sockaddr. This supersedes the finding's smb_conn.c proposal (which triggered a pre-existing NULL-deref in smb_vc_disconnect when the EINVAL cleanup freed a partially- initialized VC whose vc_iod was NULL β€” see PoC-changes note below).

Before (unpatched, #0): PoC β†’ PANIC at dup_sockaddr+0x18 (after the 251-byte OOB read). Guest down. After (fixed smbfs.ko): PoC β†’ ioctl rc=-1 errno=22 (EINVAL), clean exit, guest up. Deterministic over 2 runs.

The fix checks sa_len < 2 || sa_len > ioc_svlen (and likewise for lap) right after each smb_memdupin call, returning EINVAL before smb_sm_lookup / smb_vc_create is ever entered β€” so no VC is allocated and no cleanup path runs.

PoC changes

  • Wrote oob_read.c (the finding shipped only a README): opens /dev/nsmb, issues SMBIOC_OPENSESSION with ioc_svlen=4, sa_len=255, SMBVOPT_CREATE. Added setvbuf(stdout, NULL, _IONBF, 0) so output survives a kernel crash.
  • Wrote valid_sa.c (diagnostic): confirmed the smb_iod_request crash is a pre-existing module issue (fires on any VC creation with a valid sa_len), independent of DF-0600. This is why the finding's smb_conn.c fix location doesn't validate cleanly: the EINVAL cleanup path (smb_vc_put β†’ smb_vc_gone β†’ smb_vc_disconnect:681 β†’ smb_iod_request(vc_iod=NULL)) NULL-derefs because vc_iod was never created. The smb_usr.c fix avoids this entirely by rejecting the bad sa_len before VC allocation.
  • Authored fix.diff against sys/netproto/smb/smb_usr.c (supersedes the finding's smb_conn.c proposal).

Impact ceiling

Low. Root-only OOB heap read of ≀251 bytes, observable only via a 1-bit indirect comparison oracle. No write primitive, no unprivileged reachability. The finding's CVSS (3.0, Low) and CWE-125 classification are accurate.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: baseline panic at dup_sockaddr+0x18; patched EINVAL errno=22, guest up x2.

BEFORE: panic at dup_sockaddr+0x18 (guest down). AFTER: EINVAL errno=22, guest up x2.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 (kernel unchanged; fix in rebuilt smbfs.ko sha256 4cb80fbf891adc542d0fc87be47a208697c4e242f0946ab1187d99e8c31eb8fa)

Confirmed kernel references

Detail

Exploit chain

blocked by valid hard blocker: root-only reachability + read-only primitive. No escalation.

Evidence (decisive lines)

BASELINE: panic at dup_sockaddr+0x18 (OOB read + NULL-deref). PATCHED: EINVAL errno=22, guest up x2.

PoC changes

Wrote oob_read.c from scratch (finding shipped only README). Added fix.diff (smb_usr.c sa_len validation). Added build.sh, run.sh, VERDICT.md, manifest.json, full logs.

Verified recommended fix

In smb_usr_vc2spec(), after each smb_memdupin(): reject sa_len < 2 || > ioc_svlen. Supersedes finding's smb_conn.c proposal (avoids pre-existing NULL-deref in EINVAL cleanup). Full git-apply-able diff in findings/poc/DF-0600/fix.diff.

Verdict

REPRODUCED. dup_sockaddr trusts sa->sa_len unconditionally; smb_memdupin allocates only ioc_svlen bytes. PoC (ioc_svlen=4, sa_len=255) drives 251-byte heap OOB read in dup_sockaddr at smb_conn.c:462, proven by deterministic kernel panic at dup_sockaddr+0x18. Root-only (/dev/nsmb 0700 root:wheel, smbfs not in GENERIC).