Skip to content

Commit 293e519

Browse files
kainino0xDawn LUCI CQ
authored andcommitted
[dawn][wire] Crash client when using objects from another wire
This is of course the incorrect behavior (we are supposed to produce a "[object] cannot be used with [device]" validation error), but it does prevent old client objects from misassociating with new server objects. This avoids any possible renderer-process security issue when that happens (though none has been identified). (There are not believed to be any GPU-process problems here since that interface is already fuzzed.) This crash can only happen after the GPU process has already crashed. This is of course possible to do intentionally, but the most likely situation where this happens in the wild is in an application that has *some* device loss recovery support but it's not well tested, or it mixes up objects in a way that doesn't break the application. It's also technically possible for an application to try to rely on push+popErrorScope to check whether objects are from the same device. That would work fine in most cases (e.g. device.destroy(), which would be the typical way for applications to test their recovery behavior), it would just not work in cases where the GPU process has crashed. Fixed: 435630456 Bug: 440387003 Change-Id: I73a2d816358bbfe9840cfecfe954c3d3c1b34bcf Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/256757 Commit-Queue: Kai Ninomiya <[email protected]> Reviewed-by: Corentin Wallez <[email protected]> Reviewed-by: Loko Kung <[email protected]>
1 parent 7ee7050 commit 293e519

27 files changed

+232
-73
lines changed

generator/templates/dawn/wire/client/ApiProcs.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ namespace dawn::wire::client {
8585
, {{as_varName(arg.name)}}
8686
{%- endfor -%}
8787
);
88-
cmd.result = returnObject->GetWireHandle();
88+
cmd.result = returnObject->GetWireHandle(self->GetClient());
8989
{% endif %}
9090

9191
{% for arg in method.arguments %}

generator/templates/dawn/wire/client/ClientBase.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ namespace dawn::wire::client {
4747
if (object == nullptr) {
4848
return WireResult::FatalError;
4949
}
50-
*out = reinterpret_cast<{{as_wireType(type)}}>(object)->GetWireId();
50+
*out = reinterpret_cast<{{as_wireType(type)}}>(object)->GetWireHandle(this).id;
5151
return WireResult::Success;
5252
}
5353
WireResult GetOptionalId({{as_cType(type.name)}} object, ObjectId* out) const final {
5454
DAWN_ASSERT(out != nullptr);
55-
*out = (object == nullptr ? 0 : reinterpret_cast<{{as_wireType(type)}}>(object)->GetWireId());
55+
*out = (object == nullptr ? 0 : reinterpret_cast<{{as_wireType(type)}}>(object)->GetWireHandle(this).id);
5656
return WireResult::Success;
5757
}
5858
{% endfor %}

