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

smb_vc_create error path NULL-deferences vc_iod via smb_vc_disconnect (trivially-triggerable kernel panic via invalid charset name)

Field Value
ID DF-0599
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
CWE CWE-476 NULL Pointer Dereference
File sys/netproto/smb/smb_conn.c
Lines 460-514 (create), 550-556 (gone), 678-684 (disconnect)
Area netproto/smb (Netsmb in-kernel SMB client)
Confidence certain
Discovered 2026-07-02
Reported pending

Summary

If smb_vc_create fails on any step before smb_iod_create() succeeds (most easily: any of the four iconv_open() calls at lines 486/489/494/498 returning ENOENT for an attacker-supplied charset name), the error cleanup at line 513 calls smb_vc_put(vcp) β†’ smb_co_put β†’ smb_co_gone β†’ cp->co_gone = smb_vc_gone β†’ smb_vc_disconnect β†’ smb_iod_request(vcp->vc_iod, ...). vc_iod is still NULL because smb_zmalloc zeroed it and smb_iod_create never ran. smb_iod_request (sys/netproto/smb/smb_iod.c:394-415) immediately executes SMB_IOD_EVLOCK(iod) = spin_lock(&iod->iod_evlock), dereferencing address ~offset_of(iod_evlock) and panicking the kernel. A second, related variant exists if smb_iod_create itself fails at kthread_create_compat (smb_iod.c:704-710): it kfrees iod but does NOT clear vcp->vc_iod, so the same call chain UAFs the freed iod.

Root cause

Trace the error path in smb_vc_create (sys/netproto/smb/smb_conn.c:460-514):

460:    do {
461:        error = ENOMEM;
462:        vcp->vc_paddr = dup_sockaddr(vcspec->sap);
...
486:        error = iconv_open("tolower", vcspec->localcs, &vcp->vc_tolower);  /* user-controlled */
487:        if (error != 0)
488:            break;
...
504:        error = smb_iod_create(vcp);   /* only place vc_iod is set */
505:        if (error != 0)
506:            break;
507:        *vcpp = vcp;
509:        error = 0;
510:    } while (0);
511:
512:    if (error)
513:        smb_vc_put(vcp, scred);

smb_vc_put β†’ smb_co_put (lines 338-364) drops usecount from 1 to 0, sets SMBO_GONE, releases+reacquires the lockmgr lock, and invokes smb_co_gone (lines 250-268). smb_co_gone line 254 dispatches cp->co_gone, which for a VC is smb_vc_gone (lines 550-556):

550: static void
551: smb_vc_gone(struct smb_connobj *cp, struct smb_cred *scred)
552: {
553:    struct smb_vc *vcp = CPTOVC(cp);
554:
555:    smb_vc_disconnect(vcp);   /* unconditional */
556: }

smb_vc_disconnect (lines 678-684) unconditionally dereferences vc_iod:

678: static int
679: smb_vc_disconnect(struct smb_vc *vcp)
680: {
681:
682:    smb_iod_request(vcp->vc_iod, SMBIOD_EV_DISCONNECT | SMBIOD_EV_SYNC, NULL);
683:    return 0;
684: }

iconv_open (sys/libiconv/iconv.c:233-267) returns ENOENT for any charset pair that has no registered converter. The from argument here is vcspec->localcs, which is the user-supplied ioc_localcs field of smbioc_ossn (smb_usr.c:74, 92) β€” only validated as non-empty (smb_usr.c:69). smb_usr_opensession / smb_usr_lookup do NOT validate that localcs/servercs correspond to a real kernel iconv cspair, so an invalid name like "BogusCS-1" reaches smb_vc_create and reliably triggers the panic.

The UAF variant is the same except vc_iod points at freed memory instead of NULL (smb_iod.c:708 kfrees iod without vcp->vc_iod = NULL).

