Skip to main content

sandlock_core/seccomp/
state.rs

1// Domain-specific state structs — each domain is locked independently so
2// handlers only contend on the state they actually need.
3
4use std::collections::{HashMap, HashSet};
5
6/// Resource-limit runtime state shared across notification handlers.
7pub struct ResourceState {
8    /// Live concurrent process count — incremented on fork, decremented on wait.
9    pub proc_count: u32,
10    /// Maximum allowed concurrent processes.
11    pub max_processes: u32,
12    /// Estimated anonymous memory usage (bytes).
13    pub mem_used: u64,
14    /// Maximum allowed anonymous memory (bytes).
15    pub max_memory_bytes: u64,
16    /// Per-PID brk base addresses for memory tracking.
17    pub brk_bases: HashMap<i32, u64>,
18    /// Whether fork notifications should be held (checkpoint/freeze).
19    pub hold_forks: bool,
20    /// Notification IDs held during a checkpoint freeze.
21    pub held_notif_ids: Vec<u64>,
22    /// Exponentially-weighted load average.
23    pub load_avg: crate::procfs::LoadAvg,
24    /// Instant when the supervisor started (for uptime reporting).
25    pub start_instant: std::time::Instant,
26}
27
28impl ResourceState {
29    /// Create a new resource state with the given limits.
30    pub fn new(max_memory_bytes: u64, max_processes: u32) -> Self {
31        Self {
32            proc_count: 0,
33            max_processes,
34            mem_used: 0,
35            max_memory_bytes,
36            brk_bases: HashMap::new(),
37            hold_forks: false,
38            held_notif_ids: Vec::new(),
39            load_avg: crate::procfs::LoadAvg::new(),
40            start_instant: std::time::Instant::now(),
41        }
42    }
43}
44
45// ============================================================
46// ProcfsState — /proc virtualization state
47// ============================================================
48
49/// /proc virtualization runtime state.
50pub struct ProcfsState {
51    /// PIDs belonging to the sandbox (for /proc PID filtering).
52    pub proc_pids: HashSet<i32>,
53    /// Cache of filtered dirent entries keyed by (pid, fd).
54    /// Populated on first getdents64 call for a /proc directory, drained on subsequent calls.
55    pub getdents_cache: HashMap<(i32, u32), Vec<Vec<u8>>>,
56    /// Base address of the last vDSO we patched (0 = not yet patched).
57    pub vdso_patched_addr: u64,
58}
59
60impl ProcfsState {
61    pub fn new() -> Self {
62        Self {
63            proc_pids: HashSet::new(),
64            getdents_cache: HashMap::new(),
65            vdso_patched_addr: 0,
66        }
67    }
68}
69
70// ============================================================
71// CowState — copy-on-write filesystem state
72// ============================================================
73
74/// Copy-on-write filesystem state.
75pub struct CowState {
76    /// Seccomp-based COW branch (None if COW disabled).
77    pub branch: Option<crate::cow::seccomp::SeccompCowBranch>,
78    /// Getdents cache for COW directories.
79    /// Value is (host_path, entries) to detect fd reuse and invalidate stale entries.
80    pub dir_cache: HashMap<(i32, u32), (String, Vec<Vec<u8>>)>,
81}
82
83impl CowState {
84    pub fn new() -> Self {
85        Self {
86            branch: None,
87            dir_cache: HashMap::new(),
88        }
89    }
90}
91
92// ============================================================
93// NetworkState — network policy and port remapping state
94// ============================================================
95
96/// Network policy and port-remapping state.
97pub struct NetworkState {
98    /// Global network policy: unrestricted or limited to a set of IPs.
99    pub network_policy: crate::seccomp::notif::NetworkPolicy,
100    /// Port binding and remapping tracker.
101    pub port_map: crate::port_remap::PortMap,
102    /// Per-PID network overrides from policy_fn.
103    pub pid_ip_overrides: std::sync::Arc<std::sync::RwLock<HashMap<u32, HashSet<std::net::IpAddr>>>>,
104    /// HTTP ACL proxy address (None if HTTP ACL not active).
105    pub http_acl_addr: Option<std::net::SocketAddr>,
106    /// TCP ports to intercept and redirect to the HTTP ACL proxy.
107    pub http_acl_ports: HashSet<u16>,
108    /// Shared map for recording original destination IPs on proxy redirect.
109    pub http_acl_orig_dest: Option<crate::http_acl::OrigDestMap>,
110}
111
112impl NetworkState {
113    pub fn new() -> Self {
114        Self {
115            network_policy: crate::seccomp::notif::NetworkPolicy::Unrestricted,
116            port_map: crate::port_remap::PortMap::new(),
117            pid_ip_overrides: std::sync::Arc::new(std::sync::RwLock::new(HashMap::new())),
118            http_acl_addr: None,
119            http_acl_ports: HashSet::new(),
120            http_acl_orig_dest: None,
121        }
122    }
123
124    /// Get the effective network policy for a PID.
125    ///
126    /// Priority: per-PID override > live policy (from PolicyFnState) > global network_policy.
127    /// The `live_policy` parameter allows checking the live policy without needing
128    /// to lock the PolicyFnState mutex.
129    pub fn effective_network_policy(
130        &self,
131        pid: u32,
132        live_policy: Option<&std::sync::Arc<std::sync::RwLock<crate::policy_fn::LivePolicy>>>,
133    ) -> crate::seccomp::notif::NetworkPolicy {
134        // Per-PID override takes priority
135        if let Ok(overrides) = self.pid_ip_overrides.read() {
136            if let Some(ips) = overrides.get(&pid) {
137                return crate::seccomp::notif::NetworkPolicy::AllowList(ips.clone());
138            }
139        }
140        // Live policy (dynamic updates from policy_fn)
141        if let Some(lp) = live_policy {
142            if let Ok(live) = lp.read() {
143                if !live.allowed_ips.is_empty() {
144                    return crate::seccomp::notif::NetworkPolicy::AllowList(live.allowed_ips.clone());
145                }
146            }
147        }
148        // Global policy
149        self.network_policy.clone()
150    }
151}
152
153// ============================================================
154// TimeRandomState — deterministic time/random state
155// ============================================================
156
157/// Time offset and deterministic random state.
158pub struct TimeRandomState {
159    /// Clock offset for time virtualization.
160    pub time_offset: Option<i64>,
161    /// Deterministic PRNG state (seeded from policy).
162    pub random_state: Option<rand_chacha::ChaCha8Rng>,
163}
164
165impl TimeRandomState {
166    pub fn new(time_offset: Option<i64>, random_state: Option<rand_chacha::ChaCha8Rng>) -> Self {
167        Self { time_offset, random_state }
168    }
169}
170
171// ============================================================
172// PolicyFnState — dynamic policy callback state
173// ============================================================
174
175/// Dynamic policy callback state.
176pub struct PolicyFnState {
177    /// Event sender for dynamic policy callback (None if no policy_fn).
178    pub event_tx: Option<tokio::sync::mpsc::UnboundedSender<crate::policy_fn::PolicyEvent>>,
179    /// Shared live policy for dynamic updates (None if no policy_fn).
180    pub live_policy: Option<std::sync::Arc<std::sync::RwLock<crate::policy_fn::LivePolicy>>>,
181    /// Dynamically denied paths from policy_fn.
182    pub denied_paths: std::sync::Arc<std::sync::RwLock<HashSet<String>>>,
183}
184
185impl PolicyFnState {
186    pub fn new() -> Self {
187        Self {
188            event_tx: None,
189            live_policy: None,
190            denied_paths: std::sync::Arc::new(std::sync::RwLock::new(HashSet::new())),
191        }
192    }
193
194    /// Check if a path is dynamically denied.
195    pub fn is_path_denied(&self, path: &str) -> bool {
196        if let Ok(denied) = self.denied_paths.read() {
197            let path = std::path::Path::new(path);
198            denied.iter().any(|d| path.starts_with(std::path::Path::new(d)))
199        } else {
200            false
201        }
202    }
203}
204
205// ============================================================
206// ChrootState — chroot-specific runtime state
207// ============================================================
208
209/// Chroot-specific runtime state.
210pub struct ChrootState {
211    /// Virtual exe path for chroot (set by handle_chroot_exec when memfd patching
212    /// rewrites PT_INTERP, since /proc/self/exe would otherwise show the memfd path).
213    pub chroot_exe: Option<std::path::PathBuf>,
214}
215
216impl ChrootState {
217    pub fn new() -> Self {
218        Self { chroot_exe: None }
219    }
220}