From f44843ac42228ca2a9b2108d5e078efc5ba7a191 Mon Sep 17 00:00:00 2001 From: Tanbir Hossain Ramim <96797470+TanbirRamim@users.noreply.github.com> Date: Wed, 7 Oct 2026 14:52:53 +0200 Subject: [PATCH 1/2] timeout: don't dump core when re-raising the child's signal --- src/uu/timeout/src/platform/unix.rs | 25 +++++++++++++++++++++++-- tests/by-util/test_timeout.rs | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/uu/timeout/src/platform/unix.rs b/src/uu/timeout/src/platform/unix.rs index 30a05a6af5a..9ff690beaef 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 prctl rlimit setrlimit DUMPABLE rlim use std::io; use std::os::unix::process::CommandExt; @@ -136,9 +136,30 @@ 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 { + #[cfg(any(target_os = "linux", target_os = "android"))] + // SAFETY: PR_SET_DUMPABLE only takes an integer argument. + if unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0) } == 0 { + return true; + } + let limit = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + // SAFETY: `limit` is a valid rlimit that outlives the call. + unsafe { libc::setrlimit(libc::RLIMIT_CORE, &raw const limit) == 0 } +} diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 3d09a6e21ac..567e0cb5b09 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -169,6 +169,24 @@ fn test_kill_signal_reports_signal_exit_code() { .no_output(); } +/// When the child dumps core, timeout re-raises its signal without dumping +/// core itself. +#[test] +#[cfg(any(target_os = "linux", target_os = "macos"))] +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(); From 9dc76731f7bdd0d402ac80e1f24379de0d3a3e3e Mon Sep 17 00:00:00 2001 From: Tanbir Hossain Ramim <96797470+TanbirRamim@users.noreply.github.com> Date: Fri, 23 Oct 2026 05:04:40 +0200 Subject: [PATCH 2/2] timeout: use rustix for disabling core dumps, keep the core dump test Linux only --- src/uu/timeout/src/platform/unix.rs | 17 +++++++++-------- tests/by-util/test_timeout.rs | 3 ++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/uu/timeout/src/platform/unix.rs b/src/uu/timeout/src/platform/unix.rs index 9ff690beaef..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 prctl rlimit setrlimit DUMPABLE rlim +// spell-checker:ignore (ToDO) sigstr setpgid sigchld sigwait getpid TTIN TTOU rlimit setrlimit Dumpable use std::io; use std::os::unix::process::CommandExt; @@ -151,15 +151,16 @@ pub(crate) fn preserve_signal_info(signal: core::ffi::c_int) -> core::ffi::c_int /// 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"))] - // SAFETY: PR_SET_DUMPABLE only takes an integer argument. - if unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0) } == 0 { + if rustix::process::set_dumpable_behavior(rustix::process::DumpableBehavior::NotDumpable) + .is_ok() + { return true; } - let limit = libc::rlimit { - rlim_cur: 0, - rlim_max: 0, + let limit = Rlimit { + current: Some(0), + maximum: Some(0), }; - // SAFETY: `limit` is a valid rlimit that outlives the call. - unsafe { libc::setrlimit(libc::RLIMIT_CORE, &raw const limit) == 0 } + setrlimit(Resource::Core, limit).is_ok() } diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 567e0cb5b09..86922a9ac83 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -171,8 +171,9 @@ fn test_kill_signal_reports_signal_exit_code() { /// 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(any(target_os = "linux", target_os = "macos"))] +#[cfg(target_os = "linux")] fn test_child_core_dump_not_repeated() { use std::os::unix::process::ExitStatusExt; let ts = TestScenario::new(util_name!());