Threat model & preconditions

  • Attacker position: local attacker with /dev/nsmb* access (mode 0700 root:root per smb_dev.c:356 β€” so root, or any process able to reach the ioctl via a setuid helper like mount_smbfs). Indirectly, any unprivileged user who can convince a privileged process to mount an SMB share from a server whose negotiated parameters cause iconv_open to fail (e.g. kernel built without the requested charset module loaded) panics the host.
  • Privileges gained or impact: kernel panic (NULL deref β†’ page fault in kernel mode β†’ fatal trap). Local DoS of the entire kernel. Because the crash happens during connection setup before any data is exchanged with the server, no authentication or server-side cooperation is required for the local trigger β€” only the attacker's choice of charset string matters.
  • Required config or capabilities: Netsmb kernel module loaded, /dev/nsmb* device accessible. The iconv module for the requested charset must not be loaded (the default on most systems).
  • Reachability: open("/dev/nsmb0") β†’ ioctl(fd, SMBIOC_OPENSESSION, &ssn) with ssn.ioc_localcs set to any string that does not match a registered kiconv cspair.

Proof of concept

PoC source: findings/poc/DF-0599/panic.c

Build & run

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

Expected output

Immediate kernel panic with a trap 12 (page fault) at smb_iod_request+0xNN while dereferencing NULL->iod_evlock:

Fatal trap 12: page fault while in kernel mode
cpuid = 0; apic id = 01
fault virtual address   = 0x40     (offset of iod_evlock inside struct smb_iod)
[code] smb_iod_request+0x...: mov ...
db> tr
    smb_iod_request+0x...
    smb_vc_disconnect+0x...
    smb_vc_gone+0x...
    smb_co_gone+0x...
    smb_co_put+0x...
    smb_vc_put+0x...
    smb_vc_create+0x...

The "THIS_CHARSET_DOES_NOT_EXIST_9" string simply must not match any registered kiconv cspair; on a stock kernel without every iconv module loaded, even reasonable-looking names like "KOI8-R" will reproduce the bug if the corresponding module is not present.

