summaryrefslogtreecommitdiffstats
path: root/docs/05-operations/05-gpubreach-audit.md
blob: 3c6038518e4846f1e82046429f127ef26110e966 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# GPUBreach-class audit and threat model (vGPU host module)

Status: audit performed 2026-10-21 against the host module at `229bec2c`
(`vgsp.ko`, `kernel/`), with the write-path guards landed in the same window
(`7d4919dd`, `7a1f4805`). Scope: every host-side write whose target address
derives from guest-supplied data. Companion reading:
[`01-performance-correctness-security.md`](01-performance-correctness-security.md).

## 1. What the GPUBreach threat is

GPUBreach (IEEE S&P 2026, U. Toronto) is a GPU Rowhammer chain: the attacker
flips bits in the physical GPU's page tables (PTEs), which turns the GPU's
MMU into an arbitrary *device-memory* read/write primitive; the paper then
escalates to CPU root by exploiting memory-safety bugs in the *driver* code
that consumes those corrupted translations. It works with the IOMMU on,
because the corruption happens inside the GPU, after translation.

The relevant question for this project is therefore not "can a guest read
device memory" (device memory is the guest's own resource here) but:

1. can guest-influenced data reach a *host* write without validation, and
2. what bounds a write that lands on a *wrong* target.

## 2. Structural mitigations (why the host is not on that path)

* **Guest GPU page tables are host-emulated.** The guest's GMMU page tables
  live in the emulated vidmem shadow (BAR1 backing) or in guest RAM; the
  host resolves guest GPU VAs through its own walk of the guest's published
  page directories (`SET_PAGE_DIRECTORY`) or through content-verified pins.
  There is no physical GPU MMU walk of guest-authored tables.
* **The physical GPU's PTEs are host-owned.** Backend jobs are submitted by
  the host module on host-authored PTE lines; the guest never supplies a
  descriptor that the real hardware MMU consumes for host memory.
* **Every write is aperture-bounded.** The one write primitive,
  `svc_write_ap()`, sends SYS writes through `vfio_dma_rw()` on the mdev's
  own VFIO container - the IOMMU/vfio layer only maps the guest's RAM - and
  FB writes are bounds-checked against the emulated shadow size. A
  guest-influenced target can therefore only ever land inside the guest's
  own RAM or the vidmem shadow; a wrong target is a correctness bug, not a
  host-memory write.
* **No guest DMA to host buffers.** The guest's drivers talk to the
  emulated device; they cannot address host kernel buffers. IOMMU is on
  (vfio), and the mdev exposes only guest RAM plus the emulated windows.
* **Guest descriptors are not trusted by construction.** Every path that
  turns a guest VA into a host PA goes through the resolution chain
  (guest-verified injected pins -> write/read landing ledger -> published
  page-directory walk -> targeted record scan), and the chain's outputs are
  re-validated per page (see §3).

## 3. Write-path inventory and validation state

| Write path | Target derivation | Validation today |
|---|---|---|
| CE fill / memset (`svc_fill`) | guest VA or physical FB/GPA from the push | per-page destination resolution (pin -> ledger -> walk), page-boundary clamped; a page with no resolution truncates loudly and demands the VA through the pin channel (`FILL-TRUNC`, `stalled VA`); zero fills onto page-table-shaped guest pages are refused (`FILL-PT-REFUSED`); below-floor window fills that resolve outside vidmem are refused (`LOWFILL-REFUSED`) |
| CE copy (`svc_copy`) | dst/src VAs from the push | per-page resolution with pin-agreement (a fresh pin beats a stale ledger entry and repairs it); device-dst copies without a pin-resolved translation are held (`sema_copy_dst_pin_guard`) |
| GRCE inline writes (`LOAD_INLINE_DATA`) | destination VA from the push | once-only identity gate keyed by (push VA, push content hash, dst, bytes), session-scoped and 8192 slots wide |
| GR launch operand staging (`svc_gr_data_fill`) | argument pointers scanned from the QMD | per-page pin/ledger resolution; the resolution is stored in the ledger as the landing authority; dropped pages are named (`GRFILL-DROP`) and demanded |
| Launch copy-out (engine results back to the shadow/guest page) | the launch's own landing records | per-page destination resolution; every write is witnessed (`OUT-PAGE-WIT`/`OUT-PAGE-POST`); staged-identity rule (only the page staged into a slot may receive the slot's content) |
| Release/counter writes (semaphores, completion counters) | guest semaphore VAs from pushes | pool-range writes go through a pin/record-verified cache (`pool-pa`), re-validated against the current pin before the 4-byte write; exact-slot fallback for the client's tracker; forward-only apply |
| GPFIFO/USERD writes (put/get bookkeeping) | channel registration data | the channel's own USERD/instance blocks from the alloc params, aperture-tagged |
| Window/PTE installs (forward PDE install) | the launch's windows | installs are refused loudly when the backend rejects the PDE; the regular path leaves guest VAs in place (no host-side table mutation) |

## 4. Residual risks and findings

* **Wrong-but-valid writes remain possible** when every resolver agrees on a
  stale page (e.g. a guest freed and reallocated a VA). The r391/r398/r401
  fixes removed the known instances (contiguous fill past the first page,
  replayed inline writes, capture-window eviction), but the class is bounded,
  not eliminated: the guards convert silent corruption into loud
  truncation/refusal where a page is unresolved, and the ledger is
  session-scoped and pin-repaired where a fresh pin exists.
* **The boot-corruption lottery** (a zero fill landing on guest page-table
  pages) is structurally mitigated by the page-table-shape refusal and the
  per-page clamping; no instance has been observed since r391.
* **Diagnostics stay on**: `DST-DIVERGE` (copy-dst ledger vs probe),
  `FILL-TRUNC`, `GRFILL-DROP`, `NOWRITE-*` and the stalled-VA demand rows
  exist so a future divergence is visible in the boot log rather than
  silent.
* **What this audit does not claim**: no isolation or side-channel claim.
  GPU Rowhammer itself is a *physical* fault-injection threat that this
  software emulation cannot mitigate; the mitigation argument here is only
  that the host write paths cannot be steered outside the guest's own
  memory, and that wrong targets are bounded by IOMMU-mapped guest RAM or
  the emulated shadow.

## 5. Recommended follow-ups

1. Keep the copy-destination ledger under the `DST-DIVERGE` witness and
   promote it to a bounded hold only if a failing run names it.
2. Extend the pin-demand channel to client device VAs (today the in-guest
   scanner resolves only its own push/table windows), so an unresolved
   operand page becomes a wait on real data instead of a truncated fill.
3. Re-run this audit when the write-path guards change (any new
   `svc_write_ap()` caller with a guest-derived target is in scope by
   default).