monitor/mon/compartment/
runcomp.rs

1use std::{
2    alloc::Layout,
3    collections::HashMap,
4    ffi::{CStr, CString},
5    ptr::{addr_of, NonNull},
6    sync::atomic::{AtomicU64, Ordering},
7};
8
9use dynlink::{compartment::CompartmentId, context::Context};
10use monitor_api::{
11    CompartmentFlags, RuntimeThreadControl, SharedCompConfig, ThreadInfo, TlsTemplateInfo,
12};
13use secgate::util::SimpleBuffer;
14use talc::{ErrOnOom, Talc};
15use twizzler_abi::{
16    syscall::{
17        DeleteFlags, ObjectControlCmd, ThreadSync, ThreadSyncFlags, ThreadSyncOp,
18        ThreadSyncReference, ThreadSyncSleep, ThreadSyncWake,
19    },
20    upcall::{ResumeFlags, UpcallData, UpcallFrame},
21};
22use twizzler_rt_abi::{
23    core::{CompartmentInitInfo, CtorSet, InitInfoPtrs, RuntimeInfo, RUNTIME_INIT_COMP},
24    error::TwzError,
25    object::{MapFlags, ObjID},
26};
27
28use super::{compconfig::CompConfigObject, compthread::CompThread, StackObject};
29use crate::mon::{
30    get_monitor,
31    space::{MapHandle, MapInfo, Space},
32    thread::ThreadMgr,
33};
34
35/// Compartment is ready (loaded, reloacated, runtime started and ctors run).
36pub const COMP_READY: u64 = CompartmentFlags::READY.bits();
37/// Compartment is a binary, not a library.
38pub const COMP_IS_BINARY: u64 = CompartmentFlags::IS_BINARY.bits();
39/// Compartment runtime thread may exit.
40pub const COMP_THREAD_CAN_EXIT: u64 = CompartmentFlags::THREAD_CAN_EXIT.bits();
41/// Compartment thread has been started once.
42pub const COMP_STARTED: u64 = CompartmentFlags::STARTED.bits();
43/// Compartment destructors have run.
44pub const COMP_DESTRUCTED: u64 = CompartmentFlags::DESTRUCTED.bits();
45/// Compartment thread has exited.
46pub const COMP_EXITED: u64 = CompartmentFlags::EXITED.bits();
47
48/// A runnable or running compartment.
49pub struct RunComp {
50    /// The security context for this compartment.
51    pub sctx: ObjID,
52    /// The instance of the security context.
53    pub instance: ObjID,
54    /// The name of this compartment.
55    pub name: String,
56    /// The dynlink ID of this compartment.
57    pub compartment_id: CompartmentId,
58    main: Option<CompThread>,
59    pub deps: Vec<ObjID>,
60    comp_config_object: CompConfigObject,
61    alloc: Talc<ErrOnOom>,
62    mapped_objects: HashMap<MapInfo, MapHandle>,
63    flags: Box<AtomicU64>,
64    pub per_thread: HashMap<ObjID, PerThread>,
65    init_info: Option<(StackObject, usize, usize, Vec<CtorSet>)>,
66    is_debugging: bool,
67    pub(crate) use_count: u64,
68    pub controller: Option<ObjID>,
69}
70
71impl Drop for RunComp {
72    fn drop(&mut self) {
73        // TODO: check if we need to do anything.
74        let _ = twizzler_abi::syscall::sys_object_ctrl(
75            self.instance,
76            ObjectControlCmd::Delete(DeleteFlags::empty()),
77        )
78        .inspect_err(|e| tracing::warn!("failed to delete instance on RunComp drop: {}", e));
79    }
80}
81
82impl core::fmt::Debug for RunComp {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.debug_struct("RunComp")
85            .field("sctx", &self.sctx)
86            .field("instance", &self.instance)
87            .field("name", &self.name)
88            .field("deps", &self.deps)
89            .field("usecount", &self.use_count)
90            .field("dynlink_id", &self.compartment_id)
91            .finish_non_exhaustive()
92    }
93}
94
95/// Per-thread data in a compartment.
96pub struct PerThread {
97    simple_buffer: Option<(SimpleBuffer, MapHandle)>,
98}
99
100impl PerThread {
101    /// Create a new PerThread. Note that this must succeed, so any allocation failures must be
102    /// handled gracefully. This means that if the thread fails to allocate a simple buffer, it
103    /// will just forego having one. This may cause a failure down the line, but it's the best we
104    /// can do without panicing.
105    fn new(instance: ObjID, _th: ObjID) -> Self {
106        let handle = Space::safe_create_and_map_runtime_object(
107            &get_monitor().space,
108            instance,
109            MapFlags::READ | MapFlags::WRITE,
110        )
111        .ok();
112
113        Self {
114            simple_buffer: handle
115                .map(|handle| (SimpleBuffer::new(unsafe { handle.object_handle() }), handle)),
116        }
117    }
118
119    /// Write bytes into this compartment-thread's simple buffer.
120    pub fn write_bytes(&mut self, bytes: &[u8]) -> usize {
121        self.simple_buffer
122            .as_mut()
123            .map(|sb| sb.0.write(bytes))
124            .unwrap_or(0)
125    }
126
127    /// Read bytes from this compartment-thread's simple buffer.
128    pub fn read_bytes(&mut self, len: usize) -> Vec<u8> {
129        let mut v = vec![0; len];
130        let readlen = self
131            .simple_buffer
132            .as_mut()
133            .map(|sb| sb.0.read(&mut v))
134            .unwrap_or(0);
135        v.truncate(readlen);
136        v
137    }
138
139    /// Get the Object ID of this compartment thread's simple buffer.
140    pub fn simple_buffer_id(&self) -> Option<ObjID> {
141        Some(self.simple_buffer.as_ref()?.0.handle().id())
142    }
143}
144
145impl RunComp {
146    /// Build a new runtime compartment.
147    #[allow(clippy::too_many_arguments)]
148    pub fn new(
149        sctx: ObjID,
150        instance: ObjID,
151        name: String,
152        compartment_id: CompartmentId,
153        deps: Vec<ObjID>,
154        comp_config_object: CompConfigObject,
155        flags: u64,
156        main_stack: StackObject,
157        entry: usize,
158        main_entry: usize,
159        ctors: &[CtorSet],
160        is_debugging: bool,
161        controller: Option<ObjID>,
162        alloc: Talc<ErrOnOom>,
163    ) -> Self {
164        Self {
165            sctx,
166            is_debugging,
167            instance,
168            name,
169            compartment_id,
170            main: None,
171            deps,
172            comp_config_object,
173            alloc,
174            mapped_objects: HashMap::default(),
175            flags: Box::new(AtomicU64::new(flags)),
176            per_thread: HashMap::new(),
177            init_info: Some((main_stack, entry, main_entry, ctors.to_vec())),
178            use_count: 0,
179            controller,
180        }
181    }
182
183    /// Get per-thread data in this compartment.
184    pub fn get_per_thread(&mut self, id: ObjID) -> &mut PerThread {
185        self.per_thread
186            .entry(id)
187            .or_insert_with(|| PerThread::new(self.instance, id))
188    }
189
190    /// Remove all per-thread data for a given thread.
191    pub fn clean_per_thread_data(&mut self, id: ObjID) {
192        self.per_thread.remove(&id);
193    }
194
195    /// Map an object into this compartment.
196    pub fn map_object(&mut self, info: MapInfo, handle: MapHandle) -> Result<MapHandle, TwzError> {
197        self.mapped_objects.insert(info, handle.clone());
198        Ok(handle)
199    }
200
201    /// Unmap and object from this compartment.
202    pub fn unmap_object(&mut self, info: MapInfo) -> Option<MapHandle> {
203        let x = self.mapped_objects.remove(&info);
204        if x.is_none() {
205            // TODO:: this happens occasionally, but it doesn't seem to be an issue?
206            tracing::debug!(
207                "tried to comp-unmap an object that was not mapped by compartment ({}): {:?}",
208                self.name,
209                info
210            );
211        }
212        x
213    }
214
215    /// Get a pointer to the compartment config.
216    pub fn comp_config_ptr(&self) -> *const SharedCompConfig {
217        self.comp_config_object.get_comp_config()
218    }
219
220    /// Allocate some space in the compartment allocator, and initialize it.
221    pub fn monitor_new<T: Copy + Sized>(&mut self, data: T) -> Result<*mut T, ()> {
222        unsafe {
223            let place: NonNull<T> = self.alloc.malloc(Layout::new::<T>())?.cast();
224            place.as_ptr().write(data);
225            Ok(place.as_ptr())
226        }
227    }
228
229    /// Allocate some space in the compartment allocator for a slice, and initialize it.
230    pub fn monitor_new_slice<T: Copy + Sized>(&mut self, data: &[T]) -> Result<*mut T, ()> {
231        unsafe {
232            let place = self.alloc.malloc(Layout::array::<T>(data.len()).unwrap())?;
233            let slice = core::slice::from_raw_parts_mut(place.as_ptr() as *mut T, data.len());
234            slice.copy_from_slice(data);
235            Ok(place.as_ptr() as *mut T)
236        }
237    }
238
239    /// Set a flag on this compartment, and wakeup anyone waiting on flag change.
240    pub fn set_flag(&self, val: u64) {
241        tracing::trace!("compartment {} set flag {:x}", self.name, val);
242        self.flags.fetch_or(val, Ordering::SeqCst);
243        self.notify_state_changed();
244    }
245
246    /// Set a flag on this compartment, and wakeup anyone waiting on flag change.
247    pub fn cas_flag(&self, old: u64, new: u64) -> Result<u64, u64> {
248        let r = self
249            .flags
250            .compare_exchange(old, new, Ordering::SeqCst, Ordering::SeqCst);
251        if r.is_ok() {
252            tracing::trace!("compartment {} cas flag {:x} -> {:x}", self.name, old, new);
253            self.notify_state_changed();
254        }
255        r
256    }
257
258    pub fn notify_state_changed(&self) {
259        let _ = twizzler_abi::syscall::sys_thread_sync(
260            &mut [ThreadSync::new_wake(ThreadSyncWake::new(
261                ThreadSyncReference::Virtual(&*self.flags),
262                usize::MAX,
263            ))],
264            None,
265        );
266    }
267
268    /// Check if a flag is set.
269    pub fn has_flag(&self, flag: u64) -> bool {
270        self.flags.load(Ordering::SeqCst) & flag != 0
271    }
272
273    /// Setup a [ThreadSyncSleep] for waiting until the flag is set. Returns None if the flag is
274    /// already set.
275    pub fn until_change(&self, cur: u64) -> [ThreadSync; 2] {
276        let ccp = self.comp_config_ptr();
277        let ps = unsafe { addr_of!((*ccp).posted_signals) };
278        [
279            ThreadSync::new_sleep(ThreadSyncSleep::new(
280                ThreadSyncReference::Virtual(&*self.flags),
281                cur,
282                ThreadSyncOp::Equal,
283                ThreadSyncFlags::empty(),
284            )),
285            ThreadSync::new_sleep(ThreadSyncSleep::new(
286                ThreadSyncReference::Virtual(ps),
287                0,
288                ThreadSyncOp::Equal,
289                ThreadSyncFlags::empty(),
290            )),
291        ]
292    }
293
294    /// Get the raw flags bits for this RC.
295    pub fn raw_flags(&self) -> u64 {
296        self.flags.load(Ordering::SeqCst)
297    }
298
299    pub(crate) fn start_main_thread(
300        &mut self,
301        state: u64,
302        tmgr: &mut ThreadMgr,
303        dynlink: &mut Context,
304        args: &[&CStr],
305        env: &[&CStr],
306        suspend_on_start: bool,
307    ) -> Option<bool> {
308        if self.has_flag(COMP_STARTED) {
309            return Some(false);
310        }
311        let state = state & !COMP_STARTED;
312        if self
313            .flags
314            .compare_exchange(
315                state,
316                state | COMP_STARTED,
317                Ordering::SeqCst,
318                Ordering::SeqCst,
319            )
320            .is_err()
321        {
322            return None;
323        }
324
325        tracing::debug!("starting main thread for compartment {}", self.name);
326        debug_assert!(self.main.is_none());
327        // Unwrap-Ok: we only take this once, when starting the main thread.
328        let (stack, entry, main_entry, ctors) = self.init_info.take().unwrap();
329        let mut build_init_info = || -> Option<_> {
330            let comp_config_info =
331                self.comp_config_object.get_comp_config() as *mut SharedCompConfig;
332            let ctors_in_comp = self.monitor_new_slice(&ctors).ok()?;
333
334            // TODO: unwrap
335            let mut args_in_comp: Vec<_> = args
336                .iter()
337                .map(|arg| self.monitor_new_slice(arg.to_bytes_with_nul()).unwrap())
338                .collect();
339
340            if args_in_comp.len() == 0 {
341                let cname = CString::new(self.name.as_bytes()).unwrap();
342                args_in_comp = vec![self.monitor_new_slice(cname.as_bytes()).unwrap()];
343            }
344            let argc = args_in_comp.len();
345
346            let mut envs_in_comp: Vec<_> = env
347                .iter()
348                .map(|arg| self.monitor_new_slice(arg.to_bytes_with_nul()).unwrap())
349                .collect();
350
351            args_in_comp.push(core::ptr::null_mut());
352            envs_in_comp.push(core::ptr::null_mut());
353
354            let args_in_comp_in_comp = self.monitor_new_slice(&args_in_comp).unwrap();
355            let envs_in_comp_in_comp = self.monitor_new_slice(&envs_in_comp).unwrap();
356
357            let comp_init_info = CompartmentInitInfo {
358                ctor_set_array: ctors_in_comp,
359                ctor_set_len: ctors.len(),
360                comp_config_info: comp_config_info.cast(),
361            };
362            let comp_init_info_in_comp = self.monitor_new(comp_init_info).ok()?;
363            let rtinfo = RuntimeInfo {
364                flags: 0,
365                kind: RUNTIME_INIT_COMP,
366                args: args_in_comp_in_comp.cast(),
367                argc,
368                entry: main_entry,
369                envp: envs_in_comp_in_comp.cast(),
370                init_info: InitInfoPtrs {
371                    comp: comp_init_info_in_comp,
372                },
373            };
374            self.monitor_new(rtinfo).ok()
375        };
376        let arg = match build_init_info() {
377            Some(arg) => arg as usize,
378            None => {
379                self.set_flag(COMP_EXITED);
380                return None;
381            }
382        };
383        if self.build_tls_template(dynlink).is_none() {
384            self.set_flag(COMP_EXITED);
385            return None;
386        }
387        let mt = match CompThread::new(
388            tmgr,
389            dynlink,
390            stack,
391            self.instance,
392            Some(self.instance),
393            if main_entry != 0 { main_entry } else { entry },
394            arg,
395            suspend_on_start,
396        ) {
397            Ok(mt) => mt,
398            Err(_) => {
399                self.set_flag(COMP_EXITED);
400                return None;
401            }
402        };
403        self.main = Some(mt);
404        self.notify_state_changed();
405
406        Some(true)
407    }
408
409    fn build_tls_template(&mut self, dynlink: &mut Context) -> Option<()> {
410        let region = dynlink
411            .get_compartment_mut(self.compartment_id)
412            .unwrap()
413            .build_tls_region(RuntimeThreadControl::default(), |layout| {
414                unsafe { self.alloc.malloc(layout) }.ok()
415            })
416            .ok()?;
417
418        let template: TlsTemplateInfo = region.into();
419        let tls_template = self.monitor_new(template).ok()?;
420
421        let config = self.comp_config_object.read_comp_config();
422        config.set_tls_template(tls_template);
423        self.comp_config_object.write_config(config);
424        Some(())
425    }
426
427    #[allow(dead_code)]
428    pub fn read_error_code(&self) -> u64 {
429        let Some(ref main) = self.main else {
430            return 0;
431        };
432        main.thread.repr.get_repr().get_code()
433    }
434
435    pub fn get_nth_thread_info(&self, n: usize) -> Option<ThreadInfo> {
436        let Some(ref main) = self.main else {
437            return None;
438        };
439        if n == 0 {
440            return Some(ThreadInfo {
441                repr_id: main.thread.id,
442            });
443        }
444        self.per_thread
445            .keys()
446            .filter(|t| **t != main.thread.id)
447            .nth(n - 1)
448            .map(|id| ThreadInfo { repr_id: *id })
449    }
450
451    pub fn main_thread(&self) -> &Option<CompThread> {
452        &self.main
453    }
454
455    pub fn upcall_handle(
456        &self,
457        frame: &mut UpcallFrame,
458        info: &UpcallData,
459    ) -> Result<Option<ResumeFlags>, TwzError> {
460        let flags = if self.is_debugging {
461            tracing::info!("got monitor upcall {:?} {:?}", frame, info);
462            Some(ResumeFlags::SUSPEND)
463        } else {
464            tracing::warn!(
465                "supervisor exception in {}, thread {}: {:?}",
466                self.name,
467                info.thread_id,
468                info.info
469            );
470            None
471        };
472        Ok(flags)
473    }
474
475    pub(crate) fn inc_use_count(&mut self) {
476        self.use_count += 1;
477        tracing::trace!(
478            "compartment {} inc use count -> {}",
479            self.name,
480            self.use_count
481        );
482    }
483
484    pub(crate) fn dec_use_count(&mut self) -> bool {
485        debug_assert!(self.use_count > 0);
486        self.use_count -= 1;
487
488        tracing::trace!(
489            "compartment {} dec use count -> {}",
490            self.name,
491            self.use_count
492        );
493        let z = self.use_count == 0;
494        if z {
495            self.set_flag(COMP_THREAD_CAN_EXIT);
496        }
497        z
498    }
499}