Skip to main content

test_exec/
exec.rs

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}