Skip to main content

capsec_core/
has.rs

1//! The [`Has<P>`] trait — proof that a capability token includes permission `P`.
2//!
3//! This is the trait you use in function signatures to declare capability requirements:
4//!
5//! ```rust,ignore
6//! fn read_config(cap: &impl Has<FsRead>) -> String { ... }
7//! ```
8//!
9//! # Multiple capabilities
10//!
11//! Bundle permissions in a single token with a tuple:
12//!
13//! ```rust,ignore
14//! fn sync_data(cap: &(impl Has<FsRead> + Has<NetConnect>)) { ... }
15//!
16//! let root = test_root();
17//! let cap = root.grant::<(FsRead, NetConnect)>();
18//! sync_data(&cap);
19//! ```
20//!
21//! Or use separate parameters:
22//!
23//! ```rust,ignore
24//! fn sync_data(fs: &impl Has<FsRead>, net: &impl Has<NetConnect>) { ... }
25//! ```
26//!
27//! Or use a subsumption type like [`FsAll`] or
28//! [`Ambient`] that satisfies multiple bounds.
29//!
30//! # Subsumption
31//!
32//! `Cap<FsAll>` satisfies `Has<FsRead>` and `Has<FsWrite>` because `FsAll`
33//! subsumes both. `Cap<Ambient>` satisfies `Has<P>` for every permission.
34
35use crate::cap::{Cap, SendCap};
36use crate::permission::*;
37
38/// Proof that a capability token includes permission `P`.
39///
40/// This trait is open for implementation — custom context structs can implement
41/// `Has<P>` to delegate capability access. Security is maintained because
42/// `Cap::new()` is `pub(crate)`: no external code can forge a `Cap<P>` in safe Rust.
43///
44/// Use [`CapRoot::grant()`](crate::root::CapRoot::grant) to obtain capability tokens,
45/// or implement `Has<P>` on your own structs using the `#[capsec::context]` macro.
46///
47/// # Example
48///
49/// ```rust,ignore
50/// # use capsec_core::root::test_root;
51/// # use capsec_core::permission::FsRead;
52/// # use capsec_core::has::Has;
53/// fn needs_fs(cap: &impl Has<FsRead>) {
54///     let _ = cap.cap_ref(); // proof of permission
55/// }
56///
57/// let root = test_root();
58/// let cap = root.grant::<FsRead>();
59/// needs_fs(&cap);
60/// ```
61pub trait Has<P: Permission> {
62    /// Returns a new `Cap<P>` proving the permission is available.
63    fn cap_ref(&self) -> Cap<P>;
64}
65
66//  Direct: Cap<P> implements Has<P>
67
68impl<P: Permission> Has<P> for Cap<P> {
69    fn cap_ref(&self) -> Cap<P> {
70        Cap::new()
71    }
72}
73
74//  SendCap<P> delegates to Has<P>
75
76impl<P: Permission> Has<P> for SendCap<P> {
77    fn cap_ref(&self) -> Cap<P> {
78        self.as_cap()
79    }
80}
81
82//  Subsumption: FsAll, NetAll
83
84macro_rules! impl_subsumes {
85    ($super:ty => $($sub:ty),+) => {
86        $(
87            impl Has<$sub> for Cap<$super> {
88                fn cap_ref(&self) -> Cap<$sub> { Cap::new() }
89            }
90        )+
91    }
92}
93
94impl_subsumes!(FsAll => FsRead, FsWrite);
95impl_subsumes!(NetAll => NetConnect, NetBind);
96
97//  Ambient: satisfies everything
98//
99// Cannot use a blanket `impl<P> Has<P> for Cap<Ambient> where Ambient: Subsumes<P>`
100// because it conflicts with the direct `impl<P> Has<P> for Cap<P>` when P = Ambient.
101// Enumerated via macro to stay in sync with the permission set.
102
103macro_rules! impl_ambient {
104    ($($perm:ty),+) => {
105        $(
106            impl Has<$perm> for Cap<Ambient> {
107                fn cap_ref(&self) -> Cap<$perm> { Cap::new() }
108            }
109        )+
110    }
111}
112
113// If you add a permission here, also add it to the
114// `ambient_covers_all_permissions` test at the bottom of this file.
115impl_ambient!(
116    FsRead, FsWrite, FsAll, NetConnect, NetBind, NetAll, EnvRead, EnvWrite, Spawn
117);
118
119//  Tuples: Cap<(A, B)> satisfies Has<A> and Has<B>
120//
121// Because Rust's coherence rules reject overlapping generic impls when A == B,
122// we enumerate all concrete permission pairs via two macros.
123//
124// Macro 1: Has<first_element> for all (A, B) pairs including self-pairs (100 impls).
125// Macro 2: Has<second_element> for distinct pairs only (90 impls).
126//
127// Total: 190 unique impls. No conflicts. No changes to the Has<P> trait signature.
128
129macro_rules! impl_tuple_has_first {
130    ([$($a:ident),+]; $all:tt) => {
131        $( impl_tuple_has_first!(@inner $a; $all); )+
132    };
133    (@inner $a:ident; [$($b:ident),+]) => {
134        $(
135            impl Has<$a> for Cap<($a, $b)> {
136                fn cap_ref(&self) -> Cap<$a> { Cap::new() }
137            }
138        )+
139    };
140}
141
142macro_rules! impl_tuple_has_second {
143    ($first:ident $(, $rest:ident)+) => {
144        $(
145            impl Has<$first> for Cap<($rest, $first)> {
146                fn cap_ref(&self) -> Cap<$first> { Cap::new() }
147            }
148            impl Has<$rest> for Cap<($first, $rest)> {
149                fn cap_ref(&self) -> Cap<$rest> { Cap::new() }
150            }
151        )+
152        impl_tuple_has_second!($($rest),+);
153    };
154    ($single:ident) => {};
155}
156
157// NOTE: Scaling cliff — these macros enumerate all concrete permission pairs.
158// Current: 10 types → 190 impls (100 first-element + 90 second-element).
159// Adding 3 more types → ~319 impls. 3-tuples are not supported at all.
160// This is a Rust coherence limitation (no generic impl without specialization).
161// Workaround: use #[capsec::context] structs instead of tuples for >2 permissions.
162// If the permission count grows significantly, consider generating impls via build script.
163impl_tuple_has_first!(
164    [FsRead, FsWrite, FsAll, NetConnect, NetBind, NetAll, EnvRead, EnvWrite, Spawn, Ambient];
165    [FsRead, FsWrite, FsAll, NetConnect, NetBind, NetAll, EnvRead, EnvWrite, Spawn, Ambient]
166);
167
168impl_tuple_has_second!(
169    FsRead, FsWrite, FsAll, NetConnect, NetBind, NetAll, EnvRead, EnvWrite, Spawn, Ambient
170);
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use crate::root::test_root;
176
177    #[test]
178    fn direct_cap_satisfies_has() {
179        let root = test_root();
180        let cap = root.grant::<FsRead>();
181        fn needs_fs(_: &impl Has<FsRead>) {}
182        needs_fs(&cap);
183    }
184
185    #[test]
186    fn fs_all_subsumes_read_and_write() {
187        let root = test_root();
188        let cap = root.grant::<FsAll>();
189        fn needs_read(_: &impl Has<FsRead>) {}
190        fn needs_write(_: &impl Has<FsWrite>) {}
191        needs_read(&cap);
192        needs_write(&cap);
193    }
194
195    #[test]
196    fn net_all_subsumes_connect_and_bind() {
197        let root = test_root();
198        let cap = root.grant::<NetAll>();
199        fn needs_connect(_: &impl Has<NetConnect>) {}
200        fn needs_bind(_: &impl Has<NetBind>) {}
201        needs_connect(&cap);
202        needs_bind(&cap);
203    }
204
205    #[test]
206    fn ambient_satisfies_anything() {
207        let root = test_root();
208        let cap = root.grant::<Ambient>();
209        fn needs_fs(_: &impl Has<FsRead>) {}
210        fn needs_net(_: &impl Has<NetConnect>) {}
211        fn needs_spawn(_: &impl Has<Spawn>) {}
212        needs_fs(&cap);
213        needs_net(&cap);
214        needs_spawn(&cap);
215    }
216
217    #[test]
218    fn multiple_cap_params() {
219        fn sync_data(_fs: &impl Has<FsRead>, _net: &impl Has<NetConnect>) {}
220        let root = test_root();
221        let fs = root.grant::<FsRead>();
222        let net = root.grant::<NetConnect>();
223        sync_data(&fs, &net);
224    }
225
226    #[test]
227    fn tuple_cap_satisfies_both_has() {
228        let root = test_root();
229        let cap = root.grant::<(FsRead, NetConnect)>();
230        fn needs_fs(_: &impl Has<FsRead>) {}
231        fn needs_net(_: &impl Has<NetConnect>) {}
232        needs_fs(&cap);
233        needs_net(&cap);
234    }
235
236    #[test]
237    fn tuple_self_pair() {
238        let root = test_root();
239        let cap = root.grant::<(FsRead, FsRead)>();
240        fn needs_fs(_: &impl Has<FsRead>) {}
241        needs_fs(&cap);
242    }
243
244    #[test]
245    fn tuple_with_subsumption_type() {
246        let root = test_root();
247        let cap = root.grant::<(FsAll, NetConnect)>();
248        fn needs_fs_all(_: &impl Has<FsAll>) {}
249        fn needs_net(_: &impl Has<NetConnect>) {}
250        needs_fs_all(&cap);
251        needs_net(&cap);
252    }
253
254    #[test]
255    fn tuple_cap_ref_returns_correct_type() {
256        let root = test_root();
257        let cap = root.grant::<(FsRead, NetConnect)>();
258        let _fs: Cap<FsRead> = Has::<FsRead>::cap_ref(&cap);
259        let _net: Cap<NetConnect> = Has::<NetConnect>::cap_ref(&cap);
260    }
261
262    #[test]
263    fn tuple_is_zst() {
264        use std::mem::size_of;
265        assert_eq!(size_of::<Cap<(FsRead, NetConnect)>>(), 0);
266    }
267
268    /// Compile-time proof that Cap<Ambient> satisfies Has<P> for every permission.
269    /// If a new permission is added to permission.rs but not to impl_ambient!,
270    /// this test fails to compile.
271    #[test]
272    fn ambient_covers_all_permissions() {
273        fn assert_ambient_has<P: Permission>()
274        where
275            Cap<Ambient>: Has<P>,
276        {
277        }
278
279        assert_ambient_has::<FsRead>();
280        assert_ambient_has::<FsWrite>();
281        assert_ambient_has::<FsAll>();
282        assert_ambient_has::<NetConnect>();
283        assert_ambient_has::<NetBind>();
284        assert_ambient_has::<NetAll>();
285        assert_ambient_has::<EnvRead>();
286        assert_ambient_has::<EnvWrite>();
287        assert_ambient_has::<Spawn>();
288        // Ambient itself is covered by the direct impl<P> Has<P> for Cap<P>
289    }
290}