Impact

  • Blast radius: any DragonFly system using the in-kernel SMB client with /dev/nsmb* accessible to a privileged or confused-deputy process.
  • Severity rationale: Medium. Trivially-triggerable, deterministic local kernel panic. Requires /dev/nsmb* access (root). No info leak or code execution (it's a NULL-deref at a fixed small offset, and map_at_zero is off by default on DragonFlyBSD, so no privilege escalation primitive). CVSS 3.1 base β‰ˆ 6.2.
  • Reliability: 100% β€” straight-line code path, no race.

Make the gone hook tolerant of an absent iod, AND have smb_iod_create leave vc_iod NULL on its own failure:

--- a/sys/netproto/smb/smb_conn.c
+++ b/sys/netproto/smb/smb_conn.c
@@ -549,7 +549,9 @@ static void
 smb_vc_gone(struct smb_connobj *cp, struct smb_cred *scred)
 {
    struct smb_vc *vcp = CPTOVC(cp);
-
-   smb_vc_disconnect(vcp);
+   /* vc_iod may be NULL if smb_vc_create failed before smb_iod_create
+    * succeeded; nothing to disconnect in that case. */
+   if (vcp->vc_iod != NULL)
+       smb_vc_disconnect(vcp);
 }
@@ -678,6 +680,7 @@ smb_vc_disconnect(struct smb_vc *vcp)
 {
+   KKASSERT(vcp->vc_iod != NULL);
    smb_iod_request(vcp->vc_iod, SMBIOD_EV_DISCONNECT | SMBIOD_EV_SYNC, NULL);
    return 0;

Companion fix in smb_iod.c (required for the UAF variant):

--- a/sys/netproto/smb/smb_iod.c
+++ b/sys/netproto/smb/smb_iod.c
@@ -706,6 +706,7 @@
    if (error) {
        SMBERROR("can't start smbiod: %d", error);
+       vcp->vc_iod = NULL;
        kfree(iod, M_SMBIOD);
        return error;
    }

Optionally also add early validation of localcs/servercs against the registered iconv cspairs in smb_usr_vc2spec (smb_usr.c) so the failure happens before any allocation, but the defensive checks above are the real fix.

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-0599 Β· 14 files
FileTypeDescriptionSize
panic.c trigger-source minimal SMBIOC_OPENSESSION trigger with bogus localcs (NULL vc_iod panic) 5.1 KB view raw
build.sh build-script cc -Wall -O2 -o panic panic.c 167 B view raw
run.sh run-script kldload smbfs + ./panic (root) 441 B view raw
build.log build-log userland trigger build, full output 63 B view raw
run.log run-log baseline (unpatched) decisive run + panic signature 1.3 KB view raw
panic.txt panic-signature fatal trap 12 at smb_iod_request+0x58, fault 0x58 (from boot.log) 776 B view raw
env.txt environment uname, kern.version, cc, kldstat, patched smbfs.ko sha256 554 B view raw
fix.diff suggested-fix guard smb_vc_gone (NULL vc_iod) + clear vc_iod in smb_iod_create failure 867 B view raw
fix_build.log fix-build-log patched smbfs.ko module build, full output (-Werror, rc=0) 19.8 KB view raw
fix_run.log fix-run-log patched-module decisive run (ENOENT, no panic) + 3x determinism 1.3 KB view raw
VERDICT.md verdict full narrative: mechanism, evidence, PoC changes, fix validation 7.3 KB ↓ raw
README.md readme human-facing build/run/expected + reproduce notes 2.9 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 human-facing build/run/expected + reproduce notes
↓ download raw

DF-0599 β€” PoC: smb_vc_create NULL-iod panic via invalid charset

Privileged local deterministic kernel panic. iconv_open returns ENOENT for any charset pair that has no registered converter; smb_vc_create's error path invokes smb_vc_gone β†’ smb_vc_disconnect β†’ smb_iod_request(vcp->vc_iod=NULL, ...), which executes SMB_IOD_EVLOCK(NULL) = smb_sl_lock(&NULL->iod_evlock) and page-faults.

Files

  • panic.c β€” minimal reproducer (SMBIOC_OPENSESSION with an unregistered ioc_localcs).
  • build.sh / run.sh β€” exact build/run commands.
  • fix.diff β€” git-apply-able fix (NULL-guard in smb_vc_gone + clear vc_iod in smb_iod_create failure path).
  • VERDICT.md β€” full root-cause confirmation + before/after fix validation.
  • manifest.json β€” machine-readable artifact catalog.
  • Logs: build.log, run.log (baseline panic), panic.txt, fix_build.log, fix_run.log (patched no-panic), env.txt.

Preconditions

  • DragonFly master DEV kernel 6.5-DEVELOPMENT #0 (unpatched baseline).
  • The netsmb stack is a loadable module: kldload smbfs (pulls in libmchain.ko + libiconv.ko) creates /dev/nsmb.
  • No iconv converter modules loaded (the default) β‡’ any charset name yields ENOENT from iconv_open. We use "BOGUSCS-9" (≀15 chars; ioc_localcs is 16 bytes).
  • Must run as root β€” /dev/nsmb is mode 0700 root:wheel (smb_dev.c:356). On this guest mount_smbfs is 0555 root:wheel (not setuid), so the direct ioctl trigger is the repro path. The realistic unprivileged vector is a privileged confused-deputy mounting with a negotiated charset whose module is absent.

Build & run

./build.sh                 # cc -Wall -O2 -o panic panic.c
sudo ./run.sh              # kldload smbfs ; ./panic   (run as root)

Expected outcome

Unpatched #0 + original smbfs.ko β€” immediate kernel panic (serial console captured in panic.txt / run.log):

Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0x58
Stopped at      smb_iod_request+0x58:   lock xaddl      %edx,0x58(%rbx)
db>

(%rbx = 0 β‡’ vc_iod == NULL; 0x58 = offset of iod_evlock in struct smbiod.)

Patched smbfs.ko (rebuilt from fix.diff) β€” the ioctl returns cleanly:

[*] DF-0599: issuing SMBIOC_OPENSESSION with localcs="BOGUSCS-9" (size=488)
[!] ioctl returned rc=-1 errno=2 (m)        # ENOENT, no panic, guest stays up

Reproducing the fix validation

Because DF-0599 lives in the loadable smbfs.ko module (not the base kernel), the fix is validated by rebuilding only that module:

# on the guest, with fix.diff applied to /usr/src:
cd /usr/src/sys/vfs/smbfs && make
cp /usr/obj/usr/src/sys/vfs/smbfs/smbfs.ko /boot/kernel/smbfs.ko && sync
kldload smbfs
./panic          # now returns ENOENT, no panic

See VERDICT.md for the full mechanism trace and before/after evidence.

VERDICT.md verdict full narrative: mechanism, evidence, PoC changes, fix validation
↓ download raw

DF-0599 β€” VERDICT

Verdict: REPRODUCED (panic), then FIX VALIDATED

The bug is real and deterministically triggerable on the audited DragonFly master DEV kernel (6.5-DEVELOPMENT #0). A single SMBIOC_OPENSESSION ioctl with an unregistered ioc_localcs charset name panics the kernel via a NULL vc_iod dereference in smb_iod_request. The authored fix.diff closes it: on the patched smbfs.ko the ioctl returns ENOENT cleanly with no panic.

Mechanism (confirmed, every hop cited)

  1. Trigger (root) β€” open("/dev/nsmb") then ioctl(fd, SMBIOC_OPENSESSION, &ssn) with ioc_localcs="BOGUSCS-9". /dev/nsmb is mode 0700 root:wheel (sys/netproto/smb/smb_dev.c:356), so the direct trigger needs root (or a privileged confused-deputy such as a hypothetical setuid mount_smbfs β€” on this guest mount_smbfs is 0555 root:wheel, not setuid). The netsmb stack is a loadable module: kldload smbfs brings in smbfs.ko + libmchain.ko + libiconv.ko and creates /dev/nsmb.

  2. Dispatch β€” SMBIOC_OPENSESSION (sys/netproto/smb/smb_dev.c:187-191) β†’ smb_usr_opensession (sys/netproto/smb/smb_usr.c:163-180) β†’ smb_usr_vc2spec (only validates ioc_user[0], ioc_server!=NULL, ioc_localcs[0]!=0; does not validate the charset against a real kiconv cspair β€” smb_usr.c:65-72) β†’ smb_sm_lookup with SMBV_CREATE.

  3. Create + charset failure β€” smb_sm_lookup (smb_conn.c:202) calls smb_vc_create. At smb_conn.c:486 it does iconv_open("tolower", vcspec->localcs, &vcp->vc_tolower). With zero iconv converter modules loaded (only the libiconv.ko framework; no cspairs registered), iconv_open (sys/libiconv/iconv.c:233-267) finds no matching cspair and returns ENOENT (iconv.c:266). The do/while breaks.

  4. Error cleanup β†’ NULL iod deref β€” smb_vc_create error path (smb_conn.c:512-513) calls smb_vc_put(vcp) β†’ smb_co_put (smb_conn.c:337-364) drops usecount to 0 β†’ smb_co_gone (smb_conn.c:249-268) β†’ dispatches cp->co_gone = smb_vc_gone (smb_conn.c:550-556) β†’ smb_vc_disconnect(vcp) (smb_conn.c:678-684) β†’ smb_iod_request(vcp->vc_iod, ...). But vc_iod is still NULL: smb_zmalloc zeroed it (smb_conn.c:437) and smb_iod_create (smb_conn.c:504) never ran.

  5. PANIC β€” smb_iod_request(NULL, ...) executes SMB_IOD_EVLOCK(iod) = smb_sl_lock(&iod->iod_evlock) (sys/netproto/smb/smb_iod.c:403, macro at smb_iod.c:58), which dereferences NULL + offsetof(struct smbiod, iod_evlock) (= 0x58 on this build) and takes a page fault.

Evidence β€” baseline (unpatched #0 + original smbfs.ko)

Serial console (dfbsd-qemu/boot.log):

Fatal user address access from kernel mode from panic at ffffffff82609ab8
Fatal trap 12: page fault while in kernel mode
cpuid = 0; lapic id = 0
fault virtual address   = 0x58
fault code              = supervisor write data, page not present
instruction pointer     = 0x8:0xffffffff82609ab8
kernel: type 12 trap, code=2
Stopped at      smb_iod_request+0x58:   lock xaddl      %edx,0x58(%rbx)
db>

IP 0xffffffff82609ab8 = smbfs.ko base 0xffffffff82600000 + smb_iod_request@0x9a60 + 0x58. %rbx=0 β‡’ vc_iod==NULL. The fault address 0x58 is the offset of iod_evlock inside struct smbiod (sys/netproto/smb/smb_conn.h:448), exactly as predicted. Deterministic β€” straight-line code, no race.

(The finding predicted fault addr β‰ˆ 0x40; the actual offset is 0x58 because iod_evlock sits after several pointer/int fields. Same bug, same function, same root cause.)

PoC changes (what I authored and why)

The finding shipped no trigger source β€” only a README. I authored panic.c from the cited path. One precondition fix during iteration:

  • First run panicked at dup_sockaddr+0x18 (fault addr 0x0), not at smb_iod_request. Root cause: my first PoC left ioc_local=NULL, and smb_vc_create calls dup_sockaddr(vcspec->lap) unconditionally at smb_conn.c:466 β€” before reaching the iconv_open at line 486 β€” so it NULL-deref'd lap first. Fix: supply a valid ioc_local sockaddr so the flow reaches the charset error path. (This is an incidental additional latent NULL-deref in the same function β€” smb_usr_vc2spec skips setting lap when ioc_local==NULL but smb_vc_create assumes it is set β€” same class, same trigger surface; noted but not the subject of DF-0599.)
  • Used a 9-char bogus charset ("BOGUSCS-9") because ioc_localcs is only 16 bytes; the README's "THIS_CHARSET_DOES_NOT_EXIST_9" would not fit.
  • Set ioc_servercs[0]='\0' so only the two tolower/toupper iconv_open calls run (the trigger), skipping the toserver/tolocal block.
  • Defined the smbioc_ossn struct field-for-field from sys/netproto/smb/smb_dev.h:65-83 so the ioctl copyin size matches the kernel exactly (computed via _IOW('n',100,struct smbioc_ossn)).

Exploit chain

Not a memory-corruption primitive β€” it is a NULL-deref page fault at a fixed small offset (0x58). map_at_zero is off by default on DragonFly, so the fault address is unmappable and yields only a local kernel panic / DoS (whole-host). No info leak, no code execution, no privilege escalation. The finding's severity (Medium, CVSS 6.2, C:N/I:N/A:H) is accurate.

Fix (verified)

fix.diff (git-apply-able, plain unified diff so DragonFly patch -p1 also accepts it) makes two minimal changes:

  1. smb_vc_gone (sys/netproto/smb/smb_conn.c) β€” guard the disconnect: if (vcp->vc_iod != NULL) smb_vc_disconnect(vcp);. This is the real fix for the NULL-deref: when smb_vc_create failed before smb_iod_create succeeded, there is no iod to disconnect from.

  2. smb_iod_create (sys/netproto/smb/smb_iod.c) β€” on kthread_create_compat failure, set vcp->vc_iod = NULL before kfree(iod). This closes the related UAF variant the finding describes (smb_iod.c:699 sets vc_iod=iod, then :708 frees iod without clearing vc_iod, so the same gone-hook would UAF the freed iod).

This matches the finding's ## Recommended fix proposal (gone-hook guard + iod_create NULL-clear). It omits the optional KKASSERT in smb_vc_disconnect (the guard makes it unreachable with NULL) and omits the optional early localcs/servercs validation in smb_usr.c (defense in depth, not required to close the panic).

Why I rebuilt the module, not nativekernel

smbfs is optional netsmb in sys/conf/files and ships as a loadable module (sys/vfs/smbfs/Makefile, .PATH netproto/smb) β€” it is not compiled into the X86_64_GENERIC kernel (confirmed: no smb symbols / /dev/nsmb until kldload smbfs). Therefore make nativekernel would not have picked up the fix. The correct validation unit was rebuilding only smbfs.ko from patched source (cd /usr/src/sys/vfs/smbfs && make), replacing /boot/kernel/smbfs.ko, and kldload-ing it. The base kernel stays #0 (unchanged) β€” the fix lives entirely in the module.

Before / after

kernel smbfs.ko result
baseline #0 (unpatched) original panic: smb_iod_request+0x58, fault 0x58, guest DOWN
patched #0 (unchanged) rebuilt w/ fix ioctl β†’ ENOENT (errno 2), guest UP, no panic, deterministic Γ—3

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED. The PoC panicked deterministically on the unpatched #0 baseline + original smbfs.ko (fatal trap 12 at smb_iod_request+0x58, fault 0x58, guest DOWN), and does NOT panic on the single-fix smbfs.ko: the ioctl returns ENOENT (errno 2), the guest stays UP, and there are zero panic lines in the current boot.log across 3 repeat runs. The NULL vc_iod guard in smb_vc_gone() makes smb_vc_create's iconv_open(ENOENT) error path return gracefully instead of dereferencing NULL->iod_evlock. fix closes the bug. (NOTE: smbfs is a loadable module, so nativekernel would NOT have exercised the fix; the validation rebuilt the smbfs.ko module, which is the correct unit for this code.)

BASELINE (unpatched): Stopped at smb_iod_request+0x58: lock xaddl %edx,0x58(%rbx) ; fault virtual address = 0x58 ; guest DOWN.
PATCHED (smbfs.ko rebuilt w/ fix.diff, base kernel still #0): [!] ioctl returned rc=-1 errno=2 (m) ; guest UP ; no panic ; deterministic x3.
fix build: patched smbfs.ko built with -Werror, MODULE_BUILD_RC=0 (full log in fix_build.log).
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (base kernel UNCHANGED). DF-0599 lives in the loadable smbfs.ko module (sys/vfs/smbfs/Makefile, .PATH netproto/smb), NOT in the base kernel, so the fix was validated by rebuilding ONLY smbfs.ko from patched source and replacing /boot/kernel/smbfs.ko (original sha256 d854773e...4895 -> patched sha256 b522d330...06d2), then kldload.

Confirmed kernel references

Detail

Exploit chain

none. Not a memory-corruption primitive: it is a NULL-pointer page fault at a fixed small offset (0x58, the iod_evlock spinlock). map_at_zero is off by default on DragonFly so the fault address is unmappable; the only achievable effect is a local whole-host kernel panic (DoS). No info leak, code execution, or privilege escalation derivable.

Evidence (decisive lines)

BASELINE (unpatched #0 + original smbfs.ko), from dfbsd-qemu/boot.log:
  Fatal user address access from kernel mode from panic at ffffffff82609ab8
  Fatal trap 12: page fault while in kernel mode
  fault virtual address   = 0x58
  instruction pointer     = 0x8:0xffffffff82609ab8
  Stopped at      smb_iod_request+0x58:   lock xaddl      %edx,0x58(%rbx)
  (IP = smbfs.ko@0xffffffff82600000 + smb_iod_request@0x9a60 + 0x58; %rbx=0 => vc_iod==NULL)
PATCHED (rebuilt smbfs.ko with fix.diff):
  [*] DF-0599: issuing SMBIOC_OPENSESSION with localcs="BOGUSCS-9" (size=488)
  [!] ioctl returned rc=-1 errno=2 (m)   # ENOENT, no panic, guest UP
  (deterministic x3; 0 panic lines in current boot.log)

PoC changes

The finding shipped only a README (no trigger source). I authored panic.c: open(/dev/nsmb) + ioctl(SMBIOC_OPENSESSION) with ioc_localcs="BOGUSCS-9", ioc_opt=SMBVOPT_CREATE, and valid ioc_server/ioc_local sockaddrs (the struct smbioc_ossn is defined field-for-field from smb_dev.h so the ioctl copyin size matches). First build/run panicked at dup_sockaddr(vcspec->lap) (smb_conn.c:466) because ioc_local was NULL; fixed by supplying a valid ioc_local so the flow reaches the iconv_open ENOENT path at smb_conn.c:486. Authored build.sh/run.sh. Authored fix.diff (NULL-guard in smb_vc_gone + clear vc_iod in smb_iod_create failure path). Full narrative + before/after in findings/poc/DF-0599/VERDICT.md.

Verified recommended fix

In sys/netproto/smb/smb_conn.c smb_vc_gone(), guard the disconnect: 'if (vcp->vc_iod != NULL) smb_vc_disconnect(vcp);' β€” when smb_vc_create failed before smb_iod_create succeeded (e.g. iconv_open ENOENT) there is no iod to disconnect from. Plus in sys/netproto/smb/smb_iod.c smb_iod_create(), set 'vcp->vc_iod = NULL;' before kfree(iod) on kthread_create_compat failure, closing the related UAF variant (smb_iod.c:699 sets vc_iod then :708 frees iod without clearing it). MATCHES the finding's ## Recommended fix proposal (gone-hook guard + iod_create NULL-clear). git-apply-able diff at findings/poc/DF-0599/fix.diff (plain unified form so DragonFly patch -p1 also accepts it).

Verdict

REPRODUCED. The bug is real and deterministic on the audited 6.5-DEVELOPMENT #0 kernel. A single SMBIOC_OPENSESSION ioctl with an unregistered ioc_localcs (e.g. "BOGUSCS-9") makes iconv_open return ENOENT inside smb_vc_create (smb_conn.c:486); the do/while error cleanup does smb_vc_put (smb_conn.c:513) -> smb_co_put -> smb_co_gone -> smb_vc_gone (smb_conn.c:550) -> smb_vc_disconnect (smb_conn.c:678) -> smb_iod_request(vcp->vc_iod=NULL) (smb_iod.c:403). vc_iod is still NULL because smb_zmalloc zeroed it (smb_conn.c:437) and smb_iod_create never ran, so SMB_IOD_EVLOCK(NULL)=smb_sl_lock(&NULL->iod_evlock) page-faults. Confirmed by the serial-console panic: 'Fatal trap 12 ... fault virtual address = 0x58 ... Stopped at smb_iod_request+0x58: lock xaddl %edx,0x58(%rbx)' with %rbx=0 (IP 0xffffffff82609ab8 = smbfs.ko base + smb_iod_request@0x9a60 + 0x58). It is a NULL-deref at a fixed small offset with map_at_zero off, so impact is local kernel panic/DoS only (no leak/code-exec/privesc) β€” the finding's Medium/CVSS-6.2/C:N/I:N/A:H rating is accurate. The finding shipped NO trigger source; I authored panic.c. One iteration was needed: my first PoC left ioc_local=NULL and panicked earlier at dup_sockaddr(vcspec->lap) (smb_conn.c:466) before reaching iconv_open β€” supplying a valid ioc_local reached the real trigger (this also exposed an incidental related NULL-deref in the same function, noted in VERDICT.md).