Skip to content

Commit ad9c0db

Browse files
committed
perfetto: scope RelayPort to explicitly opted-in producer sockets
With --enable-relay-endpoint set and multiple producer sockets bound, traced exposed the RelayPort IPC service on every one of them, including local AF_UNIX sockets reachable by unprivileged apps. A local caller could bind RelayPort and cache forged SystemInfo / RemoteClockSync data that traced later emitted with its trusted packet sequence ID, defeating PacketStreamValidator. Gate RelayPort exposure per ListenEndpoint via a new expose_relay_endpoint bit honoured by ServiceIPCHostImpl. Add --enable-relay-endpoint-on=<sock> as a narrower variant of --enable-relay-endpoint that turns RelayPort on only for the named producer sockets, which must already appear in PERFETTO_PRODUCER_SOCK_NAME. On Android the init-bound local producer socket is never relay-capable; traced.relay_producer_port keeps adding its own relay socket. --enable-relay-endpoint retains its existing semantics for the common single-socket multi-machine setup.
1 parent f564063 commit ad9c0db

8 files changed

Lines changed: 133 additions & 42 deletions

File tree

docs/learning-more/multi-machine-tracing.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ UNIX path to a TCP listener that remote machines can reach.
5555
`--enable-relay-endpoint` makes that socket accept `traced_relay`
5656
connections in addition to ordinary local producers.
5757

58+
For deployments that need to keep a local AF_UNIX producer socket
59+
listening alongside the TCP relay socket — so host-side unprivileged
60+
producers and remote `traced_relay` clients use separate sockets —
61+
list both in `PERFETTO_PRODUCER_SOCK_NAME` and use the narrower
62+
`--enable-relay-endpoint-on` form, which names the socket that should
63+
carry `RelayPort`:
64+
65+
```bash
66+
PERFETTO_PRODUCER_SOCK_NAME=/tmp/perfetto-producer,0.0.0.0:20001 \
67+
tracebox traced --enable-relay-endpoint-on=0.0.0.0:20001
68+
```
69+
70+
In this form the local UNIX socket continues to serve unprivileged
71+
producers and never accepts relay calls, which keeps the `RelayPort`
72+
service unreachable from local apps. The flag selects from the producer
73+
sockets already listed in `PERFETTO_PRODUCER_SOCK_NAME` — it does not
74+
introduce a new endpoint, so the named socket must appear in the env
75+
var.
76+
5877
Leave this process running.
5978

6079
### Step 2: Start `traced_probes` on the host
@@ -69,7 +88,10 @@ PERFETTO_PRODUCER_SOCK_NAME=127.0.0.1:20001 \
6988
The same env var that rebound `traced`'s listener also tells local
7089
producers where to connect — without it, `traced_probes` would still try
7190
the default UNIX socket and fail. `sudo -E` preserves the env var across
72-
the privilege escalation needed for ftrace.
91+
the privilege escalation needed for ftrace. (If you used the
92+
`--enable-relay-endpoint-on` form in Step 1 instead, omit the env var:
93+
the default UNIX producer socket is still up, and `traced_probes` will
94+
connect to it without any extra configuration.)
7395

7496
### Step 3: Start `traced_relay` on the guest
7597

