From b8b8b78b0924a4db59119cccd0d31519a8b2191f Mon Sep 17 00:00:00 2001 From: Mollusk Date: Sun, 26 Jul 2026 02:01:07 -0400 Subject: [PATCH] host/taint: pin what "resolved" must mean before F11-1 is implemented MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 12 re-examined the deferral and agreed it holds while evaluate() is audit-only, and that the recorded rule closes the path without unbounding Pulse-emulated apps — but only under one reading of "a node whose Client cannot be resolved at all". The trap is worth writing down before anyone implements it: reading "resolved" as "a unique Client object exists" passes for a unique Client with sec_pid = None, which supplies no protected identity and leaves exactly the self-claimed-PID hole the rule exists to close. It has to mean an unambiguous Client yielding Some(pipewire.sec.pid), taken before pipewire-pulse suppression. That also means the §5.1 matrix needs five Client cases rather than two: absent, ambiguous, unique-but-pid-less, resolved-native, and resolved-to-pipewire-pulse. The pid-less row is the one that distinguishes the two readings and the one a two-case matrix skips without saying so. Docs only. Still deferred, still to be decided with matrix data in hand. 220 tests green, clippy clean. Co-Authored-By: Claude Opus 5 --- src/host/taint/owner.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/host/taint/owner.rs b/src/host/taint/owner.rs index ac7d0eb..464d4d0 100644 --- a/src/host/taint/owner.rs +++ b/src/host/taint/owner.rs @@ -300,6 +300,28 @@ fn keys_of(node: &NodeSnapshot, ctx: &OwnerCtx) -> Vec<(OwnerKey, KeyValue)> { /// ⚠️ Do this **with the §5.1 matrix data in hand**, not before: the whole /// question is how much over-exclusion the rule actually causes on a real /// graph, and that is measurable rather than arguable. +/// +/// ## Round 12 — the deferral holds, and "resolved" has a trap in it +/// +/// Codex re-examined this and agreed the deferral is defensible while +/// `evaluate()` is audit-only, and that the rule above closes the recorded path +/// without unbounding normal Pulse-emulated apps — **but only under one +/// reading of "resolves"**, and the wrong reading reintroduces the hole: +/// +/// - ✅ "Resolved" must mean **an unambiguous Client that yields +/// `Some(pipewire.sec.pid)`**, taken *before* the pipewire-pulse suppression +/// step. A Pulse app then still has the daemon's protected PID as +/// provenance, even though that value is omitted from the bridge keys, so it +/// stays bounded and the eligible half survives. +/// - ❌ **Do not** implement it as "a unique Client object exists". A unique +/// Client with `sec_pid = None` would satisfy that test while providing no +/// protected identity at all, leaving exactly the self-claimed-PID hole this +/// rule is meant to close. +/// +/// So the matrix needs five Client cases, not two: **absent**, **ambiguous**, +/// **unique but pid-less**, **resolved-native**, and +/// **resolved-to-pipewire-pulse**. The third is the one that distinguishes the +/// two readings, and it is the row a two-case matrix would silently skip. pub fn owner_is_bounded(node: &NodeSnapshot, ctx: &OwnerCtx) -> bool { keys_of(node, ctx) .iter()