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