docs/reference/traced.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,27 @@ Entities interact with `traced` primarily through two channels:
8383
<prod_group>:<prod_mode>:<cons_group>:<cons_mode>`: Sets the group ownership
8484
and permission mode for the producer and consumer sockets. This is important
8585
for controlling which users and processes can connect to `traced`.
86-
* `--enable-relay-endpoint`: Enables an endpoint for
87-
[multi-machine tracing](/docs/deployment/multi-machine-architecture.md) via
88-
`traced_relay`. See
86+
* `--enable-relay-endpoint`: Exposes the `RelayPort` service used by
87+
[multi-machine tracing](/docs/deployment/multi-machine-architecture.md)
88+
on every producer socket named by `PERFETTO_PRODUCER_SOCK_NAME` (or the
89+
default producer socket). This is the standard switch for multi-machine
90+
setups; see
8991
[Multi-machine recording](/docs/learning-more/multi-machine-tracing.md)
9092
for the host-side setup.
9193

94+
> **Security note:** when the producer socket list mixes a local
95+
> AF_UNIX socket with a remote-capable one, this flag exposes
96+
> `RelayPort` on the local socket too. Deployments that combine
97+
> local and remote producer sockets should use
98+
> `--enable-relay-endpoint-on` below to keep the relay port off the
99+
> local socket.
100+
* `--enable-relay-endpoint-on <socket>`: Like `--enable-relay-endpoint`,
101+
but exposes the `RelayPort` service only on the named producer socket.
102+
`<socket>` must be one of the entries in `PERFETTO_PRODUCER_SOCK_NAME`
103+
(or the default producer socket): the flag selects which existing
104+
producer sockets get `RelayPort`; it does not introduce new endpoints.
105+
May be repeated.
106+
92107
## Built-in Producer
93108

94109
On Android, `traced` also includes a built-in producer with several key

include/perfetto/ext/tracing/core/tracing_service.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,6 @@ struct PERFETTO_EXPORT_COMPONENT TracingServiceInitOpts {
313313
using CompressorFn = void (*)(std::vector<TracePacket>*);
314314
CompressorFn compressor_fn = nullptr;
315315

316-
// Whether the relay endpoint is enabled on producer transport(s).
317-
bool enable_relay_endpoint = false;
318-
319316
// An (optional) list of proto extension descriptors to dump into each trace
320317
// recorded. This is to support injecting protos that are known by the
321318
// embedder (e.g. the Android vendor image) but not by the upstream perfetto.

include/perfetto/ext/tracing/ipc/service_ipc_host.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ struct ListenEndpoint {
5858
std::string sock_name;
5959
base::ScopedSocketHandle sock_handle;
6060
std::unique_ptr<ipc::Host> ipc_host;
61+
62+
// If true, the RelayPort IPC service is also exposed on this endpoint.
63+
// Should only be set on endpoints reserved for cross-machine peers (e.g.
64+
// vsock, TCP), not on local sockets shared with unprivileged producers.
65+
bool expose_relay_endpoint = false;
6166
};
6267

6368
// Creates an instance of the service (business logic + UNIX socket transport).

src/traced/service/service.cc

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <stdio.h>
1818
#include <algorithm>
19+
#include <set>
1920

2021
#include "perfetto/base/status.h"
2122
#include "perfetto/ext/base/android_utils.h"
@@ -57,9 +58,19 @@ Options and arguments
5758
<prod_mode> is the mode bits (e.g. 0660) for chmod the produce socket,
5859
<cons_group> is the group name for chgrp the consumer socket, and
5960
<cons_mode> is the mode bits (e.g. 0660) for chmod the consumer socket.
60-
--enable-relay-endpoint : enables the relay endpoint on producer socket(s)
61-
for traced_relay to communicate with traced in a multiple-machine
62-
tracing session.
61+
--enable-relay-endpoint : exposes the RelayPort service (used by
62+
traced_relay in multi-machine tracing) on every producer socket named
63+
by PERFETTO_PRODUCER_SOCK_NAME / the default producer socket. This
64+
is the standard switch for multi-machine setups.
65+
--enable-relay-endpoint-on <socket> : narrower variant of the above,
66+
intended for security-conscious deployments. Exposes the RelayPort
67+
service only on the named producer socket. <socket> must match one
68+
of the entries in PERFETTO_PRODUCER_SOCK_NAME (or the default
69+
producer socket); the flag selects which producer sockets get
70+
RelayPort, it does NOT introduce new endpoints. May be repeated.
71+
Useful when the producer socket list mixes local AF_UNIX sockets
72+
with remote-capable ones and the relay port must not be reachable
73+
on the local socket.
6374
6475
Example:
6576
%s --set-socket-permissions traced-producer:0660:traced-consumer:0660
@@ -76,11 +87,13 @@ int PERFETTO_EXPORT_ENTRYPOINT ServiceMain(int argc, char** argv) {
7687
OPT_VERSION = 1000,
7788
OPT_SET_SOCKET_PERMISSIONS = 1001,
7889
OPT_BACKGROUND,
79-
OPT_ENABLE_RELAY_ENDPOINT
90+
OPT_ENABLE_RELAY_ENDPOINT,
91+
OPT_ENABLE_RELAY_ENDPOINT_ON
8092
};
8193

8294
bool background = false;
8395
bool enable_relay_endpoint = false;
96+
std::set<std::string> relay_endpoint_sockets;
8497

8598
static const option long_options[] = {
8699
{"background", no_argument, nullptr, OPT_BACKGROUND},
@@ -89,6 +102,8 @@ int PERFETTO_EXPORT_ENTRYPOINT ServiceMain(int argc, char** argv) {
89102
OPT_SET_SOCKET_PERMISSIONS},
90103
{"enable-relay-endpoint", no_argument, nullptr,
91104
OPT_ENABLE_RELAY_ENDPOINT},
105+
{"enable-relay-endpoint-on", required_argument, nullptr,
106+
OPT_ENABLE_RELAY_ENDPOINT_ON},
92107
{nullptr, 0, nullptr, 0}};
93108

94109
std::string producer_socket_group, consumer_socket_group,
@@ -121,6 +136,9 @@ int PERFETTO_EXPORT_ENTRYPOINT ServiceMain(int argc, char** argv) {
121136
case OPT_ENABLE_RELAY_ENDPOINT:
122137
enable_relay_endpoint = true;
123138
break;
139+
case OPT_ENABLE_RELAY_ENDPOINT_ON:
140+
relay_endpoint_sockets.emplace(optarg);
141+
break;
124142
default:
125143
PrintUsage(argv[0]);
126144
return 1;
@@ -154,17 +172,14 @@ int PERFETTO_EXPORT_ENTRYPOINT ServiceMain(int argc, char** argv) {
154172
}
155173
#endif
156174

157-
std::string relay_producer_socket;
175+
std::string android_relay_socket;
158176
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
159-
relay_producer_socket = base::GetAndroidProp("traced.relay_producer_port");
160-
// If a guest producer port is defined, then the relay endpoint should be
161-
// enabled regardless. This is used in cases where perf data is passed
162-
// from guest machines or the hypervisor to Android.
163-
if (!relay_producer_socket.empty())
164-
init_opts.enable_relay_endpoint = true;
177+
// On Android, a relay-only producer socket can be configured via system
178+
// property — e.g. when perf data is forwarded from guest VMs or the
179+
// hypervisor into the host's traced. The named socket is appended to the
180+
// producer endpoint list below and marked relay-capable.
181+
android_relay_socket = base::GetAndroidProp("traced.relay_producer_port");
165182
#endif
166-
if (enable_relay_endpoint)
167-
init_opts.enable_relay_endpoint = true;
168183
svc = ServiceIPCHost::CreateInstance(&task_runner, init_opts);
169184

170185
// When built as part of the Android tree, the two socket are created and
@@ -180,18 +195,45 @@ int PERFETTO_EXPORT_ENTRYPOINT ServiceMain(int argc, char** argv) {
180195
#else
181196
ListenEndpoint consumer_ep(base::ScopedFile(atoi(env_cons)));
182197
std::list<ListenEndpoint> producer_eps;
198+
// The init-bound FD is the local producer socket. The relay endpoint, if
199+
// any, is the separate socket named by traced.relay_producer_port (added
200+
// below), never this one.
183201
producer_eps.emplace_back(ListenEndpoint(base::ScopedFile(atoi(env_prod))));
184-
if (!relay_producer_socket.empty()) {
185-
producer_eps.emplace_back(ListenEndpoint(relay_producer_socket));
202+
if (!android_relay_socket.empty()) {
203+
ListenEndpoint relay_ep(android_relay_socket);
204+
relay_ep.expose_relay_endpoint = true;
205+
producer_eps.emplace_back(std::move(relay_ep));
186206
}
187207
started = svc->Start(std::move(producer_eps), std::move(consumer_ep));
188208
#endif
189209
} else {
190210
std::list<ListenEndpoint> producer_eps;
191211
auto producer_socket_names = TokenizeProducerSockets(GetProducerSocket());
212+
std::set<std::string> seen_relay_selectors;
192213
for (const auto& producer_socket_name : producer_socket_names) {
193214
remove(producer_socket_name.c_str());
194-
producer_eps.emplace_back(ListenEndpoint(producer_socket_name));
215+
ListenEndpoint ep(producer_socket_name);
216+
// RelayPort is exposed on this socket if either:
217+
// --enable-relay-endpoint was passed (applies to all
218+
// PERFETTO_PRODUCER_SOCK_NAME sockets), OR
219+
// --enable-relay-endpoint-on=<this socket> was passed (selects
220+
// specific producer sockets).
221+
bool selected = relay_endpoint_sockets.count(producer_socket_name) > 0;
222+
if (selected)
223+
seen_relay_selectors.insert(producer_socket_name);
224+
ep.expose_relay_endpoint = enable_relay_endpoint || selected;
225+
producer_eps.emplace_back(std::move(ep));
226+
}
227+
for (const auto& selector : relay_endpoint_sockets) {
228+
if (seen_relay_selectors.count(selector) == 0) {
229+
PERFETTO_ELOG(
230+
"--enable-relay-endpoint-on=%s does not match any socket in "
231+
"PERFETTO_PRODUCER_SOCK_NAME / the default producer socket "
232+
"(%s). The flag selects from existing producer sockets; it does "
233+
"not introduce new endpoints.",
234+
selector.c_str(), GetProducerSocket());
235+
return 1;
236+
}
195237
}
196238
remove(GetConsumerSocket());
197239
started = svc->Start(std::move(producer_eps),

src/tracing/ipc/service/service_ipc_host_impl.cc

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ bool ServiceIPCHostImpl::Start(std::list<ListenEndpoint> producer_sockets,
7777

7878
// Initialize the IPC transport.
7979
for (auto& sock : producer_sockets) {
80-
producer_ipc_ports_.emplace_back(
81-
CreateIpcHost(task_runner_, std::move(sock)));
80+
bool expose_relay = sock.expose_relay_endpoint;
81+
producer_ipc_ports_.push_back(
82+
{CreateIpcHost(task_runner_, std::move(sock)), expose_relay});
8283
}
8384
consumer_ipc_port_ = CreateIpcHost(task_runner_, std::move(consumer_socket));
8485

@@ -98,10 +99,9 @@ bool ServiceIPCHostImpl::DoStart() {
9899
init_opts_);
99100

100101
if (producer_ipc_ports_.empty() || !consumer_ipc_port_ ||
101-
std::any_of(producer_ipc_ports_.begin(), producer_ipc_ports_.end(),
102-
[](const std::unique_ptr<ipc::Host>& port) {
103-
return port == nullptr;
104-
})) {
102+
std::any_of(
103+
producer_ipc_ports_.begin(), producer_ipc_ports_.end(),
104+
[](const ProducerIPCPort& port) { return port.host == nullptr; })) {
105105
Shutdown();
106106
return false;
107107
}
@@ -115,20 +115,20 @@ bool ServiceIPCHostImpl::DoStart() {
115115
// consumer port ipcs might exhaust the send buffer under normal operation
116116
// due to large messages such as ReadBuffersResponse.
117117
for (auto& producer_ipc_port : producer_ipc_ports_)
118-
producer_ipc_port->SetSocketSendTimeoutMs(kProducerSocketTxTimeoutMs);
118+
producer_ipc_port.host->SetSocketSendTimeoutMs(kProducerSocketTxTimeoutMs);
119119

120120
// TODO(fmayer): add a test that destroys the ServiceIPCHostImpl soon after
121121
// Start() and checks that no spurious callbacks are issued.
122122
for (auto& producer_ipc_port : producer_ipc_ports_) {
123-
bool producer_service_exposed = producer_ipc_port->ExposeService(
123+
bool producer_service_exposed = producer_ipc_port.host->ExposeService(
124124
std::unique_ptr<ipc::Service>(new ProducerIPCService(svc_.get())));
125125
PERFETTO_CHECK(producer_service_exposed);
126126

127-
if (!init_opts_.enable_relay_endpoint)
127+
// Optionally also expose the RelayPort service on this port, when the
128+
// caller has explicitly opted in for cross-machine relay traffic.
129+
if (!producer_ipc_port.expose_relay_endpoint)
128130
continue;
129-
// Expose a secondary service for sync with remote relay service
130-
// if requested.
131-
bool relay_service_exposed = producer_ipc_port->ExposeService(
131+
bool relay_service_exposed = producer_ipc_port.host->ExposeService(
132132
std::unique_ptr<ipc::Service>(new RelayIPCService(svc_.get())));
133133
PERFETTO_CHECK(relay_service_exposed);
134134
}

src/tracing/ipc/service/service_ipc_host_impl.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ class ServiceIPCHostImpl : public ServiceIPCHost {
5656
// Note that there can be multiple producer sockets if it's specified in the
5757
// producer socket name (e.g. for listening both on vsock for VMs and AF_UNIX
5858
// for processes on the same machine).
59-
std::vector<std::unique_ptr<ipc::Host>> producer_ipc_ports_;
59+
// The `expose_relay_endpoint` bit gates whether the RelayIPCService is
60+
// exposed on that specific port (see ListenEndpoint).
61+
struct ProducerIPCPort {
62+
std::unique_ptr<ipc::Host> host;
63+
bool expose_relay_endpoint = false;
64+
};
65+
std::vector<ProducerIPCPort> producer_ipc_ports_;
6066

6167
// As above, but for the Consumer port.
6268
std::unique_ptr<ipc::Host> consumer_ipc_port_;

test/test_helper.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,8 @@ class ServiceThread {
132132
{"PERFETTO_PRODUCER_SOCK_NAME", "PERFETTO_CONSUMER_SOCK_NAME"});
133133
runner_ = base::ThreadTaskRunner::CreateAndStart("perfetto.svc");
134134
runner_->PostTaskAndWaitForTesting([this]() {
135-
TracingService::InitOpts init_opts = {};
136-
if (enable_relay_endpoint_)
137-
init_opts.enable_relay_endpoint = true;
138-
svc_ = ServiceIPCHost::CreateInstance(runner_->get(), init_opts);
135+
svc_ = ServiceIPCHost::CreateInstance(runner_->get(),
136+
TracingService::InitOpts{});
139137
auto producer_sockets = TokenizeProducerSockets(producer_socket_.c_str());
140138
for (const auto& producer_socket : producer_sockets) {
141139
// In some cases the socket is a TCP or abstract unix.
@@ -152,8 +150,14 @@ class ServiceThread {
152150
}
153151
base::SetEnv("PERFETTO_PRODUCER_SOCK_NAME", producer_socket_);
154152
base::SetEnv("PERFETTO_CONSUMER_SOCK_NAME", consumer_socket_);
155-
bool res =
156-
svc_->Start(producer_socket_.c_str(), consumer_socket_.c_str());
153+
std::list<ListenEndpoint> producer_eps;
154+
for (const auto& producer_socket : producer_sockets) {
155+
ListenEndpoint ep(producer_socket);
156+
ep.expose_relay_endpoint = enable_relay_endpoint_;
157+
producer_eps.emplace_back(std::move(ep));
158+
}
159+
bool res = svc_->Start(std::move(producer_eps),
160+
ListenEndpoint(consumer_socket_));
157161
if (!res) {
158162
PERFETTO_FATAL("Failed to start service listening on %s and %s",
159163
producer_socket_.c_str(), consumer_socket_.c_str());

0 commit comments

Comments
 (0)