1use std::{collections::HashMap, ffi::CStr, time::Instant};
2
3use dynlink::{
4 compartment::{Compartment, CompartmentId},
5 context::{Context, LoadedOrUnloaded, NewCompartmentFlags},
6 library::UnloadedLibrary,
7};
8use happylock::ThreadKey;
9use monitor_api::{
10 CompartmentInfoRaw, CompartmentLoaderConfig, CompartmentMgrStats, ControllerOption, ThreadInfo,
11 MONITOR_INSTANCE_ID,
12};
13use secgate::util::Descriptor;
14use twizzler_abi::syscall::{sys_thread_change_state, sys_thread_sync, ThreadSync};
15use twizzler_rt_abi::{
16 error::{ArgumentError, GenericError, NamingError, ResourceError, TwzError},
17 object::ObjID,
18};
19
20mod compconfig;
21mod compthread;
22mod loader;
23mod runcomp;
24
25pub use compconfig::*;
26pub(crate) use compthread::StackObject;
27pub use runcomp::*;
28
29#[derive(Default)]
31pub struct CompartmentMgr {
32 names: HashMap<String, ObjID>,
33 instances: HashMap<ObjID, RunComp>,
34 controllers: HashMap<ObjID, Vec<ObjID>>,
35 dynlink_map: HashMap<CompartmentId, ObjID>,
36 cleanup_queue: Vec<RunComp>,
37}
38
39impl CompartmentMgr {
40 pub fn get(&self, id: ObjID) -> Result<&RunComp, TwzError> {
42 self.instances.get(&id).ok_or(TwzError::INVALID_ARGUMENT)
43 }
44
45 pub fn _get_name(&self, name: &str) -> Result<&RunComp, TwzError> {
47 let id = self.names.get(name).ok_or(TwzError::INVALID_ARGUMENT)?;
48 self.get(*id)
49 }
50
51 pub fn get_mut(&mut self, id: ObjID) -> Result<&mut RunComp, TwzError> {
53 self.instances
54 .get_mut(&id)
55 .ok_or(TwzError::INVALID_ARGUMENT)
56 }
57
58 pub fn get_name_mut(&mut self, name: &str) -> Result<&mut RunComp, TwzError> {
60 let id = self.names.get(name).ok_or(TwzError::INVALID_ARGUMENT)?;
61 self.get_mut(*id)
62 }
63
64 pub fn get_dynlinkid(&self, id: CompartmentId) -> Result<&RunComp, TwzError> {
66 let id = self
67 .dynlink_map
68 .get(&id)
69 .ok_or(TwzError::INVALID_ARGUMENT)?;
70 self.get(*id)
71 }
72
73 pub fn _get_dynlinkid_mut(&mut self, id: CompartmentId) -> Result<&mut RunComp, TwzError> {
75 let id = self
76 .dynlink_map
77 .get(&id)
78 .ok_or(TwzError::INVALID_ARGUMENT)?;
79 self.get_mut(*id)
80 }
81
82 pub fn insert(&mut self, mut rc: RunComp) {
84 if self.names.contains_key(&rc.name) {
85 rc.name = format!("{}-dup", rc.name);
87 return self.insert(rc);
88 }
89 self.names.insert(rc.name.clone(), rc.instance);
90 self.dynlink_map.insert(rc.compartment_id, rc.instance);
91 self.remove_from_controllers(rc.instance);
92 if let Some(controller) = rc.controller {
93 tracing::debug!(
94 "setting controller for new compartment {}: {}",
95 rc.instance,
96 controller
97 );
98 self.controllers
99 .entry(controller)
100 .or_default()
101 .push(rc.instance);
102 }
103 self.instances.insert(rc.instance, rc);
104 }
105
106 fn remove_from_controllers(&mut self, id: ObjID) {
107 for c in self.controllers.iter_mut() {
108 c.1.retain(|t| *t != id);
109 }
110 }
111
112 pub fn remove(&mut self, id: ObjID) -> Option<RunComp> {
114 let rc = self.instances.remove(&id)?;
115 self.names.remove(&rc.name);
116 self.dynlink_map.remove(&rc.compartment_id);
117 self.remove_from_controllers(id);
118 Some(rc)
119 }
120
121 pub fn set_controller(&mut self, target: ObjID, controller: ObjID) -> Result<(), TwzError> {
122 let comp = self.get_mut(target)?;
123 comp.controller = Some(controller);
124 tracing::debug!(
125 "setting controller for compartment {}: {}",
126 target,
127 controller
128 );
129 self.remove_from_controllers(target);
130 self.controllers.entry(controller).or_default().push(target);
131 Ok(())
132 }
133
134 pub fn find_controller_targets(&self, controller: ObjID) -> Vec<ObjID> {
135 self.controllers
136 .get(&controller)
137 .cloned()
138 .unwrap_or_default()
139 }
140
141 pub fn _get_monitor(&self) -> &RunComp {
143 self.get(MONITOR_INSTANCE_ID).unwrap()
145 }
146
147 pub fn _get_monitor_mut(&mut self) -> &mut RunComp {
149 self.get_mut(MONITOR_INSTANCE_ID).unwrap()
151 }
152
153 pub fn _compartments(&self) -> impl Iterator<Item = &RunComp> {
155 self.instances.values()
156 }
157
158 pub fn compartments_mut(&mut self) -> impl Iterator<Item = &mut RunComp> {
160 self.instances.values_mut()
161 }
162
163 fn update_compartment_flags(
164 &mut self,
165 instance: ObjID,
166 f: impl FnOnce(u64) -> Option<u64>,
167 ) -> bool {
168 let Ok(rc) = self.get_mut(instance) else {
169 return false;
170 };
171
172 let flags = rc.raw_flags();
173 let Some(new_flags) = f(flags) else {
174 return false;
175 };
176 if flags == new_flags {
177 return true;
178 }
179
180 rc.cas_flag(flags, new_flags).is_ok()
181 }
182
183 fn load_compartment_flags(&self, instance: ObjID) -> u64 {
184 let Ok(rc) = self.get(instance) else {
185 return 0;
186 };
187 rc.raw_flags()
188 }
189
190 fn wait_for_compartment_state_change(
191 &self,
192 instance: ObjID,
193 state: u64,
194 ) -> Result<[ThreadSync; 2], TwzError> {
195 let rc = self.get(instance)?;
196 Ok(rc.until_change(state))
197 }
198
199 pub fn main_thread_exited(&mut self, instance: ObjID) {
200 tracing::debug!("main thread for compartment {} exited", instance);
201 while !self.update_compartment_flags(instance, |old| Some(old | COMP_EXITED)) {}
202
203 let Ok(rc) = self.get(instance) else {
204 tracing::warn!("failed to find compartment {} during exit", instance);
205 return;
206 };
207
208 for thread in rc.per_thread.keys() {
209 let _ = sys_thread_change_state(*thread, twizzler_abi::thread::ExecutionState::Exited);
210 }
211
212 for dep in rc.deps.clone() {
213 self.dec_use_count(dep);
214 }
215
216 let Ok(rc) = self.get_mut(instance) else {
217 tracing::warn!("failed to find compartment {} during exit", instance);
218 return;
219 };
220 tracing::trace!("runcomp usecount: {}", rc.use_count);
221 if rc.use_count == 0 {
222 if let Some(rc) = self.remove(instance) {
223 self.cleanup_queue.push(rc)
224 }
225 }
226 }
227
228 pub fn dec_use_count(&mut self, instance: ObjID) {
229 let Ok(rc) = self.get_mut(instance) else {
230 return;
231 };
232
233 let z = rc.dec_use_count();
234 let ex = rc.has_flag(COMP_EXITED);
235 if z && ex {
236 if let Some(rc) = self.remove(instance) {
237 self.cleanup_queue.push(rc)
238 }
239 }
240 }
241
242 pub fn stat(&self) -> CompartmentMgrStats {
243 CompartmentMgrStats {
244 nr_compartments: self.instances.len(),
245 }
246 }
247
248 pub fn process_cleanup_queue(
249 &mut self,
250 dynlink: &mut Context,
251 ) -> (Vec<Option<Compartment>>, Vec<Vec<LoadedOrUnloaded>>) {
252 let (comps, libs) = self
253 .cleanup_queue
254 .drain(..)
255 .map(|c| dynlink.unload_compartment(c.compartment_id))
256 .unzip();
257 (comps, libs)
258 }
259}
260
261impl super::Monitor {
262 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
265 pub fn get_compartment_info(
266 &self,
267 instance: ObjID,
268 thread: ObjID,
269 desc: Option<Descriptor>,
270 ) -> Result<CompartmentInfoRaw, TwzError> {
271 let (_, ref mut comps, ref dynlink, _, ref comphandles) =
272 *self.locks.lock(ThreadKey::get().unwrap());
273 let comp_id = desc
274 .map(|comp| comphandles.lookup(instance, comp).map(|ch| ch.instance))
275 .unwrap_or(Some(instance))
276 .ok_or(TwzError::INVALID_ARGUMENT)?;
277
278 let name = comps.get(comp_id)?.name.clone();
279 let pt = comps.get_mut(instance)?.get_per_thread(thread);
280 let name_len = pt.write_bytes(name.as_bytes());
281 let comp = comps.get(comp_id)?;
282 let nr_libs = dynlink
283 .get_compartment(comp.compartment_id)
284 .ok()
285 .ok_or(TwzError::INVALID_ARGUMENT)?
286 .library_ids()
287 .count();
288
289 Ok(CompartmentInfoRaw {
290 name_len,
291 id: comp_id,
292 sctx: comp.sctx,
293 flags: comp.raw_flags(),
294 nr_libs,
295 exit_code: comp.read_error_code(),
296 })
297 }
298
299 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
302 pub fn get_compartment_gate_address(
303 &self,
304 instance: ObjID,
305 thread: ObjID,
306 desc: Option<Descriptor>,
307 name_len: usize,
308 ) -> Result<usize, TwzError> {
309 let name = self.read_thread_simple_buffer(instance, thread, name_len)?;
310 let (_, ref comps, ref dynlink, _, ref comphandles) =
311 *self.locks.lock(ThreadKey::get().unwrap());
312 let comp_id = desc
313 .map(|comp| comphandles.lookup(instance, comp).map(|ch| ch.instance))
314 .unwrap_or(Some(instance))
315 .ok_or(TwzError::INVALID_ARGUMENT)?;
316 let name = String::from_utf8(name)
317 .ok()
318 .ok_or(TwzError::INVALID_ARGUMENT)?;
319
320 let comp = comps.get(comp_id)?;
321 let dc = dynlink
322 .get_compartment(comp.compartment_id)
323 .ok()
324 .ok_or(TwzError::INVALID_ARGUMENT)?;
325 for lid in dc.library_ids() {
326 let lib = dynlink
327 .get_library(lid)
328 .map_err(|_| GenericError::Internal)?;
329 if let Some(gates) = lib.iter_secgates() {
330 for gate in gates {
331 if gate.name().to_str().ok() == Some(name.as_str()) {
332 return Ok(gate.imp);
333 }
334 }
335 }
336 }
337 Err(NamingError::NotFound.into())
338 }
339
340 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
342 pub fn get_compartment_handle(
343 &self,
344 caller: ObjID,
345 compartment: ObjID,
346 ) -> Result<Descriptor, TwzError> {
347 let (_, ref mut comps, _, _, ref mut ch) = *self.locks.lock(ThreadKey::get().unwrap());
348 let comp = comps.get_mut(compartment)?;
349 comp.inc_use_count();
350 ch.insert(
351 caller,
352 super::CompartmentHandle {
353 instance: if compartment.raw() == 0 {
354 caller
355 } else {
356 compartment
357 },
358 },
359 )
360 .ok_or(ResourceError::OutOfResources.into())
361 }
362
363 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
365 pub fn lookup_compartment_id(
366 &self,
367 instance: ObjID,
368 thread: ObjID,
369 comp: ObjID,
370 ) -> Result<Descriptor, TwzError> {
371 let (_, ref mut comps, _, _, ref mut ch) = *self.locks.lock(ThreadKey::get().unwrap());
372 let comp = comps.get_mut(comp)?;
373 comp.inc_use_count();
374 ch.insert(
375 instance,
376 super::CompartmentHandle {
377 instance: comp.instance,
378 },
379 )
380 .ok_or(ResourceError::OutOfResources.into())
381 }
382
383 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
385 pub fn lookup_compartment(
386 &self,
387 instance: ObjID,
388 thread: ObjID,
389 name_len: usize,
390 ) -> Result<Descriptor, TwzError> {
391 let name = self.read_thread_simple_buffer(instance, thread, name_len)?;
392 let name = String::from_utf8(name)
393 .ok()
394 .ok_or(TwzError::INVALID_ARGUMENT)?;
395 let (_, ref mut comps, _, _, ref mut ch) = *self.locks.lock(ThreadKey::get().unwrap());
396 let comp = comps.get_name_mut(&name)?;
397 comp.inc_use_count();
398 ch.insert(
399 instance,
400 super::CompartmentHandle {
401 instance: comp.instance,
402 },
403 )
404 .ok_or(ResourceError::OutOfResources.into())
405 }
406
407 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
408 pub fn compartment_wait(&self, caller: ObjID, desc: Option<Descriptor>, flags: u64) -> u64 {
409 let Some(instance) = ({
410 let comphandles = self._compartment_handles.write(ThreadKey::get().unwrap());
411 let comp_id = desc
412 .map(|comp| comphandles.lookup(caller, comp).map(|ch| ch.instance))
413 .unwrap_or(Some(caller));
414 comp_id
415 }) else {
416 return 0;
417 };
418 self.wait_for_compartment_state_change(instance, flags);
419 self.load_compartment_flags(instance)
420 }
421
422 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
424 pub fn get_compartment_deps(
425 &self,
426 caller: ObjID,
427 desc: Option<Descriptor>,
428 dep_n: usize,
429 ) -> Result<Descriptor, TwzError> {
430 let dep = {
431 let (_, ref mut comps, _, _, ref mut comphandles) =
432 *self.locks.lock(ThreadKey::get().unwrap());
433 let comp_id = desc
434 .map(|comp| comphandles.lookup(caller, comp).map(|ch| ch.instance))
435 .unwrap_or(Some(caller))
436 .ok_or(ArgumentError::InvalidArgument)?;
437 let comp = comps.get_mut(comp_id)?;
438 comp.deps.get(dep_n).cloned()
439 }
440 .ok_or(TwzError::INVALID_ARGUMENT)?;
441 self.get_compartment_handle(caller, dep)
442 }
443
444 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
446 pub fn get_compartment_thread_info(
447 &self,
448 caller: ObjID,
449 desc: Option<Descriptor>,
450 t_n: usize,
451 ) -> Result<ThreadInfo, TwzError> {
452 let dep = {
453 let (_, ref mut comps, _, _, ref mut comphandles) =
454 *self.locks.lock(ThreadKey::get().unwrap());
455 let comp_id = desc
456 .map(|comp| comphandles.lookup(caller, comp).map(|ch| ch.instance))
457 .unwrap_or(Some(caller))
458 .ok_or(ArgumentError::InvalidArgument)?;
459 let comp = comps.get_mut(comp_id)?;
460 comp.get_nth_thread_info(t_n)
461 }
462 .ok_or(TwzError::INVALID_ARGUMENT);
463 dep
464 }
465
466 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
468 pub fn load_compartment(
469 &self,
470 caller: ObjID,
471 thread: ObjID,
472 root_object: ObjID,
473 name_len: usize,
474 args_len: usize,
475 env_len: usize,
476 new_comp_flags: NewCompartmentFlags,
477 config: *const CompartmentLoaderConfig,
478 ) -> Result<Descriptor, TwzError> {
479 let _start_1 = Instant::now();
481 let config = unsafe { config.read() };
482 let total_bytes = name_len + args_len + env_len;
483 let str_bytes = self.read_thread_simple_buffer(caller, thread, total_bytes)?;
484 let name_bytes = &str_bytes[0..name_len];
485 let arg_bytes = &str_bytes[name_len..(name_len + args_len)];
486 let env_bytes = &str_bytes[(name_len + args_len)..total_bytes];
487
488 let input = String::from_utf8_lossy(&name_bytes);
489 let mut split = input.split("::");
490 let compname = split.next().ok_or(TwzError::INVALID_ARGUMENT)?;
491 let libname = split.next().ok_or(TwzError::INVALID_ARGUMENT)?;
492 let root = UnloadedLibrary::new_object(libname, root_object);
493
494 let args_bytes = arg_bytes.split_inclusive(|b| *b == 0);
496 let args = args_bytes
497 .map(CStr::from_bytes_with_nul)
498 .try_collect::<Vec<_>>()
499 .map_err(|_| TwzError::INVALID_ARGUMENT)?;
500 tracing::debug!("load {}: args: {:?}", compname, args);
501
502 let envs_bytes = env_bytes.split_inclusive(|b| *b == 0);
504 let env = envs_bytes
505 .map(CStr::from_bytes_with_nul)
506 .try_collect::<Vec<_>>()
507 .map_err(|_| TwzError::INVALID_ARGUMENT)?;
508
509 let extras = env
510 .iter()
511 .filter_map(|item| {
512 let item = item.to_str().ok()?;
513 if item.starts_with("LD_PRELOAD=") {
514 Some(UnloadedLibrary::new(item.trim_start_matches("LD_PRELOAD=")))
515 } else {
516 None
517 }
518 })
519 .collect::<Vec<_>>();
520 tracing::debug!("ld preload extras: {:?}", extras);
521 let extras_sctx = env
522 .iter()
523 .filter_map(|item| {
524 let item = item.to_str().ok()?;
525 if item.starts_with("SCTX_PRELOAD=") {
526 Some(UnloadedLibrary::new(
527 item.trim_start_matches("SCTX_PRELOAD="),
528 ))
529 } else {
530 None
531 }
532 })
533 .collect::<Vec<_>>();
534 tracing::debug!("sctx preload extras: {:?}", extras);
535
536 let mondebug = env
537 .iter()
538 .find(|s| s.to_string_lossy().starts_with("MONDEBUG="))
539 .is_some();
540
541 let _start_2 = Instant::now();
542 let loader = {
543 let mut dynlink = self.dynlink.write(ThreadKey::get().unwrap());
544 loader::RunCompLoader::new(
545 *dynlink,
546 compname,
547 root,
548 &extras,
549 &extras_sctx,
550 new_comp_flags,
551 mondebug,
552 )
553 }
554 .inspect_err(|e| tracing::error!("failed to load new compartment: {}", e))
555 .map_err(|_| GenericError::Internal)?;
556
557 let root_comp = {
558 let (_, ref mut cmp, ref mut dynlink, _, _) =
559 &mut *self.locks.lock(ThreadKey::get().unwrap());
560
561 let controller = match config.controller {
562 ControllerOption::Inherit => cmp.get(caller)?.controller,
563 ControllerOption::NoController => None,
564 ControllerOption::Object(id) => Some(id),
565 };
566 loader
568 .build_rcs(
569 &mut *cmp,
570 &mut *dynlink,
571 mondebug,
572 new_comp_flags.contains(NewCompartmentFlags::DEBUG),
573 controller,
574 config,
575 )
576 .inspect_err(|e| tracing::error!("failed to setup new compartment: {}", e))
577 .map_err(|_| GenericError::Internal)?
578 };
579 tracing::trace!("loaded {} as {}", compname, root_comp);
580
581 let desc = self.get_compartment_handle(caller, root_comp)?;
582
583 let _start_3 = Instant::now();
584 self.start_compartment(
585 root_comp,
586 &args,
587 &env,
588 mondebug,
589 new_comp_flags.contains(NewCompartmentFlags::DEBUG),
590 )
591 .inspect_err(|e| tracing::error!("failed to start new compartment: {}", e))?;
592 tracing::trace!(
593 "parse strings in {}ms, load in {}ms, start in {}ms",
594 (_start_2 - _start_1).as_millis(),
595 (_start_3 - _start_2).as_millis(),
596 _start_3.elapsed().as_millis()
597 );
598
599 Ok(desc)
600 }
601
602 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
604 pub fn drop_compartment_handle(&self, caller: ObjID, desc: Descriptor) {
605 let comps = {
606 let (_, ref mut cmgr, ref mut dynlink, _, ref mut comp_handles) =
607 *self.locks.lock(ThreadKey::get().unwrap());
608 let comp = comp_handles.remove(caller, desc);
609
610 if let Some(comp) = comp {
611 cmgr.dec_use_count(comp.instance);
612 }
613 cmgr.process_cleanup_queue(&mut *dynlink)
614 };
615 drop(comps);
616 }
617
618 #[tracing::instrument(skip(self, f), level = tracing::Level::DEBUG)]
619 pub fn update_compartment_flags(
620 &self,
621 instance: ObjID,
622 f: impl FnOnce(u64) -> Option<u64>,
623 ) -> bool {
624 let mut cmp = self.comp_mgr.write(ThreadKey::get().unwrap());
625 cmp.update_compartment_flags(instance, f)
626 }
627
628 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
629 pub fn load_compartment_flags(&self, instance: ObjID) -> u64 {
630 let cmp = self.comp_mgr.write(ThreadKey::get().unwrap());
631 cmp.load_compartment_flags(instance)
632 }
633
634 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
635 pub fn wait_for_compartment_state_change(&self, instance: ObjID, state: u64) {
636 let mut sl = {
637 let cmp = self.comp_mgr.write(ThreadKey::get().unwrap());
638 let Ok(sl) = cmp.wait_for_compartment_state_change(instance, state) else {
639 return;
640 };
641
642 if sl.iter().any(|sl| sl.ready()) {
643 return;
644 }
645 drop(cmp);
646 sl
647 };
648
649 let _ = sys_thread_sync(&mut sl, None);
650 }
651}
652
653pub struct CompartmentHandle {
655 pub instance: ObjID,
656}