monitor/mon/
compartment.rs

1use std::{collections::HashMap, ffi::CStr};
2
3use dynlink::{
4    compartment::{Compartment, CompartmentId},
5    context::{Context, LoadedOrUnloaded, NewCompartmentFlags},
6    library::UnloadedLibrary,
7};
8use happylock::ThreadKey;
9use monitor_api::MONITOR_INSTANCE_ID;
10use secgate::util::Descriptor;
11use twizzler_abi::syscall::{sys_thread_sync, ThreadSync, ThreadSyncSleep};
12use twizzler_rt_abi::{
13    error::{ArgumentError, GenericError, NamingError, ResourceError, TwzError},
14    object::ObjID,
15};
16
17use crate::gates::{CompartmentInfo, CompartmentMgrStats, ThreadInfo};
18
19mod compconfig;
20mod compthread;
21mod loader;
22mod runcomp;
23
24pub use compconfig::*;
25pub(crate) use compthread::StackObject;
26pub use runcomp::*;
27
28/// Manages compartments.
29#[derive(Default)]
30pub struct CompartmentMgr {
31    names: HashMap<String, ObjID>,
32    instances: HashMap<ObjID, RunComp>,
33    dynlink_map: HashMap<CompartmentId, ObjID>,
34    cleanup_queue: Vec<RunComp>,
35}
36
37impl CompartmentMgr {
38    /// Get a [RunComp] by instance ID.
39    pub fn get(&self, id: ObjID) -> Result<&RunComp, TwzError> {
40        self.instances.get(&id).ok_or(TwzError::INVALID_ARGUMENT)
41    }
42
43    /// Get a [RunComp] by name.
44    pub fn _get_name(&self, name: &str) -> Result<&RunComp, TwzError> {
45        let id = self.names.get(name).ok_or(TwzError::INVALID_ARGUMENT)?;
46        self.get(*id)
47    }
48
49    /// Get a [RunComp] by instance ID.
50    pub fn get_mut(&mut self, id: ObjID) -> Result<&mut RunComp, TwzError> {
51        self.instances
52            .get_mut(&id)
53            .ok_or(TwzError::INVALID_ARGUMENT)
54    }
55
56    /// Get a [RunComp] by name.
57    pub fn get_name_mut(&mut self, name: &str) -> Result<&mut RunComp, TwzError> {
58        let id = self.names.get(name).ok_or(TwzError::INVALID_ARGUMENT)?;
59        self.get_mut(*id)
60    }
61
62    /// Get a [RunComp] by dynamic linker ID.
63    pub fn get_dynlinkid(&self, id: CompartmentId) -> Result<&RunComp, TwzError> {
64        let id = self
65            .dynlink_map
66            .get(&id)
67            .ok_or(TwzError::INVALID_ARGUMENT)?;
68        self.get(*id)
69    }
70
71    /// Get a [RunComp] by dynamic linker ID.
72    pub fn _get_dynlinkid_mut(&mut self, id: CompartmentId) -> Result<&mut RunComp, TwzError> {
73        let id = self
74            .dynlink_map
75            .get(&id)
76            .ok_or(TwzError::INVALID_ARGUMENT)?;
77        self.get_mut(*id)
78    }
79
80    /// Insert a [RunComp].
81    pub fn insert(&mut self, mut rc: RunComp) {
82        if self.names.contains_key(&rc.name) {
83            // TODO
84            rc.name = format!("{}-dup", rc.name);
85            return self.insert(rc);
86        }
87        self.names.insert(rc.name.clone(), rc.instance);
88        self.dynlink_map.insert(rc.compartment_id, rc.instance);
89        self.instances.insert(rc.instance, rc);
90    }
91
92    /// Remove a [RunComp].
93    pub fn remove(&mut self, id: ObjID) -> Option<RunComp> {
94        let rc = self.instances.remove(&id)?;
95        self.names.remove(&rc.name);
96        self.dynlink_map.remove(&rc.compartment_id);
97        Some(rc)
98    }
99
100    /// Get the [RunComp] for the monitor.
101    pub fn _get_monitor(&self) -> &RunComp {
102        // Unwrap-Ok: this instance is always present.
103        self.get(MONITOR_INSTANCE_ID).unwrap()
104    }
105
106    /// Get the [RunComp] for the monitor.
107    pub fn _get_monitor_mut(&mut self) -> &mut RunComp {
108        // Unwrap-Ok: this instance is always present.
109        self.get_mut(MONITOR_INSTANCE_ID).unwrap()
110    }
111
112    /// Get an iterator over all compartments.
113    pub fn _compartments(&self) -> impl Iterator<Item = &RunComp> {
114        self.instances.values()
115    }
116
117    /// Get an iterator over all compartments (mutable).
118    pub fn compartments_mut(&mut self) -> impl Iterator<Item = &mut RunComp> {
119        self.instances.values_mut()
120    }
121
122    fn update_compartment_flags(
123        &mut self,
124        instance: ObjID,
125        f: impl FnOnce(u64) -> Option<u64>,
126    ) -> bool {
127        let Ok(rc) = self.get_mut(instance) else {
128            return false;
129        };
130
131        let flags = rc.raw_flags();
132        let Some(new_flags) = f(flags) else {
133            return false;
134        };
135        if flags == new_flags {
136            return true;
137        }
138
139        rc.cas_flag(flags, new_flags).is_ok()
140    }
141
142    fn load_compartment_flags(&self, instance: ObjID) -> u64 {
143        let Ok(rc) = self.get(instance) else {
144            return 0;
145        };
146        rc.raw_flags()
147    }
148
149    fn wait_for_compartment_state_change(
150        &self,
151        instance: ObjID,
152        state: u64,
153    ) -> Result<ThreadSyncSleep, TwzError> {
154        let rc = self.get(instance)?;
155        Ok(rc.until_change(state))
156    }
157
158    pub fn main_thread_exited(&mut self, instance: ObjID) {
159        tracing::debug!("main thread for compartment {} exited", instance);
160        while !self.update_compartment_flags(instance, |old| Some(old | COMP_EXITED)) {}
161
162        let Ok(rc) = self.get(instance) else {
163            tracing::warn!("failed to find compartment {} during exit", instance);
164            return;
165        };
166        for dep in rc.deps.clone() {
167            self.dec_use_count(dep);
168        }
169
170        let Ok(rc) = self.get_mut(instance) else {
171            tracing::warn!("failed to find compartment {} during exit", instance);
172            return;
173        };
174        tracing::trace!("runcomp usecount: {}", rc.use_count);
175        if rc.use_count == 0 {
176            if let Some(rc) = self.remove(instance) {
177                self.cleanup_queue.push(rc)
178            }
179        }
180    }
181
182    pub fn dec_use_count(&mut self, instance: ObjID) {
183        let Ok(rc) = self.get_mut(instance) else {
184            return;
185        };
186
187        let z = rc.dec_use_count();
188        let ex = rc.has_flag(COMP_EXITED);
189        if z && ex {
190            if let Some(rc) = self.remove(instance) {
191                self.cleanup_queue.push(rc)
192            }
193        }
194    }
195
196    pub fn stat(&self) -> CompartmentMgrStats {
197        CompartmentMgrStats {
198            nr_compartments: self.instances.len(),
199        }
200    }
201
202    pub fn process_cleanup_queue(
203        &mut self,
204        dynlink: &mut Context,
205    ) -> (Vec<Option<Compartment>>, Vec<Vec<LoadedOrUnloaded>>) {
206        let (comps, libs) = self
207            .cleanup_queue
208            .drain(..)
209            .map(|c| dynlink.unload_compartment(c.compartment_id))
210            .unzip();
211        (comps, libs)
212    }
213}
214
215impl super::Monitor {
216    /// Get CompartmentInfo for this caller. Note that this will write to the compartment-thread's
217    /// simple buffer.
218    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
219    pub fn get_compartment_info(
220        &self,
221        instance: ObjID,
222        thread: ObjID,
223        desc: Option<Descriptor>,
224    ) -> Result<CompartmentInfo, TwzError> {
225        let (_, ref mut comps, ref dynlink, _, ref comphandles) =
226            *self.locks.lock(ThreadKey::get().unwrap());
227        let comp_id = desc
228            .map(|comp| comphandles.lookup(instance, comp).map(|ch| ch.instance))
229            .unwrap_or(Some(instance))
230            .ok_or(TwzError::INVALID_ARGUMENT)?;
231
232        let name = comps.get(comp_id)?.name.clone();
233        let pt = comps.get_mut(instance)?.get_per_thread(thread);
234        let name_len = pt.write_bytes(name.as_bytes());
235        let comp = comps.get(comp_id)?;
236        let nr_libs = dynlink
237            .get_compartment(comp.compartment_id)
238            .ok()
239            .ok_or(TwzError::INVALID_ARGUMENT)?
240            .library_ids()
241            .count();
242
243        Ok(CompartmentInfo {
244            name_len,
245            id: comp_id,
246            sctx: comp.sctx,
247            flags: comp.raw_flags(),
248            nr_libs,
249        })
250    }
251
252    /// Get CompartmentInfo for this caller. Note that this will write to the compartment-thread's
253    /// simple buffer.
254    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
255    pub fn get_compartment_gate_address(
256        &self,
257        instance: ObjID,
258        thread: ObjID,
259        desc: Option<Descriptor>,
260        name_len: usize,
261    ) -> Result<usize, TwzError> {
262        let name = self.read_thread_simple_buffer(instance, thread, name_len)?;
263        let (_, ref comps, ref dynlink, _, ref comphandles) =
264            *self.locks.lock(ThreadKey::get().unwrap());
265        let comp_id = desc
266            .map(|comp| comphandles.lookup(instance, comp).map(|ch| ch.instance))
267            .unwrap_or(Some(instance))
268            .ok_or(TwzError::INVALID_ARGUMENT)?;
269        let name = String::from_utf8(name)
270            .ok()
271            .ok_or(TwzError::INVALID_ARGUMENT)?;
272
273        let comp = comps.get(comp_id)?;
274        let dc = dynlink
275            .get_compartment(comp.compartment_id)
276            .ok()
277            .ok_or(TwzError::INVALID_ARGUMENT)?;
278        for lid in dc.library_ids() {
279            let lib = dynlink
280                .get_library(lid)
281                .map_err(|_| GenericError::Internal)?;
282            if let Some(gates) = lib.iter_secgates() {
283                for gate in gates {
284                    if gate.name().to_str().ok() == Some(name.as_str()) {
285                        return Ok(gate.imp);
286                    }
287                }
288            }
289        }
290        Err(NamingError::NotFound.into())
291    }
292
293    /// Open a compartment handle for this caller compartment.
294    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
295    pub fn get_compartment_handle(
296        &self,
297        caller: ObjID,
298        compartment: ObjID,
299    ) -> Result<Descriptor, TwzError> {
300        let (_, ref mut comps, _, _, ref mut ch) = *self.locks.lock(ThreadKey::get().unwrap());
301        let comp = comps.get_mut(compartment)?;
302        comp.inc_use_count();
303        ch.insert(
304            caller,
305            super::CompartmentHandle {
306                instance: if compartment.raw() == 0 {
307                    caller
308                } else {
309                    compartment
310                },
311            },
312        )
313        .ok_or(ResourceError::OutOfResources.into())
314    }
315
316    /// Open a compartment handle for this caller compartment.
317    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
318    pub fn lookup_compartment(
319        &self,
320        instance: ObjID,
321        thread: ObjID,
322        name_len: usize,
323    ) -> Result<Descriptor, TwzError> {
324        let name = self.read_thread_simple_buffer(instance, thread, name_len)?;
325        let name = String::from_utf8(name)
326            .ok()
327            .ok_or(TwzError::INVALID_ARGUMENT)?;
328        let (_, ref mut comps, _, _, ref mut ch) = *self.locks.lock(ThreadKey::get().unwrap());
329        let comp = comps.get_name_mut(&name)?;
330        comp.inc_use_count();
331        ch.insert(
332            instance,
333            super::CompartmentHandle {
334                instance: comp.instance,
335            },
336        )
337        .ok_or(ResourceError::OutOfResources.into())
338    }
339
340    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
341    pub fn compartment_wait(&self, caller: ObjID, desc: Option<Descriptor>, flags: u64) -> u64 {
342        let Some(instance) = ({
343            let comphandles = self._compartment_handles.write(ThreadKey::get().unwrap());
344            let comp_id = desc
345                .map(|comp| comphandles.lookup(caller, comp).map(|ch| ch.instance))
346                .unwrap_or(Some(caller));
347            comp_id
348        }) else {
349            return 0;
350        };
351        self.wait_for_compartment_state_change(instance, flags);
352        self.load_compartment_flags(instance)
353    }
354
355    /// Open a handle to the n'th dependency compartment of a given compartment.
356    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
357    pub fn get_compartment_deps(
358        &self,
359        caller: ObjID,
360        desc: Option<Descriptor>,
361        dep_n: usize,
362    ) -> Result<Descriptor, TwzError> {
363        let dep = {
364            let (_, ref mut comps, _, _, ref mut comphandles) =
365                *self.locks.lock(ThreadKey::get().unwrap());
366            let comp_id = desc
367                .map(|comp| comphandles.lookup(caller, comp).map(|ch| ch.instance))
368                .unwrap_or(Some(caller))
369                .ok_or(ArgumentError::InvalidArgument)?;
370            let comp = comps.get_mut(comp_id)?;
371            comp.deps.get(dep_n).cloned()
372        }
373        .ok_or(TwzError::INVALID_ARGUMENT)?;
374        self.get_compartment_handle(caller, dep)
375    }
376
377    /// Get the n'th thread's info from a compartment.
378    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
379    pub fn get_compartment_thread_info(
380        &self,
381        caller: ObjID,
382        desc: Option<Descriptor>,
383        t_n: usize,
384    ) -> Result<ThreadInfo, TwzError> {
385        let dep = {
386            let (_, ref mut comps, _, _, ref mut comphandles) =
387                *self.locks.lock(ThreadKey::get().unwrap());
388            let comp_id = desc
389                .map(|comp| comphandles.lookup(caller, comp).map(|ch| ch.instance))
390                .unwrap_or(Some(caller))
391                .ok_or(ArgumentError::InvalidArgument)?;
392            let comp = comps.get_mut(comp_id)?;
393            comp.get_nth_thread_info(t_n)
394        }
395        .ok_or(TwzError::INVALID_ARGUMENT);
396        dep
397    }
398
399    /// Load a new compartment with a root library ID, and return a compartment handle.
400    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
401    pub fn load_compartment(
402        &self,
403        caller: ObjID,
404        thread: ObjID,
405        name_len: usize,
406        args_len: usize,
407        env_len: usize,
408        new_comp_flags: NewCompartmentFlags,
409    ) -> Result<Descriptor, TwzError> {
410        let total_bytes = name_len + args_len + env_len;
411        let str_bytes = self.read_thread_simple_buffer(caller, thread, total_bytes)?;
412        let name_bytes = &str_bytes[0..name_len];
413        let arg_bytes = &str_bytes[name_len..(name_len + args_len)];
414        let env_bytes = &str_bytes[(name_len + args_len)..total_bytes];
415
416        let input = String::from_utf8_lossy(&name_bytes);
417        let mut split = input.split("::");
418        let compname = split.next().ok_or(TwzError::INVALID_ARGUMENT)?;
419        let libname = split.next().ok_or(TwzError::INVALID_ARGUMENT)?;
420        let root = UnloadedLibrary::new(libname);
421
422        // parse args
423        let args_bytes = arg_bytes.split_inclusive(|b| *b == 0);
424        let args = args_bytes
425            .map(CStr::from_bytes_with_nul)
426            .try_collect::<Vec<_>>()
427            .map_err(|_| TwzError::INVALID_ARGUMENT)?;
428        tracing::debug!("load {}: args: {:?}", compname, args);
429
430        // parse env
431        let envs_bytes = env_bytes.split_inclusive(|b| *b == 0);
432        let env = envs_bytes
433            .map(CStr::from_bytes_with_nul)
434            .try_collect::<Vec<_>>()
435            .map_err(|_| TwzError::INVALID_ARGUMENT)?;
436        tracing::trace!("load {}: env: {:?}", compname, env);
437
438        let extras = env
439            .iter()
440            .filter_map(|item| {
441                let item = item.to_str().ok()?;
442                if item.starts_with("LD_PRELOAD=") {
443                    Some(UnloadedLibrary::new(item.trim_start_matches("LD_PRELOAD=")))
444                } else {
445                    None
446                }
447            })
448            .collect::<Vec<_>>();
449        tracing::debug!("ld preload extras: {:?}", extras);
450
451        let mondebug = env
452            .iter()
453            .find(|s| s.to_string_lossy().starts_with("MONDEBUG="))
454            .is_some();
455        let loader = {
456            let mut dynlink = self.dynlink.write(ThreadKey::get().unwrap());
457            loader::RunCompLoader::new(*dynlink, compname, root, &extras, new_comp_flags, mondebug)
458        }
459        .map_err(|_| GenericError::Internal)?;
460
461        let root_comp = {
462            let (_, ref mut cmp, ref mut dynlink, _, _) =
463                &mut *self.locks.lock(ThreadKey::get().unwrap());
464            // TODO: dynlink err map
465            loader
466                .build_rcs(
467                    &mut *cmp,
468                    &mut *dynlink,
469                    mondebug,
470                    new_comp_flags.contains(NewCompartmentFlags::DEBUG),
471                )
472                .map_err(|_| GenericError::Internal)?
473        };
474        tracing::trace!("loaded {} as {}", compname, root_comp);
475
476        let desc = self.get_compartment_handle(caller, root_comp)?;
477
478        self.start_compartment(
479            root_comp,
480            &args,
481            &env,
482            mondebug,
483            new_comp_flags.contains(NewCompartmentFlags::DEBUG),
484        )?;
485
486        Ok(desc)
487    }
488
489    /// Drop a compartment handle.
490    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
491    pub fn drop_compartment_handle(&self, caller: ObjID, desc: Descriptor) {
492        let comps = {
493            let (_, ref mut cmgr, ref mut dynlink, _, ref mut comp_handles) =
494                *self.locks.lock(ThreadKey::get().unwrap());
495            let comp = comp_handles.remove(caller, desc);
496
497            if let Some(comp) = comp {
498                cmgr.dec_use_count(comp.instance);
499            }
500            cmgr.process_cleanup_queue(&mut *dynlink)
501        };
502        tracing::trace!("HRE");
503        drop(comps);
504    }
505
506    #[tracing::instrument(skip(self, f), level = tracing::Level::DEBUG)]
507    pub fn update_compartment_flags(
508        &self,
509        instance: ObjID,
510        f: impl FnOnce(u64) -> Option<u64>,
511    ) -> bool {
512        let mut cmp = self.comp_mgr.write(ThreadKey::get().unwrap());
513        cmp.update_compartment_flags(instance, f)
514    }
515
516    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
517    pub fn load_compartment_flags(&self, instance: ObjID) -> u64 {
518        let cmp = self.comp_mgr.write(ThreadKey::get().unwrap());
519        cmp.load_compartment_flags(instance)
520    }
521
522    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
523    pub fn wait_for_compartment_state_change(&self, instance: ObjID, state: u64) {
524        let sl = {
525            let cmp = self.comp_mgr.write(ThreadKey::get().unwrap());
526            let Ok(sl) = cmp.wait_for_compartment_state_change(instance, state) else {
527                return;
528            };
529
530            if sl.ready() {
531                return;
532            }
533            drop(cmp);
534            sl
535        };
536
537        let _ = sys_thread_sync(&mut [ThreadSync::new_sleep(sl)], None);
538    }
539}
540
541/// A handle to a compartment.
542pub struct CompartmentHandle {
543    pub instance: ObjID,
544}