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:
nsmb_dev_ioctlSMBIOC_OPENSESSION/SMBIOC_LOOKUP(smb_dev.c:187, 268) βsmb_usr_opensession/lookupβsmb_usr_vc2spec(smb_usr.c:60-100) wherespec->sap = smb_memdupin(dp->ioc_server, dp->ioc_svlen);.smb_memdupin(smb_subr.c:137-148) only enforceslen > 8*1024(signed int compare) and otherwise copies exactlyioc_svlenuser bytes into a fresh heap buffer of sizeioc_svlen. The resulting buffer is then cast tostruct sockaddr *whosesa_lenfield is the first byte.smb_vc_create(smb_conn.c:462) doesvcp->vc_paddr = dup_sockaddr(vcspec->sap);βdup_sockaddr(kern/uipc_socket2.c:809-816) allocatessa->sa_lenbytes andbcopy()ssa->sa_lenbytes fromsa. Ifsa->sa_len > ioc_svlen, thebcopyreads past thesmb_memdupinallocation.- No validation of
sa_lenversus the parent buffer's actual size is performed insmb_conn.c,smb_usr.c, ordup_sockaddritself.
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 (mode0700 root:rootper smb_dev.c:356 β so root, or via a setuidmount_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 theCONNADDREQcomparison 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)withssn.ioc_serverpointing at a craftedsockaddrwhosesa_lenbyte exceedsssn.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.
Recommended fix
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
sys/kern/uipc_socket2.c:808-816(dup_sockaddr) β trustssa_lenunconditionally.sys/netproto/smb/smb_subr.c:137-148(smb_memdupin) β allocates from user-suppliedlen, notsa_len.sys/netproto/smb/smb_conn.h:178-179(CONNADDREQ) β the comparison oracle that makes the OOB read indirectly observable.
Timeline
- 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
- 2026-07-02 Reported to DragonFlyBSD security contact (pending).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0600 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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_OPENSESSIONwith mismatchedioc_svlenvssa_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
CONNADDREQinsmb_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(thesa_lenvalidation beforedup_sockaddrcalls insmb_vc_create); after the fix the ioctl should returnEINVALcleanly without OOB read.
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:
- Module not loaded by default.
netsmb/smbfsisoptional netsmbinsys/conf/filesand is NOT inX86_64_GENERIC(sys/config/X86_64_GENERIC lists onlysmbus/smbacpiβ the SMBus hardware bus, unrelated). The guest boots with no/dev/nsmb*. Loading requireskldload smbfsβ root only. - Device is 0700 root:root.
make_autoclone_dev(... 0700, NSMB_NAME)at sys/netproto/smb/smb_dev.c:355-356. Opening/dev/nsmbrequires root. - No setuid helper.
mount_smbfsis 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)
nsmb_dev_ioctlSMBIOC_OPENSESSION(smb_dev.c:187) βsmb_usr_opensession(smb_usr.c:164-178).smb_usr_vc2spec(smb_usr.c:60):spec->sap = smb_memdupin(dp->ioc_server, dp->ioc_svlen)β allocates exactlyioc_svlenbytes (smb_subr.c:137-148), no check thatsap->sa_len <= ioc_svlen.smb_usr_opensessionsetsSMBV_CREATE(smb_usr.c:175) βsmb_sm_lookup(smb_conn.c:183): empty VC list βsmb_sm_lookupintreturns ENOENT βsmb_vc_create(smb_conn.c:202).smb_vc_create(smb_conn.c:462):vcp->vc_paddr = dup_sockaddr(vcspec->sap).dup_sockaddr(uipc_socket2.c:809-815):kmalloc(sa->sa_len); bcopy(sa, sa2, sa->sa_len)β ifsa_len > ioc_svlen, thebcopyreads(sa_len - ioc_svlen)bytes past thesmb_memdupinallocation β heap OOB read.- sa_len is
u_char(max 255); withioc_svlen=4the OOB is 251 bytes, all within the same slab page β silent read (no panic from the OOB itself). The leaked bytes land invcp->vc_paddr; reachable indirectly via theCONNADDREQ1-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, issuesSMBIOC_OPENSESSIONwithioc_svlen=4, sa_len=255, SMBVOPT_CREATE. Addedsetvbuf(stdout, NULL, _IONBF, 0)so output survives a kernel crash. - Wrote
valid_sa.c(diagnostic): confirmed thesmb_iod_requestcrash 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 becausevc_iodwas never created. The smb_usr.c fix avoids this entirely by rejecting the bad sa_len before VC allocation. - Authored
fix.diffagainstsys/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
fixedVALIDATED: 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.
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).
No comments yet.