Skip to content

Commit 4ec6804

Browse files
committed
env: fix handling of real-time signals
The nix::sys::signal::Signal enum type does not cover real-time signals. In the current env code, this means that trying to block e.g. SIGRTMIN+7 is silently ignored, apply_signal_action() silently drops signals that cannot be represented as that enum. However, the enum cannot simply be extended to represent the real-time signals, because SIGRTMIN and SIGRTMAX cannot be considered constants. To workaround this, do not use the Signal enum type, and use libc where needed instead of the wrappers. Fixes: #11151
1 parent 2196769 commit 4ec6804

2 files changed

Lines changed: 86 additions & 30 deletions

File tree

src/uu/env/src/env.rs

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore (ToDO) chdir progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction Sigmask sigprocmask elidable
6+
// spell-checker:ignore (ToDO) chdir progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction Sigmask sigprocmask elidable sigset sigaddset sigemptyset
77

88
pub mod native_int_str;
99
pub mod split_iterator;
@@ -20,10 +20,7 @@ use native_int_str::{
2020
#[cfg(unix)]
2121
use nix::libc;
2222
#[cfg(unix)]
23-
use nix::sys::signal::{
24-
SigHandler::{SigDfl, SigIgn},
25-
SigSet, SigmaskHow, Signal, signal, sigprocmask,
26-
};
23+
use nix::sys::signal::{SigSet, SigmaskHow, Signal, sigprocmask};
2724
#[cfg(unix)]
2825
use nix::unistd::execvp;
2926
use std::borrow::Cow;
@@ -37,13 +34,18 @@ use std::io;
3734
use std::io::Write as _;
3835
use std::io::stderr;
3936
#[cfg(unix)]
37+
use std::mem::zeroed;
38+
#[cfg(unix)]
4039
use std::os::unix::ffi::OsStrExt;
4140

4241
use uucore::display::{Quotable, print_all_env_vars};
4342
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError};
4443
use uucore::line_ending::LineEnding;
4544
#[cfg(unix)]
46-
use uucore::signals::{signal_by_name_or_value, signal_name_by_value, signal_number_upper_bound};
45+
use uucore::signals::{
46+
realtime_signal_bounds, signal_by_name_or_value, signal_name_by_value,
47+
signal_number_upper_bound,
48+
};
4749
use uucore::translate;
4850
use uucore::{format_usage, show_warning};
4951

@@ -294,19 +296,6 @@ fn build_signal_request(
294296
Ok(request)
295297
}
296298

297-
#[cfg(unix)]
298-
fn signal_from_value(sig_value: usize) -> UResult<Signal> {
299-
Signal::try_from(sig_value as i32).map_err(|_| {
300-
USimpleError::new(
301-
125,
302-
translate!(
303-
"env-error-invalid-signal",
304-
"signal" => sig_value.to_string().quote()
305-
),
306-
)
307-
})
308-
}
309-
310299
fn load_config_file(opts: &mut Options) -> UResult<()> {
311300
// NOTE: config files are parsed using an INI parser b/c it's available and compatible with ".env"-style files
312301
// ... * but support for actual INI files, although working, is not intended, nor claimed
@@ -1064,6 +1053,19 @@ fn apply_specified_env_vars(opts: &Options<'_>) {
10641053
}
10651054
}
10661055

