1use reverie::Error;
39use reverie::Guest;
40use reverie::reserved_input::InputInjectionError;
41use reverie::reserved_input::InputUnavailable;
42use reverie::reserved_input::InputWrite;
43use reverie::reserved_input::ReservedInputImage;
44use reverie::syscalls;
45use reverie::syscalls::Addr;
46use reverie::syscalls::AddrMut;
47use reverie::syscalls::Errno;
48use reverie::syscalls::MemoryAccess;
49use reverie::syscalls::Syscall;
50use reverie::syscalls::SyscallArgs;
51use reverie::syscalls::SyscallInfo;
52use reverie::syscalls::Sysno;
53use serde::Deserialize;
54use serde::Serialize;
55use tracing::debug;
56use tracing::warn;
57
58use crate::Detcore;
59use crate::record_or_replay::RecordOrReplay;
60use crate::types::DetTid;
61
62#[cfg(test)]
63mod injection_tests;
64
65pub(crate) const CLONE_ARGS_SIZE_VER2: usize = 88;
70const CLONE_ARGS_SIZE_VER1: usize = 80;
72const CLONE_ARGS_SIZE_VER0: usize = 64;
74const SET_TID_OFFSET: usize = 64;
75
76#[cfg(target_arch = "x86_64")]
81const USER_ADDRESS_LIMIT: u64 = (1 << 47) - 4096;
82#[cfg(not(target_arch = "x86_64"))]
85const USER_ADDRESS_LIMIT: u64 = 1 << 48;
86const SET_TID_SIZE_OFFSET: usize = 72;
87
88#[derive(Clone, Debug, PartialEq, Eq)]
90pub(crate) enum ChosenIdCall {
91 Clone3 {
95 args: [u8; CLONE_ARGS_SIZE_VER2],
96 size: usize,
97 },
98 Unchanged,
105 Invalid(Errno),
108 GuestChosen {
113 args: [u8; CLONE_ARGS_SIZE_VER2],
114 size: usize,
115 },
116 Inexpressible(String),
118}
119
120impl ChosenIdCall {
121 pub(crate) fn with_set_tid(
124 mut args: [u8; CLONE_ARGS_SIZE_VER2],
125 set_tid_address: u64,
126 set_tid_size: usize,
127 ) -> [u8; CLONE_ARGS_SIZE_VER2] {
128 args[SET_TID_OFFSET..SET_TID_OFFSET + 8].copy_from_slice(&set_tid_address.to_ne_bytes());
129 args[SET_TID_SIZE_OFFSET..SET_TID_SIZE_OFFSET + 8]
130 .copy_from_slice(&(set_tid_size as u64).to_ne_bytes());
131 args
132 }
133}
134
135pub(crate) fn clone3_form(
163 sysno: Sysno,
164 args: &SyscallArgs,
165 clone3_args: Option<&[u8]>,
166) -> ChosenIdCall {
167 let fields = match sysno {
168 #[cfg(not(target_arch = "aarch64"))]
169 Sysno::fork => LegacyClone {
170 flags: 0,
171 exit_signal: libc::SIGCHLD as u64,
172 ..LegacyClone::default()
173 },
174 #[cfg(not(target_arch = "aarch64"))]
175 Sysno::vfork => LegacyClone {
176 flags: (libc::CLONE_VFORK | libc::CLONE_VM) as u64,
177 exit_signal: libc::SIGCHLD as u64,
178 ..LegacyClone::default()
179 },
180 Sysno::clone => LegacyClone::of_clone(args, CloneArgOrder::NATIVE),
181 Sysno::clone3 => return clone3_with_room_for_set_tid(clone3_args),
182 _ => {
183 return ChosenIdCall::Inexpressible(format!(
184 "{sysno} is not a clone-family call Detcore knows"
185 ));
186 }
187 };
188 fields.as_clone3()
189}
190
191#[derive(Clone, Copy, Debug, Default)]
193struct LegacyClone {
194 flags: u64,
195 pidfd: u64,
196 child_tid: u64,
197 parent_tid: u64,
198 exit_signal: u64,
199 stack: u64,
200 tls: u64,
201}
202
203#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206enum CloneArgOrder {
207 #[cfg_attr(
209 target_arch = "aarch64",
210 allow(
211 dead_code,
212 reason = "the other architecture's order, built for its test"
213 )
214 )]
215 ChildTidThenTls,
216 #[cfg_attr(
219 not(target_arch = "aarch64"),
220 allow(
221 dead_code,
222 reason = "the other architecture's order, built for its test"
223 )
224 )]
225 TlsThenChildTid,
226}
227
228impl CloneArgOrder {
229 #[cfg(target_arch = "aarch64")]
230 const NATIVE: Self = CloneArgOrder::TlsThenChildTid;
231 #[cfg(not(target_arch = "aarch64"))]
232 const NATIVE: Self = CloneArgOrder::ChildTidThenTls;
233}
234
235impl LegacyClone {
236 fn of_clone(args: &SyscallArgs, order: CloneArgOrder) -> Self {
240 let low = args.arg0 as u32 as u64;
241 let csignal = libc::CSIGNAL as u64;
242 let (child_tid, tls) = match order {
243 CloneArgOrder::ChildTidThenTls => (args.arg3, args.arg4),
244 CloneArgOrder::TlsThenChildTid => (args.arg4, args.arg3),
245 };
246 LegacyClone {
247 flags: low & !csignal,
248 pidfd: args.arg2 as u64,
249 child_tid: child_tid as u64,
250 parent_tid: args.arg2 as u64,
251 exit_signal: low & csignal,
252 stack: args.arg1 as u64,
253 tls: tls as u64,
254 }
255 }
256
257 fn as_clone3(self) -> ChosenIdCall {
258 let shares_parent = (libc::CLONE_THREAD | libc::CLONE_PARENT) as u64;
259 if self.flags & shares_parent != 0 && self.exit_signal > 64 {
266 return ChosenIdCall::Invalid(Errno::EINVAL);
267 }
268 let thread = self.flags & libc::CLONE_THREAD as u64 != 0;
274 let sighand = self.flags & libc::CLONE_SIGHAND as u64 != 0;
275 let vm = self.flags & libc::CLONE_VM as u64 != 0;
276 if (thread && !sighand) || (sighand && !vm) {
277 return ChosenIdCall::Unchanged;
278 }
279 let detached = libc::CLONE_DETACHED as u64;
280 let pidfd = libc::CLONE_PIDFD as u64;
281 if self.flags & pidfd != 0 && self.flags & detached != 0 {
286 return ChosenIdCall::Unchanged;
287 }
288 let exit_signal = if self.flags & shares_parent != 0 {
289 0
290 } else {
291 self.exit_signal
292 };
293 if exit_signal > 64 {
294 return ChosenIdCall::Inexpressible(format!(
295 "its exit signal {exit_signal} is not a signal number, which clone3 refuses"
296 ));
297 }
298 let (stack, stack_size) = match self.stack {
299 0 => (0, 0),
300 sp if sp == 1 || sp > USER_ADDRESS_LIMIT => {
303 return ChosenIdCall::Inexpressible(format!(
304 "its new stack pointer {sp:#x} has no clone3 form, which needs a nonzero \
305 stack in user memory below it"
306 ));
307 }
308 sp => (sp - 1, 1),
309 };
310 let fields: [u64; 11] = [
311 self.flags & !detached,
312 self.pidfd,
313 self.child_tid,
314 self.parent_tid,
315 exit_signal,
316 stack,
317 stack_size,
318 self.tls,
319 0, 0, 0, ];
323 let mut args = [0u8; CLONE_ARGS_SIZE_VER2];
324 for (chunk, field) in args.as_chunks_mut::<8>().0.iter_mut().zip(fields) {
325 *chunk = field.to_ne_bytes();
326 }
327 ChosenIdCall::Clone3 {
328 args,
329 size: CLONE_ARGS_SIZE_VER1,
330 }
331 }
332}
333
334fn clone3_with_room_for_set_tid(bytes: Option<&[u8]>) -> ChosenIdCall {
343 let Some(bytes) = bytes else {
344 return ChosenIdCall::Inexpressible(
347 "Detcore could not read its struct clone_args with the guest's own access, though \
348 it is mapped"
349 .to_owned(),
350 );
351 };
352 let size = bytes.len();
353 if size < CLONE_ARGS_SIZE_VER0 {
354 return ChosenIdCall::Unchanged;
356 }
357 if bytes
358 .iter()
359 .skip(CLONE_ARGS_SIZE_VER2)
360 .any(|byte| *byte != 0)
361 {
362 return ChosenIdCall::Inexpressible(format!(
365 "its struct clone_args has nonzero bytes past the {CLONE_ARGS_SIZE_VER2} Detcore \
366 knows, which a newer kernel may read"
367 ));
368 }
369 let mut args = [0u8; CLONE_ARGS_SIZE_VER2];
370 let known = size.min(CLONE_ARGS_SIZE_VER2);
371 args[..known].copy_from_slice(&bytes[..known]);
372 if args[SET_TID_OFFSET..CLONE_ARGS_SIZE_VER1]
373 .iter()
374 .any(|byte| *byte != 0)
375 {
376 return ChosenIdCall::GuestChosen {
380 args,
381 size: known.max(CLONE_ARGS_SIZE_VER1),
382 };
383 }
384 ChosenIdCall::Clone3 {
385 args,
386 size: known.max(CLONE_ARGS_SIZE_VER1),
387 }
388}
389
390#[derive(Debug)]
393pub(crate) struct ChosenId {
394 set_tid: Vec<libc::pid_t>,
396 id: Option<i32>,
399 fresh_allocation: bool,
402 restart: Option<Vec<u8>>,
404 args: [u8; CLONE_ARGS_SIZE_VER2],
406 size: usize,
408 untraced: bool,
410 guest_entries: bool,
412}
413
414#[derive(Debug)]
433struct AddedIdEvidence {
434 id: i32,
435 maximum: i32,
436 args: [u8; CLONE_ARGS_SIZE_VER2],
437 size: usize,
438 set_tid: Vec<libc::pid_t>,
439}
440
441impl AddedIdEvidence {
442 fn capture<M: MemoryAccess>(
443 memory: &M,
444 tid: i32,
445 call: &Syscall,
446 chosen: &ChosenId,
447 maximum: Option<i32>,
448 ) -> Option<Self> {
449 let id = chosen.id?;
450 let maximum = maximum?;
451 if !chosen.fresh_allocation || id <= 0 || id >= maximum {
452 return None;
453 }
454 let Syscall::Clone3(original) = call else {
458 return None;
459 };
460 if original.size() != chosen.size
461 || !(CLONE_ARGS_SIZE_VER1..=CLONE_ARGS_SIZE_VER2).contains(&chosen.size)
462 {
463 return None;
464 }
465 let Clone3Read::Bytes(bytes) = read_clone_args(memory, FaultWitness::Unproven, original)
466 else {
467 return None;
468 };
469 let mut original_args = [0; CLONE_ARGS_SIZE_VER2];
470 original_args[..bytes.len()].copy_from_slice(&bytes);
471 if original_args != chosen.args {
472 return None;
473 }
474 let flags = u64::from_ne_bytes(original_args[..8].try_into().unwrap());
475 let depth = pid_namespace_depth(tid, flags)?;
476 if chosen.set_tid.len() != depth + 1
477 || chosen.set_tid.len() > MAX_SET_TID
478 || chosen.set_tid.last() != Some(&id)
479 {
480 return None;
481 }
482 if chosen.guest_entries {
483 let original_entries =
484 read_guest_set_tid(memory, FaultWitness::Unproven, &original_args).ok()?;
485 if original_entries != chosen.set_tid[..depth] {
486 return None;
487 }
488 } else if original_args[SET_TID_OFFSET..SET_TID_SIZE_OFFSET + 8] != [0; 16]
489 || !(depth == 0
490 || (depth == 1 && flags & libc::CLONE_NEWPID as u64 != 0 && chosen.set_tid[0] == 1))
491 {
492 return None;
493 }
494 Some(Self {
495 id,
496 maximum,
497 args: original_args,
498 size: chosen.size,
499 set_tid: chosen.set_tid.clone(),
500 })
501 }
502
503 fn staged_matches(
504 &self,
505 image: &ReservedInputImage,
506 rewritten: &syscalls::Clone3,
507 array: u64,
508 ) -> bool {
509 if rewritten.size() != self.size {
510 return false;
511 }
512 let Some(address) = rewritten.args() else {
513 return false;
514 };
515 let mut observed = [0; CLONE_ARGS_SIZE_VER2];
516 let expected = ChosenIdCall::with_set_tid(self.args, array, self.set_tid.len());
517 let mut entries = vec![0; self.set_tid.len() * std::mem::size_of::<libc::pid_t>()];
518 image.read(address.as_raw() as u64, &mut observed)
519 && observed == expected
520 && image.read(array, &mut entries)
521 && entries
522 .as_chunks::<4>()
523 .0
524 .iter()
525 .map(|entry| i32::from_ne_bytes(*entry))
526 .eq(self.set_tid.iter().copied())
527 }
528}
529
530fn reserved_clone_image(
534 layout: reverie::reserved_input::ReservedInputLayout,
535 original: Syscall,
536 args: [u8; CLONE_ARGS_SIZE_VER2],
537 entries: &[libc::pid_t],
538 size: usize,
539) -> Result<(ReservedInputImage, syscalls::Clone3, u64), InputUnavailable> {
540 use crate::reserved_input::Span;
541 use crate::reserved_input::effective_spans;
542 let page = Span::bytes(layout.base, layout.page_bytes as u64);
543 let excludes = |address: u64, bytes: u64| {
544 effective_spans(address, bytes)
545 .into_iter()
546 .any(|span| span.overlaps(page))
547 };
548 let field = |offset: usize| u64::from_ne_bytes(args[offset..offset + 8].try_into().unwrap());
549 let flags = field(0);
550 if let Syscall::Clone3(call) = original {
551 let address = call.args().map_or(0, |address| address.as_raw() as u64);
552 if excludes(address, call.size() as u64) {
553 return Err(InputUnavailable::new(
554 "clone-original-input-overlaps-reserved-page",
555 ));
556 }
557 }
558 if field(72) != 0
559 && field(72)
560 .checked_mul(4)
561 .is_none_or(|bytes| excludes(field(64), bytes))
562 {
563 return Err(InputUnavailable::new(
564 "clone-set-tid-input-overlaps-reserved-page",
565 ));
566 }
567 for (active, offset) in [
568 (libc::CLONE_PIDFD as u64, 8),
569 (
570 (libc::CLONE_CHILD_SETTID | libc::CLONE_CHILD_CLEARTID) as u64,
571 16,
572 ),
573 (libc::CLONE_PARENT_SETTID as u64, 24),
574 ] {
575 if flags & active != 0 && excludes(field(offset), 4) {
576 return Err(InputUnavailable::new(
577 "clone-native-output-overlaps-reserved-page",
578 ));
579 }
580 }
581 if field(48) != 0 && excludes(field(40), field(48)) {
582 return Err(InputUnavailable::new(
583 "clone-child-stack-overlaps-reserved-page",
584 ));
585 }
586 if flags & libc::CLONE_SETTLS as u64 != 0 && excludes(field(56), 1) {
587 return Err(InputUnavailable::new("clone-tls-overlaps-reserved-page"));
588 }
589 if entries.len() > MAX_SET_TID {
590 return Err(InputUnavailable::new("clone-set-tid-image-too-large"));
591 }
592 let mut array = [0; MAX_SET_TID * std::mem::size_of::<libc::pid_t>()];
593 for (entry, bytes) in entries.iter().zip(array.as_chunks_mut::<4>().0.iter_mut()) {
594 bytes.copy_from_slice(&entry.to_ne_bytes());
595 }
596 let array_address = layout.base;
597 let args_address = layout.base + array.len() as u64;
598 let staged_args = ChosenIdCall::with_set_tid(args, array_address, entries.len());
599 let image = ReservedInputImage::new(
600 layout,
601 &[
602 InputWrite {
603 offset: 0,
604 bytes: &array,
605 },
606 InputWrite {
607 offset: array.len(),
608 bytes: &staged_args,
609 },
610 ],
611 )?;
612 let rewritten = syscalls::Clone3::new()
613 .with_args(AddrMut::from_raw(args_address as usize))
614 .with_size(size);
615 Ok((image, rewritten, array_address))
616}
617
618#[derive(Debug)]
620pub(crate) enum ClonePlan {
621 Kernel,
624 Chosen(ChosenId),
626 AsMade { reason: String },
630 KernelRefuses,
635 KernelRefusesUntraced,
640 GuestChosen,
644}
645
646impl<T: RecordOrReplay> Detcore<T> {
647 pub(crate) async fn plan_clone_id<G: Guest<Self>>(
662 &self,
663 guest: &mut G,
664 call: &Syscall,
665 ) -> Result<ClonePlan, Error> {
666 let detcore_model::pid::GuestIds::Chosen {
667 refuse_host_ids, ..
668 } = self.cfg.guest_ids
669 else {
670 return Ok(ClonePlan::Kernel);
671 };
672 let (sysno, args) = call.into_parts();
673 let tid = guest.tid().as_raw();
676 let witness = FaultWitness::for_backend(&guest.config().backend, tid);
677 let clone3_read = match call {
678 Syscall::Clone3(clone) => Some(read_clone_args(&guest.memory(), witness, clone)),
679 _ => None,
680 };
681 if clone3_read == Some(Clone3Read::Refused) {
682 return Ok(ClonePlan::KernelRefuses);
683 }
684 let flags = match (&clone3_read, sysno) {
689 (_, Sysno::clone) => args.arg0 as u64,
690 (Some(Clone3Read::Bytes(bytes)), _) => bytes
691 .get(..8)
692 .map_or(0, |flags| u64::from_ne_bytes(flags.try_into().unwrap())),
693 (Some(Clone3Read::Unreadable { flags }), _) => {
694 flags.unwrap_or(libc::CLONE_UNTRACED as u64)
695 }
696 _ => 0,
697 };
698 let untraced = flags & libc::CLONE_UNTRACED as u64 != 0;
699 let clone3_args = match clone3_read {
700 Some(Clone3Read::Bytes(bytes)) => Some(bytes),
701 _ => None,
702 };
703 let form = clone3_form(sysno, &args, clone3_args.as_deref());
704 let restarted = guest.thread_state_mut().restarted_clone_id.take();
707 let tid = guest.tid().as_raw();
708 let (args, size, set_tid, guest_entries) = match form {
709 ChosenIdCall::Invalid(errno) => return Err(errno.into()),
710 ChosenIdCall::Clone3 { args, size } => {
711 let flags = u64::from_ne_bytes(args[..8].try_into().unwrap());
712 let inner = match pid_namespace_depth(tid, flags) {
717 Some(0) => Vec::new(),
718 Some(1) if flags & libc::CLONE_NEWPID as u64 != 0 => vec![1],
719 Some(depth) => {
720 let why = format!(
721 "its task is created {depth} PID namespace(s) below Hermit's, in one \
722 the guest made, whose IDs the kernel picks"
723 );
724 return self
725 .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
726 .await;
727 }
728 None => {
729 let why =
730 "the PID namespace its task is created in could not be read".to_owned();
731 return self
732 .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
733 .await;
734 }
735 };
736 if let Some(why) = self.clone3_could_fare_otherwise(guest) {
737 return self
738 .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
739 .await;
740 }
741 (args, size, inner, false)
742 }
743 ChosenIdCall::GuestChosen { args, size } => {
744 let tids = match read_guest_set_tid(&guest.memory(), witness, &args) {
750 Ok(tids) => tids,
751 Err(SetTidUnread::Refused) => return Ok(ClonePlan::GuestChosen),
752 Err(SetTidUnread::Unreadable) => {
753 let why = "Detcore could not read its set_tid array with the guest's own \
754 access"
755 .to_owned();
756 return self
757 .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
758 .await;
759 }
760 };
761 let flags = u64::from_ne_bytes(args[..8].try_into().unwrap());
762 match pid_namespace_depth(tid, flags) {
764 Some(depth) if tids.len() > depth => {
766 if !untraced {
767 return Ok(ClonePlan::GuestChosen);
768 }
769 let args = without_untraced(args);
771 return Ok(ClonePlan::Chosen(ChosenId {
772 set_tid: tids,
773 id: None,
774 fresh_allocation: false,
775 restart: None,
776 args,
777 size,
778 untraced,
779 guest_entries: true,
780 }));
781 }
782 Some(depth) if tids.len() == depth && depth < MAX_SET_TID => {
784 (args, size, tids, true)
785 }
786 Some(depth) => {
787 let why = format!(
788 "its set_tid names {} ID(s), but its task is created {depth} PID \
789 namespace(s) below Hermit's, so the IDs in between are the kernel's",
790 tids.len()
791 );
792 return self
793 .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
794 .await;
795 }
796 None => {
797 let why =
798 "the PID namespace its task is created in could not be read".to_owned();
799 return self
800 .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
801 .await;
802 }
803 }
804 }
805 ChosenIdCall::Unchanged if untraced => return Ok(ClonePlan::KernelRefusesUntraced),
809 ChosenIdCall::Unchanged => return Ok(ClonePlan::KernelRefuses),
810 ChosenIdCall::Inexpressible(why) => {
811 return self
812 .host_id_planned(guest, sysno, why, refuse_host_ids, untraced)
813 .await;
814 }
815 };
816 let args = without_untraced(args);
822 let call = RestartedCloneId::call(sysno, &args, size);
823 let (id, fresh_allocation) = match restarted {
824 Some(restarted) if restarted.call == call => (restarted.id, false),
825 _ => match crate::tool_global::next_guest_id(guest).await {
826 Some(id) => (id, true),
827 None => {
830 return self
831 .host_id_planned(
832 guest,
833 sysno,
834 "Detcore has no unused positive process ID left".to_owned(),
835 refuse_host_ids,
836 untraced,
837 )
838 .await;
839 }
840 },
841 };
842 let mut set_tid = set_tid;
843 set_tid.push(id);
844 Ok(ClonePlan::Chosen(ChosenId {
845 set_tid,
846 id: Some(id),
847 fresh_allocation,
848 restart: Some(call),
849 args,
850 size,
851 untraced,
852 guest_entries,
853 }))
854 }
855
856 fn clone3_could_fare_otherwise<G: Guest<Self>>(&self, guest: &G) -> Option<String> {
861 let tid = guest.tid().as_raw();
862 let expected = match self.cfg.inherited_seccomp {
863 detcore_model::config::InheritedSeccomp::Disabled => 1,
865 detcore_model::config::InheritedSeccomp::Filter(Some(filters)) => filters + 1,
866 _ => {
867 return Some(
868 "Hermit could not count the seccomp filters it runs under, so a filter of \
869 the guest's own could not be told from them"
870 .to_owned(),
871 );
872 }
873 };
874 let status = std::fs::read_to_string(format!("/proc/{tid}/status")).unwrap_or_default();
875 let filters = status
876 .lines()
877 .find_map(|line| line.strip_prefix("Seccomp_filters:"))
878 .and_then(|count| count.trim().parse::<u32>().ok());
879 match filters {
880 Some(filters) if filters <= expected => None,
881 Some(_) => Some(
882 "the guest installed a seccomp filter, which could trap, kill or refuse clone3 \
883 where it allows this call"
884 .to_owned(),
885 ),
886 None => Some(
887 "this kernel does not report the task's seccomp filters (Linux 5.9 and later \
888 do), so one of the guest's own could not be ruled out"
889 .to_owned(),
890 ),
891 }
892 }
893
894 async fn host_id_planned<G: Guest<Self>>(
902 &self,
903 guest: &mut G,
904 sysno: Sysno,
905 why: String,
906 refuse_host_ids: bool,
907 untraced: bool,
908 ) -> Result<ClonePlan, Error> {
909 let reason = format!("this {sysno} cannot be given a chosen process ID: {why}");
910 if untraced {
911 refuse_untraced(guest, reason).await
912 }
913 if refuse_host_ids {
914 refuse_host_id(guest, reason).await
915 }
916 Ok(ClonePlan::AsMade { reason })
917 }
918
919 fn free_base_hint(&self) -> String {
923 let base = self
924 .cfg
925 .guest_ids
926 .base()
927 .map_or_else(|| "?".to_owned(), |base| base.to_string());
928 match self
929 .cfg
930 .replay_preemptions_from
931 .as_ref()
932 .or(self.cfg.replay_schedule_from.as_ref())
933 {
934 Some(record) => format!(
935 "The replayed record {} takes base {base}; replay it where no host task holds \
936 the IDs from that base",
937 record.display()
938 ),
939 None => "Pass a --pid-base whose IDs no host task holds".to_owned(),
940 }
941 }
942
943 pub(crate) async fn gave_host_id<G: Guest<Self>>(
948 &self,
949 guest: &mut G,
950 child: i64,
951 reason: &str,
952 ) {
953 let reason = format!("task {child} got a host ID, not a chosen one: {reason}");
954 if matches!(
955 self.cfg.guest_ids,
956 detcore_model::pid::GuestIds::Chosen {
957 refuse_host_ids: true,
958 ..
959 }
960 ) {
961 refuse_host_id(guest, reason).await
962 }
963 warn!(
964 "[detcore, dtid {}] {reason}; that ID differs from run to run",
965 guest.thread_state().dettid
966 );
967 crate::tool_global::note_host_id_task(guest).await;
968 }
969
970 async fn vfork_gets_host_id<G: Guest<Self>>(&self, guest: &mut G, reason: &str) {
977 let reason = format!(
978 "this vfork's task gets a host ID, not a chosen one, reported before the call: \
979 {reason}"
980 );
981 if matches!(
982 self.cfg.guest_ids,
983 detcore_model::pid::GuestIds::Chosen {
984 refuse_host_ids: true,
985 ..
986 }
987 ) {
988 refuse_host_id(guest, reason).await
989 }
990 warn!(
991 "[detcore, dtid {}] {reason}; that ID differs from run to run",
992 guest.thread_state().dettid
993 );
994 crate::tool_global::note_host_id_task(guest).await;
995 }
996
997 async fn issue_reported_vfork<G: Guest<Self>>(
1002 &self,
1003 guest: &mut G,
1004 call: Syscall,
1005 reason: &str,
1006 ) -> Result<i64, Errno> {
1007 self.vfork_gets_host_id(guest, reason).await;
1008 let result = crate::reserved_input::inject_original_clone(guest, call).await;
1009 if let Err(errno) = result
1010 && !is_restart(errno)
1011 {
1012 warn!(
1013 "[detcore, dtid {}] the vfork reported above failed with {errno} and created no \
1014 task; the run's count of accepted host-ID reports includes it",
1015 guest.thread_state().dettid
1016 );
1017 }
1018 result
1019 }
1020
1021 async fn issue_kernel_refused<G: Guest<Self>>(
1025 &self,
1026 guest: &mut G,
1027 call: Syscall,
1028 ) -> Result<i64, Errno> {
1029 let result = crate::reserved_input::inject_original_clone(guest, call).await;
1030 if let Ok(child) = result {
1031 self.gave_host_id(
1032 guest,
1033 child,
1034 "unexpectedly, as Detcore expected every kernel to refuse it",
1035 )
1036 .await;
1037 }
1038 result
1039 }
1040
1041 pub(crate) async fn inject_clone_planned<G: Guest<Self>>(
1044 &self,
1045 guest: &mut G,
1046 call: Syscall,
1047 plan: ClonePlan,
1048 vfork: bool,
1049 ) -> Result<i64, Errno> {
1050 match plan {
1051 ClonePlan::Kernel | ClonePlan::GuestChosen => {
1052 crate::reserved_input::inject_original_clone(guest, call).await
1053 }
1054 ClonePlan::AsMade { reason } => {
1055 if vfork {
1056 return self.issue_reported_vfork(guest, call, &reason).await;
1057 }
1058 let result = crate::reserved_input::inject_original_clone(guest, call).await;
1059 if let Ok(child) = result {
1060 self.gave_host_id(guest, child, &reason).await;
1061 }
1062 result
1063 }
1064 ClonePlan::KernelRefuses => self.issue_kernel_refused(guest, call).await,
1065 ClonePlan::KernelRefusesUntraced => {
1066 let (sysno, args) = call.into_parts();
1067 self.issue_kernel_refused(
1068 guest,
1069 Syscall::from_raw(sysno, traced_legacy_clone(args)),
1070 )
1071 .await
1072 }
1073 ClonePlan::Chosen(chosen) => {
1074 self.inject_with_chosen_id(guest, call, chosen, vfork).await
1075 }
1076 }
1077 }
1078
1079 async fn stage_chosen_clone<G: Guest<Self>>(
1083 &self,
1084 guest: &mut G,
1085 call: Syscall,
1086 args: [u8; CLONE_ARGS_SIZE_VER2],
1087 set_tid: &[libc::pid_t],
1088 size: usize,
1089 attribution: Option<&AddedIdEvidence>,
1090 ) -> Result<(Result<i64, Errno>, bool), InputInjectionError> {
1091 let (control, task) = crate::reserved_input::capability(guest)
1092 .ok_or_else(|| InputUnavailable::new("reserved-input-facility-unavailable"))?;
1093 let layout = control
1094 .layout(task)
1095 .ok_or_else(|| InputUnavailable::new("reserved-input-layout-unqualified"))?;
1096 let (image, rewritten, array) = reserved_clone_image(layout, call, args, set_tid, size)?;
1097 let input_matches =
1101 attribution.is_some_and(|proof| proof.staged_matches(&image, &rewritten, array));
1102 let scope = control.begin_operation(task, guest.thread_state().stats.syscall_count)?;
1103 let args_address = rewritten.args().expect("fixed reserved address").as_raw();
1104 let args_offset = args_address - layout.base as usize;
1105 let description = crate::reserved_input::clone_description(
1106 rewritten.into(),
1107 Some((
1108 args_address as u64,
1109 &image.bytes()[args_offset..args_offset + CLONE_ARGS_SIZE_VER2],
1110 )),
1111 guest.reserved_input_process_exit_signal().ok(),
1112 );
1113 let receipt = match control.prepare_injection(&scope, description) {
1114 Ok(receipt) => receipt,
1115 Err(error) => {
1116 control.finish_operation(scope)?;
1117 return Err(error.into());
1118 }
1119 };
1120 let result = guest
1121 .inject_staged_input(rewritten, &scope, image, receipt)
1122 .await;
1123 control
1126 .finish_operation(scope)
1127 .map_err(|error| InputInjectionError::Fatal(error.to_string()))?;
1128 result.map(|native| (native, input_matches))
1129 }
1130
1131 async fn inject_with_chosen_id<G: Guest<Self>>(
1145 &self,
1146 guest: &mut G,
1147 call: Syscall,
1148 chosen: ChosenId,
1149 vfork: bool,
1150 ) -> Result<i64, Errno> {
1151 let refuse_host_ids = matches!(
1152 self.cfg.guest_ids,
1153 detcore_model::pid::GuestIds::Chosen {
1154 refuse_host_ids: true,
1155 ..
1156 }
1157 );
1158 let maximum_before = pid_max();
1159 let mut attribution = (self.cfg.sequentialize_threads
1165 && self.cfg.inherited_seccomp == detcore_model::config::InheritedSeccomp::Disabled
1166 && self.clone3_could_fare_otherwise(guest).is_none())
1167 .then(|| {
1168 AddedIdEvidence::capture(
1169 &guest.memory(),
1170 guest.tid().as_raw(),
1171 &call,
1172 &chosen,
1173 maximum_before,
1174 )
1175 })
1176 .flatten();
1177 if let Some(evidence) = &attribution
1178 && crate::tool_global::thread_id_refused(guest, DetTid::from_raw(evidence.id)).await
1179 {
1180 attribution = None;
1181 }
1182 let ChosenId {
1183 set_tid,
1184 id,
1185 fresh_allocation: _,
1186 restart,
1187 args,
1188 size,
1189 untraced,
1190 guest_entries,
1191 } = chosen;
1192 if let Some(cause) = id.and_then(|id| chosen_id_range_refusal(id, maximum_before)) {
1195 if refuse_host_ids {
1196 refuse_host_id(
1197 guest,
1198 format!(
1199 "this {} cannot create its task with its chosen ID: {cause}. The guest \
1200 has created more tasks than the IDs from its base to pid_max hold; the \
1201 run needs a lower base",
1202 call.number()
1203 ),
1204 )
1205 .await
1206 }
1207 return self
1208 .issue_as_made(guest, call, &cause, untraced, vfork)
1209 .await;
1210 }
1211 let cause = match self
1212 .stage_chosen_clone(guest, call, args, &set_tid, size, attribution.as_ref())
1213 .await
1214 {
1215 Ok((result, staged_matches)) => {
1216 if !staged_matches {
1217 attribution = None;
1218 }
1219 if result == Err(Errno::EINVAL)
1224 && let Some(evidence) = &attribution
1225 && pid_max() == Some(evidence.maximum)
1226 && !crate::tool_global::thread_id_refused(guest, DetTid::from_raw(evidence.id))
1227 .await
1228 {
1229 return Err(Errno::EINVAL);
1230 }
1231 match result {
1232 Ok(child) => {
1233 debug!(
1234 "[detcore, dtid {}] {} created task {child} with set_tid {set_tid:?}",
1235 guest.thread_state().dettid,
1236 call.number()
1237 );
1238 return Ok(child);
1239 }
1240 Err(errno) if is_restart(errno) => {
1242 if let (Some(id), Some(call)) = (id, restart) {
1243 guest.thread_state_mut().restarted_clone_id =
1244 Some(RestartedCloneId { id, call });
1245 }
1246 return Err(errno);
1247 }
1248 Err(errno) => match id.and_then(|_| set_tid_failure(errno)) {
1249 None => return Err(errno),
1250 Some(SetTidFailure::Taken) => {
1251 let id = id.unwrap();
1252 let cause = if guest_entries {
1253 format!(
1254 "ID {id}, or one of the guest's own set_tid entries, is \
1255 taken (EEXIST); ID {id}: {}",
1256 holder_of(id)
1257 )
1258 } else {
1259 format!("ID {id} is taken: {}", holder_of(id))
1260 };
1261 if refuse_host_ids {
1262 refuse_host_id(
1263 guest,
1264 format!(
1265 "this {} cannot create its task with its chosen ID: \
1266 {cause}. {}",
1267 call.number(),
1268 self.free_base_hint()
1269 ),
1270 )
1271 .await
1272 }
1273 cause
1274 }
1275 Some(SetTidFailure::NotPermitted) => format!(
1276 "the rewrite with ID {} in Hermit's PID namespace failed with EPERM, \
1277 taken as the guest task's lacking CAP_SYS_ADMIN and \
1278 CAP_CHECKPOINT_RESTORE there (for example in a user namespace of its \
1279 own)",
1280 id.unwrap()
1281 ),
1282 Some(SetTidFailure::PossiblyOutOfRange) => format!(
1283 "the rewrite with ID {} in Hermit's PID namespace failed with EINVAL; \
1284 the added ID lacks complete unchanged-input, stable-pid-range and \
1285 registration evidence for attributing that error to the guest",
1286 id.unwrap()
1287 ),
1288 },
1289 }
1290 }
1291 Err(InputInjectionError::Unavailable(error)) => {
1292 format!("its reserved input is unavailable: {error}")
1293 }
1294 Err(InputInjectionError::Fatal(error)) => {
1295 panic!("reserved-input native ownership failure: {error}")
1298 }
1299 };
1300 if refuse_host_ids {
1303 refuse_host_id(
1304 guest,
1305 format!(
1306 "this {} cannot create its task with a chosen ID: {cause}",
1307 call.number()
1308 ),
1309 )
1310 .await
1311 }
1312 self.issue_as_made(guest, call, &cause, untraced, vfork)
1313 .await
1314 }
1315
1316 async fn issue_as_made<G: Guest<Self>>(
1322 &self,
1323 guest: &mut G,
1324 call: Syscall,
1325 cause: &str,
1326 untraced: bool,
1327 vfork: bool,
1328 ) -> Result<i64, Errno> {
1329 if untraced {
1330 refuse_untraced(
1331 guest,
1332 format!(
1333 "this {} cannot create its task with its chosen ID ({cause}), and the guest's \
1334 own call would create a task the tracer does not follow (CLONE_UNTRACED)",
1335 call.number()
1336 ),
1337 )
1338 .await
1339 }
1340 if vfork {
1341 return self.issue_reported_vfork(guest, call, cause).await;
1342 }
1343 let original = crate::reserved_input::inject_original_clone(guest, call).await;
1344 if let Ok(child) = original {
1345 self.gave_host_id(guest, child, cause).await;
1346 }
1347 original
1348 }
1349}
1350
1351fn traced_legacy_clone(mut args: SyscallArgs) -> SyscallArgs {
1354 args.arg0 &= !(libc::CLONE_UNTRACED as usize);
1355 args
1356}
1357
1358#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1369pub(crate) struct RestartedCloneId {
1370 pub(crate) id: i32,
1371 pub(crate) call: Vec<u8>,
1372}
1373
1374impl RestartedCloneId {
1375 fn call(sysno: Sysno, args: &[u8], size: usize) -> Vec<u8> {
1378 let mut call = Vec::with_capacity(16 + args.len());
1379 call.extend_from_slice(&(sysno as u64).to_ne_bytes());
1380 call.extend_from_slice(&(size as u64).to_ne_bytes());
1381 call.extend_from_slice(args);
1382 call
1383 }
1384}
1385
1386fn without_untraced(mut args: [u8; CLONE_ARGS_SIZE_VER2]) -> [u8; CLONE_ARGS_SIZE_VER2] {
1388 let flags = u64::from_ne_bytes(args[..8].try_into().unwrap());
1389 args[..8].copy_from_slice(&(flags & !(libc::CLONE_UNTRACED as u64)).to_ne_bytes());
1390 args
1391}
1392
1393fn pid_max() -> Option<i32> {
1396 std::fs::read_to_string("/proc/sys/kernel/pid_max")
1397 .ok()?
1398 .trim()
1399 .parse()
1400 .ok()
1401}
1402
1403fn chosen_id_range_refusal(id: i32, maximum: Option<i32>) -> Option<String> {
1406 if id <= 0 {
1407 Some(format!("chosen ID {id} is not positive"))
1408 } else {
1409 maximum
1410 .filter(|maximum| id >= *maximum)
1411 .map(|maximum| format!("ID {id} is not below pid_max, {maximum}"))
1412 }
1413}
1414
1415#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1420enum SetTidFailure {
1421 Taken,
1428 NotPermitted,
1434 PossiblyOutOfRange,
1437}
1438
1439fn set_tid_failure(errno: Errno) -> Option<SetTidFailure> {
1444 match errno {
1445 Errno::EEXIST => Some(SetTidFailure::Taken),
1446 Errno::EPERM => Some(SetTidFailure::NotPermitted),
1447 Errno::EINVAL => Some(SetTidFailure::PossiblyOutOfRange),
1448 _ => None,
1449 }
1450}
1451
1452const MAX_SET_TID: usize = 32;
1455
1456fn read_guest_set_tid<M: MemoryAccess>(
1463 memory: &M,
1464 witness: FaultWitness,
1465 args: &[u8; CLONE_ARGS_SIZE_VER2],
1466) -> Result<Vec<libc::pid_t>, SetTidUnread> {
1467 let (address, size) = guest_set_tid_shape(args).ok_or(SetTidUnread::Refused)?;
1468 let mut bytes = vec![0u8; size * std::mem::size_of::<libc::pid_t>()];
1469 if !within_user_memory(address as usize, bytes.len()) {
1470 return Err(if range_faults(witness, address as usize, bytes.len()) {
1471 SetTidUnread::Refused
1472 } else {
1473 SetTidUnread::Unreadable
1474 });
1475 }
1476 let read = memory.read_exact_with_user_access(
1479 Addr::<u8>::from_raw(address as usize).ok_or(SetTidUnread::Refused)?,
1480 &mut bytes,
1481 );
1482 if read.is_err() {
1483 return Err(if range_faults(witness, address as usize, bytes.len()) {
1484 SetTidUnread::Refused
1485 } else {
1486 SetTidUnread::Unreadable
1487 });
1488 }
1489 Ok(bytes
1490 .as_chunks::<4>()
1491 .0
1492 .iter()
1493 .map(|chunk| libc::pid_t::from_ne_bytes(*chunk))
1494 .collect())
1495}
1496
1497pub(crate) fn read_pid_array<M: MemoryAccess>(
1503 memory: &M,
1504 address: usize,
1505 tids: &mut [libc::pid_t],
1506) -> Option<()> {
1507 const ENTRY: usize = std::mem::size_of::<libc::pid_t>();
1508 let mut bytes = vec![0u8; tids.len() * ENTRY];
1509 memory
1510 .read_exact_with_user_access(Addr::<u8>::from_raw(address)?, &mut bytes)
1511 .ok()?;
1512 for (tid, chunk) in tids.iter_mut().zip(bytes.as_chunks::<ENTRY>().0) {
1513 *tid = libc::pid_t::from_ne_bytes(*chunk);
1514 }
1515 Some(())
1516}
1517
1518#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1520enum SetTidUnread {
1521 Refused,
1526 Unreadable,
1530}
1531
1532fn guest_set_tid_shape(args: &[u8; CLONE_ARGS_SIZE_VER2]) -> Option<(u64, usize)> {
1535 let field = |offset: usize| u64::from_ne_bytes(args[offset..offset + 8].try_into().unwrap());
1536 let (address, size) = (field(SET_TID_OFFSET), field(SET_TID_SIZE_OFFSET));
1537 (address != 0 && size != 0 && size <= MAX_SET_TID as u64).then_some((address, size as usize))
1538}
1539
1540fn pid_namespace_depth(tid: i32, flags: u64) -> Option<usize> {
1550 use std::os::fd::AsRawFd;
1551 use std::os::fd::FromRawFd;
1552 use std::os::unix::fs::MetadataExt;
1553 const NS_GET_PARENT: u64 = 0xb702;
1555 let new = usize::from(flags & libc::CLONE_NEWPID as u64 != 0);
1556 let own = match std::fs::metadata("/proc/self/ns/pid") {
1557 Ok(own) => own,
1558 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
1560 return std::path::Path::new("/proc/self/ns")
1561 .is_dir()
1562 .then_some(new);
1563 }
1564 Err(_) => return None,
1565 };
1566 let mut namespace = std::fs::File::open(format!("/proc/{tid}/ns/pid_for_children")).ok()?;
1567 for depth in 0..=MAX_SET_TID {
1568 let here = namespace.metadata().ok()?;
1569 if (here.dev(), here.ino()) == (own.dev(), own.ino()) {
1570 return Some(depth + new);
1571 }
1572 let parent = unsafe { libc::ioctl(namespace.as_raw_fd(), NS_GET_PARENT as _) };
1575 if parent < 0 {
1576 return None;
1577 }
1578 namespace = unsafe { std::fs::File::from_raw_fd(parent) };
1580 }
1581 None
1582}
1583
1584fn is_restart(errno: Errno) -> bool {
1587 [
1588 Errno::ERESTARTSYS,
1589 Errno::ERESTARTNOINTR,
1590 Errno::ERESTARTNOHAND,
1591 Errno::ERESTART_RESTARTBLOCK,
1592 ]
1593 .contains(&errno)
1594}
1595
1596pub(crate) fn read_clone_args<M: MemoryAccess>(
1599 memory: &M,
1600 witness: FaultWitness,
1601 clone: &syscalls::Clone3,
1602) -> Clone3Read {
1603 let Ok(page) = usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) }) else {
1604 return Clone3Read::Unreadable { flags: None };
1605 };
1606 let size = clone.size();
1607 let raw = clone.args().map_or(0, |args| args.as_raw());
1608 let address = Addr::<u8>::from_raw(raw).filter(|_| within_user_memory(raw, size));
1612 clone3_read_decision(
1613 size,
1614 page,
1615 || {
1616 let mut bytes = vec![0u8; size];
1619 memory
1620 .read_exact_with_user_access(address?, &mut bytes)
1621 .ok()
1622 .map(|()| bytes)
1623 },
1624 || range_faults(witness, raw, size),
1625 || {
1626 let mut flags = [0u8; 8];
1629 memory
1630 .read_exact(address?, &mut flags)
1631 .ok()
1632 .map(|()| u64::from_ne_bytes(flags))
1633 },
1634 )
1635}
1636
1637fn clone3_read_decision(
1644 size: usize,
1645 page: usize,
1646 read: impl FnOnce() -> Option<Vec<u8>>,
1647 faults: impl FnOnce() -> bool,
1648 flags: impl FnOnce() -> Option<u64>,
1649) -> Clone3Read {
1650 if size < CLONE_ARGS_SIZE_VER0 || size > page {
1651 return Clone3Read::Refused;
1652 }
1653 if let Some(bytes) = read() {
1654 return Clone3Read::Bytes(bytes);
1655 }
1656 if faults() {
1657 return Clone3Read::Refused;
1658 }
1659 Clone3Read::Unreadable { flags: flags() }
1660}
1661
1662#[derive(Clone, Debug, PartialEq, Eq)]
1665pub(crate) enum Clone3Read {
1666 Bytes(Vec<u8>),
1668 Refused,
1673 Unreadable { flags: Option<u64> },
1677}
1678
1679fn user_memory_end() -> Option<usize> {
1686 let page = usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) }).ok()?;
1687 let word_end = super::threads::user_ptr_max().checked_add(std::mem::size_of::<u32>())?;
1688 Some(word_end & !(page - 1))
1689}
1690
1691#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1694pub(crate) enum FaultWitness {
1695 HostMaps(i32),
1700 Unproven,
1709}
1710
1711impl FaultWitness {
1712 pub(crate) fn for_backend(backend: &reverie::BackendCapabilities, tid: i32) -> Self {
1714 if backend.host_task_reads_reach_guest_threads {
1715 FaultWitness::HostMaps(tid)
1716 } else {
1717 FaultWitness::Unproven
1718 }
1719 }
1720}
1721
1722pub(crate) fn range_faults(witness: FaultWitness, address: usize, len: usize) -> bool {
1731 if in_kernel_half(address) {
1734 return true;
1735 }
1736 let tid = match witness {
1737 FaultWitness::HostMaps(tid) => tid,
1738 FaultWitness::Unproven => return false,
1739 };
1740 let Some(limit) = user_memory_end() else {
1741 return false;
1742 };
1743 let Ok(smaps) = std::fs::read_to_string(format!("/proc/{tid}/smaps")) else {
1744 return false;
1745 };
1746 range_faults_in(&smaps, limit, address, len)
1747}
1748
1749pub(crate) fn within_user_memory(address: usize, len: usize) -> bool {
1755 user_memory_end()
1756 .zip(address.checked_add(len))
1757 .is_some_and(|(end, last)| last <= end)
1758}
1759
1760fn past_end_faults(address: usize) -> bool {
1770 in_kernel_half(address) || (cfg!(target_arch = "aarch64") && address >> 56 == 0)
1771}
1772
1773fn in_kernel_half(address: usize) -> bool {
1777 if cfg!(target_arch = "aarch64") {
1778 address & (1 << 55) != 0
1779 } else {
1780 address & (1 << 63) != 0
1781 }
1782}
1783
1784fn range_faults_in(smaps: &str, limit: usize, address: usize, len: usize) -> bool {
1800 if address >= limit {
1801 return past_end_faults(address);
1802 }
1803 let Some(end) = address.checked_add(len.max(1)) else {
1804 return true;
1805 };
1806 if end > limit {
1807 return true;
1808 }
1809 struct Mapping {
1813 start: usize,
1814 stop: usize,
1815 permitted: Option<bool>,
1816 grows_down: Option<bool>,
1817 flag_records: u32,
1818 }
1819 let mut mappings: Vec<Mapping> = Vec::new();
1820 for line in smaps.lines() {
1821 if let Some(flags) = line.strip_prefix("VmFlags:") {
1822 if let Some(last) = mappings.last_mut() {
1823 last.flag_records += 1;
1824 let flags: Vec<&str> = flags.split_whitespace().collect();
1825 let well_formed = !flags.is_empty()
1826 && flags.iter().all(|flag| {
1827 flag.len() == 2 && flag.bytes().all(|b| b.is_ascii_lowercase())
1828 });
1829 last.grows_down =
1830 (well_formed && last.flag_records == 1).then(|| flags.contains(&"gd"));
1831 }
1832 continue;
1833 }
1834 let mut fields = line.split_whitespace();
1835 let Some((start, stop)) = fields.next().and_then(|range| range.split_once('-')) else {
1836 continue;
1837 };
1838 let (Ok(start), Ok(stop), Some(perms)) = (
1839 usize::from_str_radix(start, 16),
1840 usize::from_str_radix(stop, 16),
1841 fields.next(),
1842 ) else {
1843 continue;
1844 };
1845 let perms = perms.as_bytes();
1846 let permitted = (perms.len() == 4
1847 && matches!(perms[0], b'r' | b'-')
1848 && matches!(perms[1], b'w' | b'-')
1849 && matches!(perms[2], b'x' | b'-')
1850 && matches!(perms[3], b'p' | b's'))
1851 .then(|| perms[..3] != *b"---");
1852 mappings.push(Mapping {
1853 start,
1854 stop,
1855 permitted,
1856 grows_down: None,
1857 flag_records: 0,
1858 });
1859 }
1860 if mappings.is_empty() {
1861 return false;
1862 }
1863 let mut at = address;
1864 while at < end {
1865 match mappings
1866 .iter()
1867 .find(|mapping| (mapping.start..mapping.stop).contains(&at))
1868 {
1869 Some(Mapping {
1870 permitted: Some(false),
1871 ..
1872 }) => return true,
1873 Some(Mapping {
1874 permitted: Some(true),
1875 stop,
1876 ..
1877 }) => at = *stop,
1878 Some(Mapping {
1879 permitted: None, ..
1880 }) => return false,
1881 None => {
1882 let above = mappings
1883 .iter()
1884 .filter(|mapping| mapping.start > at)
1885 .min_by_key(|mapping| mapping.start);
1886 return matches!(
1889 above,
1890 None | Some(Mapping {
1891 grows_down: Some(false),
1892 ..
1893 })
1894 );
1895 }
1896 }
1897 }
1898 false
1899}
1900
1901async fn refuse_host_id<T, G>(guest: &mut G, reason: String) -> !
1906where
1907 T: RecordOrReplay,
1908 G: Guest<Detcore<T>>,
1909{
1910 let reason = format!(
1911 "this run needs every guest process ID chosen (--strict or --pid-base), and a task would \
1912 have, or has, a host ID, which differs from run to run: {reason}"
1913 );
1914 tracing::error!("[detcore, dtid {}] {reason}", guest.thread_state().dettid);
1915 crate::tool_global::report_policy_refusal(guest, reason).await;
1916 crate::tool_global::unrecoverable_shutdown(guest, detcore_model::HERMIT_POLICY_REFUSAL_EXIT)
1917 .await
1918}
1919
1920async fn refuse_untraced<T, G>(guest: &mut G, reason: String) -> !
1926where
1927 T: RecordOrReplay,
1928 G: Guest<Detcore<T>>,
1929{
1930 let reason = format!(
1931 "a task the tracer does not follow (CLONE_UNTRACED) would run outside Detcore's \
1932 scheduler: {reason}"
1933 );
1934 tracing::error!("[detcore, dtid {}] {reason}", guest.thread_state().dettid);
1935 crate::tool_global::report_policy_refusal(guest, reason).await;
1936 crate::tool_global::unrecoverable_shutdown(guest, detcore_model::HERMIT_POLICY_REFUSAL_EXIT)
1937 .await
1938}
1939
1940pub fn holder_of(id: i32) -> String {
1943 let status = std::fs::read_to_string(format!("/proc/{id}/status")).ok();
1944 let field = |name: &str| {
1945 status.as_deref().and_then(|status| {
1946 status
1947 .lines()
1948 .find_map(|line| line.strip_prefix(name))
1949 .map(|value| value.trim().to_owned())
1950 })
1951 };
1952 match (field("Name:"), field("Tgid:")) {
1953 (Some(name), Some(tgid)) if tgid == id.to_string() => {
1954 format!("process {id} ({name})")
1955 }
1956 (Some(name), Some(tgid)) => format!("thread {id} ({name}) of process {tgid}"),
1957 _ => format!(
1958 "no live task: a process group or session of an exited process keeps ID {id}, or \
1959 its holder has exited since"
1960 ),
1961 }
1962}
1963
1964#[cfg(test)]
1965mod tests {
1966 use super::*;
1967
1968 fn fields(call: &ChosenIdCall) -> ([u64; 11], usize) {
1969 match call {
1970 ChosenIdCall::Clone3 { args, size } => {
1971 let mut fields = [0u64; 11];
1972 for (field, chunk) in fields.iter_mut().zip(args.as_chunks::<8>().0) {
1973 *field = u64::from_ne_bytes(*chunk);
1974 }
1975 (fields, *size)
1976 }
1977 other => panic!("expected a clone3 form, got {other:?}"),
1978 }
1979 }
1980
1981 fn clone_args(flags: u64, sp: u64, ptid: u64, ctid: u64, tls: u64) -> SyscallArgs {
1982 #[cfg(target_arch = "aarch64")]
1983 let (arg3, arg4) = (tls, ctid);
1984 #[cfg(not(target_arch = "aarch64"))]
1985 let (arg3, arg4) = (ctid, tls);
1986 SyscallArgs {
1987 arg0: flags as usize,
1988 arg1: sp as usize,
1989 arg2: ptid as usize,
1990 arg3: arg3 as usize,
1991 arg4: arg4 as usize,
1992 arg5: 0,
1993 }
1994 }
1995
1996 const THREAD_FLAGS: u64 = (libc::CLONE_VM
1997 | libc::CLONE_FS
1998 | libc::CLONE_FILES
1999 | libc::CLONE_SIGHAND
2000 | libc::CLONE_THREAD
2001 | libc::CLONE_SYSVSEM
2002 | libc::CLONE_SETTLS
2003 | libc::CLONE_PARENT_SETTID
2004 | libc::CLONE_CHILD_CLEARTID) as u64;
2005
2006 #[test]
2009 fn a_thread_creation_keeps_every_field() {
2010 let call = clone3_form(
2011 Sysno::clone,
2012 &clone_args(THREAD_FLAGS, 0x7000_1000, 0x5000, 0x5000, 0x6000),
2013 None,
2014 );
2015 assert_eq!(
2016 fields(&call),
2017 (
2018 [
2019 THREAD_FLAGS,
2020 0x5000,
2021 0x5000,
2022 0x5000,
2023 0,
2024 0x7000_0fff,
2025 1,
2026 0x6000,
2027 0,
2028 0,
2029 0
2030 ],
2031 CLONE_ARGS_SIZE_VER1
2032 )
2033 );
2034 }
2035
2036 #[test]
2039 fn the_exit_signal_moves_out_of_the_flags() {
2040 let sigchld = libc::SIGCHLD as u64;
2041 let (process, _) = fields(&clone3_form(
2042 Sysno::clone,
2043 &clone_args(sigchld, 0, 0, 0, 0),
2044 None,
2045 ));
2046 assert_eq!(
2047 (process[0], process[4], process[5], process[6]),
2048 (0, sigchld, 0, 0)
2049 );
2050 for shared in [
2051 libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM,
2052 libc::CLONE_PARENT,
2053 ] {
2054 let (fields, _) = fields(&clone3_form(
2055 Sysno::clone,
2056 &clone_args(shared as u64 | sigchld, 0, 0, 0, 0),
2057 None,
2058 ));
2059 assert_eq!((fields[0], fields[4]), (shared as u64, 0));
2060 }
2061 }
2062
2063 #[test]
2064 fn invalid_original_signals_are_not_normalized_into_valid_clones() {
2065 for shared in [
2066 libc::CLONE_PARENT,
2067 libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM,
2068 ] {
2069 for signal in [65, 128, 255] {
2070 let call = clone3_form(
2071 Sysno::clone,
2072 &clone_args(shared as u64 | signal, 0, 0, 0, 0),
2073 None,
2074 );
2075 assert_eq!(call, ChosenIdCall::Invalid(Errno::EINVAL));
2076 }
2077 for signal in [0, libc::SIGCHLD as u64, 64] {
2078 let (valid, _) = fields(&clone3_form(
2079 Sysno::clone,
2080 &clone_args(shared as u64 | signal, 0, 0, 0, 0),
2081 None,
2082 ));
2083 assert_eq!((valid[0], valid[4]), (shared as u64, 0));
2084 }
2085 }
2086 }
2087
2088 #[test]
2091 fn high_bits_and_clone_detached_are_dropped() {
2092 let flags = (1u64 << 40) | libc::CLONE_DETACHED as u64 | libc::CLONE_FILES as u64;
2093 let (fields, _) = fields(&clone3_form(
2094 Sysno::clone,
2095 &clone_args(flags, 0, 0, 0, 0),
2096 None,
2097 ));
2098 assert_eq!(fields[0], libc::CLONE_FILES as u64);
2099 }
2100
2101 #[test]
2107 fn refused_and_inexpressible_calls() {
2108 let detached = (libc::CLONE_PIDFD | libc::CLONE_DETACHED) as u64;
2109 assert_eq!(
2110 clone3_form(Sysno::clone, &clone_args(detached, 0, 0, 0, 0), None),
2111 ChosenIdCall::Unchanged
2112 );
2113 let pidfd_thread =
2114 (libc::CLONE_PIDFD | libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM) as u64;
2115 let (pidfd_fields, _) = fields(&clone3_form(
2116 Sysno::clone,
2117 &clone_args(pidfd_thread, 0, 0, 0, 0),
2118 None,
2119 ));
2120 assert_eq!(pidfd_fields[0], pidfd_thread);
2121 assert!(matches!(
2122 clone3_form(Sysno::clone, &clone_args(0x80, 0, 0, 0, 0), None),
2123 ChosenIdCall::Inexpressible(_)
2124 ));
2125 let thread = (libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM) as u64;
2126 assert_eq!(
2127 clone3_form(Sysno::clone, &clone_args(thread | 0x80, 0, 0, 0, 0), None),
2128 ChosenIdCall::Invalid(Errno::EINVAL)
2129 );
2130
2131 for untraced in [0, libc::CLONE_UNTRACED] {
2135 for invalid in [
2136 libc::CLONE_THREAD,
2137 libc::CLONE_THREAD | libc::CLONE_VM,
2138 libc::CLONE_SIGHAND,
2139 libc::CLONE_THREAD | libc::CLONE_SIGHAND,
2140 ] {
2141 let args = clone_args(
2142 (invalid | untraced) as u64 | libc::SIGCHLD as u64,
2143 1,
2144 0x1234,
2145 0x2345,
2146 0x3456,
2147 );
2148 assert_eq!(
2149 clone3_form(Sysno::clone, &args, None),
2150 ChosenIdCall::Unchanged
2151 );
2152 let mut expected = args;
2153 expected.arg0 &= !(libc::CLONE_UNTRACED as usize);
2154 assert_eq!(traced_legacy_clone(args), expected);
2155 }
2156 for valid in [
2157 0,
2158 libc::CLONE_VM,
2159 libc::CLONE_SIGHAND | libc::CLONE_VM,
2160 libc::CLONE_THREAD | libc::CLONE_SIGHAND | libc::CLONE_VM,
2161 ] {
2162 let flags = (valid | untraced) as u64;
2163 let (converted, _) = fields(&clone3_form(
2164 Sysno::clone,
2165 &clone_args(flags, 0, 0, 0, 0),
2166 None,
2167 ));
2168 assert_eq!(converted[0], flags);
2169 }
2170 }
2171 assert_eq!(
2173 clone3_form(
2174 Sysno::clone,
2175 &clone_args(libc::CLONE_THREAD as u64 | 65, 1, 0, 0, 0),
2176 None
2177 ),
2178 ChosenIdCall::Invalid(Errno::EINVAL)
2179 );
2180 let mut guest_args = [0; CLONE_ARGS_SIZE_VER2];
2182 guest_args[..8].copy_from_slice(&(libc::CLONE_SIGHAND as u64).to_ne_bytes());
2183 let (converted, _) = fields(&clone3_form(
2184 Sysno::clone3,
2185 &clone_args(0, 0, 0, 0, 0),
2186 Some(&guest_args),
2187 ));
2188 assert_eq!(converted[0], libc::CLONE_SIGHAND as u64);
2189 }
2190
2191 #[test]
2196 fn both_clone_argument_orders() {
2197 let args = SyscallArgs::new(THREAD_FLAGS as usize, 0, 0x5000, 0x3333, 0x4444, 0);
2199 for (order, child_tid, tls) in [
2200 (CloneArgOrder::ChildTidThenTls, 0x3333, 0x4444),
2201 (CloneArgOrder::TlsThenChildTid, 0x4444, 0x3333),
2202 ] {
2203 let (fields, _) = fields(&LegacyClone::of_clone(&args, order).as_clone3());
2204 assert_eq!((fields[2], fields[3], fields[7]), (child_tid, 0x5000, tls));
2207 }
2208 #[cfg(target_arch = "aarch64")]
2209 assert_eq!(CloneArgOrder::NATIVE, CloneArgOrder::TlsThenChildTid);
2210 #[cfg(target_arch = "x86_64")]
2211 assert_eq!(CloneArgOrder::NATIVE, CloneArgOrder::ChildTidThenTls);
2212 }
2213
2214 #[cfg(not(target_arch = "aarch64"))]
2216 #[test]
2217 fn fork_and_vfork() {
2218 let none = clone_args(0, 0, 0, 0, 0);
2219 let (fork, _) = fields(&clone3_form(Sysno::fork, &none, None));
2220 assert_eq!((fork[0], fork[4]), (0, libc::SIGCHLD as u64));
2221 let (vfork, _) = fields(&clone3_form(Sysno::vfork, &none, None));
2222 assert_eq!(
2223 (vfork[0], vfork[4]),
2224 (
2225 (libc::CLONE_VFORK | libc::CLONE_VM) as u64,
2226 libc::SIGCHLD as u64
2227 )
2228 );
2229 }
2230
2231 #[test]
2238 fn a_guest_clone3() {
2239 let none = SyscallArgs::new(0, 0, 0, 0, 0, 0);
2240 let mut ver0 = [0u8; CLONE_ARGS_SIZE_VER0];
2241 ver0[..8].copy_from_slice(&(libc::CLONE_FILES as u64).to_ne_bytes());
2242 let (ver0_fields, size) = fields(&clone3_form(Sysno::clone3, &none, Some(&ver0)));
2243 assert_eq!(
2244 (ver0_fields[0], size),
2245 (libc::CLONE_FILES as u64, CLONE_ARGS_SIZE_VER1)
2246 );
2247
2248 let mut own = [0u8; CLONE_ARGS_SIZE_VER2];
2249 own[SET_TID_OFFSET..SET_TID_OFFSET + 8].copy_from_slice(&0x1000u64.to_ne_bytes());
2250 own[SET_TID_SIZE_OFFSET..SET_TID_SIZE_OFFSET + 8].copy_from_slice(&1u64.to_ne_bytes());
2251 assert_eq!(
2252 clone3_form(Sysno::clone3, &none, Some(&own)),
2253 ChosenIdCall::GuestChosen {
2254 args: own,
2255 size: CLONE_ARGS_SIZE_VER2
2256 }
2257 );
2258 let mut short = [0u8; 70];
2259 short[SET_TID_OFFSET] = 1;
2260 assert!(matches!(
2261 clone3_form(Sysno::clone3, &none, Some(&short)),
2262 ChosenIdCall::GuestChosen { .. }
2263 ));
2264 assert_eq!(
2266 clone3_form(Sysno::clone3, &none, Some(&[0u8; 32])),
2267 ChosenIdCall::Unchanged
2268 );
2269 for unproven in [Some(&[[0u8; 96], [1u8; 96]].concat()[..]), None] {
2272 assert!(matches!(
2273 clone3_form(Sysno::clone3, &none, unproven),
2274 ChosenIdCall::Inexpressible(_)
2275 ));
2276 }
2277 assert!(matches!(
2278 clone3_form(Sysno::getpid, &none, None),
2279 ChosenIdCall::Inexpressible(_)
2280 ));
2281 let mut long = [0u8; 96];
2282 long[..8].copy_from_slice(&(libc::CLONE_FILES as u64).to_ne_bytes());
2283 let (long_fields, size) = fields(&clone3_form(Sysno::clone3, &none, Some(&long)));
2284 assert_eq!(
2285 (long_fields[0], size),
2286 (libc::CLONE_FILES as u64, CLONE_ARGS_SIZE_VER2)
2287 );
2288 }
2289
2290 #[test]
2291 fn set_tid_is_filled_in() {
2292 let args = ChosenIdCall::with_set_tid([0u8; CLONE_ARGS_SIZE_VER2], 0x1234, 2);
2293 assert_eq!(
2294 u64::from_ne_bytes(args[SET_TID_OFFSET..SET_TID_OFFSET + 8].try_into().unwrap()),
2295 0x1234
2296 );
2297 assert_eq!(
2298 u64::from_ne_bytes(
2299 args[SET_TID_SIZE_OFFSET..SET_TID_SIZE_OFFSET + 8]
2300 .try_into()
2301 .unwrap()
2302 ),
2303 2
2304 );
2305 }
2306
2307 #[test]
2311 fn a_pid_namespace_depth_counts_from_hermits() {
2312 let own = unsafe { libc::gettid() };
2313 assert_eq!(super::pid_namespace_depth(own, 0), Some(0));
2314 assert_eq!(
2315 super::pid_namespace_depth(own, libc::CLONE_NEWPID as u64),
2316 Some(1)
2317 );
2318 }
2319
2320 #[test]
2323 fn a_restarted_clone_keeps_its_id_only_for_the_same_call() {
2324 use super::RestartedCloneId;
2325 let args = [7u8; super::CLONE_ARGS_SIZE_VER2];
2326 let mut other = args;
2327 other[0] ^= 1;
2328 let call = RestartedCloneId::call(Sysno::clone, &args, 64);
2329 assert_eq!(call, RestartedCloneId::call(Sysno::clone, &args, 64));
2330 assert_ne!(call, RestartedCloneId::call(Sysno::clone, &other, 64));
2331 assert_ne!(call, RestartedCloneId::call(Sysno::clone3, &args, 64));
2332 assert_ne!(call, RestartedCloneId::call(Sysno::clone, &args, 88));
2333 }
2334
2335 #[test]
2338 fn an_added_id_can_also_make_the_rewrite_invalid() {
2339 use super::SetTidFailure;
2340 assert_eq!(
2341 super::set_tid_failure(Errno::EEXIST),
2342 Some(SetTidFailure::Taken)
2343 );
2344 assert_eq!(
2345 super::set_tid_failure(Errno::EPERM),
2346 Some(SetTidFailure::NotPermitted)
2347 );
2348 assert_eq!(
2349 super::set_tid_failure(Errno::EINVAL),
2350 Some(SetTidFailure::PossiblyOutOfRange)
2351 );
2352 for errno in [
2353 Errno::EFAULT,
2354 Errno::EBADF,
2355 Errno::EACCES,
2356 Errno::EAGAIN,
2357 Errno::EINTR,
2358 ] {
2359 assert_eq!(super::set_tid_failure(errno), None, "{errno}");
2360 }
2361 for maximum in [None, Some(100)] {
2362 for id in [i32::MIN, -1, 0] {
2363 assert!(super::chosen_id_range_refusal(id, maximum).is_some());
2364 }
2365 assert_eq!(super::chosen_id_range_refusal(99, maximum), None);
2366 }
2367 assert!(super::chosen_id_range_refusal(100, Some(100)).is_some());
2368 assert_eq!(super::chosen_id_range_refusal(100, None), None);
2369 }
2370
2371 #[test]
2374 fn a_guest_set_tid_array_has_the_shape_the_kernel_reads() {
2375 let shape = |address: u64, size: u64| {
2376 let mut args = [0u8; CLONE_ARGS_SIZE_VER2];
2377 args[SET_TID_OFFSET..SET_TID_OFFSET + 8].copy_from_slice(&address.to_ne_bytes());
2378 args[SET_TID_SIZE_OFFSET..SET_TID_SIZE_OFFSET + 8].copy_from_slice(&size.to_ne_bytes());
2379 super::guest_set_tid_shape(&args)
2380 };
2381 assert_eq!(shape(0x1000, 1), Some((0x1000, 1)));
2382 assert_eq!(shape(0x1000, 32), Some((0x1000, 32)));
2383 assert_eq!(shape(0x1000, 0), None);
2384 assert_eq!(shape(0, 1), None);
2385 assert_eq!(shape(0x1000, 33), None);
2386 }
2387
2388 #[test]
2390 fn the_rewrite_drops_only_clone_untraced() {
2391 let flags = (libc::CLONE_VM | libc::CLONE_UNTRACED | libc::CLONE_SETTLS) as u64;
2392 let mut args = [9u8; CLONE_ARGS_SIZE_VER2];
2393 args[..8].copy_from_slice(&flags.to_ne_bytes());
2394 let cleared = super::without_untraced(args);
2395 assert_eq!(
2396 u64::from_ne_bytes(cleared[..8].try_into().unwrap()),
2397 (libc::CLONE_VM | libc::CLONE_SETTLS) as u64
2398 );
2399 assert_eq!(cleared[8..], args[8..]);
2400 }
2401
2402 #[test]
2406 fn a_stack_pointer_clone3_cannot_express_is_inexpressible() {
2407 for sp in [1usize, (super::USER_ADDRESS_LIMIT + 1) as usize] {
2408 let args = SyscallArgs::new(libc::SIGCHLD as usize, sp, 0, 0, 0, 0);
2409 assert!(
2410 matches!(
2411 clone3_form(Sysno::clone, &args, None),
2412 ChosenIdCall::Inexpressible(why) if why.contains("stack pointer")
2413 ),
2414 "{sp:#x}"
2415 );
2416 }
2417 let args = SyscallArgs::new(libc::SIGCHLD as usize, 0x7fff_0000_1000, 0, 0, 0, 0);
2418 assert!(matches!(
2419 clone3_form(Sysno::clone, &args, None),
2420 ChosenIdCall::Clone3 { .. }
2421 ));
2422 }
2423
2424 #[test]
2429 fn only_a_definite_fault_is_taken_as_the_kernels_refusal() {
2430 let smaps = "\
24311000-2000 rw-p 00000000 00:00 0
2432VmFlags: rd wr mr mw me ac
24332000-3000 ---p 00000000 00:00 0
2434VmFlags: mr mw me ac
24355000-6000 -w-p 00000000 00:00 0
2436VmFlags: wr mr mw me ac
24379000-a000 rw-p 00000000 00:00 0 [stack]
2438VmFlags: rd wr mr mw me gd ac
2439";
2440 let limit = 0x10000;
2441 let faults =
2442 |address: usize, len: usize| super::range_faults_in(smaps, limit, address, len);
2443 assert!(!faults(0x1ff0, 0x10));
2445 assert!(!faults(0x5000, 88));
2446 assert!(faults(0x1ff8, 0x10));
2448 assert!(faults(0x4000, 8));
2450 assert!(faults(0x1000 - 4, 8));
2451 assert!(!faults(0x8ff8, 8));
2453 for len in [1, 4, 8, 88] {
2455 assert!(!super::range_faults_in(
2456 "f000-10000 -w-p 00000000 00:00 0\nVmFlags: wr\n",
2457 limit,
2458 limit - len,
2459 len
2460 ));
2461 if len > 1 {
2464 assert!(faults(limit - len + 1, len));
2465 }
2466 }
2467 assert!(faults(usize::MAX - 4095, 88));
2470 assert!(faults(usize::MAX - 2, 8));
2471 assert!(!faults(0x0f00_0000_0000_4000, 8));
2472 assert_eq!(faults(limit, 8), cfg!(target_arch = "aarch64"));
2475 assert!(!super::range_faults_in("", limit, 0x4000, 8));
2477 use reverie::BackendCapabilities as B;
2480 for backend in [B::PTRACE, B::E9PATCH] {
2481 assert_eq!(
2482 super::FaultWitness::for_backend(&backend, 7),
2483 super::FaultWitness::HostMaps(7)
2484 );
2485 }
2486 for backend in [B::LITEINST_IN_GUEST, B::SABRE, B::DBT, B::KVM] {
2487 assert_eq!(
2488 super::FaultWitness::for_backend(&backend, 7),
2489 super::FaultWitness::Unproven
2490 );
2491 }
2492 assert!(!super::range_faults(
2494 super::FaultWitness::Unproven,
2495 0x4000,
2496 8
2497 ));
2498 assert!(super::range_faults(
2499 super::FaultWitness::Unproven,
2500 usize::MAX - 4095,
2501 88
2502 ));
2503 let unflagged = "5000-6000 rw-p 00000000 00:00 0\n";
2506 assert!(!super::range_faults_in(unflagged, limit, 0x4000, 8));
2507 assert!(super::range_faults_in(unflagged, limit, 0x7000, 8));
2508 assert!(!super::range_faults_in(
2511 "0-1000 rw-p 00000000 00:00 0\nVmFlags: rd wr\n",
2512 limit,
2513 0,
2514 88
2515 ));
2516 for record in [
2519 "VmFlags:\n",
2520 "VmFlags: rd wr GD\n",
2521 "VmFlags: rd wr\nVmFlags: rd wr\n",
2522 ] {
2523 let smaps = format!("5000-6000 rw-p 00000000 00:00 0\n{record}");
2524 assert!(
2525 !super::range_faults_in(&smaps, limit, 0x4000, 8),
2526 "{record:?}"
2527 );
2528 }
2529 assert!(!super::range_faults_in(
2530 "5000-6000 --- 00000000 00:00 0\nVmFlags: mr\n",
2531 limit,
2532 0x5000,
2533 8
2534 ));
2535 let end = super::user_memory_end().unwrap();
2538 let word = super::super::threads::user_ptr_max();
2539 assert_eq!(end % 4096, 0, "{end:#x}");
2540 assert!(word < end + 4096 && end <= word + 4, "{word:#x} {end:#x}");
2541 }
2542
2543 #[test]
2547 fn a_clone3_struct_is_refused_only_when_every_kernel_refuses_it() {
2548 use super::Clone3Read;
2549 let decide = |size: usize, read: Option<Vec<u8>>, faults: bool, flags: Option<u64>| {
2550 super::clone3_read_decision(size, 4096, || read, || faults, || flags)
2551 };
2552 assert_eq!(
2553 decide(32, Some(vec![0; 32]), false, None),
2554 Clone3Read::Refused
2555 );
2556 assert_eq!(decide(8192, None, false, None), Clone3Read::Refused);
2557 assert_eq!(
2558 decide(88, Some(vec![1; 88]), false, None),
2559 Clone3Read::Bytes(vec![1; 88])
2560 );
2561 assert_eq!(decide(88, None, true, None), Clone3Read::Refused);
2563 assert_eq!(
2564 decide(88, None, false, None),
2565 Clone3Read::Unreadable { flags: None }
2566 );
2567 assert_eq!(
2568 decide(88, None, false, Some(7)),
2569 Clone3Read::Unreadable { flags: Some(7) }
2570 );
2571 }
2572
2573 #[test]
2576 fn a_traced_legacy_clone_drops_only_clone_untraced() {
2577 let flags = (libc::CLONE_UNTRACED | libc::CLONE_PIDFD | libc::CLONE_DETACHED) as usize
2578 | libc::SIGCHLD as usize;
2579 let args = SyscallArgs::new(flags, 0x1000, 0x2000, 0x3000, 0x4000, 0x5000);
2580 let traced = super::traced_legacy_clone(args);
2581 assert_eq!(traced.arg0, flags & !(libc::CLONE_UNTRACED as usize));
2582 assert_eq!(
2583 (
2584 traced.arg1,
2585 traced.arg2,
2586 traced.arg3,
2587 traced.arg4,
2588 traced.arg5
2589 ),
2590 (0x1000, 0x2000, 0x3000, 0x4000, 0x5000)
2591 );
2592 }
2593}