1use std::{collections::HashMap, ffi::CStr};
2
3use dynlink::{
4 compartment::{Compartment, CompartmentId},
5 context::{Context, LoadedOrUnloaded, NewCompartmentFlags},
6 library::UnloadedLibrary,
7};
8use happylock::ThreadKey;
9use monitor_api::MONITOR_INSTANCE_ID;
10use secgate::util::Descriptor;
11use twizzler_abi::syscall::{sys_thread_sync, ThreadSync, ThreadSyncSleep};
12use twizzler_rt_abi::{
13 error::{ArgumentError, GenericError, NamingError, ResourceError, TwzError},
14 object::ObjID,
15};
16
17use crate::gates::{CompartmentInfo, CompartmentMgrStats, ThreadInfo};
18
19mod compconfig;
20mod compthread;
21mod loader;
22mod runcomp;
23
24pub use compconfig::*;
25pub(crate) use compthread::StackObject;
26pub use runcomp::*;
27
28#[derive(Default)]
30pub struct CompartmentMgr {
31 names: HashMap<String, ObjID>,
32 instances: HashMap<ObjID, RunComp>,
33 dynlink_map: HashMap<CompartmentId, ObjID>,
34 cleanup_queue: Vec<RunComp>,
35}
36
37impl CompartmentMgr {
38 pub fn get(&self, id: ObjID) -> Result<&RunComp, TwzError> {
40 self.instances.get(&id).ok_or(TwzError::INVALID_ARGUMENT)
41 }
42
43 pub fn _get_name(&self, name: &str) -> Result<&RunComp, TwzError> {
45 let id = self.names.get(name).ok_or(TwzError::INVALID_ARGUMENT)?;
46 self.get(*id)
47 }
48
49 pub fn get_mut(&mut self, id: ObjID) -> Result<&mut RunComp, TwzError> {
51 self.instances
52 .get_mut(&id)
53 .ok_or(TwzError::INVALID_ARGUMENT)
54 }
55
56 pub fn get_name_mut(&mut self, name: &str) -> Result<&mut RunComp, TwzError> {
58 let id = self.names.get(name).ok_or(TwzError::INVALID_ARGUMENT)?;
59 self.get_mut(*id)
60 }
61
62 pub fn get_dynlinkid(&self, id: CompartmentId) -> Result<&RunComp, TwzError> {
64 let id = self
65 .dynlink_map
66 .get(&id)
67 .ok_or(TwzError::INVALID_ARGUMENT)?;
68 self.get(*id)
69 }
70
71 pub fn _get_dynlinkid_mut(&mut self, id: CompartmentId) -> Result<&mut RunComp, TwzError> {
73 let id = self
74 .dynlink_map
75 .get(&id)
76 .ok_or(TwzError::INVALID_ARGUMENT)?;
77 self.get_mut(*id)
78 }
79
80 pub fn insert(&mut self, mut rc: RunComp) {
82 if self.names.contains_key(&rc.name) {
83 rc.name = format!("{}-dup", rc.name);
85 return self.insert(rc);
86 }
87 self.names.insert(rc.name.clone(), rc.instance);
88 self.dynlink_map.insert(rc.compartment_id, rc.instance);
89 self.instances.insert(rc.instance, rc);
90 }
91
92 pub fn remove(&mut self, id: ObjID) -> Option<RunComp> {
94 let rc = self.instances.remove(&id)?;
95 self.names.remove(&rc.name);
96 self.dynlink_map.remove(&rc.compartment_id);
97 Some(rc)
98 }
99
100 pub fn _get_monitor(&self) -> &RunComp {
102 self.get(MONITOR_INSTANCE_ID).unwrap()
104 }
105
106 pub fn _get_monitor_mut(&mut self) -> &mut RunComp {
108 self.get_mut(MONITOR_INSTANCE_ID).unwrap()
110 }
111
112 pub fn _compartments(&self) -> impl Iterator<Item = &RunComp> {
114 self.instances.values()
115 }
116
117 pub fn compartments_mut(&mut self) -> impl Iterator<Item = &mut RunComp> {
119 self.instances.values_mut()
120 }
121
122 fn update_compartment_flags(
123 &mut self,
124 instance: ObjID,
125 f: impl FnOnce(u64) -> Option<u64>,
126 ) -> bool {
127 let Ok(rc) = self.get_mut(instance) else {
128 return false;
129 };
130
131 let flags = rc.raw_flags();
132 let Some(new_flags) = f(flags) else {
133 return false;
134 };
135 if flags == new_flags {
136 return true;
137 }
138
139 rc.cas_flag(flags, new_flags).is_ok()
140 }
141
142 fn load_compartment_flags(&self, instance: ObjID) -> u64 {
143 let Ok(rc) = self.get(instance) else {
144 return 0;
145 };
146 rc.raw_flags()
147 }
148
149 fn wait_for_compartment_state_change(
150 &self,
151 instance: ObjID,
152 state: u64,
153 ) -> Result<ThreadSyncSleep, TwzError> {
154 let rc = self.get(instance)?;
155 Ok(rc.until_change(state))
156 }
157
158 pub fn main_thread_exited(&mut self, instance: ObjID) {
159 tracing::debug!("main thread for compartment {} exited", instance);
160 while !self.update_compartment_flags(instance, |old| Some(old | COMP_EXITED)) {}
161
162 let Ok(rc) = self.get(instance) else {
163 tracing::warn!("failed to find compartment {} during exit", instance);
164 return;
165 };
166 for dep in rc.deps.clone() {
167 self.dec_use_count(dep);
168 }
169
170 let Ok(rc) = self.get_mut(instance) else {
171 tracing::warn!("failed to find compartment {} during exit", instance);
172 return;
173 };
174 tracing::trace!("runcomp usecount: {}", rc.use_count);
175 if rc.use_count == 0 {
176 if let Some(rc) = self.remove(instance) {
177 self.cleanup_queue.push(rc)
178 }
179 }
180 }
181
182 pub fn dec_use_count(&mut self, instance: ObjID) {
183 let Ok(rc) = self.get_mut(instance) else {
184 return;
185 };
186
187 let z = rc.dec_use_count();
188 let ex = rc.has_flag(COMP_EXITED);
189 if z && ex {
190 if let Some(rc) = self.remove(instance) {
191 self.cleanup_queue.push(rc)
192 }
193 }
194 }
195
196 pub fn stat(&self) -> CompartmentMgrStats {
197 CompartmentMgrStats {
198 nr_compartments: self.instances.len(),
199 }
200 }
201
202 pub fn process_cleanup_queue(
203 &mut self,
204 dynlink: &mut Context,
205 ) -> (Vec<Option<Compartment>>, Vec<Vec<LoadedOrUnloaded>>) {
206 let (comps, libs) = self
207 .cleanup_queue
208 .drain(..)
209 .map(|c| dynlink.unload_compartment(c.compartment_id))
210 .unzip();
211 (comps, libs)
212 }
213}
214
215impl super::Monitor {
216 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
219 pub fn get_compartment_info(
220 &self,
221 instance: ObjID,
222 thread: ObjID,
223 desc: Option<Descriptor>,
224 ) -> Result<CompartmentInfo, TwzError> {
225 let (_, ref mut comps, ref dynlink, _, ref comphandles) =
226 *self.locks.lock(ThreadKey::get().unwrap());
227 let comp_id = desc
228 .map(|comp| comphandles.lookup(instance, comp).map(|ch| ch.instance))
229 .unwrap_or(Some(instance))
230 .ok_or(TwzError::INVALID_ARGUMENT)?;
231
232 let name = comps.get(comp_id)?.name.clone();
233 let pt = comps.get_mut(instance)?.get_per_thread(thread);
234 let name_len = pt.write_bytes(name.as_bytes());
235 let comp = comps.get(comp_id)?;
236 let nr_libs = dynlink
237 .get_compartment(comp.compartment_id)
238 .ok()
239 .ok_or(TwzError::INVALID_ARGUMENT)?
240 .library_ids()
241 .count();
242
243 Ok(CompartmentInfo {
244 name_len,
245 id: comp_id,
246 sctx: comp.sctx,
247 flags: comp.raw_flags(),
248 nr_libs,
249 })
250 }
251
252 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
255 pub fn get_compartment_gate_address(
256 &self,
257 instance: ObjID,
258 thread: ObjID,
259 desc: Option<Descriptor>,
260 name_len: usize,
261 ) -> Result<usize, TwzError> {
262 let name = self.read_thread_simple_buffer(instance, thread, name_len)?;
263 let (_, ref comps, ref dynlink, _, ref comphandles) =
264 *self.locks.lock(ThreadKey::get().unwrap());
265 let comp_id = desc
266 .map(|comp| comphandles.lookup(instance, comp).map(|ch| ch.instance))
267 .unwrap_or(Some(instance))
268 .ok_or(TwzError::INVALID_ARGUMENT)?;
269 let name = String::from_utf8(name)
270 .ok()
271 .ok_or(TwzError::INVALID_ARGUMENT)?;
272
273 let comp = comps.get(comp_id)?;
274 let dc = dynlink
275 .get_compartment(comp.compartment_id)
276 .ok()
277 .ok_or(TwzError::INVALID_ARGUMENT)?;
278 for lid in dc.library_ids() {
279 let lib = dynlink
280 .get_library(lid)
281 .map_err(|_| GenericError::Internal)?;
282 if let Some(gates) = lib.iter_secgates() {
283 for gate in gates {
284 if gate.name().to_str().ok() == Some(name.as_str()) {
285 return Ok(gate.imp);
286 }
287 }
288 }
289 }
290 Err(NamingError::NotFound.into())
291 }
292
293 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
295 pub fn get_compartment_handle(
296 &self,
297 caller: ObjID,
298 compartment: ObjID,
299 ) -> Result<Descriptor, TwzError> {
300 let (_, ref mut comps, _, _, ref mut ch) = *self.locks.lock(ThreadKey::get().unwrap());
301 let comp = comps.get_mut(compartment)?;
302 comp.inc_use_count();
303 ch.insert(
304 caller,
305 super::CompartmentHandle {
306 instance: if compartment.raw() == 0 {
307 caller
308 } else {
309 compartment
310 },
311 },
312 )
313 .ok_or(ResourceError::OutOfResources.into())
314 }
315
316 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
318 pub fn lookup_compartment(
319 &self,
320 instance: ObjID,
321 thread: ObjID,
322 name_len: usize,
323 ) -> Result<Descriptor, TwzError> {
324 let name = self.read_thread_simple_buffer(instance, thread, name_len)?;
325 let name = String::from_utf8(name)
326 .ok()
327 .ok_or(TwzError::INVALID_ARGUMENT)?;
328 let (_, ref mut comps, _, _, ref mut ch) = *self.locks.lock(ThreadKey::get().unwrap());
329 let comp = comps.get_name_mut(&name)?;
330 comp.inc_use_count();
331 ch.insert(
332 instance,
333 super::CompartmentHandle {
334 instance: comp.instance,
335 },
336 )
337 .ok_or(ResourceError::OutOfResources.into())
338 }
339
340 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
341 pub fn compartment_wait(&self, caller: ObjID, desc: Option<Descriptor>, flags: u64) -> u64 {
342 let Some(instance) = ({
343 let comphandles = self._compartment_handles.write(ThreadKey::get().unwrap());
344 let comp_id = desc
345 .map(|comp| comphandles.lookup(caller, comp).map(|ch| ch.instance))
346 .unwrap_or(Some(caller));
347 comp_id
348 }) else {
349 return 0;
350 };
351 self.wait_for_compartment_state_change(instance, flags);
352 self.load_compartment_flags(instance)
353 }
354
355 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
357 pub fn get_compartment_deps(
358 &self,
359 caller: ObjID,
360 desc: Option<Descriptor>,
361 dep_n: usize,
362 ) -> Result<Descriptor, TwzError> {
363 let dep = {
364 let (_, ref mut comps, _, _, ref mut comphandles) =
365 *self.locks.lock(ThreadKey::get().unwrap());
366 let comp_id = desc
367 .map(|comp| comphandles.lookup(caller, comp).map(|ch| ch.instance))
368 .unwrap_or(Some(caller))
369 .ok_or(ArgumentError::InvalidArgument)?;
370 let comp = comps.get_mut(comp_id)?;
371 comp.deps.get(dep_n).cloned()
372 }
373 .ok_or(TwzError::INVALID_ARGUMENT)?;
374 self.get_compartment_handle(caller, dep)
375 }
376
377 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
379 pub fn get_compartment_thread_info(
380 &self,
381 caller: ObjID,
382 desc: Option<Descriptor>,
383 t_n: usize,
384 ) -> Result<ThreadInfo, TwzError> {
385 let dep = {
386 let (_, ref mut comps, _, _, ref mut comphandles) =
387 *self.locks.lock(ThreadKey::get().unwrap());
388 let comp_id = desc
389 .map(|comp| comphandles.lookup(caller, comp).map(|ch| ch.instance))
390 .unwrap_or(Some(caller))
391 .ok_or(ArgumentError::InvalidArgument)?;
392 let comp = comps.get_mut(comp_id)?;
393 comp.get_nth_thread_info(t_n)
394 }
395 .ok_or(TwzError::INVALID_ARGUMENT);
396 dep
397 }
398
399 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
401 pub fn load_compartment(
402 &self,
403 caller: ObjID,
404 thread: ObjID,
405 name_len: usize,
406 args_len: usize,
407 env_len: usize,
408 new_comp_flags: NewCompartmentFlags,
409 ) -> Result<Descriptor, TwzError> {
410 let total_bytes = name_len + args_len + env_len;
411 let str_bytes = self.read_thread_simple_buffer(caller, thread, total_bytes)?;
412 let name_bytes = &str_bytes[0..name_len];
413 let arg_bytes = &str_bytes[name_len..(name_len + args_len)];
414 let env_bytes = &str_bytes[(name_len + args_len)..total_bytes];
415
416 let input = String::from_utf8_lossy(&name_bytes);
417 let mut split = input.split("::");
418 let compname = split.next().ok_or(TwzError::INVALID_ARGUMENT)?;
419 let libname = split.next().ok_or(TwzError::INVALID_ARGUMENT)?;
420 let root = UnloadedLibrary::new(libname);
421
422 let args_bytes = arg_bytes.split_inclusive(|b| *b == 0);
424 let args = args_bytes
425 .map(CStr::from_bytes_with_nul)
426 .try_collect::<Vec<_>>()
427 .map_err(|_| TwzError::INVALID_ARGUMENT)?;
428 tracing::debug!("load {}: args: {:?}", compname, args);
429
430 let envs_bytes = env_bytes.split_inclusive(|b| *b == 0);
432 let env = envs_bytes
433 .map(CStr::from_bytes_with_nul)
434 .try_collect::<Vec<_>>()
435 .map_err(|_| TwzError::INVALID_ARGUMENT)?;
436 tracing::trace!("load {}: env: {:?}", compname, env);
437
438 let extras = env
439 .iter()
440 .filter_map(|item| {
441 let item = item.to_str().ok()?;
442 if item.starts_with("LD_PRELOAD=") {
443 Some(UnloadedLibrary::new(item.trim_start_matches("LD_PRELOAD=")))
444 } else {
445 None
446 }
447 })
448 .collect::<Vec<_>>();
449 tracing::debug!("ld preload extras: {:?}", extras);
450
451 let mondebug = env
452 .iter()
453 .find(|s| s.to_string_lossy().starts_with("MONDEBUG="))
454 .is_some();
455 let loader = {
456 let mut dynlink = self.dynlink.write(ThreadKey::get().unwrap());
457 loader::RunCompLoader::new(*dynlink, compname, root, &extras, new_comp_flags, mondebug)
458 }
459 .map_err(|_| GenericError::Internal)?;
460
461 let root_comp = {
462 let (_, ref mut cmp, ref mut dynlink, _, _) =
463 &mut *self.locks.lock(ThreadKey::get().unwrap());
464 loader
466 .build_rcs(
467 &mut *cmp,
468 &mut *dynlink,
469 mondebug,
470 new_comp_flags.contains(NewCompartmentFlags::DEBUG),
471 )
472 .map_err(|_| GenericError::Internal)?
473 };
474 tracing::trace!("loaded {} as {}", compname, root_comp);
475
476 let desc = self.get_compartment_handle(caller, root_comp)?;
477
478 self.start_compartment(
479 root_comp,
480 &args,
481 &env,
482 mondebug,
483 new_comp_flags.contains(NewCompartmentFlags::DEBUG),
484 )?;
485
486 Ok(desc)
487 }
488
489 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
491 pub fn drop_compartment_handle(&self, caller: ObjID, desc: Descriptor) {
492 let comps = {
493 let (_, ref mut cmgr, ref mut dynlink, _, ref mut comp_handles) =
494 *self.locks.lock(ThreadKey::get().unwrap());
495 let comp = comp_handles.remove(caller, desc);
496
497 if let Some(comp) = comp {
498 cmgr.dec_use_count(comp.instance);
499 }
500 cmgr.process_cleanup_queue(&mut *dynlink)
501 };
502 tracing::trace!("HRE");
503 drop(comps);
504 }
505
506 #[tracing::instrument(skip(self, f), level = tracing::Level::DEBUG)]
507 pub fn update_compartment_flags(
508 &self,
509 instance: ObjID,
510 f: impl FnOnce(u64) -> Option<u64>,
511 ) -> bool {
512 let mut cmp = self.comp_mgr.write(ThreadKey::get().unwrap());
513 cmp.update_compartment_flags(instance, f)
514 }
515
516 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
517 pub fn load_compartment_flags(&self, instance: ObjID) -> u64 {
518 let cmp = self.comp_mgr.write(ThreadKey::get().unwrap());
519 cmp.load_compartment_flags(instance)
520 }
521
522 #[tracing::instrument(skip(self), level = tracing::Level::DEBUG)]
523 pub fn wait_for_compartment_state_change(&self, instance: ObjID, state: u64) {
524 let sl = {
525 let cmp = self.comp_mgr.write(ThreadKey::get().unwrap());
526 let Ok(sl) = cmp.wait_for_compartment_state_change(instance, state) else {
527 return;
528 };
529
530 if sl.ready() {
531 return;
532 }
533 drop(cmp);
534 sl
535 };
536
537 let _ = sys_thread_sync(&mut [ThreadSync::new_sleep(sl)], None);
538 }
539}
540
541pub struct CompartmentHandle {
543 pub instance: ObjID,
544}