generator/templates/dawn/wire/client/ClientHandlers.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ namespace dawn::wire::client {
4242

4343
{% if member.type.dict_name == "ObjectHandle" %}
4444
{{Type}}* {{name}} = Get<{{Type}}>(cmd.{{name}}.id);
45-
if ({{name}} != nullptr && {{name}}->GetWireGeneration() != cmd.{{name}}.generation) {
45+
if ({{name}} != nullptr && {{name}}->GetWireHandle(this).generation != cmd.{{name}}.generation) {
4646
{{name}} = nullptr;
4747
}
4848
{% endif %}

src/dawn/common/Assert.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
DAWN_ASSERT(DAWN_ASSERT_LOOP_CONDITION && "Unreachable code hit"); \
105105
DAWN_BUILTIN_UNREACHABLE(); \
106106
} while (DAWN_ASSERT_LOOP_CONDITION)
107-
// Release-mode assert (similar to Chromium DAWN_CHECK).
107+
// Release-mode assert (similar to Chromium CHECK).
108108
// First does a debug-mode assert for better a better debugging experience, then hard-aborts.
109109
#define DAWN_CHECK(condition) DAWN_CHECK_CALLSITE_HELPER(__FILE__, __func__, __LINE__, condition)
110110

src/dawn/tests/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ dawn_test("dawn_unittests") {
432432
"unittests/wire/WireArgumentTests.cpp",
433433
"unittests/wire/WireBasicTests.cpp",
434434
"unittests/wire/WireBufferMappingTests.cpp",
435+
"unittests/wire/WireConfusionDeathTests.cpp",
435436
"unittests/wire/WireCreatePipelineAsyncTests.cpp",
436437
"unittests/wire/WireDeviceLifetimeTests.cpp",
437438
"unittests/wire/WireDisconnectTests.cpp",
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright 2025 The Dawn & Tint Authors
2+
//
3+
// Redistribution and use in source and binary forms, with or without
4+
// modification, are permitted provided that the following conditions are met:
5+
//
6+
// 1. Redistributions of source code must retain the above copyright notice, this
7+
// list of conditions and the following disclaimer.
8+
//
9+
// 2. Redistributions in binary form must reproduce the above copyright notice,
10+
// this list of conditions and the following disclaimer in the documentation
11+
// and/or other materials provided with the distribution.
12+
//
13+
// 3. Neither the name of the copyright holder nor the names of its
14+
// contributors may be used to endorse or promote products derived from
15+
// this software without specific prior written permission.
16+
//
17+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23+
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
28+
#include "dawn/tests/unittests/wire/WireTest.h"
29+
30+
#include "dawn/utils/WGPUHelpers.h"
31+
#include "dawn/wire/WireClient.h"
32+
33+
namespace dawn::wire {
34+
namespace {
35+
36+
enum class TestState {
37+
// Mix the two wires while both clients are connected.
38+
Alive,
39+
// Disconnect WireTwo before creating an object on it.
40+
DisconnectBefore,
41+
// Disconnect WireTwo after creating an object on it.
42+
DisconnectMid,
43+
};
44+
std::ostream& operator<<(std::ostream& stream, TestState flavor) {
45+
switch (flavor) {
46+
case TestState::Alive:
47+
stream << "Alive";
48+
break;
49+
case TestState::DisconnectBefore:
50+
stream << "DisconnectBefore";
51+
break;
52+
case TestState::DisconnectMid:
53+
stream << "DisconnectMid";
54+
break;
55+
}
56+
return stream;
57+
}
58+
59+
// Two copies of WireTest to set up a second parallel wire.
60+
// Create two classes that inherit WireTest, so that the test can inherit both
61+
// of them, to set up two parallel wires to test with.
62+
class WireOne : public WireTest {};
63+
class WireTwo : public WireTest {};
64+
65+
// Tests for intentional wire client CHECK crashes when trying to use objects
66+
// from the wrong wire which would have bogus IDs. (The crash is intended, but
67+
// in principle we shouldn't actually crash. See crbug.com/440387003.)
68+
class WireConfusionDeathTest : public WireOne,
69+
public WireTwo,
70+
public ::testing::WithParamInterface<TestState> {
71+
protected:
72+
void SetUp() override {
73+
WireOne::SetUp();
74+
WireTwo::SetUp();
75+
76+
if (GetParam() == TestState::DisconnectBefore) {
77+
WireTwo::GetWireClient()->Disconnect();
78+
}
79+
}
80+
81+
void TearDown() override {
82+
WireTwo::TearDown();
83+
WireOne::TearDown();
84+
}
85+
86+
template <typename Lambda>
87+
void MaybeDisconnectAndExpectDeath(bool expectDeath, Lambda lambda) {
88+
if (GetParam() == TestState::DisconnectMid) {
89+
WireTwo::GetWireClient()->Disconnect();
90+
}
91+
if (expectDeath) {
92+
#if defined(DAWN_ENABLE_ASSERTS)
93+
EXPECT_DEATH(lambda(), "forClient == mClient");
94+
#else
95+
EXPECT_DEATH(lambda(), "");
96+
#endif
97+
} else {
98+
lambda();
99+
}
100+
}
101+
};
102+
103+
// Test calling queue.WriteBuffer using a buffer from another device.
104+
TEST_P(WireConfusionDeathTest, WriteBuffer) {
105+
wgpu::BufferDescriptor bufDesc{
106+
.usage = wgpu::BufferUsage::CopyDst,
107+
.size = 4,
108+
};
109+
wgpu::Buffer two_buf = WireTwo::device.CreateBuffer(&bufDesc);
110+
111+
MaybeDisconnectAndExpectDeath(true, [&]() {
112+
WireOne::queue.WriteBuffer(two_buf, 0, nullptr, 0); //
113+
});
114+
}
115+
116+
// Test creating a bind group using a layout from an old device.
117+
TEST_P(WireConfusionDeathTest, NewBindGroupFromOldLayout) {
118+
wgpu::BindGroupLayout two_bgl = utils::MakeBindGroupLayout(WireTwo::device, {});
119+
120+
wgpu::BindGroupDescriptor bgDesc{.layout = two_bgl};
121+
MaybeDisconnectAndExpectDeath(true, [&]() {
122+
WireOne::device.CreateBindGroup(&bgDesc); //
123+
});
124+
}
125+
126+
// Test creating a bind group on an old device using a layout from a new device.
127+
TEST_P(WireConfusionDeathTest, OldBindGroupFromNewLayout) {
128+
wgpu::BindGroupLayout one_bgl = utils::MakeBindGroupLayout(WireOne::device, {});
129+
130+
wgpu::BindGroupDescriptor bgDesc{.layout = one_bgl};
131+
// Should not crash if wire two is already disconnected.
132+
MaybeDisconnectAndExpectDeath(GetParam() == TestState::Alive,
133+
[&]() { WireTwo::device.CreateBindGroup(&bgDesc); });
134+
}
135+
136+
INSTANTIATE_TEST_SUITE_P(,
137+
WireConfusionDeathTest,
138+
::testing::Values(TestState::Alive,
139+
TestState::DisconnectBefore,
140+
TestState::DisconnectMid),
141+
testing::PrintToStringParamName());
142+
143+
} // anonymous namespace
144+
} // namespace dawn::wire

src/dawn/tests/unittests/wire/WireTest.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ using testing::WithArg;
4949

5050
namespace dawn {
5151

52+
namespace {
53+
// WireTest sets the wire proc table as the global proc table.
54+
// Tests that use multiple wires may inherit WireTest multiple times (see
55+
// WireConfusionDeathTest). Refcount how many WireTest instances are running
56+
// to make sure we don't unset the proc table until the test is done.
57+
uint32_t sWireProcTableRefCount = 0;
58+
} // namespace
59+
5260
WireTest::WireTest() {}
5361

5462
WireTest::~WireTest() {}
@@ -84,7 +92,10 @@ void WireTest::SetUp() {
8492
mWireClient.reset(new wire::WireClient(clientDesc));
8593
mS2cBuf->SetHandler(mWireClient.get());
8694

87-
dawnProcSetProcs(&wire::client::GetProcs());
95+
if (sWireProcTableRefCount == 0) {
96+
dawnProcSetProcs(&wire::client::GetProcs());
97+
}
98+
++sWireProcTableRefCount;
8899

89100
auto reservedInstance = GetWireClient()->ReserveInstance();
90101
instance = wgpu::Instance::Acquire(reservedInstance.instance);
@@ -193,7 +204,11 @@ void WireTest::TearDown() {
193204
adapter = nullptr;
194205
device = nullptr;
195206
queue = nullptr;
196-
dawnProcSetProcs(nullptr);
207+
208+
--sWireProcTableRefCount;
209+
if (sWireProcTableRefCount == 0) {
210+
dawnProcSetProcs(nullptr);
211+
}
197212

198213
// Derived classes should call the base TearDown() first. The client must
199214
// be reset before any mocks are deleted.

src/dawn/tests/unittests/wire/WireTest.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ namespace utils {
141141
class TerribleCommandBuffer;
142142
} // namespace utils
143143

144-
class WireTest : public testing::Test {
144+
class WireTest : virtual public testing::Test {
145145
protected:
146146
WireTest();
147147
~WireTest() override;

src/dawn/wire/ChunkedCommandSerializer.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
namespace dawn::wire {
3131

3232
ChunkedCommandSerializer::ChunkedCommandSerializer(CommandSerializer* serializer)
33-
: mSerializer(serializer), mMaxAllocationSize(serializer->GetMaximumAllocationSize()) {}
33+
: mSerializer(serializer), mMaxAllocationSize(serializer->GetMaximumAllocationSize()) {
34+
DAWN_ASSERT(mMaxAllocationSize > 0);
35+
}
3436

3537
void ChunkedCommandSerializer::SerializeChunkedCommand(const char* allocatedBuffer,
3638
size_t remainingSize) {

src/dawn/wire/WireClient.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ void WireClient::Disconnect() {
8282

8383
Handle WireClient::GetWireHandle(WGPUDevice device) const {
8484
client::Device* wireDevice = client::FromAPI(device);
85-
return {wireDevice->GetWireId(), wireDevice->GetWireGeneration()};
85+
return wireDevice->GetWireHandle(mImpl.get());
8686
}
8787

8888
namespace client {

0 commit comments

Comments
 (0)