1#![no_std]
2#![no_main]
3
4#[cfg(not(test))]
5use core::panic::PanicInfo;
6use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
7use strat9_syscall::{call, error::Error, flag};
8
9const F_SETFD: usize = 2;
10const FD_CLOEXEC: usize = 1;
11const WNOHANG: usize = 1;
12const SIGUSR1: usize = 10;
13const SIGUSR2: usize = 12;
14const HELPER_FD: usize = 19;
15const HELPER_PATH: &[u8] = b"/initfs/test_exec_helper\0";
16const MULTI_EXEC_PATH: &[u8] = b"/initfs/test_exec_helper\0";
17
18static mut EXEC_ALTSTACK: [u8; 4096] = [0; 4096];
19static mut MULTI_EXEC_STACK: [u8; 8192] = [0; 8192];
20static mut EXIT_GROUP_STACK: [u8; 8192] = [0; 8192];
21
22static MULTI_EXEC_THREAD_READY: AtomicBool = AtomicBool::new(false);
23static MULTI_EXEC_THREAD_STOP: AtomicBool = AtomicBool::new(false);
24static EXIT_GROUP_THREAD_READY: AtomicBool = AtomicBool::new(false);
25static PASS_COUNT: AtomicUsize = AtomicUsize::new(0);
26static FAIL_COUNT: AtomicUsize = AtomicUsize::new(0);
27
28#[repr(C)]
29#[derive(Clone, Copy)]
30struct SigActionRaw {
31 sa_handler: u64,
32 sa_flags: u64,
33 sa_restorer: u64,
34 sa_mask: u64,
35}
36
37#[repr(C)]
38#[derive(Clone, Copy)]
39struct SigStackRaw {
40 ss_sp: u64,
41 ss_flags: i32,
42 ss_size: usize,
43}
44
45fn write_fd(fd: usize, msg: &str) {
46 let _ = call::write(fd, msg.as_bytes());
47}
48
49fn log(msg: &str) {
50 write_fd(1, msg);
51}
52
53fn log_err(msg: &str) {
54 write_fd(2, msg);
55}
56
57fn log_u64(mut value: u64) {
58 let mut buf = [0u8; 21];
59 if value == 0 {
60 log("0");
61 return;
62 }
63 let mut idx = buf.len();
64 while value > 0 {
65 idx -= 1;
66 buf[idx] = b'0' + (value % 10) as u8;
67 value /= 10;
68 }
69 let s = unsafe { core::str::from_utf8_unchecked(&buf[idx..]) };
70 log(s);
71}
72
73fn log_error_name(err: Error) {
74 log_err(err.name());
75}
76
77fn pass(label: &str) {
78 PASS_COUNT.fetch_add(1, Ordering::SeqCst);
79 log("[PASS] ");
80 log(label);
81 log("\n");
82}
83
84fn fail_msg(label: &str, msg: &str) {
85 FAIL_COUNT.fetch_add(1, Ordering::SeqCst);
86 log_err("[FAIL] ");
87 log_err(label);
88 log_err(": ");
89 log_err(msg);
90 log_err("\n");
91}
92
93fn fail_err(label: &str, err: Error) {
94 FAIL_COUNT.fetch_add(1, Ordering::SeqCst);
95 log_err("[FAIL] ");
96 log_err(label);
97 log_err(": ");
98 log_error_name(err);
99 log_err("\n");
100}
101
102fn stack_top(buf: *mut u8, len: usize) -> usize {
103 (buf as usize + len) & !0xFusize
104}
105
106fn wait_child_exit_code(pid: usize, label: &str) -> Option<u8> {
107 let mut status = 0i32;
108 match call::waitpid_blocking(pid as isize, &mut status) {
109 Ok(_) => Some(((status >> 8) & 0xff) as u8),
110 Err(err) => {
111 fail_err(label, err);
112 None
113 }
114 }
115}
116
117extern "C" fn multithread_exec_thread(_arg0: usize) -> ! {
118 MULTI_EXEC_THREAD_READY.store(true, Ordering::SeqCst);
119 while !MULTI_EXEC_THREAD_STOP.load(Ordering::SeqCst) {
120 let _ = call::sched_yield();
121 }
122 call::thread_exit(0)
123}
124
125extern "C" fn exit_group_thread(_arg0: usize) -> ! {
126 EXIT_GROUP_THREAD_READY.store(true, Ordering::SeqCst);
127 loop {
128 let _ = call::sched_yield();
129 }
130}
131
132fn test_exec_cleanup_roundtrip() {
133 log("[test_exec] exec cleanup roundtrip\n");
134 match call::fork() {
135 Ok(0) => {
136 let fd = match call::open("/initfs/test_exec_helper", flag::O_RDONLY) {
137 Ok(fd) => fd,
138 Err(err) => call::exit(150 + err.to_errno()),
139 };
140
141 if call::dup2(fd, HELPER_FD).is_err() {
142 let _ = call::close(fd);
143 call::exit(151);
144 }
145 let _ = call::close(fd);
146
147 if call::fcntl(HELPER_FD, F_SETFD, FD_CLOEXEC).is_err() {
148 call::exit(152);
149 }
150
151 let catch_action = SigActionRaw {
152 sa_handler: 0x1234,
153 sa_flags: 0,
154 sa_restorer: 0,
155 sa_mask: 0,
156 };
157 if call::sigaction(SIGUSR1, &catch_action as *const _ as usize, 0).is_err() {
158 call::exit(153);
159 }
160
161 let ignore_action = SigActionRaw {
162 sa_handler: 1,
163 sa_flags: 0,
164 sa_restorer: 0,
165 sa_mask: 0,
166 };
167 if call::sigaction(SIGUSR2, &ignore_action as *const _ as usize, 0).is_err() {
168 call::exit(154);
169 }
170
171 let altstack = SigStackRaw {
172 ss_sp: core::ptr::addr_of_mut!(EXEC_ALTSTACK) as *mut u8 as usize as u64,
173 ss_flags: 0,
174 ss_size: 4096,
175 };
176 if call::sigaltstack(&altstack as *const _ as usize, 0).is_err() {
177 call::exit(155);
178 }
179
180 let argv = [HELPER_PATH.as_ptr() as usize, 0];
181 let envp = [0usize];
182 let exec_res = unsafe {
183 call::execve(HELPER_PATH, argv.as_ptr() as usize, envp.as_ptr() as usize)
184 };
185 let code = exec_res
186 .err()
187 .map(|err| 160 + err.to_errno())
188 .unwrap_or(161);
189 call::exit(code)
190 }
191 Ok(pid) => match wait_child_exit_code(pid, "wait helper child") {
192 Some(0) => pass("exec keeps SIG_IGN and resets handlers, altstack, CLOEXEC"),
193 Some(code) => {
194 log_err("[FAIL] exec cleanup roundtrip child exit=");
195 log_u64(code as u64);
196 log_err("\n");
197 FAIL_COUNT.fetch_add(1, Ordering::SeqCst);
198 }
199 None => {}
200 },
201 Err(err) => fail_err("fork exec cleanup child", err),
202 }
203}
204
205fn test_multithread_exec_rejected() {
206 log("[test_exec] multithread exec rejection\n");
207 MULTI_EXEC_THREAD_READY.store(false, Ordering::SeqCst);
208 MULTI_EXEC_THREAD_STOP.store(false, Ordering::SeqCst);
209
210 let stack_top = stack_top(core::ptr::addr_of_mut!(MULTI_EXEC_STACK) as *mut u8, 8192);
211 let tid = match call::thread_create(
212 multithread_exec_thread as *const () as usize,
213 stack_top,
214 0,
215 0,
216 ) {
217 Ok(tid) => tid,
218 Err(err) => {
219 fail_err("thread_create for exec rejection", err);
220 return;
221 }
222 };
223
224 for _ in 0..256 {
225 if MULTI_EXEC_THREAD_READY.load(Ordering::SeqCst) {
226 break;
227 }
228 let _ = call::sched_yield();
229 }
230
231 if !MULTI_EXEC_THREAD_READY.load(Ordering::SeqCst) {
232 fail_msg("multithread exec rejection", "worker thread did not start");
233 let mut status = 0i32;
234 let _ = call::thread_join(tid, Some(&mut status));
235 return;
236 }
237
238 let argv = [MULTI_EXEC_PATH.as_ptr() as usize, 0];
239 let envp = [0usize];
240 match unsafe {
241 call::execve(
242 MULTI_EXEC_PATH,
243 argv.as_ptr() as usize,
244 envp.as_ptr() as usize,
245 )
246 } {
247 Err(Error::NotSupported) => pass("exec rejects multithreaded caller"),
248 Err(err) => fail_err("exec rejects multithreaded caller", err),
249 Ok(_) => fail_msg(
250 "exec rejects multithreaded caller",
251 "exec unexpectedly succeeded",
252 ),
253 }
254
255 MULTI_EXEC_THREAD_STOP.store(true, Ordering::SeqCst);
256 let mut status = 0i32;
257 if call::thread_join(tid, Some(&mut status)).is_err() {
258 fail_msg("thread_join after rejected exec", "join failed");
259 }
260}
261
262fn test_exit_group_kills_siblings() {
263 log("[test_exec] exit_group kills sibling threads\n");
264 match call::fork() {
265 Ok(0) => {
266 EXIT_GROUP_THREAD_READY.store(false, Ordering::SeqCst);
267 let stack_top = stack_top(core::ptr::addr_of_mut!(EXIT_GROUP_STACK) as *mut u8, 8192);
268 if call::thread_create(exit_group_thread as *const () as usize, stack_top, 0, 0)
269 .is_err()
270 {
271 call::exit(170);
272 }
273 for _ in 0..256 {
274 if EXIT_GROUP_THREAD_READY.load(Ordering::SeqCst) {
275 break;
276 }
277 let _ = call::sched_yield();
278 }
279 if !EXIT_GROUP_THREAD_READY.load(Ordering::SeqCst) {
280 call::exit(171);
281 }
282 call::exit_group(77)
283 }
284 Ok(pid) => {
285 let mut status = 0i32;
286 for _ in 0..512 {
287 match call::waitpid(pid as isize, Some(&mut status), WNOHANG) {
288 Ok(0) => {
289 let _ = call::sched_yield();
290 }
291 Ok(_) => {
292 let code = ((status >> 8) & 0xff) as u8;
293 if code == 77 {
294 pass("exit_group terminates thread group");
295 } else {
296 log_err("[FAIL] exit_group child exit=");
297 log_u64(code as u64);
298 log_err("\n");
299 FAIL_COUNT.fetch_add(1, Ordering::SeqCst);
300 }
301 return;
302 }
303 Err(Error::Interrupted) => {
304 let _ = call::sched_yield();
305 }
306 Err(err) => {
307 fail_err("waitpid for exit_group child", err);
308 return;
309 }
310 }
311 }
312 fail_msg(
313 "exit_group terminates thread group",
314 "child stayed alive too long",
315 );
316 }
317 Err(err) => fail_err("fork exit_group child", err),
318 }
319}
320
321#[cfg(not(test))]
322#[panic_handler]
323fn panic(_info: &PanicInfo) -> ! {
324 log_err("[test_exec] panic\n");
325 call::exit(200)
326}
327
328#[no_mangle]
329pub extern "C" fn _start() -> ! {
330 log("[test_exec] starting exec regression suite\n");
331
332 test_exec_cleanup_roundtrip();
333 test_multithread_exec_rejected();
334 test_exit_group_kills_siblings();
335
336 log("[test_exec] summary pass=");
337 log_u64(PASS_COUNT.load(Ordering::SeqCst) as u64);
338 log(" fail=");
339 log_u64(FAIL_COUNT.load(Ordering::SeqCst) as u64);
340 log("\n");
341
342 if FAIL_COUNT.load(Ordering::SeqCst) == 0 {
343 call::exit(0)
344 } else {
345 call::exit(1)
346 }
347}