monitor/mon/
mod.rs

1use std::{
2    ptr::NonNull,
3    sync::{Mutex, OnceLock},
4};
5
6use compartment::{
7    StackObject, COMP_DESTRUCTED, COMP_EXITED, COMP_IS_BINARY, COMP_READY, COMP_STARTED,
8    COMP_THREAD_CAN_EXIT,
9};
10use dynlink::compartment::MONITOR_COMPARTMENT_ID;
11use happylock::{LockCollection, RwLock, ThreadKey};
12use monitor_api::{
13    CompartmentFlags, MonitorCompControlCmd, PostSignalFlags, RuntimeThreadControl,
14    SharedCompConfig, TlsTemplateInfo, MONITOR_INSTANCE_ID,
15};
16use secgate::util::HandleMgr;
17use space::Space;
18use talc::{ErrOnOom, Talc};
19use thread::DEFAULT_STACK_SIZE;
20use twizzler_abi::{
21    syscall::{sys_thread_exit, sys_thread_send_message},
22    upcall::{ResumeFlags, UpcallData, UpcallFrame},
23};
24use twizzler_rt_abi::{
25    error::{GenericError, TwzError},
26    object::{MapFlags, ObjID},
27    thread::ThreadSpawnArgs,
28};
29
30use self::{
31    compartment::{CompConfigObject, CompartmentHandle, RunComp},
32    space::{MapHandle, MapInfo, Unmapper},
33    thread::{ManagedThread, ThreadCleaner},
34};
35use crate::init::InitDynlinkContext;
36
37pub(crate) mod compartment;
38pub mod library;
39pub(crate) mod space;
40pub mod stat;
41pub(crate) mod thread;
42
43/// A security monitor instance. All monitor logic is implemented as methods for this type.
44/// We split the state into the following components: 'space', managing the virtual memory space and
45/// mapping objects, 'thread_mgr', which manages all threads owned by the monitor (typically, all
46/// threads started by compartments), 'compartments', which manages compartment state, and
47/// 'dynlink', which contains the dynamic linker state. The unmapper allows for background unmapping
48/// and cleanup of objects and handles. There are also two hangle managers, for the monitor to hand
49/// out handles to libraries and compartments to callers.
50pub struct Monitor {
51    locks: LockCollection<MonitorLocks<'static>>,
52    unmapper: OnceLock<Unmapper>,
53    /// Management of address space.
54    pub space: &'static Mutex<space::Space>,
55    /// Management of all threads.
56    pub thread_mgr: &'static RwLock<thread::ThreadMgr>,
57    /// Management of compartments.
58    pub comp_mgr: &'static RwLock<compartment::CompartmentMgr>,
59    /// Dynamic linker state.
60    pub dynlink: &'static RwLock<&'static mut dynlink::context::Context>,
61    /// Open handles to libraries.
62    pub library_handles: &'static RwLock<HandleMgr<library::LibraryHandle>>,
63    /// Open handles to compartments.
64    pub _compartment_handles: &'static RwLock<HandleMgr<CompartmentHandle>>,
65}
66
67// We allow locking individually, using eg mon.space.write(key), or locking the collection for more
68// complex operations that touch multiple pieces of state.
69type MonitorLocks<'a> = (
70    &'a RwLock<thread::ThreadMgr>,
71    &'a RwLock<compartment::CompartmentMgr>,
72    &'a RwLock<&'static mut dynlink::context::Context>,
73    &'a RwLock<HandleMgr<library::LibraryHandle>>,
74    &'a RwLock<HandleMgr<CompartmentHandle>>,
75);
76
77impl Monitor {
78    /// Start the background threads for the monitor instance. Must be done only once the monitor
79    /// has been initialized.
80    pub fn start_background_threads(&self) {
81        let cleaner = ThreadCleaner::new();
82        self.unmapper.set(Unmapper::new()).ok().unwrap();
83        self.thread_mgr
84            .write(ThreadKey::get().unwrap())
85            .set_cleaner(cleaner);
86    }
87
88    /// Build a new monitor state from the initial dynamic linker context.
89    pub fn new(init: InitDynlinkContext) -> Self {
90        let mut comp_mgr = compartment::CompartmentMgr::default();
91        let space = Mutex::new(space::Space::default());
92
93        let ctx = init.get_safe_context();
94        // Build our TLS region, and create a template for the monitor compartment.
95        let super_tls = ctx
96            .get_compartment_mut(MONITOR_COMPARTMENT_ID)
97            .unwrap()
98            .build_tls_region(RuntimeThreadControl::default(), |layout| unsafe {
99                NonNull::new(std::alloc::alloc_zeroed(layout))
100            })
101            .unwrap();
102        let template: &'static TlsTemplateInfo = Box::leak(Box::new(super_tls.into()));
103
104        // Set up the monitor's compartment.
105        let monitor_scc = SharedCompConfig::new(
106            MONITOR_INSTANCE_ID,
107            template as *const _ as *mut _,
108            monitor_api::CompartmentLoaderConfig::default(),
109        );
110        let cc_handle = Space::safe_create_and_map_runtime_object(
111            &space,
112            MONITOR_INSTANCE_ID,
113            MapFlags::READ | MapFlags::WRITE,
114        )
115        .unwrap();
116        let stack_handle = Space::safe_create_and_map_runtime_object(
117            &space,
118            MONITOR_INSTANCE_ID,
119            MapFlags::READ | MapFlags::WRITE,
120        )
121        .unwrap();
122
123        let comp_config = CompConfigObject::new(cc_handle, monitor_scc);
124        let mut alloc = Talc::new(ErrOnOom);
125        unsafe { alloc.claim(comp_config.alloc_span()).unwrap() };
126        comp_mgr.insert(RunComp::new(
127            MONITOR_INSTANCE_ID,
128            MONITOR_INSTANCE_ID,
129            "monitor".to_string(),
130            MONITOR_COMPARTMENT_ID,
131            vec![],
132            comp_config,
133            (CompartmentFlags::READY | CompartmentFlags::STARTED).bits(),
134            StackObject::new(stack_handle, DEFAULT_STACK_SIZE).unwrap(),
135            0, /* doesn't matter -- we won't be starting a main thread for this compartment in
136                * the normal way */
137            0,
138            &[],
139            false,
140            None,
141            alloc,
142        ));
143
144        // Allocate and leak all the locks (they are global and eternal, so we can do this to safely
145        // and correctly get &'static lifetime)
146        let space = Box::leak(Box::new(space));
147        let thread_mgr = Box::leak(Box::new(RwLock::new(thread::ThreadMgr::default())));
148        let comp_mgr = Box::leak(Box::new(RwLock::new(comp_mgr)));
149        let dynlink = Box::leak(Box::new(RwLock::new(ctx)));
150        let library_handles = Box::leak(Box::new(RwLock::new(HandleMgr::new(None))));
151        let compartment_handles = Box::leak(Box::new(RwLock::new(HandleMgr::new(None))));
152
153        // Okay to call try_new here, since it's not many locks and only happens once.
154        Self {
155            locks: LockCollection::try_new((
156                &*thread_mgr,
157                &*comp_mgr,
158                &*dynlink,
159                &*library_handles,
160                &*compartment_handles,
161            ))
162            .unwrap(),
163            unmapper: OnceLock::new(),
164            space,
165            thread_mgr,
166            comp_mgr,
167            dynlink,
168            library_handles,
169            _compartment_handles: compartment_handles,
170        }
171    }
172
173    /// Start a managed monitor thread.
174    #[tracing::instrument(skip(self, main), level = tracing::Level::DEBUG)]
175    pub fn start_thread(
176        &self,
177        instance: ObjID,
178        main: Box<dyn FnOnce()>,
179    ) -> Result<ManagedThread, TwzError> {
180        let key = ThreadKey::get().unwrap();
181        let locks = &mut *self.locks.lock(key);
182
183        let monitor_dynlink_comp = locks.2.get_compartment_mut(MONITOR_COMPARTMENT_ID).unwrap();
184        locks
185            .0
186            .start_thread(monitor_dynlink_comp, main, None, instance)
187    }
188
189    /// Spawn a thread into a given compartment, using initial thread arguments.
190    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
191    pub fn spawn_compartment_thread(
192        &self,
193        instance: ObjID,
194        args: ThreadSpawnArgs,
195        stack_ptr: usize,
196        thread_ptr: usize,
197    ) -> Result<ObjID, TwzError> {
198        let thread = self.start_thread(
199            instance,
200            Box::new(move || {
201                if instance.raw() != 0 {
202                    let _ = twizzler_abi::syscall::sys_sctx_attach(instance);
203                }
204                let frame = UpcallFrame::new_entry_frame(
205                    stack_ptr,
206                    args.stack_size,
207                    thread_ptr,
208                    instance,
209                    args.start,
210                    args.arg,
211                );
212                unsafe {
213                    twizzler_abi::syscall::sys_thread_resume_from_upcall(
214                        &frame,
215                        ResumeFlags::empty(),
216                    )
217                };
218            }),
219        )?;
220        let mon = get_monitor();
221        let mut comps = mon.comp_mgr.write(ThreadKey::get().unwrap());
222        // This creates a per-thread structure in the compartment.
223        let _pt = comps.get_mut(instance)?.get_per_thread(thread.id);
224        Ok(thread.id)
225    }
226
227    /// Get the compartment config for the given compartment.
228    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
229    pub fn get_comp_config(&self, sctx: ObjID) -> Result<*const SharedCompConfig, TwzError> {
230        let comps = self.comp_mgr.write(ThreadKey::get().unwrap());
231        Ok(comps.get(sctx)?.comp_config_ptr())
232    }
233
234    /// Map an object into a given compartment.
235    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
236    pub fn map_object(&self, sctx: ObjID, info: MapInfo) -> Result<MapHandle, TwzError> {
237        let handle = Space::map(&self.space, info)?;
238
239        let mut comp_mgr = self.comp_mgr.write(ThreadKey::get().unwrap());
240        let rc = comp_mgr.get_mut(sctx)?;
241        let handle = rc.map_object(info, handle)?;
242        Ok(handle)
243    }
244
245    /// Map a pair of objects into a given compartment.
246    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
247    pub fn map_pair(
248        &self,
249        sctx: ObjID,
250        info: MapInfo,
251        info2: MapInfo,
252    ) -> Result<(MapHandle, MapHandle), TwzError> {
253        let (handle, handle2) = self.space.lock().unwrap().map_pair(info, info2)?;
254
255        let mut comp_mgr = self.comp_mgr.write(ThreadKey::get().unwrap());
256        let rc = comp_mgr.get_mut(sctx)?;
257        let handle = rc.map_object(info, handle)?;
258        let handle2 = rc.map_object(info2, handle2)?;
259        Ok((handle, handle2))
260    }
261
262    /// Unmap an object from a given compartmen.
263    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
264    pub fn unmap_object(&self, sctx: ObjID, info: MapInfo) {
265        let Some(key) = ThreadKey::get() else {
266            tracing::warn!("todo: recursive locked unmap");
267            return;
268        };
269
270        let mut comp_mgr = self.comp_mgr.write(key);
271        if let Ok(comp) = comp_mgr.get_mut(sctx) {
272            let handle = comp.unmap_object(info);
273            drop(comp_mgr);
274            drop(handle);
275        }
276    }
277
278    /// Get the object ID for this compartment-thread's simple buffer.
279    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
280    pub fn get_thread_simple_buffer(&self, sctx: ObjID, thread: ObjID) -> Result<ObjID, TwzError> {
281        let mut locks = self.locks.lock(ThreadKey::get().unwrap());
282        let (_, ref mut comps, _, _, _) = *locks;
283        let rc = comps.get_mut(sctx)?;
284        let pt = rc.get_per_thread(thread);
285        pt.simple_buffer_id().ok_or(GenericError::Internal.into())
286    }
287
288    /// Write bytes to this per-compartment thread's simple buffer.
289    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
290    pub fn _write_thread_simple_buffer(
291        &self,
292        sctx: ObjID,
293        thread: ObjID,
294        bytes: &[u8],
295    ) -> Result<usize, TwzError> {
296        let mut locks = self.locks.lock(ThreadKey::get().unwrap());
297        let (_, ref mut comps, _, _, _) = *locks;
298        let rc = comps.get_mut(sctx)?;
299        let pt = rc.get_per_thread(thread);
300        Ok(pt.write_bytes(bytes))
301    }
302
303    /// Read bytes from this per-compartment thread's simple buffer.
304    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
305    pub fn read_thread_simple_buffer(
306        &self,
307        sctx: ObjID,
308        thread: ObjID,
309        len: usize,
310    ) -> Result<Vec<u8>, TwzError> {
311        let t = ThreadKey::get().unwrap();
312        let mut locks = self.locks.lock(t);
313        let (_, ref mut comps, _, _, _) = *locks;
314        let rc = comps.get_mut(sctx)?;
315        let pt = rc.get_per_thread(thread);
316        Ok(pt.read_bytes(len))
317    }
318
319    /// Read the name of a compartment.
320    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
321    pub fn comp_name(&self, id: ObjID) -> Result<String, TwzError> {
322        self.comp_mgr
323            .read(ThreadKey::get().unwrap())
324            .get(id)
325            .map(|rc| rc.name.clone())
326    }
327
328    pub fn upcall_handle(
329        &self,
330        frame: &mut UpcallFrame,
331        info: &UpcallData,
332    ) -> Result<Option<ResumeFlags>, TwzError> {
333        self.comp_mgr
334            .write(ThreadKey::get().unwrap())
335            .get_mut(frame.prior_ctx)?
336            .upcall_handle(frame, info)
337    }
338
339    /// Perform a compartment control action on the calling compartment.
340    #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
341    pub fn compartment_ctrl(
342        &self,
343        info: &secgate::GateCallInfo,
344        cmd: MonitorCompControlCmd,
345    ) -> Option<i32> {
346        let src = info.source_context()?;
347        tracing::debug!(
348            "compartment ctrl from: {:?}, thread = {:?}: {:?}",
349            src,
350            info.thread_id(),
351            cmd
352        );
353        match cmd {
354            // Here, the thread has indicated that it has initialized the runtime (and run
355            // constructors), and so is ready to call main. At this point, we make sure
356            // no errors have occurred and that we should continue. Update flags to
357            // ready via compare-and-swap to ensure no one has set an error flag, and
358            // return. If this compartment is a binary, then return None so the runtime will call
359            // main. Otherwise return Some(SUCCESS) so that the runtime immediately
360            // calls the post-main hook.
361            MonitorCompControlCmd::RuntimeReady => loop {
362                let state = self.load_compartment_flags(src);
363                if state & COMP_STARTED == 0
364                    || state & COMP_DESTRUCTED != 0
365                    || state & COMP_EXITED != 0
366                {
367                    tracing::warn!(
368                        "runtime main thread {} encountered invalid compartment {} state: {}",
369                        info.thread_id(),
370                        src,
371                        state
372                    );
373                    sys_thread_exit(127);
374                }
375
376                if self.update_compartment_flags(src, |state| Some(state | COMP_READY)) {
377                    tracing::debug!(
378                        "runtime main thread reached compartment ready state in {}: {:x}",
379                        self.comp_name(src)
380                            .unwrap_or_else(|_| String::from("unknown")),
381                        state
382                    );
383                    break if state & COMP_IS_BINARY == 0 {
384                        Some(0)
385                    } else {
386                        None
387                    };
388                }
389            },
390            MonitorCompControlCmd::RuntimePostMain => {
391                // First we want to check if we are a binary, and if so, we don't have to wait
392                // around in here.
393                loop {
394                    if self.update_compartment_flags(src, |state| {
395                        // Binaries can exit immediately. All future cross-compartment calls fail.
396                        if state & COMP_IS_BINARY != 0 {
397                            Some(state | COMP_THREAD_CAN_EXIT)
398                        } else {
399                            Some(state)
400                        }
401                    }) {
402                        tracing::debug!(
403                            "runtime main thread reached compartment post-main state in {}",
404                            self.comp_name(src)
405                                .unwrap_or_else(|_| String::from("unknown"))
406                        );
407                        break;
408                    }
409                }
410                // Wait until we are allowed to exit (no one has a living, callable reference to us,
411                // or we are a binary), ant then set the destructed flag and return.
412                loop {
413                    let flags = self.load_compartment_flags(src);
414                    if flags & COMP_THREAD_CAN_EXIT != 0
415                        && self.update_compartment_flags(src, |state| Some(state | COMP_DESTRUCTED))
416                    {
417                        tracing::debug!(
418                            "runtime main thread destructing in {}",
419                            self.comp_name(src)
420                                .unwrap_or_else(|_| String::from("unknown"))
421                        );
422                        break None;
423                    }
424                    self.wait_for_compartment_state_change(src, flags);
425                }
426            }
427        }
428    }
429
430    pub fn post_signal(
431        &self,
432        info: &secgate::GateCallInfo,
433        target: Option<ObjID>,
434        signal: u64,
435        flags: PostSignalFlags,
436    ) -> Result<(), TwzError> {
437        let target = target.unwrap_or(info.source_context().unwrap_or(MONITOR_INSTANCE_ID));
438        let post_signal = |target: ObjID, sig: u64| -> Result<(), TwzError> {
439            tracing::debug!("posting signal {} to {}", sig, target);
440            let comp = self.comp_mgr.read(ThreadKey::get().unwrap());
441            let comp = comp.get(target)?;
442            let scc = comp.comp_config_ptr();
443            let scc = unsafe { &*scc };
444            scc.post_signal(signal);
445            if let Some(thread) = comp.main_thread() {
446                sys_thread_send_message(thread.thread.id, signal, 0)?;
447            }
448            Ok(())
449        };
450        if flags.contains(PostSignalFlags::GROUP) {
451            return Err(TwzError::NOT_SUPPORTED);
452        }
453        if flags.contains(PostSignalFlags::CONTROLLER) {
454            let targets = self
455                .comp_mgr
456                .read(ThreadKey::get().unwrap())
457                .find_controller_targets(target);
458            for t in targets {
459                let _ = post_signal(t, signal).inspect_err(|e| {
460                    tracing::warn!(
461                        "failed to raise signal via controller {} to target {}: {}",
462                        target,
463                        t,
464                        e
465                    )
466                });
467            }
468        } else {
469            post_signal(target, signal)?;
470        }
471        return Ok(());
472    }
473
474    pub fn set_controller(
475        &self,
476        _info: &secgate::GateCallInfo,
477        target: ObjID,
478        controller: ObjID,
479    ) -> Result<(), TwzError> {
480        let mut cm = self.comp_mgr.write(ThreadKey::get().unwrap());
481        cm.set_controller(target, controller)?;
482        return Ok(());
483    }
484
485    pub fn libname_map(
486        &self,
487        caller: ObjID,
488        thread: ObjID,
489        namelen: usize,
490        id: ObjID,
491    ) -> Result<(), TwzError> {
492        let str_bytes = self.read_thread_simple_buffer(caller, thread, namelen)?;
493
494        let name = str::from_utf8(&str_bytes).map_err(|_| TwzError::INVALID_ARGUMENT)?;
495        tracing::trace!("libname map: {}", name);
496        let mut dynlink = self.dynlink.write(ThreadKey::get().unwrap());
497        dynlink.engine.add_name_map(name, id);
498
499        Ok(())
500    }
501
502    pub fn libname_unmap(
503        &self,
504        caller: ObjID,
505        thread: ObjID,
506        namelen: Option<usize>,
507        id: Option<ObjID>,
508    ) -> Result<(), TwzError> {
509        let str_bytes = self.read_thread_simple_buffer(caller, thread, namelen.unwrap_or(0))?;
510        let name = namelen
511            .map(|_| str::from_utf8(&str_bytes).map_err(|_| TwzError::INVALID_ARGUMENT))
512            .transpose()?;
513        let mut dynlink = self.dynlink.write(ThreadKey::get().unwrap());
514        dynlink.engine.remove_name_map(name, id);
515
516        Ok(())
517    }
518}
519
520static MONITOR: OnceLock<Monitor> = OnceLock::new();
521
522/// Get the monitor instance. Panics if called before first call to [set_monitor].
523pub fn get_monitor() -> &'static Monitor {
524    MONITOR.get().unwrap()
525}
526
527/// Set the monitor instance. Can only be called once. Must be called before any call to
528/// [get_monitor].
529pub fn set_monitor(monitor: Monitor) {
530    if MONITOR.set(monitor).is_err() {
531        panic!("second call to set_monitor");
532    }
533}
534
535pub use space::early_object_map;