1056+
#[cfg(unix)]
1057+
fn signal_is_valid(sig: usize) -> bool {
1058+
if Signal::try_from(sig as i32).is_err() {
1059+
// nix::sys::signal does not know about real-time signals, so check that
1060+
// ourselves.
1061+
if let Some((rtmin, rtmax)) = realtime_signal_bounds() {
1062+
return sig >= rtmin && sig <= rtmax;
1063+
}
1064+
}
1065+
1066+
true
1067+
}
1068+
10671069
#[cfg(unix)]
10681070
fn apply_signal_action<F>(
10691071
request: &SignalRequest,
@@ -1072,15 +1074,16 @@ fn apply_signal_action<F>(
10721074
signal_fn: F,
10731075
) -> UResult<()>
10741076
where
1075-
F: Fn(Signal) -> UResult<()>,
1077+
F: Fn(usize) -> UResult<()>,
10761078
{
10771079
request.for_each_signal(|sig_value, explicit| {
10781080
// On some platforms ALL_SIGNALS may contain values that are not valid in libc.
10791081
// Skip those invalid ones and continue (GNU env also ignores undefined signals).
1080-
let Ok(sig) = signal_from_value(sig_value) else {
1082+
if !signal_is_valid(sig_value) {
10811083
return Ok(());
1082-
};
1083-
signal_fn(sig)?;
1084+
}
1085+
1086+
signal_fn(sig_value)?;
10841087
log.record(sig_value, action_kind, explicit);
10851088

10861089
// Set environment variable to communicate to Rust child processes
@@ -1096,9 +1099,14 @@ where
10961099
}
10971100

10981101
#[cfg(unix)]
1099-
fn ignore_signal(sig: Signal) -> UResult<()> {
1102+
fn ignore_signal(sig: usize) -> UResult<()> {
11001103
// SAFETY: This is safe because we write the handler for each signal only once, and therefore "the current handler is the default", as the documentation requires it.
1101-
let result = unsafe { signal(sig, SigIgn) };
1104+
// nix::sys::signal::Signal does not cover real-time signals, so we need to call
1105+
// libc::signal directly.
1106+
let result = unsafe {
1107+
let res = libc::signal(sig as libc::c_int, libc::SIG_IGN);
1108+
nix::errno::Errno::result(res)
1109+
};
11021110
if let Err(err) = result {
11031111
return Err(USimpleError::new(
11041112
125,
@@ -1109,8 +1117,13 @@ fn ignore_signal(sig: Signal) -> UResult<()> {
11091117
}
11101118

11111119
#[cfg(unix)]
1112-
fn reset_signal(sig: Signal) -> UResult<()> {
1113-
let result = unsafe { signal(sig, SigDfl) };
1120+
fn reset_signal(sig: usize) -> UResult<()> {
1121+
// nix::sys::signal::Signal does not cover real-time signals, so we need to call
1122+
// libc::signal directly.
1123+
let result = unsafe {
1124+
let res = libc::signal(sig as libc::c_int, libc::SIG_DFL);
1125+
nix::errno::Errno::result(res)
1126+
};
11141127
if let Err(err) = result {
11151128
return Err(USimpleError::new(
11161129
125,
@@ -1121,9 +1134,37 @@ fn reset_signal(sig: Signal) -> UResult<()> {
11211134
}
11221135

11231136
#[cfg(unix)]
1124-
fn block_signal(sig: Signal) -> UResult<()> {
1125-
let mut set = SigSet::empty();
1126-
set.add(sig);
1137+
fn block_signal(sig: usize) -> UResult<()> {
1138+
// nix::sys::signal::Signal does not cover real time signals, so we need to build
1139+
// sigset_t manually using libc.
1140+
let set = unsafe {
1141+
let mut sigset: libc::sigset_t = zeroed();
1142+
1143+
if let Err(err) = nix::errno::Errno::result(libc::sigemptyset(&raw mut sigset)) {
1144+
return Err(USimpleError::new(
1145+
125,
1146+
translate!(
1147+
"env-error-failed-set-signal-action",
1148+
"signal" => (sig as i32),
1149+
"error" => err.desc()
1150+
),
1151+
));
1152+
}
1153+
if let Err(err) =
1154+
nix::errno::Errno::result(libc::sigaddset(&raw mut sigset, sig as libc::c_int))
1155+
{
1156+
return Err(USimpleError::new(
1157+
125,
1158+
translate!(
1159+
"env-error-failed-set-signal-action",
1160+
"signal" => (sig as i32),
1161+
"error" => err.desc()
1162+
),
1163+
));
1164+
}
1165+
SigSet::from_sigset_t_unchecked(sigset)
1166+
};
1167+
11271168
if let Err(err) = sigprocmask(SigmaskHow::SIG_BLOCK, Some(&set), None) {
11281169
return Err(USimpleError::new(
11291170
125,

tests/by-util/test_env.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,21 @@ fn test_env_block_signal_flag() {
945945
.no_stderr();
946946
}
947947

948+
#[test]
949+
#[cfg(any(target_os = "linux", target_os = "android"))]
950+
fn test_env_block_realtime_signal() {
951+
new_ucmd!()
952+
.env("PATH", PATH)
953+
.args(&["--block-signal=SIGRTMIN+7", "true"])
954+
.succeeds()
955+
.no_stderr();
956+
new_ucmd!()
957+
.env("PATH", PATH)
958+
.args(&["--block-signal=SIGRTMAX-7", "true"])
959+
.succeeds()
960+
.no_stderr();
961+
}
962+
948963
#[test]
949964
#[cfg(unix)]
950965
fn test_env_list_signal_handling_reports_ignore() {

0 commit comments

Comments
 (0)