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