PTIOCSETTIMEOUT signed-multiply overflow wraps softc->io_timeout negative (UB), consumed as u_int32_t CDB timeout
Summary
Only the lower bound is validated before io_timeout = val*1000: >=2147484 overflows to negative (observable via GETTIMEOUT returning 0), consumed as a u_int32_t CCB timeout. Root-gated self-corruption, no memory safety. Guest-reproduced on the ptsim rig (2147484 -> GET=0); EINVAL-upper-bound fix validated. Same code upstream.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5463 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.1 KB | β raw | |
| VERDICT.md | β | 2.1 KB | β raw | |
| to5463.c | β | 1.5 KB | view raw | |
| build.sh | β | 138 B | view raw | |
| run.sh | β | 107 B | view raw | |
| run.log | β | 209 B | view raw | |
| run.patched.log | β | 168 B | view raw | |
| fix.diff | β | 379 B | view raw | |
| verdict.json | β | 2.3 KB | view raw | |
| manifest.json | β | 837 B | view raw |
DF-5463 β PTIOCSETTIMEOUT signed-multiply overflow
ptioctl() (sys/bus/cam/scsi/scsi_pt.c:648-656) stores
softc->io_timeout = *(int *)addr * 1000; /* :654 */
with no upper bound. For *(int*)addr > 2147483 the int multiply
overflows (UB) and io_timeout goes negative;
PTIOCGETTIMEOUT (:642-646) maps anything < 1000 ms to 0, so the wrap
is directly observable from userland. The negative value is later
consumed as a u_int32_t CDB timeout in ptstart() :483 β
scsi_send_receive() :691 (cam_fill_csio timeout) β a garbage
(negative-wrapped) timeout instead of a rejection.
Root-gated knob self-corruption (no memory-safety impact): hardening / correctness finding.
Prereq
Guest kernel with device pt (see ../DF-5462/README.md) and the ptsim
rig attached (pt0 present).
Reproduce (guest, root)
./build.sh ./run.sh
Expected
set=100000 -> get=100000 (expect 100000) set=2147483 -> get=2147483 (expect 2147483) set=2147484 -> get=0 (expect 2147484; 0 == OVERFLOWED to negative) OVERFLOW-MANIFEST: io_timeout wrapped negative
DF-5463 VERDICT
Status: REPRODUCED (deterministic), fix validated on the guest
Root cause
ptioctl() PTIOCSETTIMEOUT (sys/bus/cam/scsi/scsi_pt.c:648-656):
if (*(int *)addr < 1) { /* only the LOWER bound is checked */
error = EINVAL;
break;
}
softc->io_timeout = *(int *)addr * 1000; /* :654 signed overflow */
For *(int *)addr >= 2147484 the int multiply wraps (UB) and
softc->io_timeout goes negative. It is consumed as a u_int32_t
millisecond CDB timeout by ptstart() :483 β scsi_send_receive()
:691 (cam_fill_csio timeout) β e.g. 2147484 becomes
-2147483296 β 0x8007A120 as u_int32 (a ~35.8 minute-wall-clock... as far
in the wrong direction as the value allows; the point is the driver
accepted and silently mangled it). PTIOCGETTIMEOUT (:642-646) maps
any io_timeout < 1000 to 0, making the wrap directly observable:
Reproduced (guest, root, EVILPT kernel #0 / unfixed)
run.log:
set=100000 -> get=100000 set=2147483 -> get=2147483 set=2147484 -> get=0 <-- wrapped negative OVERFLOW-MANIFEST: io_timeout wrapped negative
Impact ceiling
Root-gated knob self-corruption (the caller already passed
caps_priv_check_self(SYSCAP_RESTRICTEDROOT) at open): no memory
safety impact, no cross-privilege effect. Signed-overflow UB /
silent-misconfiguration hardening finding (Info).
Fix validated (guest, kernel #1 22:30:30 with fix.diff)
fix.diff rejects *(int *)addr > 2147483 (INT_MAX/1000) with EINVAL.
run.patched.log:
set=100000 -> get=100000
set=2147483 -> get=2147483
set=2147484 -> get=2147483 <-- rejected, previous value kept;
no OVERFLOW-MANIFEST line
The overflow manifestation is gone; in-range behavior unchanged.
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
fixedWith fix.diff applied and the kernel rebuilt, PTIOCSETTIMEOUT(2147484) returns EINVAL instead of wrapping (GET yields the previous valid 2147483, OVERFLOW-MANIFEST line gone); in-range values 100000 and 2147483 behave identically to baseline.
run.patched.log vs run.log; fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (baseline: set=2147484 -> get=0 + OVERFLOW-MANIFEST), run.patched.log (fixed kernel: set=2147484 -> get=2147483, rejected), VERDICT.md
PoC changes
Authored from scratch (no seed). Requires a pt device: created by the DF-5462 ptsim rig (T_PROCESSOR fake SIM) plus a guest kernel built with 'device pt' (not in X86_64_GENERIC).
Verified recommended fix
Reject (int)addr > INT_MAX/1000 in PTIOCSETTIMEOUT (see fix.diff)
Verdict
Deterministic userland demonstration on the guest: PTIOCSETTIMEOUT with 2147484 makes ptioctl's (int)addr*1000 signed-multiply overflow, io_timeout wraps negative (observable as PTIOCGETTIMEOUT returning 0 for a set value of 2147484 while 2147483 round-trips exactly), and the negative value is consumed as a u_int32_t CDB timeout in ptstart/scsi_send_receive. Root-gated knob self-corruption; no memory-safety impact. Fix validated on a rebuilt guest kernel: the offending value is rejected with EINVAL and the manifest line disappears.
No comments yet.