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}