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