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
157impl_tuple_has_first!(
158    [FsRead, FsWrite, FsAll, NetConnect, NetBind, NetAll, EnvRead, EnvWrite, Spawn, Ambient];
159    [FsRead, FsWrite, FsAll, NetConnect, NetBind, NetAll, EnvRead, EnvWrite, Spawn, Ambient]
160);
161
162impl_tuple_has_second!(
163    FsRead, FsWrite, FsAll, NetConnect, NetBind, NetAll, EnvRead, EnvWrite, Spawn, Ambient
164);
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::root::test_root;
170
171    #[test]
172    fn direct_cap_satisfies_has() {
173        let root = test_root();
174        let cap = root.grant::<FsRead>();
175        fn needs_fs(_: &impl Has<FsRead>) {}
176        needs_fs(&cap);
177    }
178
179    #[test]
180    fn fs_all_subsumes_read_and_write() {
181        let root = test_root();
182        let cap = root.grant::<FsAll>();
183        fn needs_read(_: &impl Has<FsRead>) {}
184        fn needs_write(_: &impl Has<FsWrite>) {}
185        needs_read(&cap);
186        needs_write(&cap);
187    }
188
189    #[test]
190    fn net_all_subsumes_connect_and_bind() {
191        let root = test_root();
192        let cap = root.grant::<NetAll>();
193        fn needs_connect(_: &impl Has<NetConnect>) {}
194        fn needs_bind(_: &impl Has<NetBind>) {}
195        needs_connect(&cap);
196        needs_bind(&cap);
197    }
198
199    #[test]
200    fn ambient_satisfies_anything() {
201        let root = test_root();
202        let cap = root.grant::<Ambient>();
203        fn needs_fs(_: &impl Has<FsRead>) {}
204        fn needs_net(_: &impl Has<NetConnect>) {}
205        fn needs_spawn(_: &impl Has<Spawn>) {}
206        needs_fs(&cap);
207        needs_net(&cap);
208        needs_spawn(&cap);
209    }
210
211    #[test]
212    fn multiple_cap_params() {
213        fn sync_data(_fs: &impl Has<FsRead>, _net: &impl Has<NetConnect>) {}
214        let root = test_root();
215        let fs = root.grant::<FsRead>();
216        let net = root.grant::<NetConnect>();
217        sync_data(&fs, &net);
218    }
219
220    #[test]
221    fn tuple_cap_satisfies_both_has() {
222        let root = test_root();
223        let cap = root.grant::<(FsRead, NetConnect)>();
224        fn needs_fs(_: &impl Has<FsRead>) {}
225        fn needs_net(_: &impl Has<NetConnect>) {}
226        needs_fs(&cap);
227        needs_net(&cap);
228    }
229
230    #[test]
231    fn tuple_self_pair() {
232        let root = test_root();
233        let cap = root.grant::<(FsRead, FsRead)>();
234        fn needs_fs(_: &impl Has<FsRead>) {}
235        needs_fs(&cap);
236    }
237
238    #[test]
239    fn tuple_with_subsumption_type() {
240        let root = test_root();
241        let cap = root.grant::<(FsAll, NetConnect)>();
242        fn needs_fs_all(_: &impl Has<FsAll>) {}
243        fn needs_net(_: &impl Has<NetConnect>) {}
244        needs_fs_all(&cap);
245        needs_net(&cap);
246    }
247
248    #[test]
249    fn tuple_cap_ref_returns_correct_type() {
250        let root = test_root();
251        let cap = root.grant::<(FsRead, NetConnect)>();
252        let _fs: Cap<FsRead> = Has::<FsRead>::cap_ref(&cap);
253        let _net: Cap<NetConnect> = Has::<NetConnect>::cap_ref(&cap);
254    }
255
256    #[test]
257    fn tuple_is_zst() {
258        use std::mem::size_of;
259        assert_eq!(size_of::<Cap<(FsRead, NetConnect)>>(), 0);
260    }
261
262    /// Compile-time proof that Cap<Ambient> satisfies Has<P> for every permission.
263    /// If a new permission is added to permission.rs but not to impl_ambient!,
264    /// this test fails to compile.
265    #[test]
266    fn ambient_covers_all_permissions() {
267        fn assert_ambient_has<P: Permission>()
268        where
269            Cap<Ambient>: Has<P>,
270        {
271        }
272
273        assert_ambient_has::<FsRead>();
274        assert_ambient_has::<FsWrite>();
275        assert_ambient_has::<FsAll>();
276        assert_ambient_has::<NetConnect>();
277        assert_ambient_has::<NetBind>();
278        assert_ambient_has::<NetAll>();
279        assert_ambient_has::<EnvRead>();
280        assert_ambient_has::<EnvWrite>();
281        assert_ambient_has::<Spawn>();
282        // Ambient itself is covered by the direct impl<P> Has<P> for Cap<P>
283    }
284}