host/taint: a pid-less Client still makes its id ambiguous

Verification round on R10-3's own fix. The ambiguity guard detected a
duplicate client id by looking it up in the pid map — which is only
populated for Clients that carry a sec_pid at all. A pid-less first
claimant therefore left no trace, so the next Client claiming the same id
looked unique and its pid was used, resolving an ambiguous id: exactly
the guess the guard exists to refuse.

Reachable, not theoretical — pid-less Clients are ordinary here (the
session manager's is one). Reproduced: the bystander app went eligible
off a coin-toss owner attribution.

Claimed ids are now tracked separately from resolved pids.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 21:07:42 -04:00
co-authored by Claude Opus 5
parent 67f4ff931e
commit abaf5d9c10
2 changed files with 51 additions and 3 deletions
+9 -3
View File
@@ -100,13 +100,19 @@ pub struct OwnerCtx {
impl OwnerCtx {
pub fn new(snapshot: &GraphSnapshot, pipewire_pulse_pid: Option<u32>) -> Self {
let mut client_pids: BTreeMap<GlobalId, u32> = BTreeMap::new();
let mut ambiguous: BTreeSet<GlobalId> = BTreeSet::new();
// ⚠️ Tracked separately from `client_pids`, and that is the point: a
// Client with no `sec_pid` still *claims* its id. Detecting duplicates
// by looking in the pid map would let a pid-less first claimant leave
// no trace, so the next Client claiming the same id would look unique
// and its pid would be used — resolving an ambiguous id, which is the
// one guess this guard exists to refuse. Pid-less Clients are ordinary
// (the session manager's is one).
let mut seen: BTreeSet<GlobalId> = BTreeSet::new();
for client in snapshot.clients() {
if client_pids.contains_key(&client.id) || ambiguous.contains(&client.id) {
if !seen.insert(client.id) {
// Two Clients claiming one id: drop it entirely rather than
// pick. See the field docs.
client_pids.remove(&client.id);
ambiguous.insert(client.id);
continue;
}
if let Some(pid) = client.sec_pid {