1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
//! Permission enforcement for Unix-domain sockets — the `0600` guarantee
//! ADR-0031 and ADR-0032 cite as an existing property.
//!
//! Why: both ADRs rest their case for UDS-over-loopback-TCP on "a loopback port
//! is reachable by any local process, a `0600` socket is not". Until #5099 no
//! production code in this workspace called `set_permissions` on any socket —
//! every hit was a test fixture. Sockets were created at the process umask
//! (commonly `0755`) and two of the three path conventions placed them in
//! `$TMPDIR` falling back to a shared, world-writable `/tmp`. This module makes
//! the claimed guarantee real, in one place, so a behavior fix lands once
//! rather than at four bind sites. ADR-0034 §3 ("The trust boundary") states
//! the requirement: `0700` directory, `0600` socket, peer-uid check on accept.
//!
//! What: four primitives, in the order a daemon uses them.
//! - [`scratch_socket_dir`] resolves a per-uid directory under the system
//! scratch space, replacing the bare `$TMPDIR`-with-`/tmp`-fallback.
//! - [`bind_hardened`] creates that directory at `0700`, binds, and sets the
//! socket to `0600` before returning it to the caller.
//! - [`connect_hardened`] is the dialer's half: it verifies the directory and
//! socket a client is about to trust before writing anything to it.
//! - [`ensure_peer_is_self`] refuses an accepted connection whose peer uid is
//! not this process's own, which is what turns the permission bits into an
//! enforced boundary rather than a documented intention.
//!
//! **What this does and does not guarantee.** With the directory held at `0700`
//! and owned by this uid, no other unprivileged user can traverse to the socket
//! — that is the load-bearing property, and it holds from the moment the
//! directory is created. It is *not* an unconditional claim about every path
//! this module can be handed: a caller that points it at a directory an
//! attacker can rename or unlink entries in retains a residual swap race that
//! only `openat`/`fchmod` on a directory fd could close. Symlink pre-creation,
//! which is the practical version of that attack, is refused outright, as is a
//! non-directory sitting at the socket-directory path — see
//! [`dir::prepare_socket_dir`]. Root is not defended against and cannot be.
//!
//! Deliberately not a transport: there is no framing or JSON-RPC here. #5089
//! step 1 builds the shared UDS transport module; this is the security layer
//! that module mounts, including the dialer it needs, not a competing
//! implementation of it.
//!
//! Test: `tests.rs` — directory and socket modes after a real bind, the
//! pre-existing-wide-directory repair, symlink refusal, the pure decision
//! functions behind every refusal, and the `sun_path` budget pre-check.
//!
//! [`scratch_socket_dir`]: crate::uds::scratch_socket_dir
//! [`bind_hardened`]: crate::uds::bind_hardened
//! [`connect_hardened`]: crate::uds::connect_hardened
//! [`ensure_peer_is_self`]: crate::uds::ensure_peer_is_self
//! [`dir::prepare_socket_dir`]: crate::uds::dir::prepare_socket_dir
// #6277: the serving half of `rpc`, so a daemon migrating off HTTP under
// ADR-0032 supplies a method table rather than a fourth hand-rolled accept loop.
// #6286: the reading half of a multi-frame response, so a token stream has one
// definition of what terminates it rather than one per consumer.
/// On-demand supervision of a UDS-serving child process (#5089 step 2).
///
/// Why: ADR-0034 §1 needs `trusty-console` to start `trusty-review` /
/// `trusty-analyze` at delivery time so neither has to be resident. The
/// mechanism ADR-0032 said "does not yet exist" did exist, as `trusty-memory`'s
/// `Bm25Supervisor`; this is that supervisor with its BM25-specific parts lifted
/// into per-service configuration.
/// What: [`supervisor::UdsServiceSupervisor`] plus its config, error and probe
/// types. Gated behind the `uds-supervisor` feature because it pulls the whole
/// child-process lifecycle in, which a crate that only binds a socket does not
/// need.
/// Test: `supervisor/tests.rs`, and `trusty-memory`'s
/// `tests/bm25_supervisor_concurrency.rs` against real children.
/// The single entry point for starting `trusty-analyze` on demand (#6350).
///
/// Why it sits beside [`supervisor`] rather than inside it: the supervisor is
/// the generic machinery, and this is the one service description every client
/// crate shares. Gated behind the same feature, because it is that machinery
/// applied.
/// Test: `on_demand_tests.rs`.
pub use prepare_socket_dir;
pub use ;
pub use ;
pub use ;
pub use ;
pub use bind_singleton_hardened;
pub use ;
pub use ;
use Permissions;
use ;
use ;
use ;
/// Mode every socket-bearing directory is created at and verified against.
///
/// Owner-only `rwx`. Unix path resolution needs search (`x`) permission on
/// every directory component, so a `0700` directory makes every socket inside
/// it unreachable to any other uid regardless of the socket's own mode.
pub const SOCKET_DIR_MODE: u32 = 0o700;
/// Mode every bound socket is set to before its first `accept`.
pub const SOCKET_MODE: u32 = 0o600;
/// Failures that mean a socket could not be made private, or could not be
/// trusted. Every variant is fatal to the operation — none is a "log and
/// continue" condition, because continuing would use a socket wider than the
/// ADRs promise.
///
/// `#[non_exhaustive]`: this enum gained two variants across two review rounds
/// of #5099 alone, so it will keep growing as the checks tighten. The attribute
/// costs nothing while `trusty-common` sits unpublished at 0.30.0 against a
/// published 0.28.1, and stops being free the moment 0.30.0 ships — after which
/// each new variant would be a breaking change. On an enum it constrains
/// *matching* only (external crates need a wildcard arm); it does not bar
/// constructing variants, which is the separate effect the attribute has on a
/// struct. Matching this exhaustively from outside `trusty-common` was never
/// possible anyway — every consumer converts it (`io::Error::other`, `?` into
/// `anyhow`, or `Display` in a log) rather than inspecting variants.
/// Bytes available in this platform's `sockaddr_un.sun_path`, NUL included.
///
/// Why: 104 on macOS, 108 on Linux. Rust rejects an over-long path before the
/// syscall with a bare `invalid argument` that names neither the limit nor the
/// offending path, which is a poor diagnostic for a value assembled from
/// `$TMPDIR` plus a palace name (#5099 review finding 4).
/// What: derived from the actual struct layout rather than hardcoded.
/// Test: `sun_path_capacity_is_platform_plausible`.
/// Reject a socket path that cannot fit the kernel's address buffer.
///
/// Why: turns `invalid argument` into a message naming the budget, the actual
/// length, and the path — the difference between "the daemon is broken" and
/// "this palace name is 12 characters too long".
/// What: compares the path's byte length against [`sun_path_capacity`], leaving
/// room for the NUL terminator.
/// Test: `check_sun_path_budget_accepts_a_path_that_fits` and
/// `check_sun_path_budget_rejects_an_over_long_path`.
/// Per-uid directory under the system scratch space for daemon sockets.
///
/// Why: the convention this replaces was `$TMPDIR`, falling back to `/tmp`. On
/// macOS `$TMPDIR` is a per-user `/var/folders/…/T/` that is already `0700`, so
/// the fallback never bit there. On a Linux host with `TMPDIR` unset it
/// resolved to `/tmp` — mode `1777`, owned by root — which can be neither
/// narrowed to `0700` nor trusted, so any socket in it was reachable by every
/// local user. Interposing a uid-keyed subdirectory gives a directory this
/// process owns and can hold at `0700` on both platforms uniformly.
///
/// What: `<$TMPDIR or /tmp>/trusty-<uid>`. The name is kept short on purpose:
/// `sun_path` is 104 bytes on macOS and macOS' `$TMPDIR` already consumes
/// roughly half of that, so every byte here comes out of the budget available
/// to palace names (see `trusty-memory`'s `bm25_supervisor_concurrency` tests).
///
/// Test: `scratch_socket_dir_is_uid_keyed`; the `$TMPDIR` resolution rules are
/// asserted against [`scratch_socket_dir_from`] so no test has to mutate the
/// process-global `TMPDIR` (which reddens unrelated sibling tests — see the
/// `trusty-mpm` `tmpdir-cross-test-pollution` fragment).
/// [`scratch_socket_dir`] with its two environment inputs passed explicitly.
///
/// Why: keeps the resolution rules unit-testable without `set_var`. A test that
/// mutates `TMPDIR` is visible to every concurrently-running sibling in the
/// same test binary, and `tempfile` honors it.
/// What: treats an absent, empty, or whitespace-only `tmpdir` as `/tmp`.
/// Test: `scratch_socket_dir_from_uses_tmpdir_when_set`,
/// `scratch_socket_dir_from_falls_back_to_tmp`.
/// Resolve a socket path's parent, rejecting a bare filename.
///
/// `Path::parent` yields `Some("")` — not `None` — for a bare filename, so the
/// empty case has to be filtered explicitly or a relative socket name would be
/// treated as living in an unhardened cwd.
/// Bind a listener at `path` with its directory at `0700` and the socket at
/// `0600`.
///
/// Why: the single entry point every daemon binds through, so the permission
/// contract cannot drift between the four bind sites that previously each
/// called `UnixListener::bind` bare (`trusty-embedderd`, `trusty-bm25-daemon`,
/// and two in `trusty-agents`). See #5099.
///
/// What: checks the `sun_path` budget, hardens the parent directory via
/// [`prepare_socket_dir`], binds, then narrows the socket to [`SOCKET_MODE`]
/// before returning — so the listener is already `0600` when the caller first
/// calls `accept`. Deliberately does *not* remove a stale socket file:
/// `CtrlSocket::bind_singleton` must probe before clobbering, and folding an
/// unconditional unlink in here would break that singleton guarantee. Callers
/// that want stale-file cleanup keep doing it themselves, immediately before
/// this call.
///
/// The `0600` step is defence in depth, not the race fix — the `0700` directory
/// is what makes the socket unreachable during the window between `bind` and
/// `chmod`. [`prepare_socket_dir`]'s docs carry the reasoning and the residual
/// race it does not close.
///
/// Test: `bind_hardened_sets_socket_0600_and_dir_0700`,
/// `bind_hardened_socket_is_connectable_after_hardening`,
/// `bind_hardened_rejects_an_over_long_path`.
/// Verify a socket and its directory, then connect.
///
/// Why: hardening only the bind side leaves the client trusting whatever is at
/// the path. A daemon that predates this change, or a socket an attacker
/// planted, still answers — and the supervisor's adopt-an-existing-socket path
/// means the daemon's own `ForeignDirOwner` check may never run, because it
/// never spawns when something is already listening (#5099 review finding 3).
/// The dialer has to do its own verification.
///
/// What: refuses unless the parent directory is a non-symlink `0700` directory
/// owned by this uid, and the socket itself is a non-symlink socket owned by
/// this uid at mode `0600`. Then connects.
///
/// This is a check-then-use, so a sufficiently privileged attacker could swap
/// the target between the `lstat` and the `connect`. It is not trying to win
/// that race — it is closing the case that actually occurs, where a wrong-mode
/// or wrong-owner socket persists and would otherwise be dialled silently.
///
/// Test: `connect_hardened_accepts_a_properly_hardened_socket`,
/// `connect_hardened_refuses_a_world_readable_socket`,
/// `connect_hardened_refuses_a_socket_in_a_wide_directory`,
/// `connect_hardened_refuses_a_regular_file`.
pub async
/// The filesystem half of [`connect_hardened`], split out so it is testable
/// without standing up a listener.
///
/// Test: the `connect_hardened_*` tests plus `verify_socket_for_connect_*`.