xref: /aosp_15_r20/external/crosvm/src/sys/linux/panic_hook.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2019 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::env;
6 use std::fs::File;
7 use std::io::stderr;
8 use std::io::Read;
9 use std::panic;
10 use std::panic::PanicInfo;
11 use std::process::abort;
12 use std::string::String;
13 
14 use base::error;
15 use base::FromRawDescriptor;
16 use base::IntoRawDescriptor;
17 use libc::close;
18 use libc::dup;
19 use libc::dup2;
20 use libc::pipe2;
21 use libc::O_NONBLOCK;
22 use libc::STDERR_FILENO;
23 
24 // Opens a pipe and puts the write end into the stderr FD slot. On success, returns the read end of
25 // the pipe and the old stderr as a pair of files.
26 //
27 // This may fail if, for example, the file descriptor numbers have been exhausted.
redirect_stderr() -> Option<(File, File)>28 fn redirect_stderr() -> Option<(File, File)> {
29     let mut fds = [-1, -1];
30     // SAFETY: Trivially safe because the return value is checked.
31     unsafe {
32         let old_stderr = dup(STDERR_FILENO);
33         if old_stderr == -1 {
34             return None;
35         }
36         // Safe because pipe2 will only ever write two integers to our array and we check output.
37         let mut ret = pipe2(fds.as_mut_ptr(), O_NONBLOCK);
38         if ret != 0 {
39             // Leaks FDs, but not important right before abort.
40             return None;
41         }
42         // Safe because the FD we are duplicating is owned by us.
43         ret = dup2(fds[1], STDERR_FILENO);
44         if ret == -1 {
45             // Leaks FDs, but not important right before abort.
46             return None;
47         }
48         // The write end is no longer needed.
49         close(fds[1]);
50         // Safe because each of the fds was the result of a successful FD creation syscall.
51         Some((
52             File::from_raw_descriptor(fds[0]),
53             File::from_raw_descriptor(old_stderr),
54         ))
55     }
56 }
57 
58 // Sets stderr to the given file. Returns true on success.
restore_stderr(stderr: File) -> bool59 fn restore_stderr(stderr: File) -> bool {
60     let descriptor = stderr.into_raw_descriptor();
61 
62     // SAFETY:
63     // Safe because descriptor is guaranteed to be valid and replacing stderr
64     // should be an atomic operation.
65     unsafe { dup2(descriptor, STDERR_FILENO) != -1 }
66 }
67 
68 // Sends as much information about the panic as possible to syslog.
log_panic_info(default_panic: &(dyn Fn(&PanicInfo) + Sync + Send + 'static), info: &PanicInfo)69 fn log_panic_info(default_panic: &(dyn Fn(&PanicInfo) + Sync + Send + 'static), info: &PanicInfo) {
70     // Grab a lock of stderr to prevent concurrent threads from trampling on our stderr capturing
71     // procedure. The default_panic procedure likely uses stderr.lock as well, but the mutex inside
72     // stderr is reentrant, so it will not dead-lock on this thread.
73     let stderr = stderr();
74     let _stderr_lock = stderr.lock();
75 
76     // Redirect stderr to a pipe we can read from later.
77     let (mut read_file, old_stderr) = match redirect_stderr() {
78         Some(f) => f,
79         None => {
80             error!(
81                 "failed to capture stderr during panic: {}",
82                 std::io::Error::last_os_error()
83             );
84             // Fallback to stderr only panic logging.
85             env::set_var("RUST_BACKTRACE", "1");
86             default_panic(info);
87             return;
88         }
89     };
90     // Only through the default panic handler can we get a stacktrace. It only ever prints to
91     // stderr, hence all the previous code to redirect it to a pipe we can read.
92     env::set_var("RUST_BACKTRACE", "1");
93     default_panic(info);
94 
95     // Closes the write end of the pipe so that we can reach EOF in read_to_string. Also allows
96     // others to write to stderr without failure.
97     if !restore_stderr(old_stderr) {
98         error!("failed to restore stderr during panic");
99         return;
100     }
101     drop(_stderr_lock);
102 
103     let mut panic_output = String::new();
104     // Ignore errors and print what we got.
105     let _ = read_file.read_to_string(&mut panic_output);
106     // Split by line because the logging facilities do not handle embedded new lines well.
107     for line in panic_output.lines() {
108         error!("{}", line);
109     }
110 }
111 
112 /// The intent of our panic hook is to get panic info and a stacktrace into the syslog, even for
113 /// jailed subprocesses. It will always abort on panic to ensure a minidump is generated.
114 ///
115 /// Note that jailed processes will usually have a stacktrace of <unknown> because the backtrace
116 /// routines attempt to open this binary and are unable to do so in a jail.
set_panic_hook()117 pub fn set_panic_hook() {
118     let default_panic = panic::take_hook();
119     panic::set_hook(Box::new(move |info| {
120         log_panic_info(default_panic.as_ref(), info);
121         // Abort to trigger the crash reporter so that a minidump is generated.
122         abort();
123     }));
124 }
125