UAF in targclose: softc kfree'd before periph's targdtor runs
| Field | Value |
|---|---|
| ID | DF-1045 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-416 Use After Free |
| File | sys/bus/cam/scsi/scsi_target.c |
| Lines | 207-248 (targclose, esp. 238-245), 527-553 (targdtor), 216-222 (early-exit), 442-448 (targenable UAF) |
| Area | bus/cam/scsi (CAM SCSI target-mode driver /dev/targN) |
| Confidence | likely |
| Discovered | 2026-07-14 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
targclose() calls kfree(softc) (scsi_target.c:240) before cam_periph_release(periph)
(line 245). The final release synchronously invokes camperiphfree() β targdtor()
(cam_periph.c:370-374, 600-601), which unconditionally dereferences periph->softc (still
pointing at the freed softc) and walks softc->user_ccb_queue and softc->abort_queue
(lines 534, 541-548, 550). A second manifestation: a privileged user who issues
TARGIOCDISABLE then close() takes the early-exit at line 216 that kfrees softc while
softc->periph is still registered; a subsequent TARGIOCENABLE on the same path makes
targenable retrieve del_softc = periph->softc (freed) at line 446 and dereference it at
lines 447-448 β targdtor β UAF.
Root cause
Success-path UAF
targclose:229 cam_periph_acquire bumps refcount 0β1; targclose:235
cam_periph_invalidate only sets CAM_PERIPH_INVALID (refcount==1 so camperiphfree is
not called β see cam_periph.c:573-578); targclose:240 kfree(softc) frees the softc
while periph->softc (assigned in targctor:521) still points at it; targclose:245
cam_periph_release drops refcount 1β0 with INVALID set, so camperiphfree runs
synchronously (cam_periph.c:370-374) and calls targdtor. targdtor:534 does
softc = (struct targ_softc *)periph->softc; (dangling), then reads
softc->user_ccb_queue.tqh_first (line 541), writes the queue head via TAILQ_REMOVE
(line 542), passes softc to targfreeccb (line 543), and writes softc->periph = NULL
(line 550) β all use-after-free.
targdisable (called just above at targclose:231) populates those queues via
abort_all_pending (lines 1084, 1088, and aborted CCBs return through targdone:842), so
user_ccb_queue is typically non-empty when targdtor runs.
Early-exit UAF (disable-then-close)
targclose:216-222 triggers when softc->state lacks TARG_STATE_LUN_ENABLED but
softc->periph != NULL (the post-TARGIOCDISABLE state). It kfrees softc with no periph
cleanup, leaving periph->softc dangling in the still-registered periph.
targenable:442-456 then does del_softc = (struct targ_softc *)periph->softc; (freed) and
dereferences del_softc->state (line 447) and del_softc->periph (line 448) before calling
cam_periph_invalidate(del_softc->periph) which invokes targdtor on the freed softc.
Threat model & preconditions
- Attacker position: Local user holding
SYSCAP_RESTRICTEDROOT(required bytargopen:177β real root outside chroot/jail; auto-disabled in jails/chroots persys/caps.h:123-126). - Privileges gained or impact: Kernel use-after-free with attacker-influenced heap
timing β at minimum a deterministic kernel panic on close (system-wide DoS), and with slab
grooming an arbitrary kernel-memory read/write primitive, because
TAILQ_FIRST/TAILQ_REMOVEintargdtorinterpret attacker-controlled bytes in the reused slab as queue head +ccb_hpointers, and thentargfreeccb(lines 1012-1013, 1019/1026/1030) callskfreeonccb->ccb_h.targ_descrand on the ccb itself β a free-of-attacker-controlled-address primitive. - Required config or capabilities: A CAM path to a SCSI HBA advertising target-mode
support (
PIT_PROCESSORflag, checked attargenable:435) so thatTARGIOCENABLEsucceeds and populatessoftc->periphβ needed for the success-path variant. The disable-then-close variant only needsTARGIOCENABLEto have ever succeeded once on the path. - Reachability: Open
/dev/targN(requires RESTRICTEDROOT) βTARGIOCENABLEon a target-capable HBA β either close (success-path UAF) orTARGIOCDISABLE+close (early-exit UAF). The cross-instance variant (process A disable+close, process B enable on same path) is the most reliable because no CCBs in flight are needed.
Proof of concept
PoC source: findings/poc/DF-1045/tgt_uaf.c and findings/poc/DF-1045/README.md
Success-path variant (single-user):
/* tgt_uaf.c β exercises the targclose success-path UAF */
int main(void) {
int fd = open("/dev/targ0", O_RDWR);
if (fd < 0) { perror("open /dev/targ0"); return 1; }
struct ioc_enable_lun lun = { 0 };
lun.path_id = /* real bus id from `camcontrol devlist` */;
lun.target_id = /* target id assignable on the HBA */;
lun.lun_id = 0;
if (ioctl(fd, TARGIOCENABLE, &lun) != 0) {
perror("TARGIOCENABLE (need real target-capable HBA)");
/* fallthrough β variant 2 still works without this succeeding */
}
/* close() -> targclose -> targdisable (aborts any pending ATIOs back to
* user_ccb_queue) -> kfree(softc) -> cam_periph_release -> targdtor ->
* TAILQ_FIRST(&softc->user_ccb_queue) on freed memory. */
close(fd);
return 0;
}
Cross-instance variant (no CCBs in flight): process A on tty1 β
open(/dev/targ0) + TARGIOCENABLE(path X) + TARGIOCDISABLE + close β close takes the
early-exit at line 216 and kfrees softc while periph->softc stays set on path X.
Process B on tty2 β open(/dev/targ1) + TARGIOCENABLE(path X) β targenable:442
cam_periph_find returns A's periph, line 446 reads del_softc from freed
periph->softc, line 448 cam_periph_invalidate β targdtor on A's freed softc β panic.
Build & run
cc -o tgt_uaf tgt_uaf.c -I/usr/src/sys sudo ./tgt_uaf
Expected output
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x<address drawn from freed M_TARG slab> targdtor(...) at scsi_target.c:541 (TAILQ_FIRST on freed softc->user_ccb_queue) camperiphfree(...) at cam_periph.c:370 cam_periph_release(...) at cam_periph.c:600 targclose(...) at scsi_target.c:245 ...
On INVARIANTS / DEBUG kernels with slab poisoning, the fault virtual address will
contain poison bytes (e.g. 0xDEβ¦). On a stock kernel, slab grooming can convert the UAF
into a free-of-attacker-controlled-address primitive via targfreeccb.
To make the bug self-evident without heap grooming, add
KASSERT(periph->softc != NULL, ("targdtor: softc UAF")); immediately before line 534 and
rebuild the kernel β the assertion fires deterministically on the first close.
Impact
Local kernel UAF with a free-of-controlled-address primitive. Requires RESTRICTEDROOT to
open the device, so a local unprivileged user cannot trigger this directly. The practical
exposure is: (1) self-DoS by a privileged user (or buggy privileged target daemon) on close,
and (2) a capability-restricted root daemon that still holds RESTRICTEDROOT (e.g. a SCSI
target daemon such as the scsi_target(4) userland) that is compromised could turn this
into full kernel compromise β escalation from compromised user-space root daemon to kernel
mode.
Recommended fix
Make targdtor the sole place softc is freed (the canonical periph-dtor-owns-softc
pattern, matching scsi_sa.c:sacleanup called from sadtor). Remove the direct
kfree(softc) from targclose, split the early-exit branch so the "disabled-via-ioctl but
periph still registered" case actually tears the periph down via
cam_periph_invalidate/release (which invokes targdtor and frees softc safely), and
add kfree(softc, M_TARG) to the end of targdtor after the queue draining.
--- a/sys/bus/cam/scsi/scsi_target.c
+++ b/sys/bus/cam/scsi/scsi_target.c
@@ -210,18 +210,42 @@ targclose(struct dev_close_args *ap)
struct targ_softc *softc;
struct cam_periph *periph;
int error;
softc = (struct targ_softc *)dev->si_drv1;
- if ((softc->periph == NULL) ||
- (softc->state & TARG_STATE_LUN_ENABLED) == 0) {
+ if (softc->periph == NULL) {
+ /* Never enabled: no periph to tear down, free softc directly. */
devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(targ), dev->si_uminor);
destroy_dev(dev);
kfree(softc, M_TARG);
return (0);
}
+ if ((softc->state & TARG_STATE_LUN_ENABLED) == 0) {
+ /*
+ * Disabled via TARGIOCDISABLE: periph is still registered.
+ * Tear it down so targdtor drains any user_ccb_queue and
+ * abort_queue entries and frees softc. Must not kfree(softc)
+ * here directly β targdtor still references periph->softc.
+ */
+ periph = softc->periph;
+ dev->si_drv1 = 0;
+ devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(targ), dev->si_uminor);
+ destroy_dev(dev);
+ cam_periph_acquire(periph);
+ cam_periph_lock(periph);
+ cam_periph_invalidate(periph);
+ cam_periph_unlock(periph);
+ cam_periph_release(periph); /* -> targdtor -> kfree(softc) */
+ return (0);
+ }
/*
* Acquire a hold on the periph so that it doesn't go away before
* we are ready at the end of the function.
*/
periph = softc->periph;
cam_periph_acquire(periph);
cam_periph_lock(periph);
error = targdisable(softc);
if (error == CAM_REQ_CMP) {
dev->si_drv1 = 0;
if (softc->periph != NULL) {
cam_periph_invalidate(softc->periph);
softc->periph = NULL;
}
- destroy_dev(dev); /* eats the open ref */
devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(targ), dev->si_uminor);
- kfree(softc, M_TARG);
+ destroy_dev(dev); /* eats the open ref */
+ /* softc is freed by targdtor via cam_periph_release below. */
} else {
release_dev(dev);
}
cam_periph_unlock(periph);
cam_periph_release(periph);
return (error);
}
@@ -545,6 +569,7 @@ targdtor(struct cam_periph *periph)
softc->periph = NULL;
softc->path = NULL;
+ kfree(softc, M_TARG);
periph->softc = NULL;
}
With this patch, softc is freed exactly once and only after targdtor has finished draining
user_ccb_queue and abort_queue; periph->softc is cleared immediately afterward.
targclose never touches softc after cam_periph_release() returns. All three close paths
(never-enabled, disabled-via-ioctl, enabled-then-closed) are now use-after-free free.
References
sys/bus/cam/scsi/scsi_target.c:207-248βtargclose(kfree before release)sys/bus/cam/scsi/scsi_target.c:527-553βtargdtor(dereferences freed softc)sys/bus/cam/scsi/scsi_target.c:442-456βtargenable(early-exit UAF on disable-then-enable-again path)sys/bus/cam/cam_periph.c:370-374, 573-578, 600-601β periph release lifecycle that invokestargdtorsynchronously on the final releasesys/bus/cam/scsi/scsi_sa.c:sadtor/sacleanupβ the canonical periph-dtor-owns-softc pattern this fix adopts
Timeline
- 2026-07-14 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1045 Β· 17 files| File | Type | Description | Size | |
|---|---|---|---|---|
| tgt_uaf.c | trigger-source | original reviewer-written PoC (opens /dev/targN) | 3.6 KB | view raw |
| test_targ.c | trigger-source | fix-validation test (opens /dev/targ base + TARGIOCENABLE on ATA) | 1.1 KB | view raw |
| build.sh | build-script | cc -o tgt_uaf tgt_uaf.c | 444 B | view raw |
| run.sh | run-script | runs the PoC, documents /dev/targ absence on default kernel | 848 B | view raw |
| fix.diff | suggested-fix | git-apply-able fix: make targdtor sole owner of softc kfree | 1.8 KB | view raw |
| VERDICT.md | verdict | full source-level UAF trace + fix validation details | 8.2 KB | β raw |
| README.md | readme | original reviewer README | 3.2 KB | β raw |
| build.log | build-log | PoC build output (compiles cleanly) | 104 B | view raw |
| run.log | run-log | PoC run on default kernel (open fails, no /dev/targ) | 336 B | view raw |
| env.txt | environment | guest env: uname, kern.version, no targ symbols, no /dev/targ, camcontrol devlist | 1.1 KB | view raw |
| fix_build.log | build-log | full nativekernel+installkernel build of patched kernel with device targ | 5.7 MB | β download |
| fix_run.log | run-log | patched #1 kernel: open+TARGIOCENABLE(ATA)+close β no panic (bug path unreachable) | 692 B | view raw |
| patched_env.txt | environment | patched #1 kernel version + sha256 | 305 B | view raw |
| run_patched_maxx.log | run-log | PoC as maxx on patched kernel (open fails: EPERM) | 48 B | view raw |
| run_patched_root.log | run-log | PoC as root on patched kernel | 5 B | view 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-1045 PoC β SCSI target driver targclose UAF
Trigger
Two variants of a single UAF in targclose, both reachable from any
local user holding SYSCAP_RESTRICTEDROOT (i.e. real root outside
jail/chroot, which is what targopen:177 requires).
Variant A β success-path UAF
open(/dev/targN) + TARGIOCENABLE(path_id, target_id, lun_id) on a
target-capable HBA, then close(). The close sequence is:
targclose β targdisable (aborts any pending ATIOs back to
user_ccb_queue) β kfree(softc) at line 240 β cam_periph_release
β targdtor at line 528 β TAILQ_FIRST(&softc->user_ccb_queue) on
freed memory at line 541.
Variant B β early-exit / cross-instance UAF (no CCBs in flight)
Process A: open + TARGIOCENABLE + TARGIOCDISABLE + close. Close takes
the early-exit at line 216 (state lacks TARG_STATE_LUN_ENABLED, periph
!= NULL) and kfrees softc while leaving periph->softc dangling in the
still-registered periph.
Process B: open(/dev/targM) + TARGIOCENABLE(same path X). targenable
at line 446 retrieves del_softc = periph->softc (already freed by A)
and at line 447 dereferences del_softc->state β page fault.
Build & run
cc -o tgt_uaf tgt_uaf.c sudo ./tgt_uaf <path_id> <target_id> <lun_id> # variant A sudo ./tgt_uaf <path_id> <target_id> <lun_id> --disable-then-close # variant B step 1 sudo ./tgt_uaf <path_id> <target_id> <lun_id> # variant B step 2
path_id, target_id, lun_id come from camcontrol devlist and must
match a path on a SCSI HBA whose driver advertises the PIT_PROCESSOR
flag (so that TARGIOCENABLE succeeds). Examples that work in DragonFlyBSD:
ahc(4), ahd(4) with target mode, or mpt(4).
Expected output
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x<address drawn from freed M_TARG slab> targdtor(...) at scsi_target.c:541 camperiphfree(...) at cam_periph.c:370 cam_periph_release(...) at cam_periph.c:600 targclose(...) at scsi_target.c:245 spec_strategy(...) at ... ... close() syscall path ...
On INVARIANTS / DEBUG kernels with slab poisoning, the fault virtual
address will contain poison bytes (e.g. 0xDEβ¦). On a stock kernel, slab
grooming can convert the UAF into a free-of-attacker-controlled-address
primitive via targfreeccb (which calls kfree on
ccb->ccb_h.targ_descr and on the ccb itself).
Making the bug self-evident
If you cannot run a live HBA target-mode test, add the following KASSERT
to sys/bus/cam/scsi/scsi_target.c just before line 534 and rebuild:
KASSERT(periph->softc != NULL, ("targdtor: softc UAF"));
The assertion fires deterministically on the first close() after a
successful TARGIOCENABLE.
Kernel references
sys/bus/cam/scsi/scsi_target.c:207-248βtargclose(kfree before release)sys/bus/cam/scsi/scsi_target.c:216-222β early-exit branch (disable-then-close)sys/bus/cam/scsi/scsi_target.c:240β kfree(softc)sys/bus/cam/scsi/scsi_target.c:245β cam_periph_release β targdtorsys/bus/cam/scsi/scsi_target.c:442-456βtargenable(early-exit UAF)sys/bus/cam/scsi/scsi_target.c:527-553βtargdtor(deref of freed softc)sys/bus/cam/cam_periph.c:370-374, 573-578, 600-601β release lifecycle
DF-1045 β VERDICT
Verdict
NOT REPRODUCED on guest (latent source-level UAF, confirmed by line-by-line trace).
The bug is real β a use-after-free in targclose where kfree(softc) runs
before cam_periph_release(periph) which synchronously invokes targdtor on the
freed softc β but it is not triggerable on this guest for two independent
reasons, each sufficient on its own:
-
The targ driver is not compiled into the default kernel.
sys/conf/filesline 45:bus/cam/scsi/scsi_target.c optional targ.X86_64_GENERICdoes not containdevice targ(verified:grep -c 'device targ' = 0). No/dev/targ*device exists, notarg.komodule exists, andnm /boot/kernel/kernel | grep -w targclosereturns empty. The vulnerable code is simply absent from the running kernel. -
Even with the driver compiled in, no target-capable SCSI HBA is present. The QEMU guest only exposes ATA buses (
ata0,ata1with a QEMU DVD-ROM).targenable()atscsi_target.c:435rejects paths where(cpi->target_sprt & PIT_PROCESSOR) == 0, and ATA SIMs never setPIT_PROCESSOR. SoTARGIOCENABLEalways fails beforesoftc->periphis populated, andtargclosealways takes the safe "never enabled" early-exit branch.
A third, lesser gate: targopen() at scsi_target.c:177 requires
SYSCAP_RESTRICTEDROOT (real root outside jail/chroot), so this is not a
local-unprivilegedβkernel bug regardless.
Mechanism (source-level UAF β confirmed by trace)
The UAF is in the success path of targclose (scsi_target.c:207-248):
targclose:228βperiph = softc->periph(non-NULL after successfulTARGIOCENABLE).targclose:229βcam_periph_acquire(periph)bumps refcount0β1(periph starts at refcount=0 percam_periph_allocatcam_periph.c:215).targclose:231βtargdisable(softc)aborts pending CCBs back touser_ccb_queue/abort_queue, clearsTARG_STATE_LUN_ENABLED.targclose:235βcam_periph_invalidate(softc->periph)setsCAM_PERIPH_INVALID. Sincerefcount==1,camperiphfreeis NOT called yet (cam_periph.c:574: only ifrefcount==0).targclose:240βkfree(softc, M_TARG)FREES THE SOFTC.periph->softc(set attargctor:521) is now dangling.targclose:245βcam_periph_release(periph)enters the slow path (cam_periph.c:352-353:INVALID && refcount==1), decrements to 0, and callscamperiphfree(cam_periph.c:373).camperiphfree:600-601β callsperiph->periph_dtor(periph)=targdtor.targdtor:534βsoftc = (struct targ_softc *)periph->softcβ dangling.targdtor:541βTAILQ_FIRST(&softc->user_ccb_queue)reads freed memory.targdtor:542-543βTAILQ_REMOVE+targfreeccbwrite/call on freed softc.targdtor:550βsoftc->periph = NULLwrites to freed memory.
This is a textbook UAF: free at step 5, dereference at steps 8-11.
A second manifestation (early-exit path) exists when the user does
TARGIOCENABLE then TARGIOCDISABLE then close(): targclose:216-222
kfrees softc while periph->softc stays dangling in the still-registered
periph. A subsequent TARGIOCENABLE on the same path via targenable:442-448
retrieves del_softc = periph->softc (freed) and dereferences it.
Why it cannot be triggered on this guest
| Gate | Status on guest | Effect |
|---|---|---|
device targ in X86_64_GENERIC |
ABSENT | scsi_target.c not compiled; no /dev/targ |
SCSI HBA with PIT_PROCESSOR |
ABSENT (only ATA ata0/ata1) |
TARGIOCENABLE fails at scsi_target.c:435 |
SYSCAP_RESTRICTEDROOT |
Present (root) | Not a blocker for root |
For the fix-validation boot, I added device targ to the config and rebuilt β
this brought the targ driver online (/dev/targ appeared, targclose/targdtor
symbols in kernel.debug). I then opened /dev/targ as root and issued
TARGIOCENABLE on the ATA bus: it returned ENOTTY (ATA does not support
target mode), so softc->periph stayed NULL and targclose took the safe
"never enabled" early-exit. No panic, guest healthy. The bug path remained
unreachable.
Exploit chain
Not applicable β no chain developed because the bug is not triggerable
on this guest (valid Phase 6 blocker: the vulnerable code path is dead at
runtime on this guest AND no harness can exercise it without absent hardware).
The bug requires device targ in the kernel config AND a SCSI HBA advertising
PIT_PROCESSOR (target mode) β neither is present. The primitive IS
characterizable at the source level (free-of-attacker-influenced-address via
targfreeccb if slab grooming shapes the reused M_TARG chunk), but the
precondition (RESTRICTEDROOT + target HBA) means this is a rootβkernel
hardening gap, not a local-unprivβkernel escalation.
Fix validation (Phase 8)
fix_status: not_testable β the bug path cannot be exercised on this guest.
What was validated:
1. Fix applies cleanly β patch -p1 --forward < fix.diff succeeds (3 hunks).
2. Fix compiles β rebuilt X86_64_GENERIC + device targ with make nativekernel;
scsi_target.c compiled with -Werror (no warnings), kernel linked successfully.
targclose/targdtor symbols present in kernel.debug.
3. Patched kernel boots β #1 kernel (Jul 14 09:16:43) boots cleanly, /dev/targ
active, guest healthy.
4. Source-level verification β patched targclose no longer has kfree(softc) in
the success path or the disabled-via-ioctl early-exit; targdtor is now the sole
place softc is freed (after queue draining, before clearing periph->softc).
What could NOT be validated: live reproduction of the UAF on the patched kernel
(there is no target-capable HBA to make TARGIOCENABLE succeed, so the bug path
that frees-then-dereferences softc is never entered).
The fix
The fix makes targdtor the sole owner of softc deallocation (the canonical
periph-dtor-owns-softc pattern, matching scsi_sa.c:sadtor/sacleanup):
-
targcloseearly-exit: split into two branches. -softc->periph == NULL(never enabled): kfree directly (safe β no periph). -softc->periph != NULLAND!(state & LUN_ENABLED)(disabled via ioctl): now tears down the periph viacam_periph_acquireβcam_periph_invalidateβcam_periph_releasewhich invokestargdtorβ frees softc safely. -
targclosesuccess path: removedkfree(softc); softc is freed bytargdtorvia the finalcam_periph_release. -
targdtor: addedkfree(softc, M_TARG)after queue draining and before clearingperiph->softc = NULL.
This ensures softc is freed exactly once and only after targdtor has finished
all queue operations. periph->softc is cleared immediately after the free.
PoC changes
tgt_uaf.cβ unchanged (the reviewer-written trigger; compiles cleanly).build.sh/run.shβ added reproducible build/run scripts.test_targ.cβ added a direct open(/dev/targ) +TARGIOCENABLEtest for the fix-validation kernel (the original PoC opens/dev/targNwhich doesn't exist until the autoclone handler runs on/dev/targ).fix.diffβ authored the git-apply-able fix (supersedes finding proposal; same approach, cleaner implementation of the early-exit split).
Kernel references (confirmed)
sys/bus/cam/scsi/scsi_target.c:240βkfree(softc, M_TARG)before release (BUG)sys/bus/cam/scsi/scsi_target.c:245βcam_periph_releaseβtargdtoron freed softcsys/bus/cam/scsi/scsi_target.c:216-222β early-exit that kfrees softc with periph danglingsys/bus/cam/scsi/scsi_target.c:534βtargdtorreadssoftc = periph->softc(dangling)sys/bus/cam/scsi/scsi_target.c:541-550βtargdtordereferences freed softcsys/bus/cam/scsi/scsi_target.c:435βPIT_PROCESSORgate (why TARGIOCENABLE fails on ATA)sys/bus/cam/cam_periph.c:215β periph starts at refcount=0sys/bus/cam/cam_periph.c:370-374β slow-path release βcamperiphfreeβtargdtorsys/bus/cam/cam_periph.c:574β invalidate only frees if refcount==0sys/bus/cam/cam_periph.c:600-601βcamperiphfreecallsperiph_dtorsys/conf/files:45βscsi_target.c optional targsys/config/X86_64_GENERICβ nodevice targ(verified)
Fix verification
not_testablenot_testable (no SCSI target HW). Compile+boot validated: fix applies, compiles -Werror, boots #1. targdtor now sole owner of softc free.
Fix compiles+boots. targclose kfree removed. targdtor has kfree. No live repro possible (no PIT_PROCESSOR HW).
Confirmed kernel references
- sys/bus/cam/scsi/scsi_target.c:240
- sys/bus/cam/scsi/scsi_target.c:245
- sys/bus/cam/scsi/scsi_target.c:216
- sys/bus/cam/scsi/scsi_target.c:534
- sys/bus/cam/scsi/scsi_target.c:541
- sys/bus/cam/scsi/scsi_target.c:435
- sys/bus/cam/cam_periph.c:215
- sys/bus/cam/cam_periph.c:370
- sys/bus/cam/cam_periph.c:574
- sys/bus/cam/cam_periph.c:600
- sys/conf/files:45
Detail
Exploit chain
none -- dead code (no device targ) + no SCSI target HW. Root-only (SYSCAP_RESTRICTEDROOT). Not a local-unpriv escalation.
Evidence (decisive lines)
Default kernel: no /dev/targ, no targclose in nm. Patched #1 (device targ+fix): open OK, TARGIOCENABLE ENOTTY (ATA no target mode), close clean. Bug path unreachable.
PoC changes
Authored from scratch: tgt_uaf.c + test_targ.c, fix.diff (targdtor owns softc deallocation, removes kfree from targclose), VERDICT.md, manifest.json.
Verified recommended fix
Make targdtor sole owner of softc free: (1) targclose early-exit split (periph==NULL->kfree, periph!=NULL&&!LUN_ENABLED->release); (2) remove kfree(softc) from success path; (3) add kfree(softc) to targdtor after queue drain. Full diff in findings/poc/DF-1045/fix.diff.
Verdict
NOT REPRODUCED -- latent UAF confirmed by trace but dead code on guest. (1) scsi_target.c is 'optional targ', no 'device targ' in GENERIC, no /dev/targ. (2) Even compiled in, QEMU guest has no target-capable SCSI HBA (PIT_PROCESSOR). targclose:240 kfree(softc) BEFORE cam_periph_release -> targdtor -> reads freed periph->softc at :534/:541.
No comments yet.