Skip to content

Commit 997efae

Browse files
committed
core(exec): use libc signals and cfg(target_family="unix"); fix SIGSYS gating for Windows/*BSD
- Replace hardcoded signal numbers with libc::* on Unix (SIGINT/SIGABRT/SIGBUS/SIGFPE/SIGKILL/SIGSEGV/SIGPIPE/SIGTERM/SIGSYS). - Group Unix signal constants in a unix_sig module and re-export; removes repeated per-line cfgs. - Define SIGSYS_CODE via libc on all Unix and gate all SIGSYS uses with cfg(target_family="unix"). - Keep non-Unix fallback SIGKILL_CODE=9 to preserve synthesized 128+9 behavior. - sandbox_verdict/process_exec_tool_call: - Check SIGSYS only on Unix and classify as LikelySandbox. - Defer SIGSYS only when a sandbox was requested; otherwise, non-timeout signals error immediately. - Build “128 + signal” exit-code checks only on Unix. - Tests: gate sandbox_sigsys_codepath behind cfg(target_family="unix"). This restores cross-platform builds (Windows and non-Linux/macOS Unix) while simplifying the platform gating.
1 parent 2b517b6 commit 997efae

File tree

1 file changed

+67
-45
lines changed

1 file changed

+67
-45
lines changed

codex-rs/core/src/exec.rs

Lines changed: 67 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,31 @@ use crate::spawn::spawn_child_async;
3030

3131
const DEFAULT_TIMEOUT_MS: u64 = 10_000;
3232

33-
// Hardcode these since it does not seem worth including the libc crate just
34-
// for these.
35-
const SIGKILL_CODE: i32 = 9;
33+
// Conventional bases and codes that are platform-agnostic.
3634
const TIMEOUT_CODE: i32 = 64;
3735
const EXIT_CODE_SIGNAL_BASE: i32 = 128; // conventional shell: 128 + signal
3836
const EXEC_TIMEOUT_EXIT_CODE: i32 = 124; // conventional timeout exit code
3937
const EXIT_NOT_EXECUTABLE_CODE: i32 = 126; // "found but not executable"/shebang/perms
4038

41-
// Common Unix signal numbers used below (document for maintainers)
42-
// Keep these aligned with platform conventions; these are standard on macOS/Linux.
43-
const SIGINT_CODE: i32 = 2; // Ctrl-C / interrupt
44-
const SIGABRT_CODE: i32 = 6; // abort
45-
const SIGBUS_CODE: i32 = 7; // bus error
46-
const SIGFPE_CODE: i32 = 8; // floating point exception
47-
const SIGSEGV_CODE: i32 = 11; // segmentation fault
48-
const SIGPIPE_CODE: i32 = 13; // broken pipe
49-
const SIGTERM_CODE: i32 = 15; // termination
50-
51-
// Platform-gated SIGSYS signal numbers with no libc dep (Linux & macOS only).
52-
// Linux SIGSYS is 31; macOS SIGSYS is 12.
53-
#[cfg(target_os = "linux")]
54-
const SIGSYS_CODE: i32 = 31;
55-
#[cfg(target_os = "macos")]
56-
const SIGSYS_CODE: i32 = 12;
39+
// Unix signal constants via libc (grouped), including SIGSYS. These are only available on Unix.
40+
#[cfg(target_family = "unix")]
41+
mod unix_sig {
42+
pub const SIGINT_CODE: i32 = libc::SIGINT as i32; // Ctrl-C / interrupt
43+
pub const SIGABRT_CODE: i32 = libc::SIGABRT as i32; // abort
44+
pub const SIGBUS_CODE: i32 = libc::SIGBUS as i32; // bus error
45+
pub const SIGFPE_CODE: i32 = libc::SIGFPE as i32; // floating point exception
46+
pub const SIGSEGV_CODE: i32 = libc::SIGSEGV as i32; // segmentation fault
47+
pub const SIGPIPE_CODE: i32 = libc::SIGPIPE as i32; // broken pipe
48+
pub const SIGTERM_CODE: i32 = libc::SIGTERM as i32; // termination
49+
pub const SIGKILL_CODE: i32 = libc::SIGKILL as i32;
50+
pub const SIGSYS_CODE: i32 = libc::SIGSYS as i32;
51+
}
52+
#[cfg(target_family = "unix")]
53+
use unix_sig::*;
54+
55+
// On non-Unix (Windows), synthesize a "killed" signal using the conventional 9.
56+
#[cfg(not(target_family = "unix"))]
57+
const SIGKILL_CODE: i32 = 9;
5758

5859
// I/O buffer sizing
5960
const READ_CHUNK_SIZE: usize = 8192; // bytes per read
@@ -113,11 +114,11 @@ fn sandbox_verdict(sandbox_type: SandboxType, status: ExitStatus, stderr: &str)
113114
}
114115

115116
// Prefer the signal path when available.
116-
#[cfg(unix)]
117+
#[cfg(target_family = "unix")]
117118
{
118119
if let Some(sig) = status.signal() {
119120
if sig == SIGSYS_CODE {
120-
// SIGSYS under seccomp (Linux) or bad syscall elsewhere is a strong tell.
121+
// SIGSYS under seccomp/seatbelt or invalid syscall is a strong tell.
121122
return SandboxVerdict::LikelySandbox;
122123
}
123124
// Common user/app signals -> not sandbox.
@@ -140,29 +141,46 @@ fn sandbox_verdict(sandbox_type: SandboxType, status: ExitStatus, stderr: &str)
140141
// - 127: command not found
141142
// - 64..=78: BSD sysexits range
142143
// - 128 + {SIGINT, SIGABRT, SIGBUS, SIGFPE, SIGKILL, SIGSEGV, SIGPIPE, SIGTERM}
143-
let sig_derived_not_sandbox = [
144-
EXIT_CODE_SIGNAL_BASE + SIGINT_CODE,
145-
EXIT_CODE_SIGNAL_BASE + SIGABRT_CODE,
146-
EXIT_CODE_SIGNAL_BASE + SIGBUS_CODE,
147-
EXIT_CODE_SIGNAL_BASE + SIGFPE_CODE,
148-
EXIT_CODE_SIGNAL_BASE + SIGKILL_CODE,
149-
EXIT_CODE_SIGNAL_BASE + SIGSEGV_CODE,
150-
EXIT_CODE_SIGNAL_BASE + SIGPIPE_CODE,
151-
EXIT_CODE_SIGNAL_BASE + SIGTERM_CODE,
152-
];
153-
if code == 0
154-
|| code == 2
155-
|| code == EXEC_TIMEOUT_EXIT_CODE
156-
|| code == 127
157-
|| (64..=78).contains(&code)
158-
|| sig_derived_not_sandbox.contains(&code)
144+
#[cfg(target_family = "unix")]
159145
{
160-
return SandboxVerdict::LikelyNotSandbox;
146+
let sig_derived_not_sandbox = [
147+
EXIT_CODE_SIGNAL_BASE + SIGINT_CODE,
148+
EXIT_CODE_SIGNAL_BASE + SIGABRT_CODE,
149+
EXIT_CODE_SIGNAL_BASE + SIGBUS_CODE,
150+
EXIT_CODE_SIGNAL_BASE + SIGFPE_CODE,
151+
EXIT_CODE_SIGNAL_BASE + SIGKILL_CODE,
152+
EXIT_CODE_SIGNAL_BASE + SIGSEGV_CODE,
153+
EXIT_CODE_SIGNAL_BASE + SIGPIPE_CODE,
154+
EXIT_CODE_SIGNAL_BASE + SIGTERM_CODE,
155+
];
156+
if code == 0
157+
|| code == 2
158+
|| code == EXEC_TIMEOUT_EXIT_CODE
159+
|| code == 127
160+
|| (64..=78).contains(&code)
161+
|| sig_derived_not_sandbox.contains(&code)
162+
{
163+
return SandboxVerdict::LikelyNotSandbox;
164+
}
165+
}
166+
#[cfg(not(target_family = "unix"))]
167+
{
168+
if code == 0
169+
|| code == 2
170+
|| code == EXEC_TIMEOUT_EXIT_CODE
171+
|| code == 127
172+
|| (64..=78).contains(&code)
173+
{
174+
return SandboxVerdict::LikelyNotSandbox;
175+
}
161176
}
162177

163-
// Shell-style signal encoding for SIGSYS.
164-
if code == EXIT_CODE_SIGNAL_BASE + SIGSYS_CODE {
165-
return SandboxVerdict::LikelySandbox;
178+
// Shell-style signal encoding for SIGSYS (Unix).
179+
#[cfg(target_family = "unix")]
180+
{
181+
if code == EXIT_CODE_SIGNAL_BASE + SIGSYS_CODE {
182+
return SandboxVerdict::LikelySandbox;
183+
}
166184
}
167185

168186
// 126 is often perms/shebang; upgrade only with explicit hints.
@@ -260,10 +278,14 @@ pub async fn process_exec_tool_call(
260278
if let Some(signal) = raw_output.exit_status.signal() {
261279
if signal == TIMEOUT_CODE {
262280
timed_out = true;
263-
} else if signal == SIGSYS_CODE && !matches!(sandbox_type, SandboxType::None) {
264-
pending_signal = Some(signal);
265281
} else {
266-
return Err(CodexErr::Sandbox(SandboxErr::Signal(signal)));
282+
// Defer SIGSYS (possible sandbox) only when a sandbox was requested;
283+
// otherwise, treat any non-timeout signal as an immediate error.
284+
if signal == SIGSYS_CODE && !matches!(sandbox_type, SandboxType::None) {
285+
pending_signal = Some(signal);
286+
} else {
287+
return Err(CodexErr::Sandbox(SandboxErr::Signal(signal)));
288+
}
267289
}
268290
}
269291
}
@@ -576,7 +598,7 @@ mod tests {
576598
}
577599

578600
#[test]
579-
#[cfg(unix)]
601+
#[cfg(target_family = "unix")]
580602
fn sandbox_sigsys_codepath() {
581603
let code = EXIT_CODE_SIGNAL_BASE + SIGSYS_CODE;
582604
assert!(matches!(

0 commit comments

Comments
 (0)