# DF-3796 — ena_down tears down IO queues/RX buffers without draining the deferred RX cleanup task (UAF / double m_freem)

## What
`ena_down()` (sys/dev/virtual/amazon/ena/ena.c:2604-2638) drains the timer
callout (2611), frees the IO MSI-X IRQs (2617), **destroys all IO queues**
(2627 — frees the CQ/SQ DMA descriptor memory), **frees all RX buffers**
(2630 — `m_freem` + `bus_dmamap_unload` on every rx_info) and only much
later, inside `ena_free_all_rx_resources` (2632 → `ena_free_rx_resources`,
ena.c:830-833), cancels/drains the per-queue deferred cleanup taskqueue
`rx_ring->cmpl_tq` / `cmpl_task`.

The deferred task (`ena_deferred_rx_cleanup`, ena.c:1574-1590 — up to
CLEAN_BUDGET×RX_BUDGET packet batches, unlocking the ring around every
`if_input` call) is enqueued by the timer's `check_for_empty_rx_ring`
(ena.c:3498) whenever a ring's RX SQ drains completely — a condition a
burst of delivered packets (or a malicious device completing everything
and going quiet) produces on demand. `ena_rx_cleanup` never checks
`adapter->up` / `IFF_RUNNING`.

Interleaving (task pending or mid-run while `ena_down` proceeds):

```
deferred task                          ena_down (ioctl/reset task)
------------                          --------------
ena_rx_cleanup:                        callout_drain(timer)
  ena_com_rx_pkt(io_cq, ...)           ena_free_io_irq
  ... taskqueue_thread pre-wakeup      ena_destroy_all_io_queues   <-- kfree(cdesc)
                                       ena_free_all_rx_bufs       <-- m_freem(map unload)
  [acquires ring lock — irrelevant:
   ena_free_rx_bufs takes NO ring lock,
   contrast ena_free_tx_bufs:1111]
  ena_com_rx_pkt reads freed cdesc    (UAF read / NULL deref after
                                        cdesc_addr.virt_addr=NULL)
  ena_rx_mbuf: m = rx_info->mbuf      (freed mbuf -> UAF)
    ... m_freem(m) / m_append         (double free / UAF write)
```

Both teardown entry points race it: the ioctl `SIOCSIFFLAGS` down path
(ena.c:2417-2421) and — fully device-triggerable, no guest cooperation —
`ena_reset_task` (ena.c:3541-3615): a malicious device stops keep-alives
and drains the RX ring so the timer enqueues the task, then the missed
keep-alive sets `trigger_reset` → `ena_reset_task` → `ena_down` while the
task is still pending/running.

## Why not runtime-reproduced here
The audit guest (QEMU/KVM, virtio-only — env evidence in the DF-3797 pack)
has no Amazon ENA function, so neither the RX-completion traffic that arms
the task nor `ena_down` on a live adapter can be driven. Source-level
certainty only; the interleaving above is structural (no synchronization
exists between the task and the frees it races).

## Fix (fix.diff, git apply --check clean)
1. `ena_down`: cancel+drain every queue's `cmpl_task` immediately after
   `callout_drain` (only the drained timer can re-enqueue, so this is
   sufficient).
2. `ena_free_rx_bufs`: take the ring lock, mirroring
   `ena_free_tx_bufs` (ena.c:1111).

## Bucket
memcorrupt (kernel heap UAF / double free driven by hostile peer timing).
