cyapawrite returns uninitialized stack `error` on zero-length writes β 32 bits of stale kernel stack leak to userland as the write() result/errno
Summary
cyapawrite declares error uninitialized; the only assignment is inside the copy loop whose guard is skipped entirely for a zero-length write, so error is read unassigned at three sites and returned to userland. The syscall layer has no zero-length short-circuit, so write(fd, buf, 0) deterministically returns a stale 32-bit kernel-stack value (prior syscall remnants - pointers/flags/lengths). Repeatable 32-bit kernel-stack sampling - weak info disclosure, KASLR-relevant when pointer fragments surface. Stack-poisoning harness 3/3 returns the poison. int error = 0 fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4229 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| model_uninit_error.c | β | 4.1 KB | view raw | |
| build.sh | β | 110 B | view raw | |
| run.sh | β | 188 B | view raw | |
| build.log | β | 144 B | view raw | |
| run.log | β | 546 B | view raw | |
| run.2.log | β | 943 B | view raw | |
| env.txt | β | 576 B | view raw | |
| fix.diff | β | 325 B | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 2.1 KB | view raw |
DF-4229 β cyapa: cyapawrite() returns an uninitialized stack int on zero-length writes β stale kernel-stack data leaks to userland as the write() result
Where
- cyapa.c:791 β
int error;declared, never initialized. - cyapa.c:802-809 β the copy loop
while ((n = fifo_space(&sc->wfifo)) > 0 && uio->uio_resid)is skipped entirely whenuio->uio_resid == 0(zero-length write), soerroris never assigned. - cyapa.c:815 β
while (fifo_ready(...) && cmd_completed && error == 0)reads the uninitialized variable (when wfifo is non-empty). - cyapa.c:1054 β
if (error == 0 && (cmd_completed || uio->uio_resid))reads it unconditionally on this path. - cyapa.c:1056 β
return error;β the stale 32-bit stack slot becomes the d_write return value βdofilewriteβ write(2) result/errno in userland.
Reachability of the zero-length write (all cited)
- sys/kern/sys_generic.c:506
dofilewrite()callsfo_write()with nouio_resid == 0short-circuit; - sys/kern/vfs_vnops.c
vn_write()βVOP_WRITE_FP(); - sys/vfs/devfs/devfs_vnops.c:1366
devfs_write()βdev_dwrite(); - sys/kern/kern_device.c
dev_dwrite()βcyapawrite(). Sowrite(fd, buf, 0)on an open/dev/cyapa*executes the buggy path.
Impact
The returned int is stale kernel-stack content of the calling thread (remnants of prior syscalls: pointers, flags, lengths β 32 bits). In userland it surfaces as write()'s return value / errno. Weak but real kernel-stack info disclosure, KASLR-relevant when pointer fragments are returned. Device node is 0600 root:wheel β attacker must hold an fd (root/Xorg or an inherited fd) β Low severity.
PoC β model_uninit_error.c (runs on any UNIX)
Stack-poison harness reproducing cyapawrite()'s exact control flow
(cyapa.c:785-1056) for write(fd, buf, 0):
- 3/3 runs on the audit guest returned the 0xABABABAB poison pattern
through return error; β deterministic proof that the value flows from
an uninitialized stack slot (run.log).
Fix
int error = 0; at cyapa.c:791 (fix.diff).
Build / run
cc -O0 -Wall -o model_uninit_error model_uninit_error.c ./model_uninit_error
(-O0 so error occupies a stack slot exactly as in the kernel frame; the
kernel is built -O2 where the uninitialized load is equally present β
error is live across the gotos so the compiler materializes it.)
DF-4229 β VERDICT
Status: untested (kernel-side leak requires an open /dev/cyapa* fd,
which requires the trackpad hardware absent from this guest; the control
flow and the uninitialized stack read are reproduced deterministically by
the exact-model harness β 3/3 poison leaks on the audit guest).
Root cause (line-accurate)
cyapawrite (cyapa.c:786-1057):
int error; /* cyapa.c:791 -- never initialized */
...
again:
cyapa_lock(sc);
while ((n = fifo_space(&sc->wfifo)) > 0 && uio->uio_resid) { /* :802 */
if (n > uio->uio_resid)
n = uio->uio_resid;
error = uiomove(fifo_write(&sc->wfifo, 0), n, uio); /* :805 */
...
}
cmd_completed = (fifo_ready(&sc->wfifo) != 0); /* :814 */
while (fifo_ready(&sc->wfifo) && cmd_completed && error == 0) /* :815 uninit read */
...
if (error == 0 && (cmd_completed || uio->uio_resid)) /* :1054 uninit read */
goto again;
return error; /* :1056 leak */
When uio->uio_resid == 0 the copy loop at :802 is skipped and error
reaches :815/:1054/:1056 unassigned. The value is whatever the calling
thread's kernel stack held at that offset (prior syscall frames).
Trigger path (cited every hop)
write(2) β dofilewrite (sys/kern/sys_generic.c:506, no resid==0 short-circuit) β vn_write (sys/kern/vfs_vnops.c) β devfs_write (sys/vfs/devfs/devfs_vnops.c:1366) β dev_dwrite (sys/kern/kern_device.c) β cyapawrite with uio_resid == 0.
A second (narrower) entry β wfifo full at :802 with resid > 0 β also skips the copy loop, but wfifo-full-on-entry is not reachable through the driver's own state machine; the zero-length write is the clean trigger.
Harness result (audit guest, cc 8.3, -O0)
write(/dev/cyapa*, buf, 0) returned: 0xabababab (-1414812757) RESULT: UNINITIALIZED STACK VALUE LEAKED (poison 0xABABABAB observed) x3/3
The model reproduces cyapawrite()'s exact branch structure; the poison pattern placed in the stack region below the function's frame surfaces as the function's return value β the same data flow as the kernel leak.
Impact / severity
32 bits of stale kernel stack per call, repeatable at will by the fd holder. Device is 0600 root:wheel β attacker must be root or hold an inherited fd (Xorg children). Low severity, kernleak class.
Recommended fix
int error = 0; β one-line fix (fix.diff, applies clean).
Fix validation
Kernel-side validation needs the device (open fd on /dev/cyapa*); the fix is a trivial initializer whose correctness is self-evident against the traced path. fix_status = not_testable.
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
not_testableOne-line initializer; kernel-side validation needs the physical device (absent in QEMU). Applies cleanly to sys/.
findings/poc/DF-4229/fix.diff (git apply --check clean)
Confirmed kernel references
Detail
Exploit chain
hold/inherit fd on /dev/cyapa0-67 -> write(fd, NULL, 0) -> return path returns unassigned error -> stale kernel stack int observable as errno/retval; repeat to sample thread-stack contents (KASLR-relevant fragments)
Evidence (decisive lines)
run.log + build.log (3/3 poison 0xABABABAB through `return error;` model), VERDICT.md (every hop cited), fix.diff
PoC changes
n/a - pack authored fresh
Verified recommended fix
initialize: int error = 0; (fix.diff)
Verdict
Source-certain uninitialized-stack return in cyapawrite: zero-length write skips the uiomove loop so error (cyapa.c:791) is read at :815/:1054 and returned at :1056 as write()'s result. The exact-control-flow harness leaked a stack-poison pattern 3/3 runs on the guest (deterministic mechanism proof); the kernel-side leak itself needs an open /dev/cyapa* fd, impossible on this guest (driver never attaches - no SMBus hardware). 32-bit stale kernel-stack disclosure per call to the fd holder (0600 root:wheel node).
No comments yet.