monitor/mon/compartment/
loader.rs

1use std::{alloc::Layout, collections::HashSet, ffi::CStr, ptr::null_mut, time::Instant};
2
3use dynlink::{
4    compartment::CompartmentId,
5    context::{Context, LoadIds, NewCompartmentFlags},
6    engines::LoadCtx,
7    library::{AllowedGates, LibraryId, UnloadedLibrary},
8    DynlinkError, SMALL_STRING_SIZE, SMALL_VEC_SIZE,
9};
10use happylock::ThreadKey;
11use monitor_api::SharedCompConfig;
12use smallstr::SmallString;
13use talc::{ErrOnOom, Talc};
14use tinyvec::TinyVec;
15use twizzler_rt_abi::{
16    bindings::binding_info,
17    core::{CtorSet, RuntimeInfo},
18    error::{GenericError, TwzError},
19    object::{MapFlags, ObjID},
20};
21
22use super::{
23    CompConfigObject, CompartmentMgr, RunComp, StackObject, COMP_DESTRUCTED, COMP_EXITED,
24    COMP_IS_BINARY, COMP_READY, COMP_STARTED,
25};
26use crate::mon::{
27    get_monitor,
28    space::{MapHandle, Space},
29    thread::DEFAULT_STACK_SIZE,
30    Monitor,
31};
32
33/// Tracks info for loaded, but not yet running, compartments.
34#[derive(Debug)]
35pub struct RunCompLoader {
36    loaded_extras: dynlink::Vec<LoadInfo, SMALL_VEC_SIZE>,
37    root_comp: LoadInfo,
38}
39
40/// A single compartment, loaded but not yet running.
41#[derive(Debug, Clone)]
42struct LoadInfo {
43    // root (eg executable) library ID
44    #[allow(dead_code)]
45    root_id: LibraryId,
46    // runtime library ID (maybe injected)
47    #[allow(dead_code)]
48    rt_id: LibraryId,
49    // security context ID
50    sctx_id: ObjID,
51    name: SmallString<[u8; SMALL_STRING_SIZE]>,
52    comp_id: CompartmentId,
53    // all constructors for all libraries
54    ctor_info: dynlink::Vec<CtorSet, SMALL_VEC_SIZE>,
55    // entry point to call for the runtime to init this compartment
56    entry: Option<extern "C" fn(*const RuntimeInfo) -> !>,
57    // entry point for the runtime to call to start the program
58    main_entry: Option<extern "C" fn(*const RuntimeInfo) -> !>,
59    is_binary: bool,
60}
61
62impl Default for LoadInfo {
63    fn default() -> Self {
64        Self {
65            root_id: LibraryId::default(),
66            rt_id: LibraryId::default(),
67            sctx_id: 0.into(),
68            name: "".into(),
69            comp_id: CompartmentId::default(),
70            ctor_info: dynlink::Vec::new(),
71            entry: None,
72            main_entry: None,
73            is_binary: false,
74        }
75    }
76}
77
78impl LoadInfo {
79    fn new(
80        dynlink: &mut Context,
81        root_id: LibraryId,
82        rt_id: LibraryId,
83        sctx_id: ObjID,
84        is_binary: bool,
85        extras: &[LibraryId],
86    ) -> Result<Self, DynlinkError> {
87        let root_lib = dynlink.get_library(root_id)?;
88        let lib = dynlink.get_library(rt_id)?;
89        let extra_ctors: Vec<_> = extras
90            .iter()
91            .map(|extra| dynlink.build_ctors_list::<1>(*extra, Some(lib.compartment()), None))
92            .try_collect()?;
93        let root_ctors = dynlink.build_ctors_list::<1>(root_id, Some(lib.compartment()), None)?;
94        let mut ctor_info: dynlink::Vec<_, SMALL_VEC_SIZE> =
95            extra_ctors.iter().flatten().copied().collect();
96        ctor_info.extend_from_slice(root_ctors.as_slice());
97
98        let main_entry = root_lib.get_entry_address().ok();
99
100        Ok(Self {
101            root_id,
102            rt_id,
103            comp_id: lib.compartment(),
104            sctx_id,
105            name: dynlink
106                .get_compartment(lib.compartment())?
107                .name
108                .as_str()
109                .into(),
110            ctor_info,
111            entry: Some(lib.get_entry_address()?),
112            main_entry,
113            is_binary,
114        })
115    }
116
117    fn build_runcomp(
118        &self,
119        handle: MapHandle,
120        stack_object: StackObject,
121        is_debugging: bool,
122        controller: Option<ObjID>,
123        mut loader_config: monitor_api::CompartmentLoaderConfig,
124    ) -> Result<RunComp, DynlinkError> {
125        let _start = Instant::now();
126        let mut comp_config = CompConfigObject::new(
127            handle,
128            SharedCompConfig::new(self.sctx_id, null_mut(), loader_config),
129        );
130
131        let flags = if self.is_binary { COMP_IS_BINARY } else { 0 };
132
133        let mut alloc = Talc::new(ErrOnOom);
134        unsafe { alloc.claim(comp_config.alloc_span()).unwrap() };
135
136        if !loader_config.fd_spec.is_null() {
137            let fd_spec_layout = Layout::array::<binding_info>(loader_config.fd_spec_len).unwrap();
138            let fd_spec_ptr =
139                unsafe { alloc.malloc(fd_spec_layout).unwrap().cast::<binding_info>() };
140            let fd_spec_slice = unsafe {
141                core::slice::from_raw_parts_mut(fd_spec_ptr.as_ptr(), loader_config.fd_spec_len)
142            };
143            let src_fd_spec_slice = unsafe {
144                core::slice::from_raw_parts(loader_config.fd_spec, loader_config.fd_spec_len)
145            };
146            fd_spec_slice.copy_from_slice(src_fd_spec_slice);
147            loader_config.fd_spec = fd_spec_ptr.as_ptr();
148            comp_config.write_config(SharedCompConfig::new(
149                self.sctx_id,
150                null_mut(),
151                loader_config,
152            ));
153        }
154        tracing::trace!("build_runcomp in {}ms", _start.elapsed().as_millis());
155
156        Ok(RunComp::new(
157            self.sctx_id,
158            self.sctx_id,
159            self.name.to_string(),
160            self.comp_id,
161            vec![],
162            comp_config,
163            flags,
164            stack_object,
165            self.entry.map(|x| x as usize).unwrap_or_default(),
166            self.main_entry.map(|x| x as usize).unwrap_or_default(),
167            &self.ctor_info,
168            is_debugging,
169            controller,
170            alloc,
171        ))
172    }
173}
174
175impl Drop for RunCompLoader {
176    fn drop(&mut self) {
177        tracing::warn!("drop RunCompLoader: TODO");
178    }
179}
180
181const RUNTIME_NAME: &str = "libtwz_rt.so";
182
183impl RunCompLoader {
184    // the runtime library might be in the dependency tree from the shared object files.
185    // if not, we need to insert it.
186    fn maybe_inject_runtime(
187        dynlink: &mut Context,
188        root_id: LibraryId,
189        comp_id: CompartmentId,
190        load_ctx: &mut LoadCtx,
191    ) -> Result<LibraryId, DynlinkError> {
192        if let Some(id) = dynlink.lookup_library(comp_id, RUNTIME_NAME) {
193            return Ok(id);
194        }
195
196        let rt_unlib = UnloadedLibrary::new(RUNTIME_NAME);
197        let loads = dynlink.load_library_in_compartment(
198            comp_id,
199            rt_unlib,
200            AllowedGates::Private,
201            load_ctx,
202        )?;
203        dynlink.add_manual_dependency(root_id, loads[0].lib);
204        Ok(loads[0].lib)
205    }
206
207    /// Build a new RunCompLoader. This will load and relocate libraries in the dynamic linker, but
208    /// won't start compartment threads.
209    pub fn new(
210        dynlink: &mut Context,
211        comp_name: &str,
212        root_unlib: UnloadedLibrary,
213        extras: &[UnloadedLibrary],
214        extras_sctx: &[UnloadedLibrary],
215        new_comp_flags: NewCompartmentFlags,
216        mondebug: bool,
217    ) -> miette::Result<Self> {
218        let _start_1 = Instant::now();
219        struct UnloadOnDrop(TinyVec<[LoadIds; SMALL_VEC_SIZE]>);
220        impl Drop for UnloadOnDrop {
221            fn drop(&mut self) {
222                tracing::warn!("todo: drop");
223            }
224        }
225        let root_comp_id = dynlink.add_compartment(comp_name, new_comp_flags)?;
226        let allowed_gates = if new_comp_flags.contains(NewCompartmentFlags::EXPORT_GATES) {
227            AllowedGates::Public
228        } else {
229            AllowedGates::Private
230        };
231        let mut load_ctx = LoadCtx::default();
232        let _start_2 = Instant::now();
233
234        let mut extra_sctx_load_ids: Vec<_> = extras_sctx
235            .into_iter()
236            .map(|extra| {
237                let comp_id = dynlink
238                    .add_compartment(extra.name.clone(), NewCompartmentFlags::EXPORT_GATES)?;
239                if mondebug {
240                    tracing::info!(
241                        "loading sctx preload library: {} -> {}",
242                        extra.name,
243                        comp_id
244                    );
245                } else {
246                    tracing::debug!(
247                        "loading sctx preload library: {} -> {}",
248                        extra.name,
249                        comp_id
250                    );
251                }
252                dynlink.load_library_in_compartment(
253                    comp_id,
254                    extra.clone(),
255                    AllowedGates::Public,
256                    &mut load_ctx,
257                )
258            })
259            .try_collect()?;
260
261        let mut extra_load_ids: Vec<_> = extras
262            .into_iter()
263            .map(|extra| {
264                if mondebug {
265                    tracing::info!("loading ld preload library: {}", extra.name);
266                } else {
267                    tracing::debug!("loading ld preload library: {}", extra.name);
268                }
269                dynlink.load_library_in_compartment(
270                    root_comp_id,
271                    extra.clone(),
272                    AllowedGates::Private,
273                    &mut load_ctx,
274                )
275            })
276            .try_collect()?;
277
278        let mut loads = UnloadOnDrop(dynlink.load_library_in_compartment(
279            root_comp_id,
280            root_unlib.clone(),
281            allowed_gates,
282            &mut load_ctx,
283        )?);
284
285        extra_load_ids.append(&mut extra_sctx_load_ids);
286
287        for extra in &extra_load_ids {
288            for extra in extra {
289                loads.0.push(extra.clone());
290            }
291        }
292
293        // The dynamic linker gives us a list of loaded libraries, and which compartments they ended
294        // up in. For each of those, we may need to inject the runtime library. Collect all
295        // the information about the extra compartments.
296        let mut cache = HashSet::new();
297        let extra_compartments = loads.0.iter().filter_map(|load| {
298            tracing::debug!(
299                "extra? {} {} {}",
300                load.comp,
301                root_comp_id,
302                cache.contains(&load.comp)
303            );
304            if load.comp != root_comp_id {
305                // This compartment was loaded in addition to the root comp as part of our
306                // initial load request. Check if we haven't seen it before.
307                if cache.contains(&load.comp) {
308                    return None;
309                }
310                cache.insert(load.comp);
311
312                // Inject the runtime library, careful to collect the error and keep going.
313                let rt_id =
314                    match Self::maybe_inject_runtime(dynlink, load.lib, load.comp, &mut load_ctx) {
315                        Ok(id) => id,
316                        Err(e) => return Some(Err(e)),
317                    };
318                Some(LoadInfo::new(
319                    dynlink,
320                    load.lib,
321                    rt_id,
322                    *load_ctx.set.get(&load.comp).unwrap(),
323                    false,
324                    &[],
325                ))
326            } else {
327                None
328            }
329        });
330
331        let extra_compartments = DynlinkError::collect(
332            dynlink::DynlinkErrorKind::CompartmentLoadFail {
333                compartment: comp_name.into(),
334            },
335            extra_compartments,
336        )?;
337        tracing::trace!("extras: {:?}", extra_compartments);
338
339        let root_id = loads.0[0].lib;
340        let rt_id = Self::maybe_inject_runtime(dynlink, root_id, root_comp_id, &mut load_ctx)?;
341        let extra_lids = extra_load_ids
342            .iter()
343            .flatten()
344            .map(|x| x.lib)
345            .collect::<Vec<_>>();
346        let _start_3 = Instant::now();
347        for extra in &extra_lids {
348            dynlink.relocate_all(*extra)?;
349        }
350        dynlink.relocate_all(root_id)?;
351
352        let is_binary = dynlink.get_library(root_id)?.is_binary();
353        let root_comp = LoadInfo::new(
354            dynlink,
355            root_id,
356            rt_id,
357            *load_ctx.set.get(&root_comp_id).unwrap(),
358            is_binary,
359            extra_lids.as_slice(),
360        )?;
361
362        if mondebug {
363            let print_comp = |cmp: &LoadInfo| -> miette::Result<()> {
364                let dcmp = dynlink.get_compartment(cmp.comp_id)?;
365                tracing::info!("Loaded libraries for {}:", &dcmp.name);
366                for lid in dcmp.library_ids() {
367                    let lib = dynlink.get_library(lid)?;
368                    let mut flags = ["-", "-", "-"];
369                    if lib.is_binary() {
370                        flags[0] = "B";
371                    } else {
372                        flags[0] = "l";
373                    }
374                    if lib.id() == cmp.rt_id {
375                        flags[1] = "r";
376                    } else if lib.id() == cmp.root_id {
377                        flags[1] = "R";
378                    }
379                    if lib.allows_gates() {
380                        flags[2] = "g";
381                    }
382                    let flags = flags.join("");
383                    tracing::info!("{:16x} {} {}", lib.base_addr(), flags, &lib.name);
384                    if let Some(isg) = lib.iter_secgates() {
385                        for gate in isg {
386                            tracing::info!(
387                                "    GATE {:16x} {}",
388                                gate.imp,
389                                gate.name().to_string_lossy()
390                            )
391                        }
392                    }
393                }
394                Ok(())
395            };
396            tracing::info!("Load info for {}", comp_name);
397            let _ = print_comp(&root_comp);
398            for cmp in &extra_compartments {
399                let _ = print_comp(cmp);
400            }
401        }
402
403        // We don't want to drop anymore, since now drop-cleanup will be handled by RunCompLoader.
404        std::mem::forget(loads);
405
406        tracing::trace!(
407            "prepped in {}ms, loaded in {}ms, relocated in {}ms",
408            (_start_2 - _start_1).as_millis(),
409            (_start_3 - _start_2).as_millis(),
410            _start_3.elapsed().as_millis()
411        );
412
413        Ok(RunCompLoader {
414            loaded_extras: extra_compartments,
415            root_comp,
416        })
417    }
418
419    pub fn build_rcs(
420        self,
421        cmp: &mut CompartmentMgr,
422        dynlink: &mut Context,
423        mondebug: bool,
424        is_debugging: bool,
425        controller: Option<ObjID>,
426        loader_config: monitor_api::CompartmentLoaderConfig,
427    ) -> miette::Result<ObjID> {
428        let make_new_handle = |ty, id| {
429            if mondebug {
430                tracing::info!(
431                    "creating runtime {} object {} for compartment {}",
432                    ty,
433                    id,
434                    self.root_comp.name
435                );
436            }
437            Space::safe_create_and_map_runtime_object(
438                &get_monitor().space,
439                id,
440                MapFlags::READ | MapFlags::WRITE,
441            )
442        };
443        let stack = StackObject::new(
444            make_new_handle("stack", self.root_comp.sctx_id)?,
445            DEFAULT_STACK_SIZE,
446        )?;
447
448        let mut root_rc = self.root_comp.build_runcomp(
449            make_new_handle("comp-config", self.root_comp.sctx_id)?,
450            stack,
451            is_debugging,
452            controller,
453            loader_config,
454        )?;
455        tracing::trace!("starting {} as {}", self.root_comp.name, root_rc.instance);
456
457        let mut ids = vec![root_rc.instance];
458        // Make all the handles first, for easier cleanup.
459        let handles = self
460            .loaded_extras
461            .iter()
462            .map(|extra| {
463                let stack =
464                    StackObject::new(make_new_handle("stack", extra.sctx_id)?, DEFAULT_STACK_SIZE)?;
465                Ok::<_, miette::Report>((make_new_handle("comp-config", extra.sctx_id)?, stack))
466            })
467            .try_collect::<Vec<_>>()?;
468        // Construct the RunComps for all the extra compartments.
469        let mut extras = self
470            .loaded_extras
471            .iter()
472            .zip(handles)
473            .map(|extra| {
474                extra
475                    .0
476                    .build_runcomp(extra.1 .0, extra.1 .1, false, controller, loader_config)
477            })
478            .try_collect::<Vec<_>>()?;
479
480        for rc in extras.drain(..) {
481            ids.push(rc.instance);
482            root_rc.deps.push(rc.instance);
483            cmp.insert(rc);
484        }
485        cmp.insert(root_rc);
486        std::mem::forget(self);
487
488        // Set all the dependency information.
489        for id in &ids {
490            let Ok(comp) = cmp.get(*id) else { continue };
491            let mut deps = dynlink
492                .compartment_dependencies(comp.compartment_id)?
493                .iter()
494                .filter_map(|item| cmp.get_dynlinkid(*item).map(|rc| rc.instance).ok())
495                .collect();
496            cmp.get_mut(*id).unwrap().deps.append(&mut deps);
497
498            let Ok(comp) = cmp.get(*id) else { continue };
499            tracing::debug!("set comp {} deps to {:?}", comp.name, comp.deps);
500        }
501        Self::rec_inc_all_use_counts(cmp, ids[0], &HashSet::from_iter(ids.iter().cloned()));
502
503        Ok(ids[0])
504    }
505
506    fn rec_inc_all_use_counts(
507        cmgr: &mut CompartmentMgr,
508        start: ObjID,
509        created: &HashSet<ObjID>,
510    ) -> Option<()> {
511        debug_assert!(created.contains(&start));
512        let rc = cmgr.get(start).ok()?;
513        for dep in rc.deps.clone() {
514            if created.contains(&dep) {
515                Self::rec_inc_all_use_counts(cmgr, dep, created);
516            }
517            if let Ok(rc) = cmgr.get_mut(dep) {
518                rc.inc_use_count();
519            }
520        }
521
522        Some(())
523    }
524}
525
526impl Monitor {
527    pub(crate) fn start_compartment(
528        &self,
529        instance: ObjID,
530        args: &[&CStr],
531        env: &[&CStr],
532        mondebug: bool,
533        suspend_on_start: bool,
534    ) -> Result<(), TwzError> {
535        let deps = {
536            let cmp = self.comp_mgr.read(ThreadKey::get().unwrap());
537            let rc = cmp.get(instance)?;
538
539            if mondebug {
540                tracing::info!(
541                    "start compartment {}: {:?} {:?} flags = {:x}",
542                    instance,
543                    args,
544                    env,
545                    rc.raw_flags()
546                );
547            }
548
549            tracing::debug!(
550                "starting compartment {} ({}) (binary = {})",
551                rc.name,
552                rc.instance,
553                rc.has_flag(COMP_IS_BINARY)
554            );
555            rc.deps.clone()
556        };
557        for dep in deps {
558            self.start_compartment(dep, &[], env, false, false)?;
559        }
560        // Check the state of this compartment.
561        let state = self.load_compartment_flags(instance);
562        if state & COMP_EXITED != 0 || state & COMP_DESTRUCTED != 0 {
563            tracing::error!(
564                "tried to start compartment ({:?}, {}) that has already exited (state: {:x})",
565                self.comp_name(instance),
566                instance,
567                state
568            );
569            return Err(GenericError::Internal.into());
570        }
571
572        let _loop_start = Instant::now();
573        loop {
574            // Check the state of this compartment.
575            let state = self.load_compartment_flags(instance);
576            if state & COMP_READY != 0 {
577                tracing::trace!(
578                    "started main detected ready in {}ms",
579                    _loop_start.elapsed().as_millis()
580                );
581                return Ok(());
582            }
583            if suspend_on_start {
584                // We can't wait for ready, since that need the thread to run.
585                if state & COMP_STARTED != 0 {
586                    tracing::trace!(
587                        "started main detected started in {}ms",
588                        _loop_start.elapsed().as_millis()
589                    );
590                    return Ok(());
591                }
592            }
593            let info = {
594                let (ref mut tmgr, ref mut cmp, ref mut dynlink, _, _) =
595                    *self.locks.lock(ThreadKey::get().unwrap());
596                let rc = cmp.get_mut(instance)?;
597
598                let _start = Instant::now();
599                let r = rc.start_main_thread(
600                    state,
601                    &mut *tmgr,
602                    &mut *dynlink,
603                    args,
604                    env,
605                    suspend_on_start,
606                );
607                tracing::trace!("start_main_thread in {}ms", _start.elapsed().as_millis());
608
609                r
610            };
611            if info.is_none() {
612                return Err(GenericError::Internal.into());
613            }
614            self.wait_for_compartment_state_change(instance, state);
615        }
616    }
617}