WebGPU: Buffer bounds checks can be bypassed via integer overflow, and the parent process can be crashed through multiple unhandled panics
Categories
(Core :: Graphics: WebGPU, defect, P1)
Tracking
()
| Tracking | Status | |
|---|---|---|
| firefox-esr115 | --- | unaffected |
| firefox-esr140 | --- | disabled |
| firefox148 | --- | wontfix |
| firefox149 | --- | wontfix |
| firefox150 | --- | fixed |
People
(Reporter: kerberoasting, Assigned: ErichDonGubler)
References
Details
(5 keywords, Whiteboard: [fixed in 2017645][client-bounty-form][adv-main150+])
Attachments
(2 files)
Two related issues in Firefox's WebGPU implementation that compound each other.
The first is that wgpu-core validates buffer operations using plain integer addition (e.g. source_offset + size > buffer.size), but Firefox's release profile compiles Rust with overflow-checks disabled. A compromised content process can send crafted offset+size pairs via PWebGPU::Messages() that wrap around u64, causing the bounds check to pass when it shouldn't. The GPU driver then receives a copy/write command with an absurd size. This affects copy_buffer_to_buffer (transfer.rs:985), queue_write_buffer (queue.rs:663), and get_mapped_range (resource.rs:706). Notably, buffer_map_async already has an overflow guard (start > end check at resource.rs:610), the other functions just don't.
The second is that PWebGPU batches all commands into a single bincode blob, and the parent-side Rust code has 8+ paths that panic on malformed input — .unwrap() on bincode deserialization (server.rs:2417), unchecked array indexing (server.rs:2606), split_at on insufficient data (command.rs:770), from_utf8 on attacker-controlled bytes (command.rs:895), and MOZ_RELEASE_ASSERT on duplicate IDs (WebGPUParent.cpp:1622). Since Firefox compiles with panic=abort, each of these kills the parent process.
Together: the overflow-checks=false setting turns what should be a caught arithmetic error into a silent bypass, and the panic=abort setting turns what should be a handled error into a process kill. On Linux, where the compositor runs in the parent process with no GPU sandbox, both issues directly affect the main browser process.
This is the same vulnerability class as CVE-2025-13021 through CVE-2025-13026 (FF145), but these specific code paths were not patched — they are still present on current trunk.
Attached: full write-up (submission_final.md) and a standalone Rust PoC (crash-harness/) that reproduces the validation logic with Firefox's build profile and demonstrates 3/3 bounds checks being bypassed. The PoC operates in isolation rather than end-to-end, since triggering the overflow requires raw bincode from a compromised content process — the JS API caps values at Number precision (2^53), below the u64 overflow threshold.
=== How discovered ===
Static source code analysis.
=== Attachments ===
- submission_final.md (full write-up with line references)
- crash-harness/ (Rust PoC — cargo build --release && ./target/release/webgpu-overflow-poc)
| Reporter | ||
Comment 1•7 months ago
|
||
| Reporter | ||
Comment 2•7 months ago
|
||
Updated•7 months ago
|
| Assignee | ||
Comment 4•7 months ago
|
||
Tentatively setting P1 and S2, and investigating a fix for the overflows. I'll leave the panics in server.rs for another day.
| Assignee | ||
Comment 5•7 months ago
|
||
I have a tentative fix committed (but not filed for PR) at erichdongubler-mozilla/wgpu:23440e28f076347a755bb86d5a1c6dd0563e1029. I already see refinements I'd like to make before submitting a PR, but I believe it will work (pending testing).
Comment 6•7 months ago
|
||
Erich: did the bounds issues lead to an overflow (read or write) in your testing or analysis?
| Assignee | ||
Comment 7•7 months ago
|
||
The current branch I'm working with: https://github.com/gfx-rs/wgpu/compare/trunk...erichdongubler-mozilla:wgpu:erichdongubler-push-ruffled-gentle-lemon
| Assignee | ||
Comment 8•7 months ago
|
||
:dveditz: I haven't reproduced an overflow yet, but as the description says, this requires a compromised content process that circumvents JS' typical capping of integer values to 53 bits. I'll see if I can make a PoC that simulates a compromised content process, and get back to you.
Leaving the NI open until I have an answer.
Comment 9•7 months ago
|
||
The usual way to simulate a compromised content process is to edit the code that is running in the content process to inject bad values in the way that you want.
Comment 10•7 months ago
•
|
||
(In reply to Erich Gubler [:ErichDonGubler] (he/him) from comment #8)
:dveditz: I haven't reproduced an overflow yet, but as the description says, this requires a compromised content process that circumvents JS' typical capping of integer values to 53 bits. I'll see if I can make a PoC that simulates a compromised content process, and get back to you.
Nothing in wgpu-core should be assuming that integer values have been limited to 2**53. wgpu-core's validation should assume that all input values are valid values of their Rust type, but otherwise come straight from an attacker. So if there's a u64 or NonZeroU64, we should assume it could be any value from 0..2**64-1 or 1..2**64-1.
Comment 11•7 months ago
•
|
||
Just spot-checking the commits:
The change in wgpu-core/src/command/transfer.rs looks good to me. It does seem like that is the point at which validation of these copy parameters is being done, so any arithmetic there must be very careful, and assume that values are chosen by the attacker. In particular, that addition source_offset + size is a bug, because it could overflow.
Often times these sorts of checks can be staged such that arithmetic operations are only performed on previously checked values: operations are placed after the validation that shows that they would not overflow. For example, in the case of a source_offset + size addition, with buffer size of buffer_size (retrieved from the buffer itself, and therefore trusted):
- You would first check that
source_offset <= buffer_size. This makes no assumptions, so it's always safe. - You would then check that
size <= buffer_size - source_offset. Since we've already done the check in1),buffer_size - source_offsetis a subtraction of two trusted values, so we know that it will not underflow. And then, as before, the comparison is always safe. Note that the algebraically equivalentsource_offset + size <= buffer_sizewould not be safe, since we haven't done anything to validatesizeyet. - Then you'd compute
source_offset + size, if you really need it.
Comment 12•7 months ago
•
|
||
"Why not just use checked_add for everything?" Using checked_add suggests to the reader that the values being operated on are untrusted, whereas our job in wgpu-core is to establish clear transitions from untrusted to trusted values. So when the checks performed by functions like checked_add match the validation the WebGPU spec requires, that's great, use them. But more often, the validation we need is against the actual size of something, not u64::MAX or what-have-you, and then once we've done the validation that the spec requires, the arithmetic itself can be trusted, as demonstrated in the previous comment.
Comment 13•7 months ago
•
|
||
But more often, the validation we need is against the actual size of something, not u64::MAX or what-have-you
In fact, this is exactly the problem you're getting at in your "TODO" comment:
// TODO: Is this the right error to report?
The checked_add is reporting that something went wrong, but it doesn't have a direct relationship to a requirement found in the spec, and it's not clear what error to report.
The advantage of the approach I've described above is that you're using the validation steps from the spec to establish the safety of arithmetic operations in the implementation, so failures relate directly to something in the spec.
I think this should always be possible: if we come across a situation where our implementation has to do some validation absent from the spec in order to make something safe, that seems like it would point out a loophole in the spec.
Comment 14•7 months ago
|
||
Dveditz comment above wasn't answered. Tentatively giving sec-high.
| Assignee | ||
Comment 15•7 months ago
•
|
||
:dveditz: It's definitely possible to trigger this overflow from usage of the wgpu-core crate, and Firefox exposes those operations to JS. That's something to fix, but whether this arises to sec-high due to exposure through Firefox, I can't say yet, and I've already spent a few hours trying to figure out the exact threat model. I personally am going to prioritize plugging the code in question over spending more time trying to figure out the answer.
I'll tag in :jimb for further analysis of this issue while I work on a fix.
| Assignee | ||
Comment 16•7 months ago
|
||
Tentative fix PR, currently in draft: wgpu#9073
| Assignee | ||
Comment 17•7 months ago
|
||
PR has left draft, and I've requested Jim as a reviewer. There is outstanding work to test things, but that formality can be (fast) follow-up work if need be with confirmation of fix from Richard Belisle.
| Assignee | ||
Comment 18•7 months ago
|
||
PR has been merged, and is awaiting a wgpu revendor. I'll roll it into bug 2017645.
| Assignee | ||
Comment 19•6 months ago
|
||
Richard, the instances of problems mentioned in this bug should be fixed now, as of the latest Nightly builds. Can you please confirm?
| Reporter | ||
Comment 20•6 months ago
|
||
Hey Erich. The overflow is indeed fixed in Nightly with what looks like size > buffer.size -offset
Additionally, all 5 overflows in the PoC are now rejected. I appreciate y'all. - Richard
| Assignee | ||
Updated•6 months ago
|
Comment 21•6 months ago
|
||
The overflows try to happen, but Rust does panic on those, right? Beyond crashing the GPU process (or parent in some cases) how could this be turned into an exploit?
Updated•6 months ago
|
Updated•6 months ago
|
Updated•6 months ago
|
Updated•6 months ago
|
| Assignee | ||
Comment 22•6 months ago
|
||
(In reply to Daniel Veditz [:dveditz] from comment #21)
The overflows try to happen, but Rust does panic on those, right? Beyond crashing the GPU process (or parent in some cases) how could this be turned into an exploit?
I'm actually unfamiliar with our Rust configuration for bounds checking in release builds. If we do keep them, then I don't think an exploit is possible. I'm not sure, though.
| Reporter | ||
Comment 23•6 months ago
|
||
Howdy. From my review, Firefox's Cargo.toml sets debug-assertions = false in [profile.release] and doesn't specify overflow-checks, which defaults to match, so integer overflow wraps silently in release builds rather than panicking. That said, Rust's bounds checking on slice/array access is always enforced regardless, so wrapped values shouldn't easily lead to OOB memory access through safe code. Theoretically perhaps possible to exploit in other Rust crates but probably not practical.
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
Updated•4 months ago
|
Updated•2 months ago
|
Updated•22 days ago
|
Description
•