• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // run-pass
2 // no-prefer-dynamic
3 // ignore-wasm32-bare no libc
4 // ignore-windows
5 // ignore-sgx no libc
6 // ignore-emscripten no processes
7 // ignore-sgx no processes
8 // ignore-fuchsia no fork
9 
10 #![feature(rustc_private)]
11 #![feature(never_type)]
12 #![feature(panic_always_abort)]
13 
14 #![allow(invalid_from_utf8)]
15 
16 extern crate libc;
17 
18 use std::alloc::{GlobalAlloc, Layout};
19 use std::fmt;
20 use std::panic::{self, panic_any};
21 use std::os::unix::process::{CommandExt, ExitStatusExt};
22 use std::process::{self, Command, ExitStatus};
23 use std::sync::atomic::{AtomicU32, Ordering};
24 
25 use libc::c_int;
26 
27 /// This stunt allocator allows us to spot heap allocations in the child.
28 struct PidChecking<A> {
29     parent: A,
30     require_pid: AtomicU32,
31 }
32 
33 #[global_allocator]
34 static ALLOCATOR: PidChecking<std::alloc::System> = PidChecking {
35     parent: std::alloc::System,
36     require_pid: AtomicU32::new(0),
37 };
38 
39 impl<A> PidChecking<A> {
engage(&self)40     fn engage(&self) {
41         let parent_pid = process::id();
42         eprintln!("engaging allocator trap, parent pid={}", parent_pid);
43         self.require_pid.store(parent_pid, Ordering::Release);
44     }
check(&self)45     fn check(&self) {
46         let require_pid = self.require_pid.load(Ordering::Acquire);
47         if require_pid != 0 {
48             let actual_pid = process::id();
49             if require_pid != actual_pid {
50                 unsafe {
51                     libc::raise(libc::SIGUSR1);
52                 }
53             }
54         }
55     }
56 }
57 
58 unsafe impl<A:GlobalAlloc> GlobalAlloc for PidChecking<A> {
alloc(&self, layout: Layout) -> *mut u859     unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
60         self.check();
61         self.parent.alloc(layout)
62     }
63 
dealloc(&self, ptr: *mut u8, layout: Layout)64     unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
65         self.check();
66         self.parent.dealloc(ptr, layout)
67     }
68 
alloc_zeroed(&self, layout: Layout) -> *mut u869     unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
70         self.check();
71         self.parent.alloc_zeroed(layout)
72     }
73 
realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u874     unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
75         self.check();
76         self.parent.realloc(ptr, layout, new_size)
77     }
78 }
79 
expect_aborted(status: ExitStatus)80 fn expect_aborted(status: ExitStatus) {
81     dbg!(status);
82     let signal = status.signal().expect("expected child process to die of signal");
83 
84     #[cfg(not(target_os = "android"))]
85     assert!(signal == libc::SIGABRT || signal == libc::SIGILL || signal == libc::SIGTRAP);
86 
87     #[cfg(target_os = "android")]
88     {
89         assert!(signal == libc::SIGABRT || signal == libc::SIGSEGV);
90 
91         if signal == libc::SIGSEGV {
92             // Pre-KitKat versions of Android signal an abort() with SIGSEGV at address 0xdeadbaad
93             // See e.g. https://groups.google.com/g/android-ndk/c/laW1CJc7Icc
94             //
95             // This behavior was changed in KitKat to send a standard SIGABRT signal.
96             // See: https://r.android.com/60341
97             //
98             // Additional checks performed:
99             // 1. Find last tombstone (similar to coredump but in text format) from the
100             //    same executable (path) as we are (must be because of usage of fork):
101             //    This ensures that we look into the correct tombstone.
102             // 2. Cause of crash is a SIGSEGV with address 0xdeadbaad.
103             // 3. libc::abort call is in one of top two functions on callstack.
104             // The last two steps distinguish between a normal SIGSEGV and one caused
105             // by libc::abort.
106 
107             let this_exe = std::env::current_exe().unwrap().into_os_string().into_string().unwrap();
108             let exe_string = format!(">>> {this_exe} <<<");
109             let tombstone = (0..100)
110                 .map(|n| format!("/data/tombstones/tombstone_{n:02}"))
111                 .filter(|f| std::path::Path::new(&f).exists())
112                 .map(|f| std::fs::read_to_string(&f).expect("Cannot read tombstone file"))
113                 .filter(|f| f.contains(&exe_string))
114                 .last()
115                 .expect("no tombstone found");
116 
117             println!("Content of tombstone:\n{tombstone}");
118 
119             assert!(tombstone
120                 .contains("signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr deadbaad"));
121             let abort_on_top = tombstone
122                 .lines()
123                 .skip_while(|l| !l.contains("backtrace:"))
124                 .skip(1)
125                 .take_while(|l| l.starts_with("    #"))
126                 .take(2)
127                 .any(|f| f.contains("/system/lib/libc.so (abort"));
128             assert!(abort_on_top);
129         }
130     }
131 }
132 
main()133 fn main() {
134     ALLOCATOR.engage();
135 
136     fn run(do_panic: &dyn Fn()) -> ExitStatus {
137         let child = unsafe { libc::fork() };
138         assert!(child >= 0);
139         if child == 0 {
140             panic::always_abort();
141             do_panic();
142             process::exit(0);
143         }
144         let mut status: c_int = 0;
145         let got = unsafe { libc::waitpid(child, &mut status, 0) };
146         assert_eq!(got, child);
147         let status = ExitStatus::from_raw(status.into());
148         status
149     }
150 
151     fn one(do_panic: &dyn Fn()) {
152         let status = run(do_panic);
153         expect_aborted(status);
154     }
155 
156     one(&|| panic!());
157     one(&|| panic!("some message"));
158     one(&|| panic!("message with argument: {}", 42));
159 
160     #[derive(Debug)]
161     struct Wotsit { }
162     one(&|| panic_any(Wotsit { }));
163 
164     let mut c = Command::new("echo");
165     unsafe {
166         c.pre_exec(|| panic!("{}", "crash now!"));
167     }
168     let st = c.status().expect("failed to get command status");
169     expect_aborted(st);
170 
171     struct DisplayWithHeap;
172     impl fmt::Display for DisplayWithHeap {
173         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
174             let s = vec![0; 100];
175             let s = std::hint::black_box(s);
176             write!(f, "{:?}", s)
177         }
178     }
179 
180     // Some panics in the stdlib that we want not to allocate, as
181     // otherwise these facilities become impossible to use in the
182     // child after fork, which is really quite awkward.
183 
184     one(&|| { None::<DisplayWithHeap>.unwrap(); });
185     one(&|| { None::<DisplayWithHeap>.expect("unwrapped a none"); });
186     one(&|| { std::str::from_utf8(b"\xff").unwrap(); });
187     one(&|| {
188         let x = [0, 1, 2, 3];
189         let y = x[std::hint::black_box(4)];
190         let _z = std::hint::black_box(y);
191     });
192 
193     // Finally, check that our stunt allocator can actually catch an allocation after fork.
194     // ie, that our test is effective.
195 
196     let status = run(&|| panic!("allocating to display... {}", DisplayWithHeap));
197     dbg!(status);
198     assert_eq!(status.signal(), Some(libc::SIGUSR1));
199 }
200