# DF-1234 — VERDICT

**Finding:** `ASR_queue_i` (`I2OUSRCMD` ioctl) at
`sys/dev/raid/asr/asr.c` ships uninitialized kernel slab memory to
userspace because `ASR_fillMessage()` takes a `u_int16_t size` parameter
that silently truncates the (user-controlled) `ReplySizeInBytes`, which
can be up to ~256 KB.

**Status:** NOT REPRODUCED (latent — no DPT SmartRAID controller on this guest).
**Confidence (bug is real):** certain (traced line-by-line in `sys/`).
**Impact ceiling:** up to ~256 KB of stale kernel heap leaked to a
privileged caller per `I2OUSRCMD` invocation. Useful for slab-grooming
state disclosure / KASLR defeat — but gated by `SYSCAP_RESTRICTEDROOT`
(`asr_open` at `asr.c:3107`), so this is **root→kernel info-leak**, not
unprivileged.

## Mechanism (confirmed line-by-line in `sys/`)

1. `ASR_fillMessage` is declared at `sys/dev/raid/asr/asr.c:485-486`:
   ```c
   static PI2O_MESSAGE_FRAME
   ASR_fillMessage(void *Message, u_int16_t size)
   {
       PI2O_MESSAGE_FRAME Message_Ptr;
       Message_Ptr = (I2O_MESSAGE_FRAME *)Message;
       bzero(Message_Ptr, size);                                /* :491 */
       ...
   ```
   The `size` argument is `u_int16_t`. Any caller passing a value > 0xFFFF
   has it silently truncated to the low 16 bits before `bzero` runs.

2. `ASR_queue_i` (servicing `I2OUSRCMD` ioctl, `asr.c:3788`) computes the
   reply-buffer size from the user-supplied reply message-size field:
   ```c
   /* asr.c:3262-3263 */
   ReplySizeInBytes = (I2O_MESSAGE_FRAME_getMessageSize(
     &(Reply_Ptr->StdReplyFrame.StdMessageFrame)) << 2);
   ```
   `MessageSize` is a U16 in the I2O frame, so `ReplySizeInBytes` can be
   up to `0xFFFF << 2 = 0x3FFFC` (~256 KB). `Message_Ptr` (the *request*)
   is bounded to `MAX_INBOUND_SIZE = 512` at `asr.c:3223-3224`, but the
   *reply* size is **not** bounded.

3. The reply buffer is allocated without `M_ZERO` at `asr.c:3273-3276`:
   ```c
   if ((Reply_Ptr = (PI2O_SCSI_ERROR_REPLY_MESSAGE_FRAME)kmalloc (
     ((ReplySizeInBytes > sizeof(I2O_SCSI_ERROR_REPLY_MESSAGE_FRAME))
       ? ReplySizeInBytes : sizeof(I2O_SCSI_ERROR_REPLY_MESSAGE_FRAME)),
     M_TEMP, M_WAITOK)) == NULL) { ... }
   ```
   Then `ASR_fillMessage` is called at `asr.c:3282`:
   ```c
   (void)ASR_fillMessage((void *)Reply_Ptr, ReplySizeInBytes);
   ```
   When `ReplySizeInBytes == 0x10000` (e.g. `MessageSize == 0x4000`), the
   `u_int16_t` parameter truncates to **0** and `bzero` does nothing.

4. Only the I2O header fields are subsequently written:
   `InitiatorContext`, `TransactionContext`, `MsgFlags | REPLY`,
   `MessageSize` (`asr.c:3283-3291`). These touch only the first ~30-60
   bytes. The remaining ~64 KB of the `M_TEMP` slab allocation contains
   **uninitialized kernel heap residue**.

5. `copyout((caddr_t)Reply_Ptr, (caddr_t)Reply, ReplySizeInBytes)` at
   `asr.c:3305` (special-case path) and `asr.c:3575` (general path)
   ships the full `ReplySizeInBytes` back to userspace — leaking the
   unzeroed slab.

6. The user-supplied `ReplySizeInBytes` reaches `copyout` verbatim, so
   the leak size is attacker-controlled up to ~256 KB per call.

## Why it is NOT REPRODUCED on this guest

- `pciconf -lv` shows no DPT SmartRAID adapter. `asr_attach` never runs,
  so no `/dev/asrN` cdev is created.
- `kldstat -v` confirms `pci/asr` is **statically linked into
  X86_64_GENERIC** (the driver IS in the running kernel), so the bug path
  exists and would fire on a host with the matching controller.
- `ls /dev/asr*` returns no device nodes. PoC `asr_fillmsg_leak.c`
  confirms this at runtime.

## Threat model & privilege boundary

`asr_open` at `sys/dev/raid/asr/asr.c:3107` enforces
`caps_priv_check(ap->a_cred, SYSCAP_RESTRICTEDROOT)`. Only root (or a
process explicitly granted the restricted-root capability) can open the
device. So this is a **root→kernel info-leak** primitive, useful for:

- defeating slab randomization (leak adjacent object pointers),
- leaking previously-freed slab contents (e.g. crypto keys, auth tokens
  freed into the same `M_TEMP` bucket),
- bridging into a separate write-capable primitive via the leaked
  pointers.

It is **not** an unprivileged-user vector. The hardening gap is that a
root operator expects `I2OUSRCMD` to round-trip a well-defined reply
frame, not to be handed the kernel heap.

## Fix (authored in `fix.diff`, applied + compile-validated)

Two-part defense-in-depth:

1. **Cap `ReplySizeInBytes` to `MAX_INBOUND_SIZE`** (512) right after
   computation at `asr.c:3264`. This matches the cap already applied to
   the *request* size at `asr.c:3223-3224`, is consistent with the I2O
   limit, and immediately eliminates both the truncation and the leak.

2. **Widen `ASR_fillMessage`'s `size` from `u_int16_t` to `u_int32_t`**
   at `asr.c:486`, so the (now bounded but still up to 512) value cannot
   be silently truncated even if a future caller passes a larger size.
   This is signature-only — the body is unchanged because `bzero` and the
   `setMessageSize` math both accept any unsigned int.

## Validation

- `fix.diff` applies cleanly with `patch -p1 --forward` (verified).
- All 5 audit fixes applied together; `make -j6 nativekernel
  KERNCONF=X86_64_GENERIC` returned **rc=0** with **no errors / warnings**
  under `-Werror`. `asr.c` was compiled cleanly into both the kernel and
  the `asr.ko` module.
- Fix is **not_testable** at runtime on this guest (no DPT controller).
