CVE-2026-91734
Overview
Background
- Broker vs. non-broker node
- In Mojo’s
ipcztransport, akBrokernode (e.g. the browser process) is privileged to allocate shared state and vouch for other nodes, while akNonBrokernode (e.g. a sandboxed renderer) depends on its broker. - Isolated / broker-to-broker connection
- Two brokers can be joined directly to one another via
ConnectNode()(rather than one introducing the other), producing aNodeLinkwhose local and remote node types are bothkBroker. - Transport trust flags
- A
Transportcarries asource_type()/destination_type()(kBrokerorkNonBroker) andis_peer_trusted_/is_trusted_by_peer_, which together decide whether the peer may be treated as a source of privileged objects such asHandleOwner::kRecipienthandles or broker-destined transports. - `Transport::Deserialize`
- The
mojoipczdriver routine that reconstructs an embedded object (e.g. anotherTransport) received over a transport, deciding whether to honor a privileged destination/trust claim based on whether the sending peer is considered trusted.
Root Cause Analysis
The mojo ipcz driver decided whether the peer that sent a serialized object was a trusted source of privileged objects with the expression from_transport.is_peer_trusted() || from_transport.destination_type() == kBroker. That second disjunct was correct for a non-broker (whose transport is destined for its own broker, which it legitimately trusts) but was applied unconditionally: on a broker-to-broker link both endpoints have destination_type() == kBroker, so a broker implicitly treated a peer broker as trusted with no explicit grant. Because the two brokers of an isolated connection may belong to different, mutually-untrusting security domains, this violated the invariant that trust must be explicit between peers of equal privilege.
The fix replaces the check with is_source_trusted(), which only infers trust from a broker destination when the source is itself a non-broker (destination_type() == kBroker && source_type() != kBroker), otherwise requiring an explicit is_peer_trusted_ grant. Companion hardening in NodeLink closes related broker-to-broker paths: OnAcceptIntroduction now rejects any attempt to introduce one broker to another, and OnRelayMessage now rejects relay requests whose remote peer is not a kNormal (non-broker) node.
Attack Path
- Establish an isolated broker-to-broker link
An attacker-controlled node participating as a
kBrokerconnects directly to a victim broker, yielding aNodeLink/Transportwhere bothsource_type()anddestination_type()arekBrokerandis_peer_trusted_was never set. - Exploit the implicit-trust check
On the victim,
Transport::Deserializecomputedis_source_trustedas true purely becausedestination_type() == kBroker, so the untrusted peer broker was accepted as a source of privileged objects. - Convey a privileged object
As the regression test
TransportFromUntrustedBrokerdemonstrates, the attacker serializes and transmits aTransportclaiming akBrokerdestination (oris_peer_trusted == true); pre-patch these were honored instead of rejected withIPCZ_RESULT_INVALID_ARGUMENT. - Cross the trust boundary
The victim broker instantiates the privileged transport/handle as trusted, giving the attacker a broker-destined or trusted-peer channel it was never authorized to hold. Related variants reach comparable state via a broker→broker introduction (
OnAcceptIntroduction) or a relay request from a broker (OnRelayMessage). - Escalation Possessing a privileged, trusted IPC object inside the victim broker could be leveraged to further compromise that process’s IPC surface (inferred) — the patch itself only demonstrates the incorrect authorization, not a specific memory-corruption or code-execution primitive.
Impact Assessment
LogicError / “Incorrect authorization in Core” and reports no in-the-wild exploitation.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmojo/core/ipcz_driver/driver_for_ipcz_tests.cc |
modified | |
TEST_Fmojo/core/ipcz_driver/transport_test.cc |
modified | |
formojo/core/ipcz_driver/transport_test.cc |
modified | |
TEST_Fthird_party/ipcz/src/ipcz/node_link_test.cc |
modified |
Files Changed
third_party/ipcz/src/ipcz/node_link_test.ccmojo/core/ipcz_driver/transport_test.ccmojo/core/ipcz_driver/transport.ccthird_party/ipcz/src/ipcz/node_link.ccmojo/core/ipcz_driver/transport.hmojo/core/ipcz_driver/driver_for_ipcz_tests.cc
Audit Directions
- Implicit trust from endpoint roleAudit any check that infers a peer’s trustworthiness from a structural attribute (
destination_type() == kBroker, “peer is a broker”, process type) rather than an explicit per-connection grant, especially where both peers can share the same role. - Symmetric-privilege peer linksReview
ipcz/mojopaths that assume one side of a link is subordinate (introductions, relays, handle ownership) and verify they behave safely on isolated broker-to-broker connections where the peer may be an untrusting equal. - Serialized-object provenanceIn
Deserialize/handle-adoption code, confirm that any object claiming a privileged destination, trusted-peer status, or recipient ownership is validated against an explicit trust flag, not merely accepted because the receiving endpoint is privileged.
Patch
From 7858f67868ff8416db134d4f9e1a38731df6504b Mon Sep 17 00:00:00 2001 From: Will Harris <[email protected]> Date: Thu, 03 Sep 2026 08:19:36 -0700 Subject: [PATCH] [Mojo] Limit peer trust for isolated connections NodeLink::OnAcceptIntroduction now rejects introductions that would introduce a broker to another broker; broker-to-broker links are only ever established directly via ConnectNode, and HandleIntroductionRequest already declines to introduce two brokers, so this case is never reached by a well-behaved sender. NodeLink::OnRelayMessage now also requires the sender to be a non-broker, matching the only legitimate caller (NodeLink::Transmit via GetBrokerLink()). In the mojo ipcz driver, Transport::Deserialize no longer treats the source as trusted purely because the destination is a broker when the source is itself a broker: a non-broker still implicitly trusts its broker, but two brokers connected to one another must explicitly mark the peer as trusted before either will accept an embedded transport that claims a broker destination or a trusted peer. The mojo-ipcz test driver is updated so cooperating broker test nodes mark each other as trusted, and a new TransportFromUntrustedBroker test covers the untrusted-broker case directly. Bug: 516780835 Change-Id: I54ca58feb080a4478acd68e12bce3af9f29ba561 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8340888 Reviewed-by: Alex Gough <[email protected]> Commit-Queue: Will Harris <[email protected]> Cr-Commit-Position: refs/heads/main@{#1691601} --- diff --git a/mojo/core/ipcz_driver/driver_for_ipcz_tests.cc b/mojo/core/ipcz_driver/driver_for_ipcz_tests.cc index 212199e..3c3a39d 100644 --- a/mojo/core/ipcz_driver/driver_for_ipcz_tests.cc +++ b/mojo/core/ipcz_driver/driver_for_ipcz_tests.cc @@ -180,6 +180,12 @@ transports = Transport::CreatePair( Transport::kBroker, for_broker_target ? Transport::kBroker : Transport::kNonBroker); + if (for_broker_target) { + // Cooperating broker test nodes explicitly trust each other so that + // each end may convey broker-destined transports to the other. + transports.first->set_is_peer_trusted(true); + transports.second->set_is_peer_trusted(true); + } return { .ours = Transport::ReleaseAsHandle(std::move(transports.first)), .theirs = Transport::ReleaseAsHandle(std::move(transports.second)), @@ -225,10 +231,14 @@ } #endif // BUILDFLAG(IS_WIN) const bool is_broker = parent_process.IsValid(); - return Transport::ReleaseAsHandle(Transport::Create( + scoped_refptr<Transport> transport = Transport::Create( {.source = is_broker ? Transport::kBroker : Transport::kNonBroker, .destination = Transport::kBroker}, - std::move(endpoint), std::move(parent_process))); + std::move(endpoint), std::move(parent_process)); + if (is_broker) { + transport->set_is_peer_trusted(true); + } + return Transport::ReleaseAsHandle(std::move(transport)); } private: diff --git a/mojo/core/ipcz_driver/transport.cc b/mojo/core/ipcz_driver/transport.cc index 8d3ea1e..831a773 100644 --- a/mojo/core/ipcz_driver/transport.cc +++ b/mojo/core/ipcz_driver/transport.cc @@ -682,8 +682,10 @@ return nullptr; } - const bool is_source_trusted = from_transport.is_peer_trusted() || - from_transport.destination_type() == kBroker; + // A non-broker implicitly trusts its broker; otherwise the peer must be + // explicitly trusted. When both endpoints are brokers, the peer being a + // broker does not by itself confer any trust on this end. + const bool is_source_trusted = from_transport.is_source_trusted(); const bool is_new_peer_trusted = header.is_peer_trusted; const bool is_trusted_by_peer = header.is_trusted_by_peer; diff --git a/mojo/core/ipcz_driver/transport.h b/mojo/core/ipcz_driver/transport.h index e90011f..54b87dd9 100644 --- a/mojo/core/ipcz_driver/transport.h +++ b/mojo/core/ipcz_driver/transport.h @@ -99,6 +99,17 @@ void set_is_trusted_by_peer(bool trusted) { is_trusted_by_peer_ = trusted; } bool is_trusted_by_peer() const { return is_trusted_by_peer_; } + // Indicates whether the remote peer on this transport is trusted as a source + // of privileged objects (e.g. HandleOwner::kRecipient handles or + // broker-destined transports). A non-broker implicitly trusts its broker; + // otherwise the peer must be explicitly trusted via is_peer_trusted(). When + // both endpoints are brokers, the peer being a broker does not by itself + // confer any trust on this end. + bool is_source_trusted() const { + return is_peer_trusted_ || + (destination_type() == kBroker && source_type() != kBroker); + } + ProcessTrust remote_process_trust() const { return remote_process_trust_; } void SetErrorHandler(MojoProcessErrorHandler handler, uintptr_t context) { diff --git a/third_party/ipcz/src/ipcz/node_link.cc b/third_party/ipcz/src/ipcz/node_link.cc index 0befa9b7..adf9b75 100644 --- a/third_party/ipcz/src/ipcz/node_link.cc +++ b/third_party/ipcz/src/ipcz/node_link.cc @@ -490,6 +490,13 @@ return false; } + if (node()->type() == Node::Type::kBroker && + accept.v0()->remote_node_type == Node::Type::kBroker) { + // Brokers are never introduced to other brokers; broker-to-broker links + // are always established directly via ConnectNode(). + return false; + } + DriverMemoryMapping mapping = DriverMemory(accept.TakeDriverObject(accept.v0()->memory)).Map(); if (!mapping.is_valid()) { @@ -835,7 +842,9 @@ } bool NodeLink::OnRelayMessage(msg::RelayMessage& relay) { - if (node_->type() != Node::Type::kBroker) { + // Relay requests are only sent by non-brokers to their own broker. + if (node_->type() != Node::Type::kBroker || + remote_node_type_ != Node::Type::kNormal) { return false; } diff --git a/mojo/core/ipcz_driver/transport_test.cc b/mojo/core/ipcz_driver/transport_test.cc index ee57b3f..0b4733a 100644 --- a/mojo/core/ipcz_driver/transport_test.cc +++ b/mojo/core/ipcz_driver/transport_test.cc @@ -942,6 +942,86 @@ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h)); } +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(TransportFromUntrustedBrokerClient, + MojoIpczTransportTest, + h) { + scoped_refptr<Transport> transport = ReceiveTransport(h); + TransportListener listener(*transport); + EXPECT_EQ("ready", listener.WaitForNextMessage().as_string()); + + { + auto [our_new_transport, their_new_transport] = + Transport::CreatePair(Transport::kBroker, Transport::kNonBroker); + EXPECT_EQ(Transport::kBroker, their_new_transport->destination_type()); + SerializeObjectFor(*transport, std::move(their_new_transport)) + .Transmit(*transport); + EXPECT_EQ("got null", listener.WaitForNextMessage().as_string()); + } + + { + auto [our_new_transport, their_new_transport] = + Transport::CreatePair(Transport::kNonBroker, Transport::kNonBroker); + their_new_transport->set_is_peer_trusted(true); + SerializeObjectFor(*transport, std::move(their_new_transport)) + .Transmit(*transport); + EXPECT_EQ("got null", listener.WaitForNextMessage().as_string()); + } + + { + auto [our_new_transport, their_new_transport] = + Transport::CreatePair(Transport::kNonBroker, Transport::kNonBroker); + SerializeObjectFor(*transport, std::move(their_new_transport)) + .Transmit(*transport); + EXPECT_EQ("got untrusted", listener.WaitForNextMessage().as_string()); + } + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h)); +} + +TEST_F(MojoIpczTransportTest, TransportFromUntrustedBroker) { + // When both endpoints of a transport are brokers, the peer being a broker + // does not by itself grant it any additional trust on this end. A broker + // receiving a serialized transport over such a link must reject it if it + // claims its own peer is trusted or is itself a broker. + RunTestClientWithController( + "TransportFromUntrustedBrokerClient", [&](ClientController& c) { + PlatformChannel channel; + MojoHandle transport_for_client = + WrapPlatformHandle( + channel.TakeRemoteEndpoint().TakePlatformHandle()) + .release() + .value(); + WriteMessageWithHandles(c.pipe(), "", &transport_for_client, 1); + scoped_refptr<Transport> transport = Transport::Create( + {.source = Transport::kBroker, .destination = Transport::kBroker}, + channel.TakeLocalEndpoint(), c.process().Duplicate()); + EXPECT_FALSE(transport->is_peer_trusted()); + + TransportListener listener(*transport); + TestMessage("ready").Transmit(*transport); + + for (int i = 0; i < 2; i++) { + TestMessage message = listener.WaitForNextMessage(); + scoped_refptr<ObjectBase> object; + const IpczResult result = transport->DeserializeObject( + base::span(message.bytes), base::span(message.handles), object); + EXPECT_EQ(result, IPCZ_RESULT_INVALID_ARGUMENT); + TestMessage("got null").Transmit(*transport); + } + + { + TestMessage message = listener.WaitForNextMessage(); + scoped_refptr<Transport> received = + DeserializeObjectFrom<Transport>(*transport, message); + EXPECT_FALSE(received->is_peer_trusted()); + EXPECT_EQ(Transport::kNonBroker, received->destination_type()); + TestMessage("got untrusted").Transmit(*transport); + } + + listener.WaitForDisconnect(); + }); +} + TEST_F(MojoIpczTransportTest, TransportFromUntrusted) { #if BUILDFLAG(IS_WIN) // TODO(crbug.com/414392683) default to untrusted/untracked. diff --git a/third_party/ipcz/src/ipcz/node_link_test.cc b/third_party/ipcz/src/ipcz/node_link_test.cc index 7d8ad77..5f11258 100644 --- a/third_party/ipcz/src/ipcz/node_link_test.cc +++ b/third_party/ipcz/src/ipcz/node_link_test.cc @@ -11,6 +11,7 @@ #include "ipcz/link_side.h" #include "ipcz/link_type.h" #include "ipcz/node_link_memory.h" +#include "ipcz/node_messages.h" #include "ipcz/remote_router_link.h" #include "ipcz/router.h" #include "ipcz/sublink_id.h" @@ -65,6 +66,43 @@ return {link0, link1}; } +std::pair<Ref<NodeLink>, Ref<NodeLink>> LinkBrokers(Ref<Node> broker0, + Ref<Node> broker1) { + IpczDriverHandle handle0, handle1; + EXPECT_EQ(IPCZ_RESULT_OK, + GetDriver().CreateTransports( + IPCZ_INVALID_DRIVER_HANDLE, IPCZ_INVALID_DRIVER_HANDLE, + IPCZ_NO_FLAGS, nullptr, &handle0, &handle1)); + + auto transport0 = + MakeRefCounted<DriverTransport>(DriverObject(GetDriver(), handle0)); + auto transport1 = + MakeRefCounted<DriverTransport>(DriverObject(GetDriver(), handle1)); + + DriverMemoryWithMapping buffer = NodeLinkMemory::AllocateMemory(GetDriver()); + ABSL_ASSERT(buffer.mapping.is_valid()); + + auto link0 = NodeLink::CreateInactive( + broker0, LinkSide::kA, broker0->GetAssignedName(), + broker1->GetAssignedName(), Node::Type::kBroker, 0, broker1->features(), + transport0, + NodeLinkMemory::Create(broker0, LinkSide::kA, broker1->features(), + std::move(buffer.mapping))); + auto link1 = NodeLink::CreateInactive( + broker1, LinkSide::kB, broker1->GetAssignedName(), + broker0->GetAssignedName(), Node::Type::kBroker, 0, broker0->features(), + transport1, + NodeLinkMemory::Create(broker1, LinkSide::kB, broker0->features(), + buffer.memory.Map())); + broker0->AddConnection(broker1->GetAssignedName(), + {.link = link0, .broker = link0}); + broker1->AddConnection(broker0->GetAssignedName(), + {.link = link1, .broker = link1}); + link0->Activate(); + link1->Activate(); + return {link0, link1}; +} + std::pair<Ref<Router>, Ref<Router>> AttachRouters(Ref<NodeLink> link0, Ref<NodeLink> link1) { auto router0 = MakeRefCounted<Router>(); @@ -303,5 +341,53 @@ link1->Deactivate(); } +TEST_F(NodeLinkTest, BrokerRejectsIntroductionToBroker) { + // Two brokers connected to one another, as in a broker-to-broker handshake. + // A broker should never be the target of an introduction to another broker: + // broker-to-broker links are only ever established directly via ConnectNode. + Ref<Node> node0 = MakeRefCounted<Node>(Node::Type::kBroker, GetDriver()); + Ref<Node> node1 = MakeRefCounted<Node>(Node::Type::kBroker, GetDriver()); + auto [link0, link1] = LinkBrokers(node0, node1); + auto [router0, router1] = AttachRouters(link0, link1); + + auto new_transports = DriverTransport::CreatePair(GetDriver()); + DriverMemoryWithMapping new_buffer = + NodeLinkMemory::AllocateMemory(GetDriver()); + const NodeName introduced_name = node0->GenerateRandomName(); + EXPECT_FALSE(node1->GetLink(introduced_name)); + link0->AcceptIntroduction(introduced_name, LinkSide::kA, Node::Type::kBroker, + 0, Features{}, std::move(new_transports.first), + std::move(new_buffer.memory)); + + // The introduction is rejected and no new connection is established. + EXPECT_FALSE(node1->GetLink(introduced_name)); + EXPECT_TRUE(router1->IsPeerClosed()); + + router0->CloseRoute(); + router1->CloseRoute(); + node0->Close(); + node1->Close(); +} + +TEST_F(NodeLinkTest, BrokerRejectsRelayFromBroker) { + // Relay requests are only ever sent by non-brokers to their own broker, so a + // broker must reject any relay request arriving over a broker-to-broker link. + Ref<Node> node0 = MakeRefCounted<Node>(Node::Type::kBroker, GetDriver()); + Ref<Node> node1 = MakeRefCounted<Node>(Node::Type::kBroker, GetDriver()); + auto [link0, link1] = LinkBrokers(node0, node1); + auto [router0, router1] = AttachRouters(link0, link1); + + msg::AddBlockBuffer payload; + link0->RelayMessage(node0->GetAssignedName(), payload); + + // The relay request is rejected and the link is torn down. + EXPECT_TRUE(router1->IsPeerClosed()); + + router0->CloseRoute(); + router1->CloseRoute(); + node0->Close(); + node1->Close(); +} + } // namespace } // namespace ipcz
Regression Test / PoC
diff --git a/mojo/core/ipcz_driver/transport_test.cc b/mojo/core/ipcz_driver/transport_test.cc
index ee57b3f..0b4733a 100644
--- a/mojo/core/ipcz_driver/transport_test.cc
+++ b/mojo/core/ipcz_driver/transport_test.cc
@@ -942,6 +942,86 @@
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h));
}
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(TransportFromUntrustedBrokerClient,
+ MojoIpczTransportTest,
+ h) {
+ scoped_refptr<Transport> transport = ReceiveTransport(h);
+ TransportListener listener(*transport);
+ EXPECT_EQ("ready", listener.WaitForNextMessage().as_string());
+
+ {
+ auto [our_new_transport, their_new_transport] =
+ Transport::CreatePair(Transport::kBroker, Transport::kNonBroker);
+ EXPECT_EQ(Transport::kBroker, their_new_transport->destination_type());
+ SerializeObjectFor(*transport, std::move(their_new_transport))
+ .Transmit(*transport);
+ EXPECT_EQ("got null", listener.WaitForNextMessage().as_string());
+ }
+
+ {
+ auto [our_new_transport, their_new_transport] =
+ Transport::CreatePair(Transport::kNonBroker, Transport::kNonBroker);
+ their_new_transport->set_is_peer_trusted(true);
+ SerializeObjectFor(*transport, std::move(their_new_transport))
+ .Transmit(*transport);
+ EXPECT_EQ("got null", listener.WaitForNextMessage().as_string());
+ }
+
+ {
+ auto [our_new_transport, their_new_transport] =
+ Transport::CreatePair(Transport::kNonBroker, Transport::kNonBroker);
+ SerializeObjectFor(*transport, std::move(their_new_transport))
+ .Transmit(*transport);
+ EXPECT_EQ("got untrusted", listener.WaitForNextMessage().as_string());
+ }
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h));
+}
+
+TEST_F(MojoIpczTransportTest, TransportFromUntrustedBroker) {
+ // When both endpoints of a transport are brokers, the peer being a broker
+ // does not by itself grant it any additional trust on this end. A broker
+ // receiving a serialized transport over such a link must reject it if it
+ // claims its own peer is trusted or is itself a broker.
+ RunTestClientWithController(
+ "TransportFromUntrustedBrokerClient", [&](ClientController& c) {
+ PlatformChannel channel;
+ MojoHandle transport_for_client =
+ WrapPlatformHandle(
+ channel.TakeRemoteEndpoint().TakePlatformHandle())
+ .release()
+ .value();
+ WriteMessageWithHandles(c.pipe(), "", &transport_for_client, 1);
+ scoped_refptr<Transport> transport = Transport::Create(
+ {.source = Transport::kBroker, .destination = Transport::kBroker},
+ channel.TakeLocalEndpoint(), c.process().Duplicate());
+ EXPECT_FALSE(transport->is_peer_trusted());
+
+ TransportListener listener(*transport);
+ TestMessage("ready").Transmit(*transport);
+
+ for (int i = 0; i < 2; i++) {
+ TestMessage message = listener.WaitForNextMessage();
+ scoped_refptr<ObjectBase> object;
+ const IpczResult result = transport->DeserializeObject(
+ base::span(message.bytes), base::span(message.handles), object);
+ EXPECT_EQ(result, IPCZ_RESULT_INVALID_ARGUMENT);
+ TestMessage("got null").Transmit(*transport);
+ }
+
+ {
+ TestMessage message = listener.WaitForNextMessage();
+ scoped_refptr<Transport> received =
+ DeserializeObjectFrom<Transport>(*transport, message);
+ EXPECT_FALSE(received->is_peer_trusted());
+ EXPECT_EQ(Transport::kNonBroker, received->destination_type());
+ TestMessage("got untrusted").Transmit(*transport);
+ }
+
+ listener.WaitForDisconnect();
+ });
+}
+
TEST_F(MojoIpczTransportTest, TransportFromUntrusted) {
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/414392683) default to untrusted/untracked.
diff --git a/third_party/ipcz/src/ipcz/node_link_test.cc b/third_party/ipcz/src/ipcz/node_link_test.cc
index 7d8ad77..5f11258 100644
--- a/third_party/ipcz/src/ipcz/node_link_test.cc
+++ b/third_party/ipcz/src/ipcz/node_link_test.cc
@@ -11,6 +11,7 @@
#include "ipcz/link_side.h"
#include "ipcz/link_type.h"
#include "ipcz/node_link_memory.h"
+#include "ipcz/node_messages.h"
#include "ipcz/remote_router_link.h"
#include "ipcz/router.h"
#include "ipcz/sublink_id.h"
@@ -65,6 +66,43 @@
return {link0, link1};
}
+std::pair<Ref<NodeLink>, Ref<NodeLink>> LinkBrokers(Ref<Node> broker0,
+ Ref<Node> broker1) {
+ IpczDriverHandle handle0, handle1;
+ EXPECT_EQ(IPCZ_RESULT_OK,
+ GetDriver().CreateTransports(
+ IPCZ_INVALID_DRIVER_HANDLE, IPCZ_INVALID_DRIVER_HANDLE,
+ IPCZ_NO_FLAGS, nullptr, &handle0, &handle1));
+
+ auto transport0 =
+ MakeRefCounted<DriverTransport>(DriverObject(GetDriver(), handle0));
+ auto transport1 =
+ MakeRefCounted<DriverTransport>(DriverObject(GetDriver(), handle1));
+
+ DriverMemoryWithMapping buffer = NodeLinkMemory::AllocateMemory(GetDriver());
+ ABSL_ASSERT(buffer.mapping.is_valid());
+
+ auto link0 = NodeLink::CreateInactive(
+ broker0, LinkSide::kA, broker0->GetAssignedName(),
+ broker1->GetAssignedName(), Node::Type::kBroker, 0, broker1->features(),
+ transport0,
+ NodeLinkMemory::Create(broker0, LinkSide::kA, broker1->features(),
+ std::move(buffer.mapping)));
+ auto link1 = NodeLink::CreateInactive(
+ broker1, LinkSide::kB, broker1->GetAssignedName(),
+ broker0->GetAssignedName(), Node::Type::kBroker, 0, broker0->features(),
+ transport1,
+ NodeLinkMemory::Create(broker1, LinkSide::kB, broker0->features(),
+ buffer.memory.Map()));
+ broker0->AddConnection(broker1->GetAssignedName(),
+ {.link = link0, .broker = link0});
+ broker1->AddConnection(broker0->GetAssignedName(),
+ {.link = link1, .broker = link1});
+ link0->Activate();
+ link1->Activate();
+ return {link0, link1};
+}
+
std::pair<Ref<Router>, Ref<Router>> AttachRouters(Ref<NodeLink> link0,
Ref<NodeLink> link1) {
auto router0 = MakeRefCounted<Router>();
@@ -303,5 +341,53 @@
link1->Deactivate();
}
+TEST_F(NodeLinkTest, BrokerRejectsIntroductionToBroker) {
+ // Two brokers connected to one another, as in a broker-to-broker handshake.
+ // A broker should never be the target of an introduction to another broker:
+ // broker-to-broker links are only ever established directly via ConnectNode.
+ Ref<Node> node0 = MakeRefCounted<Node>(Node::Type::kBroker, GetDriver());
+ Ref<Node> node1 = MakeRefCounted<Node>(Node::Type::kBroker, GetDriver());
+ auto [link0, link1] = LinkBrokers(node0, node1);
+ auto [router0, router1] = AttachRouters(link0, link1);
+
+ auto new_transports = DriverTransport::CreatePair(GetDriver());
+ DriverMemoryWithMapping new_buffer =
+ NodeLinkMemory::AllocateMemory(GetDriver());
+ const NodeName introduced_name = node0->GenerateRandomName();
+ EXPECT_FALSE(node1->GetLink(introduced_name));
+ link0->AcceptIntroduction(introduced_name, LinkSide::kA, Node::Type::kBroker,
+ 0, Features{}, std::move(new_transports.first),
+ std::move(new_buffer.memory));
+
+ // The introduction is rejected and no new connection is established.
+ EXPECT_FALSE(node1->GetLink(introduced_name));
+ EXPECT_TRUE(router1->IsPeerClosed());
+
+ router0->CloseRoute();
+ router1->CloseRoute();
+ node0->Close();
+ node1->Close();
+}
+
+TEST_F(NodeLinkTest, BrokerRejectsRelayFromBroker) {
+ // Relay requests are only ever sent by non-brokers to their own broker, so a
+ // broker must reject any relay request arriving over a broker-to-broker link.
+ Ref<Node> node0 = MakeRefCounted<Node>(Node::Type::kBroker, GetDriver());
+ Ref<Node> node1 = MakeRefCounted<Node>(Node::Type::kBroker, GetDriver());
+ auto [link0, link1] = LinkBrokers(node0, node1);
+ auto [router0, router1] = AttachRouters(link0, link1);
+
+ msg::AddBlockBuffer payload;
+ link0->RelayMessage(node0->GetAssignedName(), payload);
+
+ // The relay request is rejected and the link is torn down.
+ EXPECT_TRUE(router1->IsPeerClosed());
+
+ router0->CloseRoute();
+ router1->CloseRoute();
+ node0->Close();
+ node1->Close();
+}
+
} // namespace
} // namespace ipcz