diff --git a/src/uu/timeout/src/platform/unix.rs b/src/uu/timeout/src/platform/unix.rs index 30a05a6af5a..1e10579b9af 100644 --- a/src/uu/timeout/src/platform/unix.rs +++ b/src/uu/timeout/src/platform/unix.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) sigstr setpgid sigchld sigwait getpid TTIN TTOU +// spell-checker:ignore (ToDO) sigstr setpgid sigchld sigwait getpid TTIN TTOU rlimit setrlimit Dumpable use std::io; use std::os::unix::process::CommandExt; @@ -136,9 +136,31 @@ pub(crate) fn preserve_signal_info(signal: core::ffi::c_int) -> core::ffi::c_int // The easiest way to preserve the latter seems to be to kill // ourselves with whatever signal our child exited with, which is // what the following is intended to accomplish. - if let Some(sig) = signal_from_raw(signal) { + // + // If the child dumped core, re-raising a signal like SIGSEGV would make + // us dump core too and get blamed for the crash, so only do it once + // core dumps are off. + if let Some(sig) = signal_from_raw(signal) + && disable_core_dumps() + { let _ = unblock_signal(sig); let _ = kill_process(getpid(), sig); } signal } + +/// Make sure this process won't dump core, returning whether that worked. +fn disable_core_dumps() -> bool { + use rustix::process::{Resource, Rlimit, setrlimit}; + #[cfg(any(target_os = "linux", target_os = "android"))] + if rustix::process::set_dumpable_behavior(rustix::process::DumpableBehavior::NotDumpable) + .is_ok() + { + return true; + } + let limit = Rlimit { + current: Some(0), + maximum: Some(0), + }; + setrlimit(Resource::Core, limit).is_ok() +} diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 3d09a6e21ac..86922a9ac83 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -169,6 +169,25 @@ fn test_kill_signal_reports_signal_exit_code() { .no_output(); } +/// When the child dumps core, timeout re-raises its signal without dumping +/// core itself. +// Linux only: on macOS the child would write a real core file into /cores. +#[test] +#[cfg(target_os = "linux")] +fn test_child_core_dump_not_repeated() { + use std::os::unix::process::ExitStatusExt; + let ts = TestScenario::new(util_name!()); + let script = r#"ulimit -c unlimited 2>/dev/null; exec "$0" timeout 5 sh -c 'kill -SEGV $$'"#; + let status = std::process::Command::new("sh") + .args(["-c", script]) + .arg(&ts.bin_path) + .current_dir(ts.fixtures.as_string()) + .status() + .unwrap(); + assert_eq!(status.signal(), Some(libc::SIGSEGV)); + assert!(!status.core_dumped()); +} + #[test] fn test_preserve_status_even_when_send_signal() { let (ts, bin) = scenario_with_bin();