Translate WASI signal dispositions through libc sentinels (#9024)

Keep Python disposition values distinct from WASI function-table pointers. Recognize wasi-libc SIG_ERR before updating Python handlers and preserve ignored/default/custom dispositions. Add synchronous signal and rejected-registration regressions to the existing snippet.

AI assistance covered source investigation, implementation, and automated reference/baseline tests. Isolated patched runtime validation is pending. No human validation is claimed.

Assisted-by: Codex:model-version-unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-09 23:59:43 +09:00
1 parent bf984b465e
commit d4b43b860d
3 files changed
+100 -24

No files matched your search

+45 -19
View File
@@ -46,37 +46,63 @@ mod wasm {
pub const SIGSEGV: i32 = 11;
pub const SIGTERM: i32 = 15;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Handler {
Default,
Ignore,
Custom(sighandler_t),
}
unsafe extern "C" {
fn signal(signum: i32, handler: sighandler_t) -> sighandler_t;
#[link_name = "signal"]
fn c_signal(signum: i32, handler: sighandler_t) -> sighandler_t;
#[link_name = "__SIG_IGN"]
fn c_sig_ign(signum: i32);
#[link_name = "__SIG_ERR"]
fn c_sig_err(signum: i32);
fn raise(signum: i32) -> i32;
}
impl Handler {
fn as_raw(self) -> sighandler_t {
match self {
Self::Default => 0,
Self::Ignore => c_sig_ign as *const () as sighandler_t,
Self::Custom(handler) => handler,
}
}
fn from_raw(handler: sighandler_t) -> io::Result<Self> {
// wasi-libc uses function pointers for SIG_IGN and SIG_ERR because
// table index 1 is also a valid custom Wasm function pointer.
if handler == c_sig_err as *const () as sighandler_t {
Err(io::Error::last_os_error())
} else if handler == 0 {
Ok(Self::Default)
} else if handler == c_sig_ign as *const () as sighandler_t {
Ok(Self::Ignore)
} else {
Ok(Self::Custom(handler))
}
}
}
/// # Safety
///
/// The caller must ensure `signalnum` is a valid platform signal number.
pub unsafe fn probe_handler(signalnum: i32) -> Option<sighandler_t> {
let handler = unsafe { signal(signalnum, SIG_IGN) };
if handler == SIG_ERR {
None
} else {
unsafe { signal(signalnum, handler) };
Some(handler)
}
pub unsafe fn probe_handler(signalnum: i32) -> Option<Handler> {
let handler = unsafe { install_handler(signalnum, Handler::Ignore) }.ok()?;
unsafe { install_handler(signalnum, handler) }.ok()?;
Some(handler)
}
/// # Safety
///
/// The caller must ensure `signalnum` is a valid platform signal number and
/// `handler` is accepted by the platform signal ABI.
pub unsafe fn install_handler(
signalnum: i32,
handler: sighandler_t,
) -> io::Result<sighandler_t> {
let old = unsafe { signal(signalnum, handler) };
if old == SIG_ERR {
return Err(io::Error::last_os_error());
}
Ok(old)
/// a custom `handler` is accepted by the platform signal ABI.
pub unsafe fn install_handler(signalnum: i32, handler: Handler) -> io::Result<Handler> {
let old = unsafe { c_signal(signalnum, handler.as_raw()) };
Handler::from_raw(old)
}
pub fn raise_signal(signalnum: i32) -> io::Result<()> {
+26 -5
View File
@@ -175,9 +175,16 @@ pub(crate) mod _signal {
let Some(handler) = (unsafe { host_signal::probe_handler(signum) }) else {
continue;
};
let py_handler = if handler == SIG_DFL {
let (is_default, is_ignored) = cfg_select! {
target_os = "wasi" => (
handler == host_signal::Handler::Default,
handler == host_signal::Handler::Ignore,
),
_ => (handler == SIG_DFL, handler == SIG_IGN),
};
let py_handler = if is_default {
Some(sig_dfl.clone())
} else if handler == SIG_IGN {
} else if is_ignored {
Some(sig_ign.clone())
} else {
None
@@ -223,15 +230,29 @@ pub(crate) mod _signal {
}
let sig_handler = if handler.is_callable() {
run_signal as *const () as sighandler_t
let callback = run_signal as *const () as sighandler_t;
cfg_select! {
target_os = "wasi" => host_signal::Handler::Custom(callback),
_ => callback,
}
} else {
const MSG: &str =
"signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object";
usize::try_from_borrowed_object(vm, &handler)
let disposition = usize::try_from_borrowed_object(vm, &handler)
.ok()
.filter(|&v| matches!(v, SIG_DFL | SIG_IGN))
.ok_or_else(|| vm.new_type_error(MSG))?
.ok_or_else(|| vm.new_type_error(MSG))?;
cfg_select! {
target_os = "wasi" => {
if disposition == SIG_DFL {
host_signal::Handler::Default
} else {
host_signal::Handler::Ignore
}
},
_ => disposition,
}
};
signal::check_signals(vm)?;
+29
View File
@@ -21,6 +21,35 @@ assert old_signal is signal.SIG_IGN
assert signal.getsignal(signal.SIGILL) is signal.SIG_DFL
# Ignoring a synchronously raised signal must not invoke a callback, including
# on WASI where libc uses a function pointer rather than integer 1 for SIG_IGN.
synchronous_signals = []
def synchronous_handler(signum, frame):
synchronous_signals.append(signum)
previous = signal.signal(signal.SIGILL, synchronous_handler)
try:
signal.raise_signal(signal.SIGILL)
assert synchronous_signals == [signal.SIGILL]
assert signal.signal(signal.SIGILL, signal.SIG_IGN) is synchronous_handler
signal.raise_signal(signal.SIGILL)
assert synchronous_signals == [signal.SIGILL]
assert signal.getsignal(signal.SIGILL) is signal.SIG_IGN
finally:
signal.signal(signal.SIGILL, previous)
if sys.platform == "wasi":
# wasi-libc rejects SIGKILL (9) and SIGSTOP (19), even though they are not
# exported by RustPython's minimal WASI signal module.
for uncatchable in (9, 19):
previous = signal.getsignal(uncatchable)
assert_raises(OSError, signal.signal, uncatchable, signal.SIG_IGN)
assert signal.getsignal(uncatchable) is previous
# unix, and not WASI
if "win" not in sys.platform and sys.platform != "wasi":
signal.signal(signal.SIGALRM, handler)