monitor/mon/
compartment.rs

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