monitor/mon/compartment/
loader.rs

1use std::{collections::HashSet, ffi::CStr, ptr::null_mut};
2
3use dynlink::{
4    compartment::CompartmentId,
5    context::{Context, LoadIds, NewCompartmentFlags},
6    engines::LoadCtx,
7    library::{AllowedGates, LibraryId, UnloadedLibrary},
8    DynlinkError,
9};
10use happylock::ThreadKey;
11use monitor_api::SharedCompConfig;
12use twizzler_rt_abi::{
13    core::{CtorSet, RuntimeInfo},
14    error::{GenericError, TwzError},
15    object::{MapFlags, ObjID},
16};
17
18use super::{
19    CompConfigObject, CompartmentMgr, RunComp, StackObject, COMP_DESTRUCTED, COMP_EXITED,
20    COMP_IS_BINARY, COMP_READY, COMP_STARTED,
21};
22use crate::mon::{
23    get_monitor,
24    space::{MapHandle, Space},
25    thread::DEFAULT_STACK_SIZE,
26    Monitor,
27};
28
29/// Tracks info for loaded, but not yet running, compartments.
30#[derive(Debug)]
31pub struct RunCompLoader {
32    loaded_extras: Vec<LoadInfo>,
33    root_comp: LoadInfo,
34}
35
36/// A single compartment, loaded but not yet running.
37#[derive(Debug, Clone)]
38struct LoadInfo {
39    // root (eg executable) library ID
40    #[allow(dead_code)]
41    root_id: LibraryId,
42    // runtime library ID (maybe injected)
43    #[allow(dead_code)]
44    rt_id: LibraryId,
45    // security context ID
46    sctx_id: ObjID,
47    name: String,
48    comp_id: CompartmentId,
49    // all constructors for all libraries
50    ctor_info: Vec<CtorSet>,
51    // entry point to call for the runtime to init this compartment
52    entry: extern "C" fn(*const RuntimeInfo) -> !,
53    is_binary: bool,
54}
55
56impl LoadInfo {
57    fn new(
58        dynlink: &mut Context,
59        root_id: LibraryId,
60        rt_id: LibraryId,
61        sctx_id: ObjID,
62        is_binary: bool,
63        extras: &[LibraryId],
64    ) -> Result<Self, DynlinkError> {
65        let lib = dynlink.get_library(rt_id)?;
66        let extra_ctors: Vec<_> = extras
67            .iter()
68            .map(|extra| dynlink.build_ctors_list(*extra, Some(lib.compartment())))
69            .try_collect()?;
70        let mut root_ctors = dynlink.build_ctors_list(root_id, Some(lib.compartment()))?;
71        let mut ctor_info: Vec<_> = extra_ctors.iter().flatten().copied().collect();
72        ctor_info.append(&mut root_ctors);
73        Ok(Self {
74            root_id,
75            rt_id,
76            comp_id: lib.compartment(),
77            sctx_id,
78            name: dynlink.get_compartment(lib.compartment())?.name.clone(),
79            ctor_info,
80            entry: lib.get_entry_address()?,
81            is_binary,
82        })
83    }
84
85    fn build_runcomp(
86        &self,
87        handle: MapHandle,
88        stack_object: StackObject,
89        is_debugging: bool,
90    ) -> Result<RunComp, DynlinkError> {
91        let comp_config =
92            CompConfigObject::new(handle, SharedCompConfig::new(self.sctx_id, null_mut()));
93
94        let flags = if self.is_binary { COMP_IS_BINARY } else { 0 };
95        Ok(RunComp::new(
96            self.sctx_id,
97            self.sctx_id,
98            self.name.clone(),
99            self.comp_id,
100            vec![],
101            comp_config,
102            flags,
103            stack_object,
104            self.entry as usize,
105            &self.ctor_info,
106            is_debugging,
107        ))
108    }
109}
110
111impl Drop for RunCompLoader {
112    fn drop(&mut self) {
113        tracing::warn!("drop RunCompLoader: TODO");
114    }
115}
116
117const RUNTIME_NAME: &str = "libtwz_rt.so";
118
119impl RunCompLoader {
120    // the runtime library might be in the dependency tree from the shared object files.
121    // if not, we need to insert it.
122    fn maybe_inject_runtime(
123        dynlink: &mut Context,
124        root_id: LibraryId,
125        comp_id: CompartmentId,
126        load_ctx: &mut LoadCtx,
127    ) -> Result<LibraryId, DynlinkError> {
128        if let Some(id) = dynlink.lookup_library(comp_id, RUNTIME_NAME) {
129            return Ok(id);
130        }
131
132        let rt_unlib = UnloadedLibrary::new(RUNTIME_NAME);
133        let loads = dynlink.load_library_in_compartment(
134            comp_id,
135            rt_unlib,
136            AllowedGates::Private,
137            load_ctx,
138        )?;
139        dynlink.add_manual_dependency(root_id, loads[0].lib);
140        Ok(loads[0].lib)
141    }
142
143    /// Build a new RunCompLoader. This will load and relocate libraries in the dynamic linker, but
144    /// won't start compartment threads.
145    pub fn new(
146        dynlink: &mut Context,
147        comp_name: &str,
148        root_unlib: UnloadedLibrary,
149        extras: &[UnloadedLibrary],
150        new_comp_flags: NewCompartmentFlags,
151        mondebug: bool,
152    ) -> miette::Result<Self> {
153        struct UnloadOnDrop(Vec<LoadIds>);
154        impl Drop for UnloadOnDrop {
155            fn drop(&mut self) {
156                tracing::warn!("todo: drop");
157            }
158        }
159        let root_comp_id = dynlink.add_compartment(comp_name, new_comp_flags)?;
160        let allowed_gates = if new_comp_flags.contains(NewCompartmentFlags::EXPORT_GATES) {
161            AllowedGates::Public
162        } else {
163            AllowedGates::Private
164        };
165        let mut load_ctx = LoadCtx::default();
166
167        let extra_load_ids: Vec<_> = extras
168            .into_iter()
169            .map(|extra| {
170                if mondebug {
171                    tracing::info!("loading ld preload library: {}", extra.name);
172                } else {
173                    tracing::debug!("loading ld preload library: {}", extra.name);
174                }
175                dynlink.load_library_in_compartment(
176                    root_comp_id,
177                    extra.clone(),
178                    AllowedGates::Private,
179                    &mut load_ctx,
180                )
181            })
182            .try_collect()?;
183
184        let mut loads = UnloadOnDrop(dynlink.load_library_in_compartment(
185            root_comp_id,
186            root_unlib.clone(),
187            allowed_gates,
188            &mut load_ctx,
189        )?);
190
191        for extra in &extra_load_ids {
192            for extra in extra {
193                loads.0.push(extra.clone());
194            }
195        }
196
197        // The dynamic linker gives us a list of loaded libraries, and which compartments they ended
198        // up in. For each of those, we may need to inject the runtime library. Collect all
199        // the information about the extra compartments.
200        let mut cache = HashSet::new();
201        let extra_compartments = loads.0.iter().filter_map(|load| {
202            if load.comp != root_comp_id {
203                // This compartment was loaded in addition to the root comp as part of our
204                // initial load request. Check if we haven't seen it before.
205                if cache.contains(&load.comp) {
206                    return None;
207                }
208                cache.insert(load.comp);
209
210                // Inject the runtime library, careful to collect the error and keep going.
211                let rt_id =
212                    match Self::maybe_inject_runtime(dynlink, load.lib, load.comp, &mut load_ctx) {
213                        Ok(id) => id,
214                        Err(e) => return Some(Err(e)),
215                    };
216                Some(LoadInfo::new(
217                    dynlink,
218                    load.lib,
219                    rt_id,
220                    *load_ctx.set.get(&load.comp).unwrap(),
221                    false,
222                    &[],
223                ))
224            } else {
225                None
226            }
227        });
228
229        let extra_compartments = DynlinkError::collect(
230            dynlink::DynlinkErrorKind::CompartmentLoadFail {
231                compartment: comp_name.to_string(),
232            },
233            extra_compartments,
234        )?;
235
236        let root_id = loads.0[0].lib;
237        let rt_id = Self::maybe_inject_runtime(dynlink, root_id, root_comp_id, &mut load_ctx)?;
238        let extra_lids = extra_load_ids
239            .iter()
240            .flatten()
241            .map(|x| x.lib)
242            .collect::<Vec<_>>();
243        for extra in &extra_lids {
244            dynlink.relocate_all(*extra)?;
245        }
246        dynlink.relocate_all(root_id)?;
247
248        let is_binary = dynlink.get_library(root_id)?.is_binary();
249        let root_comp = LoadInfo::new(
250            dynlink,
251            root_id,
252            rt_id,
253            *load_ctx.set.get(&root_comp_id).unwrap(),
254            is_binary,
255            extra_lids.as_slice(),
256        )?;
257
258        if mondebug {
259            let print_comp = |cmp: &LoadInfo| -> miette::Result<()> {
260                let dcmp = dynlink.get_compartment(cmp.comp_id)?;
261                tracing::info!("Loaded libraries for {}:", &dcmp.name);
262                for lid in dcmp.library_ids() {
263                    let lib = dynlink.get_library(lid)?;
264                    let mut flags = ["-", "-", "-"];
265                    if lib.is_binary() {
266                        flags[0] = "B";
267                    } else {
268                        flags[0] = "l";
269                    }
270                    if lib.id() == cmp.rt_id {
271                        flags[1] = "r";
272                    } else if lib.id() == cmp.root_id {
273                        flags[1] = "R";
274                    }
275                    if lib.allows_gates() {
276                        flags[2] = "g";
277                    }
278                    let flags = flags.join("");
279                    tracing::info!("{:16x} {} {}", lib.base_addr(), flags, &lib.name);
280                    if let Some(isg) = lib.iter_secgates() {
281                        for gate in isg {
282                            tracing::info!(
283                                "    GATE {:16x} {}",
284                                gate.imp,
285                                gate.name().to_string_lossy()
286                            )
287                        }
288                    }
289                }
290                Ok(())
291            };
292            tracing::info!("Load info for {}", comp_name);
293            let _ = print_comp(&root_comp);
294            for cmp in &extra_compartments {
295                let _ = print_comp(cmp);
296            }
297        }
298
299        // We don't want to drop anymore, since now drop-cleanup will be handled by RunCompLoader.
300        std::mem::forget(loads);
301        Ok(RunCompLoader {
302            loaded_extras: extra_compartments,
303            root_comp,
304        })
305    }
306
307    pub fn build_rcs(
308        self,
309        cmp: &mut CompartmentMgr,
310        dynlink: &mut Context,
311        _mondebug: bool,
312        is_debugging: bool,
313    ) -> miette::Result<ObjID> {
314        let make_new_handle = |id| {
315            Space::safe_create_and_map_runtime_object(
316                &get_monitor().space,
317                id,
318                MapFlags::READ | MapFlags::WRITE,
319            )
320        };
321
322        let root_rc = self.root_comp.build_runcomp(
323            make_new_handle(self.root_comp.sctx_id)?,
324            StackObject::new(make_new_handle(self.root_comp.sctx_id)?, DEFAULT_STACK_SIZE)?,
325            is_debugging,
326        )?;
327
328        let mut ids = vec![root_rc.instance];
329        // Make all the handles first, for easier cleanup.
330        let handles = self
331            .loaded_extras
332            .iter()
333            .map(|extra| {
334                Ok::<_, miette::Report>((
335                    make_new_handle(extra.sctx_id)?,
336                    StackObject::new(make_new_handle(extra.sctx_id)?, DEFAULT_STACK_SIZE)?,
337                ))
338            })
339            .try_collect::<Vec<_>>()?;
340        // Construct the RunComps for all the extra compartments.
341        let mut extras = self
342            .loaded_extras
343            .iter()
344            .zip(handles)
345            .map(|extra| extra.0.build_runcomp(extra.1 .0, extra.1 .1, false))
346            .try_collect::<Vec<_>>()?;
347
348        for rc in extras.drain(..) {
349            ids.push(rc.instance);
350            cmp.insert(rc);
351        }
352        cmp.insert(root_rc);
353        std::mem::forget(self);
354
355        // Set all the dependency information.
356        for id in &ids {
357            let Ok(comp) = cmp.get(*id) else { continue };
358            let mut deps = dynlink
359                .compartment_dependencies(comp.compartment_id)?
360                .iter()
361                .filter_map(|item| cmp.get_dynlinkid(*item).map(|rc| rc.instance).ok())
362                .collect();
363            cmp.get_mut(*id).unwrap().deps.append(&mut deps);
364
365            let Ok(comp) = cmp.get(*id) else { continue };
366            tracing::debug!("set comp {} deps to {:?}", comp.name, comp.deps);
367        }
368        Self::rec_inc_all_use_counts(cmp, ids[0], &HashSet::from_iter(ids.iter().cloned()));
369
370        Ok(ids[0])
371    }
372
373    fn rec_inc_all_use_counts(
374        cmgr: &mut CompartmentMgr,
375        start: ObjID,
376        created: &HashSet<ObjID>,
377    ) -> Option<()> {
378        debug_assert!(created.contains(&start));
379        let rc = cmgr.get(start).ok()?;
380        for dep in rc.deps.clone() {
381            if created.contains(&dep) {
382                Self::rec_inc_all_use_counts(cmgr, dep, created);
383            }
384            if let Ok(rc) = cmgr.get_mut(dep) {
385                rc.inc_use_count();
386            }
387        }
388
389        Some(())
390    }
391}
392
393impl Monitor {
394    pub(crate) fn start_compartment(
395        &self,
396        instance: ObjID,
397        args: &[&CStr],
398        env: &[&CStr],
399        mondebug: bool,
400        suspend_on_start: bool,
401    ) -> Result<(), TwzError> {
402        if mondebug {
403            tracing::info!("start compartment {}: {:?} {:?}", instance, args, env);
404        }
405        let deps = {
406            let cmp = self.comp_mgr.read(ThreadKey::get().unwrap());
407            let rc = cmp.get(instance)?;
408            tracing::debug!(
409                "starting compartment {} ({}) (binary = {})",
410                rc.name,
411                rc.instance,
412                rc.has_flag(COMP_IS_BINARY)
413            );
414            rc.deps.clone()
415        };
416        for dep in deps {
417            self.start_compartment(dep, &[], env, false, false)?;
418        }
419        // Check the state of this compartment.
420        let state = self.load_compartment_flags(instance);
421        if state & COMP_EXITED != 0 || state & COMP_DESTRUCTED != 0 {
422            tracing::error!(
423                "tried to start compartment ({:?}, {}) that has already exited (state: {:x})",
424                self.comp_name(instance),
425                instance,
426                state
427            );
428            return Err(GenericError::Internal.into());
429        }
430
431        loop {
432            // Check the state of this compartment.
433            let state = self.load_compartment_flags(instance);
434            if state & COMP_READY != 0 {
435                return Ok(());
436            }
437            if suspend_on_start {
438                // We can't wait for ready, since that need the thread to run.
439                if state & COMP_STARTED != 0 {
440                    return Ok(());
441                }
442            }
443            let info = {
444                let (ref mut tmgr, ref mut cmp, ref mut dynlink, _, _) =
445                    *self.locks.lock(ThreadKey::get().unwrap());
446                let rc = cmp.get_mut(instance)?;
447
448                rc.start_main_thread(
449                    state,
450                    &mut *tmgr,
451                    &mut *dynlink,
452                    args,
453                    env,
454                    suspend_on_start,
455                )
456            };
457            if info.is_none() {
458                return Err(GenericError::Internal.into());
459            }
460            self.wait_for_compartment_state_change(instance, state);
461        }
462    }
463}