@@ -30,30 +30,31 @@ use crate::spawn::spawn_child_async;
30
30
31
31
const DEFAULT_TIMEOUT_MS : u64 = 10_000 ;
32
32
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.
36
34
const TIMEOUT_CODE : i32 = 64 ;
37
35
const EXIT_CODE_SIGNAL_BASE : i32 = 128 ; // conventional shell: 128 + signal
38
36
const EXEC_TIMEOUT_EXIT_CODE : i32 = 124 ; // conventional timeout exit code
39
37
const EXIT_NOT_EXECUTABLE_CODE : i32 = 126 ; // "found but not executable"/shebang/perms
40
38
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 ;
57
58
58
59
// I/O buffer sizing
59
60
const READ_CHUNK_SIZE : usize = 8192 ; // bytes per read
@@ -113,11 +114,11 @@ fn sandbox_verdict(sandbox_type: SandboxType, status: ExitStatus, stderr: &str)
113
114
}
114
115
115
116
// Prefer the signal path when available.
116
- #[ cfg( unix) ]
117
+ #[ cfg( target_family = " unix" ) ]
117
118
{
118
119
if let Some ( sig) = status. signal ( ) {
119
120
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.
121
122
return SandboxVerdict :: LikelySandbox ;
122
123
}
123
124
// Common user/app signals -> not sandbox.
@@ -140,29 +141,46 @@ fn sandbox_verdict(sandbox_type: SandboxType, status: ExitStatus, stderr: &str)
140
141
// - 127: command not found
141
142
// - 64..=78: BSD sysexits range
142
143
// - 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" ) ]
159
145
{
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
+ }
161
176
}
162
177
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
+ }
166
184
}
167
185
168
186
// 126 is often perms/shebang; upgrade only with explicit hints.
@@ -260,10 +278,14 @@ pub async fn process_exec_tool_call(
260
278
if let Some ( signal) = raw_output. exit_status . signal ( ) {
261
279
if signal == TIMEOUT_CODE {
262
280
timed_out = true ;
263
- } else if signal == SIGSYS_CODE && !matches ! ( sandbox_type, SandboxType :: None ) {
264
- pending_signal = Some ( signal) ;
265
281
} 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
+ }
267
289
}
268
290
}
269
291
}
@@ -576,7 +598,7 @@ mod tests {
576
598
}
577
599
578
600
#[ test]
579
- #[ cfg( unix) ]
601
+ #[ cfg( target_family = " unix" ) ]
580
602
fn sandbox_sigsys_codepath ( ) {
581
603
let code = EXIT_CODE_SIGNAL_BASE + SIGSYS_CODE ;
582
604
assert ! ( matches!(
0 commit comments