1use reverie::Error;
12use reverie::Guest;
13use reverie::syscalls;
14use reverie::syscalls::AddrMut;
15use reverie::syscalls::Errno;
16use reverie::syscalls::MemoryAccess;
17use tracing::warn;
18
19use crate::Detcore;
20use crate::RecordOrReplay;
21
22const MADV_GUARD_INSTALL: i32 = 102;
24const MADV_GUARD_REMOVE: i32 = 103;
25
26#[derive(Debug, Clone, Copy, Eq, PartialEq)]
27enum MadviseAction {
28 ForwardHint,
29 ForwardSemantic,
30 Ignore,
31 Reject(Errno),
32 Unknown,
33}
34
35const fn madvise_action(advice: i32) -> MadviseAction {
36 match advice {
37 libc::MADV_NORMAL | libc::MADV_RANDOM | libc::MADV_SEQUENTIAL | libc::MADV_WILLNEED => {
40 MadviseAction::ForwardHint
41 }
42
43 libc::MADV_DONTNEED
46 | libc::MADV_DONTFORK
47 | libc::MADV_DOFORK
48 | libc::MADV_DONTDUMP
49 | libc::MADV_DODUMP
50 | libc::MADV_WIPEONFORK
51 | libc::MADV_KEEPONFORK
52 | libc::MADV_DONTNEED_LOCKED
53 | MADV_GUARD_INSTALL
54 | MADV_GUARD_REMOVE => MadviseAction::ForwardSemantic,
55
56 libc::MADV_FREE
61 | libc::MADV_MERGEABLE
62 | libc::MADV_UNMERGEABLE
63 | libc::MADV_HUGEPAGE
64 | libc::MADV_NOHUGEPAGE
65 | libc::MADV_COLD
66 | libc::MADV_PAGEOUT => MadviseAction::Ignore,
67
68 libc::MADV_REMOVE => MadviseAction::Reject(Errno::EINVAL),
71
72 libc::MADV_POPULATE_READ
76 | libc::MADV_POPULATE_WRITE
77 | reverie::syscalls::linux_abi::MADV_COLLAPSE => MadviseAction::Reject(Errno::EINVAL),
78
79 libc::MADV_HWPOISON | libc::MADV_SOFT_OFFLINE => MadviseAction::Reject(Errno::EPERM),
82
83 _ => MadviseAction::Unknown,
84 }
85}
86
87fn validate_common_args(call: syscalls::Madvise) -> Result<(), Error> {
88 let page = crate::page::page_size() as usize;
89 let start = call.addr().map(AddrMut::as_raw).unwrap_or(0);
90 if !start.is_multiple_of(page) {
91 return Err(Errno::EINVAL.into());
92 }
93 if call.len() == 0 {
94 return Ok(());
95 }
96
97 let end = start.checked_add(call.len()).ok_or(Errno::EINVAL)?;
98 end.checked_add(page - 1).ok_or(Errno::EINVAL)?;
99 Ok(())
100}
101
102impl<T: RecordOrReplay> Detcore<T> {
103 pub async fn handle_madvise<G: Guest<Self>>(
117 &self,
118 guest: &mut G,
119 call: syscalls::Madvise,
120 ) -> Result<i64, Error> {
121 let advice = call.advice();
122 let action = madvise_action(advice);
123 validate_common_args(call)?;
124
125 if call.len() == 0 {
126 return match action {
127 MadviseAction::Unknown => Err(Errno::EINVAL.into()),
128 _ => Ok(0),
129 };
130 }
131 if self.cfg.recordreplay_modes && action == MadviseAction::ForwardHint {
132 crate::detlog!(
133 "[dtid {}] madvise hint {} accepted as record/replay no-op",
134 guest.thread_state().dettid,
135 advice,
136 );
137 return Ok(0);
138 }
139
140 match action {
141 MadviseAction::ForwardHint | MadviseAction::ForwardSemantic => {
142 let result = self.record_or_replay(guest, call).await;
143 if result.is_err()
144 && let Some(refusal) = guest.take_unsupported_refusal().filter(|refusal| {
145 refusal.operation == reverie::UnsupportedOperation::MemoryAdvice
146 })
147 {
148 warn!(
149 "[dtid {}] madvise advice {} refused by the backend: {}",
150 guest.thread_state().dettid,
151 advice,
152 refusal.diagnostic,
153 );
154 }
155 Ok(result?)
156 }
157 MadviseAction::Ignore => {
158 crate::detlog!(
159 "[dtid {}] madvise advice {} accepted as deterministic no-op",
160 guest.thread_state().dettid,
161 advice,
162 );
163 Ok(0)
164 }
165 MadviseAction::Reject(errno) => {
166 crate::detlog!(
167 "[dtid {}] madvise advice {} rejected with {}",
168 guest.thread_state().dettid,
169 advice,
170 errno,
171 );
172 Err(errno.into())
173 }
174 MadviseAction::Unknown => {
175 crate::detlog!(
176 "[dtid {}] unknown madvise advice {} rejected with EINVAL",
177 guest.thread_state().dettid,
178 advice,
179 );
180 Err(Errno::EINVAL.into())
181 }
182 }
183 }
184
185 pub async fn handle_mincore<G: Guest<Self>>(
200 &self,
201 guest: &mut G,
202 call: syscalls::Mincore,
203 ) -> Result<i64, Error> {
204 let page = crate::page::page_size() as usize;
206 let start = call.addr().map(AddrMut::as_raw).unwrap_or(0);
207 if !start.is_multiple_of(page) {
208 return Err(Errno::EINVAL.into());
209 }
210 let len = call.len();
211 if len == 0 {
212 return Ok(0);
213 }
214 start.checked_add(len).ok_or(Errno::EINVAL)?;
216 let page_count = len.div_ceil(page);
217 let vec = call.vec().ok_or(Errno::EFAULT)?;
218 guest.inject(syscalls::Syscall::from(call)).await?;
219 let residency = vec![1u8; page_count];
220 guest.memory().write_exact(vec, &residency)?;
221 Ok(0)
222 }
223}
224
225fn brk_page_align(address: u64, page: u64) -> Option<u64> {
228 Some(address.checked_add(page - 1)? & !(page - 1))
229}
230
231fn stack_guard_gap(page: u64) -> u64 {
234 256 * page
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242struct Vma {
243 start: u64,
244 end: u64,
245 start_gap: u64,
246}
247
248fn smaps_vmas(smaps: &[u8], page: u64) -> SmapsVmas<'_> {
253 SmapsVmas { rest: smaps, page }
254}
255
256#[derive(Clone)]
257struct SmapsVmas<'a> {
258 rest: &'a [u8],
259 page: u64,
261}
262
263impl<'a> SmapsVmas<'a> {
264 fn peek(&self) -> Option<&'a [u8]> {
266 (!self.rest.is_empty()).then(|| {
267 self.rest
268 .split(|&byte| byte == b'\n')
269 .next()
270 .unwrap_or_default()
271 })
272 }
273
274 fn advance(&mut self) {
275 self.rest = match self.rest.iter().position(|&byte| byte == b'\n') {
276 Some(newline) => &self.rest[newline + 1..],
277 None => &[],
278 };
279 }
280}
281
282fn smaps_range(line: &[u8]) -> Option<(u64, u64)> {
286 let range = line.split(|&byte| byte == b' ').next()?;
287 let (start, end) = std::str::from_utf8(range).ok()?.split_once('-')?;
288 Some((
289 u64::from_str_radix(start, 16).ok()?,
290 u64::from_str_radix(end, 16).ok()?,
291 ))
292}
293
294impl Iterator for SmapsVmas<'_> {
295 type Item = Vma;
296
297 fn next(&mut self) -> Option<Vma> {
298 let (start, end) = loop {
299 let line = self.peek()?;
300 self.advance();
301 if let Some(range) = smaps_range(line) {
302 break range;
303 }
304 };
305 let mut vma = Vma {
306 start,
307 end,
308 start_gap: 0,
309 };
310 while let Some(line) = self.peek() {
312 if smaps_range(line).is_some() {
313 break;
314 }
315 self.advance();
316 if let Some(flags) = line.strip_prefix(b"VmFlags:") {
317 let mut flags = flags.split(|byte| byte.is_ascii_whitespace());
318 vma.start_gap = if flags.clone().any(|flag| flag == b"gd") {
319 stack_guard_gap(self.page)
320 } else if flags.any(|flag| flag == b"ss") {
321 self.page
322 } else {
323 0
324 };
325 }
326 }
327 Some(vma)
328 }
329}
330
331fn brk_may_grow(
337 mut vmas: impl Iterator<Item = Vma>,
338 old_end: u64,
339 new_end: u64,
340 page: u64,
341) -> bool {
342 let Some(limit) = new_end.checked_add(page) else {
343 return false;
344 };
345 let search_end = limit.saturating_add(stack_guard_gap(page));
346 let Some(next) = vmas.find(|vma| vma.end > old_end && vma.start < search_end) else {
347 return true;
348 };
349 let gap_start = next.start.saturating_sub(next.start_gap);
351 limit <= gap_start
352}
353
354fn brk_may_shrink(mut vmas: impl Iterator<Item = Vma>, new_end: u64, old_end: u64) -> bool {
359 vmas.any(|vma| vma.end > new_end && vma.start < old_end)
360}
361
362struct BrkDiagnostic<'a> {
366 dettid: i32,
367 model: &'a crate::memory::BrkModel,
368 requested: u64,
369}
370
371struct BrkDiagnosticHex<'a>(&'a [u8]);
372
373impl std::fmt::Display for BrkDiagnosticHex<'_> {
374 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375 for byte in self.0 {
376 write!(formatter, "{byte:02x}")?;
377 }
378 Ok(())
379 }
380}
381
382impl BrkDiagnostic<'_> {
383 fn rejected(
384 &self,
385 branch: &'static str,
386 limit: Option<u64>,
387 smaps: Option<&[u8]>,
388 kernel_result: &dyn std::fmt::Debug,
389 ) {
390 use std::sync::atomic::AtomicBool;
391 use std::sync::atomic::Ordering;
392 static CAPTURED: AtomicBool = AtomicBool::new(false);
393 const MAX_BYTES: usize = 256 * 1024;
394 if self.requested == 0
395 || self.requested == self.model.current
396 || !tracing::enabled!(target: "detcore::brk_diagnostic", tracing::Level::DEBUG)
397 || CAPTURED.swap(true, Ordering::Relaxed)
398 {
399 return;
400 }
401 let complete = smaps.is_some_and(|bytes| bytes.len() <= MAX_BYTES);
402 tracing::debug!(
403 target: "detcore::brk_diagnostic",
404 dettid = self.dettid,
405 requested = self.requested,
406 start = self.model.start,
407 current = self.model.current,
408 data_span = self.model.data_span,
409 launcher_break = self.model.launcher_break,
410 ?limit,
411 branch,
412 ?kernel_result,
413 smaps_len = ?smaps.map(<[u8]>::len),
414 complete,
415 "BRK_DENIAL_V1 begin",
416 );
417 if complete {
418 for (index, chunk) in smaps.unwrap().chunks(1024).enumerate() {
419 tracing::debug!(
420 target: "detcore::brk_diagnostic",
421 dettid = self.dettid,
422 index,
423 bytes = %BrkDiagnosticHex(chunk),
424 "BRK_DENIAL_V1 chunk",
425 );
426 }
427 }
428 tracing::debug!(
429 target: "detcore::brk_diagnostic",
430 dettid = self.dettid,
431 complete,
432 "BRK_DENIAL_V1 end",
433 );
434 }
435}
436
437#[derive(
441 Debug,
442 Clone,
443 Copy,
444 PartialEq,
445 Eq,
446 serde::Serialize,
447 serde::Deserialize
448)]
449pub enum BrkMappingCheck {
450 Grow { old_end: u64, new_end: u64 },
452 Shrink { new_end: u64, old_end: u64 },
454}
455
456impl BrkMappingCheck {
457 pub(crate) fn allows(self, smaps: &[u8]) -> bool {
458 self.allows_with_page(smaps, crate::page::page_size())
459 }
460
461 fn allows_with_page(self, smaps: &[u8], page: u64) -> bool {
463 match self {
464 Self::Grow { old_end, new_end } => {
465 brk_may_grow(smaps_vmas(smaps, page), old_end, new_end, page)
466 }
467 Self::Shrink { new_end, old_end } => {
468 brk_may_shrink(smaps_vmas(smaps, page), new_end, old_end)
469 }
470 }
471 }
472}
473
474impl<T: RecordOrReplay> Detcore<T> {
475 pub(crate) async fn handle_brk<G: Guest<Self>>(
493 &self,
494 guest: &mut G,
495 call: syscalls::Syscall,
496 ) -> Result<i64, Error> {
497 let (model, unmodeled) = {
498 let metadata = guest
499 .thread_state()
500 .memory_metadata
501 .lock()
502 .expect("memory metadata mutex poisoned");
503 (metadata.brk_model, metadata.brk_unmodeled.clone())
504 };
505 let Some(model) = model else {
506 let reason = format!(
507 "unsupported: brk where the kernel's program break is the launcher's and Detcore has no model of the guest's: {}",
508 unmodeled
509 .as_deref()
510 .unwrap_or("the image was never modeled"),
511 );
512 return self
513 .refuse_launched_exe_because(guest, syscalls::Sysno::brk, reason)
514 .await;
515 };
516 let requested = syscalls::SyscallInfo::into_parts(call).1.arg0 as u64;
517 let granted = match self.granted_brk(guest, &model, requested).await {
518 Ok(granted) => granted,
519 Err(reason) => {
520 return self
521 .refuse_launched_exe_because(guest, syscalls::Sysno::brk, reason.to_owned())
522 .await;
523 }
524 };
525 guest
526 .thread_state()
527 .memory_metadata
528 .lock()
529 .expect("memory metadata mutex poisoned")
530 .brk_model = Some(crate::memory::BrkModel {
531 current: granted,
532 ..model
533 });
534 Ok(granted as i64)
535 }
536
537 async fn granted_brk<G: Guest<Self>>(
542 &self,
543 guest: &mut G,
544 model: &crate::memory::BrkModel,
545 requested: u64,
546 ) -> Result<u64, &'static str> {
547 let current = model.current;
548 let diagnostic = BrkDiagnostic {
549 dettid: guest.thread_state().dettid.as_raw(),
550 model,
551 requested,
552 };
553 if requested < model.start {
554 if requested >= model.launcher_break {
555 diagnostic.rejected("below_start_launcher", None, None, &"not_injected");
556 return Err(
557 "unsupported: brk from the launcher's break, which the guest's libc read before Detcore modeled the guest's",
558 );
559 }
560 diagnostic.rejected("below_start", None, None, &"not_injected");
561 return Ok(current);
562 }
563 let limit = guest
564 .thread_state()
565 .resource_limits
566 .lock()
567 .unwrap()
568 .get(reverie::syscalls::linux_abi::RLIMIT_DATA)
569 .map_or(libc::RLIM64_INFINITY, |limit| limit.current);
570 let used = (requested - model.start).saturating_add(model.data_span);
571 if limit != libc::RLIM64_INFINITY && used > limit {
572 diagnostic.rejected("rlimit_data", Some(limit), None, &"not_injected");
573 return Ok(current);
574 }
575 let page = crate::page::page_size();
576 let (Some(new_end), Some(old_end)) = (
577 brk_page_align(requested, page),
578 brk_page_align(current, page),
579 ) else {
580 diagnostic.rejected("page_overflow", Some(limit), None, &"not_injected");
581 return Ok(current);
582 };
583 if new_end == old_end {
584 return Ok(requested);
585 }
586 let smaps = self
591 .guest_task_directory(guest)
592 .and_then(|directory| crate::util::raw_read_listing(&directory.join("smaps")).ok());
593 let check = if requested <= current {
594 BrkMappingCheck::Shrink { new_end, old_end }
595 } else {
596 BrkMappingCheck::Grow { old_end, new_end }
597 };
598 let allowed = match smaps.as_deref() {
599 Some(smaps) => Some(check.allows(smaps)),
600 None if guest
601 .thread_state()
602 .changed_root
603 .load(std::sync::atomic::Ordering::SeqCst) =>
604 {
605 crate::tool_global::check_brk_mappings(guest, check)
606 }
607 None => None,
608 }
609 .ok_or_else(|| {
610 diagnostic.rejected("smaps_unavailable", Some(limit), None, &"not_injected");
611 "unsupported: brk where Detcore cannot read the guest's mappings"
612 })?;
613 if !allowed {
614 diagnostic.rejected(
615 if requested <= current {
616 "shrink_predicate"
617 } else {
618 "grow_predicate"
619 },
620 Some(limit),
621 smaps.as_deref(),
622 &"not_injected",
623 );
624 return Ok(current);
625 }
626 if requested <= current {
627 let unmapped = guest
629 .inject_with_retry(syscalls::Syscall::Munmap(
630 syscalls::Munmap::new()
631 .with_addr(syscalls::Addr::from_raw(new_end as usize))
632 .with_len((old_end - new_end) as usize),
633 ))
634 .await;
635 if unmapped.is_err() {
636 diagnostic.rejected("munmap_error", Some(limit), smaps.as_deref(), &unmapped);
637 }
638 return Ok(if unmapped.is_ok() { requested } else { current });
639 }
640 let length = new_end - old_end;
641 let mapped = guest
642 .inject_with_retry(syscalls::Syscall::Mmap(
643 syscalls::Mmap::new()
644 .with_addr(syscalls::Addr::from_raw(old_end as usize))
645 .with_len(length as usize)
646 .with_prot(syscalls::ProtFlags::PROT_READ | syscalls::ProtFlags::PROT_WRITE)
647 .with_flags(
648 syscalls::MapFlags::MAP_PRIVATE
649 | syscalls::MapFlags::MAP_ANONYMOUS
650 | syscalls::MapFlags::MAP_FIXED_NOREPLACE,
651 )
652 .with_fd(-1)
653 .with_offset(0),
654 ))
655 .await;
656 match &mapped {
657 Ok(address) if *address as u64 == old_end => Ok(requested),
658 Ok(address) => {
662 let removed = guest
663 .inject_with_retry(syscalls::Syscall::Munmap(
664 syscalls::Munmap::new()
665 .with_addr(syscalls::Addr::from_raw(*address as usize))
666 .with_len(length as usize),
667 ))
668 .await;
669 diagnostic.rejected(
670 "mmap_wrong_address",
671 Some(limit),
672 smaps.as_deref(),
673 &(&mapped, &removed),
674 );
675 if removed.is_ok() {
676 Ok(current)
677 } else {
678 Err(
679 "unsupported: brk growth the kernel mapped elsewhere and Detcore could not remove",
680 )
681 }
682 }
683 Err(_) => {
684 diagnostic.rejected("mmap_error", Some(limit), smaps.as_deref(), &mapped);
685 Ok(current)
686 }
687 }
688 }
689}
690
691#[cfg(test)]
692mod tests {
693 use super::*;
694
695 const PAGE: u64 = 4096;
697
698 #[test]
699 fn a_shared_brk_check_uses_the_existing_mapping_policy() {
700 let smaps = b"00500000-00501000 rw-p 00000000 00:00 0\nVmFlags: rd wr\n";
701 let grow = |new_end| BrkMappingCheck::Grow {
702 old_end: 0x400000,
703 new_end,
704 };
705 assert!(grow(0x4ff000).allows_with_page(smaps, PAGE));
706 assert!(!grow(0x500000).allows_with_page(smaps, PAGE));
707 assert!(!grow(u64::MAX - PAGE + 1).allows_with_page(smaps, PAGE));
708 assert!(
709 BrkMappingCheck::Shrink {
710 new_end: 0x500000,
711 old_end: 0x501000,
712 }
713 .allows_with_page(smaps, PAGE)
714 );
715 assert!(
716 !BrkMappingCheck::Shrink {
717 new_end: 0x400000,
718 old_end: 0x500000,
719 }
720 .allows_with_page(smaps, PAGE)
721 );
722 assert!(grow(0x500000).allows_with_page(b"", PAGE));
723 assert!(
724 !BrkMappingCheck::Shrink {
725 new_end: 0x500000,
726 old_end: 0x501000,
727 }
728 .allows_with_page(b"", PAGE)
729 );
730 }
731
732 #[test]
733 fn a_break_grows_and_shrinks_only_where_linuxs_brk_would() {
734 let smaps = "\
73500400000-00401000 r-xp 00000000 00:20 1234 /image
736Size: 4 kB
737VmFlags: rd ex mr mw me
73800406000-00407000 r--p 00000000 00:00 0
739VmFlags: rd mr mw me
7407ffffff00000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
741VmFlags: rd wr mr mw me gd ac
742";
743 let vmas: Vec<Vma> = smaps_vmas(smaps.as_bytes(), PAGE).collect();
744 assert_eq!(vmas.len(), 3);
745 let mut named =
747 b"00500000-00501000 r--p 00000000 00:20 42 /n\xffme\nVmFlags: rd\n".to_vec();
748 named.extend_from_slice(smaps.as_bytes());
749 let named: Vec<Vma> = smaps_vmas(&named, PAGE).collect();
750 assert_eq!(named.len(), 4);
751 assert_eq!(named[1..], vmas[..]);
752 assert_eq!(
753 vmas[2],
754 Vma {
755 start: 0x7ffffff00000,
756 end: 0x7ffffffff000,
757 start_gap: stack_guard_gap(PAGE)
758 }
759 );
760 assert!(brk_may_grow(vmas.iter().copied(), 0x401000, 0x405000, PAGE));
763 assert!(!brk_may_grow(
764 vmas.iter().copied(),
765 0x401000,
766 0x406000,
767 PAGE
768 ));
769 let below_stack = 0x7ffffff00000 - stack_guard_gap(PAGE);
771 assert!(brk_may_grow(
772 vmas.iter().copied(),
773 0x500000,
774 below_stack - PAGE,
775 PAGE
776 ));
777 assert!(!brk_may_grow(
778 vmas.iter().copied(),
779 0x500000,
780 below_stack,
781 PAGE
782 ));
783 let shadow: Vec<Vma> = smaps_vmas(
785 b"00410000-00411000 rw-p 00000000 00:00 0\nVmFlags: rd wr mr mw me ss\n",
786 PAGE,
787 )
788 .collect();
789 assert_eq!(shadow[0].start_gap, PAGE);
790 assert!(brk_may_grow(
791 shadow.iter().copied(),
792 0x401000,
793 0x40e000,
794 PAGE
795 ));
796 assert!(!brk_may_grow(
797 shadow.iter().copied(),
798 0x401000,
799 0x40f000,
800 PAGE
801 ));
802 assert!(brk_may_shrink(vmas.iter().copied(), 0x405000, 0x407000));
804 assert!(!brk_may_shrink(vmas.iter().copied(), 0x402000, 0x406000));
805 }
806
807 #[test]
810 fn a_break_on_a_host_with_large_pages_keeps_large_page_gaps() {
811 const LARGE: u64 = 0x10000;
812 let smaps = "\
81300400000-00410000 r-xp 00000000 00:20 1234 /image
814VmFlags: rd ex mr mw me
81500460000-00470000 r--p 00000000 00:00 0
816VmFlags: rd mr mw me
8177ffffff00000-7fffffff0000 rw-p 00000000 00:00 0 [stack]
818VmFlags: rd wr mr mw me gd ac
819";
820 let vmas: Vec<Vma> = smaps_vmas(smaps.as_bytes(), LARGE).collect();
821 assert_eq!(vmas[2].start_gap, 256 * LARGE);
822 assert!(brk_may_grow(
825 vmas.iter().copied(),
826 0x410000,
827 0x450000,
828 LARGE
829 ));
830 assert!(!brk_may_grow(
831 vmas.iter().copied(),
832 0x410000,
833 0x460000,
834 LARGE
835 ));
836 let requested = 0x45f000;
840 let small = brk_page_align(requested, PAGE).unwrap();
841 let large = brk_page_align(requested, LARGE).unwrap();
842 assert_eq!((small, large), (0x45f000, 0x460000));
843 assert!(brk_may_grow(vmas.iter().copied(), 0x410000, small, PAGE));
844 assert!(!brk_may_grow(vmas.iter().copied(), 0x410000, large, LARGE));
845 let below_stack = 0x7ffffff00000 - 256 * LARGE;
847 assert!(brk_may_grow(
848 vmas.iter().copied(),
849 0x500000,
850 below_stack - LARGE,
851 LARGE
852 ));
853 assert!(!brk_may_grow(
854 vmas.iter().copied(),
855 0x500000,
856 below_stack,
857 LARGE
858 ));
859 assert_eq!(brk_page_align(0x421001, LARGE), Some(0x430000));
860 assert_eq!(brk_page_align(0x430000, LARGE), Some(0x430000));
861 assert_eq!(brk_page_align(0x421001, PAGE), Some(0x422000));
862 }
863
864 #[test]
865 fn madvise_known_linux_advice_has_an_explicit_policy() {
866 for advice in [
867 libc::MADV_NORMAL,
868 libc::MADV_RANDOM,
869 libc::MADV_SEQUENTIAL,
870 libc::MADV_WILLNEED,
871 ] {
872 assert_eq!(madvise_action(advice), MadviseAction::ForwardHint);
873 }
874
875 for advice in [
876 libc::MADV_DONTNEED,
877 libc::MADV_DONTFORK,
878 libc::MADV_DOFORK,
879 libc::MADV_DONTDUMP,
880 libc::MADV_DODUMP,
881 libc::MADV_WIPEONFORK,
882 libc::MADV_KEEPONFORK,
883 libc::MADV_DONTNEED_LOCKED,
884 MADV_GUARD_INSTALL,
885 MADV_GUARD_REMOVE,
886 ] {
887 assert_eq!(madvise_action(advice), MadviseAction::ForwardSemantic);
888 }
889
890 for advice in [
891 libc::MADV_FREE,
892 libc::MADV_MERGEABLE,
893 libc::MADV_UNMERGEABLE,
894 libc::MADV_HUGEPAGE,
895 libc::MADV_NOHUGEPAGE,
896 libc::MADV_COLD,
897 libc::MADV_PAGEOUT,
898 ] {
899 assert_eq!(madvise_action(advice), MadviseAction::Ignore);
900 }
901
902 for advice in [
903 libc::MADV_REMOVE,
904 libc::MADV_POPULATE_READ,
905 libc::MADV_POPULATE_WRITE,
906 reverie::syscalls::linux_abi::MADV_COLLAPSE,
907 ] {
908 assert_eq!(madvise_action(advice), MadviseAction::Reject(Errno::EINVAL));
909 }
910 for advice in [libc::MADV_HWPOISON, libc::MADV_SOFT_OFFLINE] {
911 assert_eq!(madvise_action(advice), MadviseAction::Reject(Errno::EPERM));
912 }
913 assert_eq!(madvise_action(i32::MAX), MadviseAction::Unknown);
914 }
915
916 #[test]
917 fn common_argument_validation_is_host_independent() {
918 let aligned = unsafe { AddrMut::<libc::c_void>::from_raw_unchecked(0x1000) };
919 assert!(
920 validate_common_args(
921 syscalls::Madvise::new()
922 .with_addr(Some(aligned))
923 .with_len(0)
924 .with_advice(libc::MADV_FREE),
925 )
926 .is_ok()
927 );
928
929 let unaligned = unsafe { AddrMut::<libc::c_void>::from_raw_unchecked(0x1001) };
930 assert!(
931 validate_common_args(
932 syscalls::Madvise::new()
933 .with_addr(Some(unaligned))
934 .with_len(0)
935 .with_advice(libc::MADV_FREE),
936 )
937 .is_err()
938 );
939
940 let page = crate::page::page_size() as usize;
941 let near_end =
942 unsafe { AddrMut::<libc::c_void>::from_raw_unchecked(usize::MAX & !(page - 1)) };
943 assert!(
944 validate_common_args(
945 syscalls::Madvise::new()
946 .with_addr(Some(near_end))
947 .with_len(page)
948 .with_advice(libc::MADV_FREE),
949 )
950 .is_err()
951 );
952 }
953}