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
//! Timing-leak dudect harness for capability scope-subset evaluation.
//!
//! Gated behind the `dudect` Cargo feature so default `cargo test -p
//! chio-kernel-core` is unaffected; opt in via:
//!
//! ```bash
//! cargo test -p chio-kernel-core --features dudect --release scope_subset
//! ```
//!
//! # What this harness measures
//!
//! [`chio_kernel_core::NormalizedScope::is_subset_of`] is the authoritative
//! capability-algebra subset check used by the proof-facing evaluation
//! lane. It walks the child scope's tool grants and asks whether each one
//! is covered by any grant in the parent scope (`grants.iter().all(|g|
//! parent.grants.iter().any(|p| g.is_subset_of(p)))`). The inner
//! short-circuit (`Iterator::any`) returns as soon as a covering parent
//! grant is found.
//!
//! Whether the input data influences how *quickly* the subset check
//! resolves is the question this harness asks. The two input classes
//! place the matching parent grant at different positions:
//!
//! - `Class::Left`: the matching parent grant is the **first** entry of
//! the parent's `grants` vector. The `any(...)` predicate short-circuits
//! on the first iteration.
//! - `Class::Right`: the matching parent grant is the **last** entry of
//! the parent's `grants` vector. The `any(...)` predicate runs through
//! every entry before short-circuiting.
//!
//! Both classes resolve to the same verdict (`true`); the harness asks
//! whether the time taken to reach that verdict is data-dependent in a
//! way that an off-path attacker could use to learn something about
//! which parent grant matched. If the runtime distributions are
//! statistically distinguishable (Welch's t > 4.5 in two consecutive
//! runs), the subset check is timing-leaky.
//!
//! # Why this matters
//!
//! Scope evaluation lives on the verdict-producing hot path of every
//! capability-bearing tool call. A timing leak here would let a tenant
//! learn the structure of another tenant's parent capability through
//! response-time analysis. A `t < 4.5` result in two consecutive CI runs
//! is the documented pass criterion (`.github/workflows/dudect.yml`).
use ;
use RngExt;
use ;
/// Number of input pairs generated per harness invocation.
const SAMPLES_PER_RUN: usize = 100_000;
/// Fan-out width of the parent scope's `grants` vector. Wide enough that
/// the difference between matching at index 0 vs index `PARENT_FANOUT - 1`
/// produces a measurable runtime gap if the subset check short-circuits.
const PARENT_FANOUT: usize = 16;
/// Build a `NormalizedToolGrant` with deterministic shape but a unique
/// `tool_name` per index. The grant is intentionally minimal (no
/// constraints, no caps) so the per-grant subset check is dominated by
/// the `tool_name` and `server_id` string compares rather than constraint
/// containment math.
/// Helper that returns a single-element `Vec<NormalizedOperation>` containing
/// `Invoke`. Pulled out because the verbose vec-literal would otherwise
/// repeat at every call site.
/// Build a parent scope whose `grants` vector has `PARENT_FANOUT` entries.
/// The matching grant for the child sits at `match_index`; every other
/// grant has a distinct `tool_name` so the subset check has to look at
/// every entry before finding the match (or before short-circuiting on it).
/// Build a child scope with the single grant that is supposed to match
/// `tool_match` in the parent.
/// Dudect harness for `NormalizedScope::is_subset_of`.
///
/// Class definitions:
///
/// - `Class::Left`: parent has the matching grant at index 0. The
/// `parent.grants.iter().any(...)` short-circuits on the first iteration.
/// - `Class::Right`: parent has the matching grant at index
/// `PARENT_FANOUT - 1`. The `any(...)` runs through every entry.
///
/// Both classes resolve to `true`. We pre-build the inputs so the
/// per-iteration work measured by `run_one` only contains the
/// `is_subset_of` call.
ctbench_main!;