Skip to main content

strat9_kernel/syscall/
exec.rs

1//! `execve()` syscall implementation.
2//! Replaces the current process image with a new one.
3
4use crate::{
5    memory::{AddressSpace, UserSliceRead, VmaFlags, VmaPageSize, VmaType},
6    process::{
7        current_task_clone,
8        elf::{load_elf_image, LoadedElfInfo, USER_STACK_PAGES},
9        get_task_ids_in_tgid,
10    },
11    syscall::{error::SyscallError, SyscallFrame},
12    vfs,
13};
14use alloc::vec::Vec;
15
16const AT_NULL: u64 = 0;
17const AT_PHDR: u64 = 3;
18const AT_PHENT: u64 = 4;
19const AT_PHNUM: u64 = 5;
20const AT_PAGESZ: u64 = 6;
21const AT_BASE: u64 = 7;
22const AT_ENTRY: u64 = 9;
23const AT_RANDOM: u64 = 25;
24const AT_EXECFN: u64 = 31;
25
26/// Read an executable image, borrowing static initfs bytes when available.
27fn read_exec_image(path: &str) -> Result<Option<Vec<u8>>, SyscallError> {
28    if crate::vfs::get_initfs_file_bytes(path).is_some() {
29        return Ok(None);
30    }
31
32    let fd = vfs::open(path, vfs::OpenFlags::READ)?;
33
34    const MAX_EXEC_SIZE: usize = 64 * 1024 * 1024;
35    let mut elf_data = Vec::new();
36    let mut buf = [0u8; 4096];
37    loop {
38        match vfs::read(fd, &mut buf) {
39            Ok(n) => {
40                if n == 0 {
41                    break;
42                }
43                if elf_data.len() + n > MAX_EXEC_SIZE {
44                    let _ = vfs::close(fd);
45                    return Err(SyscallError::OutOfMemory);
46                }
47                elf_data.extend_from_slice(&buf[..n]);
48            }
49            Err(e) => {
50                let _ = vfs::close(fd);
51                return Err(e);
52            }
53        }
54    }
55    let _ = vfs::close(fd);
56
57    Ok(Some(elf_data))
58}
59
60/// SYS_PROC_EXECVE (301): replace current process image.
61/// On success, does not return. On failure, returns an appropriate error code.
62/// This is the main syscall handler for execve, which performs the entire execve sequence:
63/// 1. Validate and read the executable image from the given path.
64/// 2. Create a new address space and load the ELF segments.
65/// 3. Set up the user stack with arguments, environment variables, and auxiliary vector.
66/// 4. Perform cleanup of the current process state (close fds, reset signals,
67///   clear TLS and TID pointer, etc) according to POSIX exec semantics.
68/// 5. Switch to the new address space and transfer control to the new image's entry point.
69/// The `setup_user_stack` function is a helper that performs step 3, which is complex enough to warrant its own function.
70/// The implementation assumes a simple model where sibling threads are not runnable during execve, which allows it to replace the entire address space without complex synchronization. This is a common approach in many kernels, but it does mean that multithreaded execve is not supported until the kernel can safely handle it.
71/// It also includes robust error handling to ensure that any failure during the execve sequence results in an appropriate error code without leaving the process in an inconsistent state.
72/// Note: This implementation does not currently support some features like setuid binaries, but it lays the groundwork for a full execve implementation with proper ELF loading and stack setup.
73///
74pub fn sys_execve(
75    frame: &mut SyscallFrame,
76    path_ptr: u64,
77    argv_ptr: u64,
78    envp_ptr: u64,
79) -> Result<u64, SyscallError> {
80    let current = current_task_clone().ok_or(SyscallError::PermissionDenied)?;
81
82    // Replacing a shared address space while sibling threads are still runnable
83    // is unsafe in the current model. Refuse multithreaded exec until the
84    // kernel can synchronize and reap sibling threads atomically.
85    if get_task_ids_in_tgid(current.tgid).len() > 1 {
86        return Err(SyscallError::NotSupported);
87    }
88
89    let mut path_buf = [0u8; 4096];
90    let path_slice = UserSliceRead::new(path_ptr, 4096).map_err(|_| SyscallError::Fault)?;
91
92    let mut len = 0;
93
94    loop {
95        if len >= 4096 {
96            return Err(SyscallError::ArgumentListTooLong);
97        } // Reused error code
98        let b = path_slice.read_u8(len).map_err(|_| SyscallError::Fault)?;
99        if b == 0 {
100            break;
101        }
102        path_buf[len] = b;
103        len += 1;
104    }
105    let path_str =
106        core::str::from_utf8(&path_buf[..len]).map_err(|_| SyscallError::InvalidArgument)?;
107
108    let exec_path = vfs::resolve_and_check_path_for_current_task(path_str, true, false, true)?;
109
110    let owned_elf_data = read_exec_image(&exec_path)?;
111    let elf_data = owned_elf_data
112        .as_deref()
113        .or_else(|| crate::vfs::get_initfs_file_bytes(&exec_path))
114        .ok_or(SyscallError::NotFound)?;
115
116    if elf_data.len() < 4 {
117        return Err(SyscallError::ExecFormatError);
118    }
119
120    let new_as = AddressSpace::new_user().map_err(|_| SyscallError::OutOfMemory)?;
121    let new_as_arc = alloc::sync::Arc::new(new_as);
122    new_as_arc.set_owner_pid(current.pid);
123
124    let load_info = match load_elf_image(elf_data, &new_as_arc) {
125        Ok(info) => info,
126        Err("PT_INTERP execute denied") => return Err(SyscallError::PermissionDenied),
127        Err(_) => return Err(SyscallError::ExecFormatError),
128    };
129
130    let stack_flags = VmaFlags {
131        readable: true,
132        writable: true,
133        executable: false,
134        user_accessible: true,
135    };
136    // Per-process stack ASLR (issue #62): draw this image's own jitter and
137    // use it consistently for both the mapping and the initial SP.
138    let stack_base = crate::kaslr::stack_base_with_jitter(crate::kaslr::draw_stack_jitter());
139    let stack_top = crate::kaslr::stack_top_for(stack_base, USER_STACK_PAGES);
140    new_as_arc
141        .map_region(
142            stack_base,
143            USER_STACK_PAGES,
144            stack_flags,
145            VmaType::Stack,
146            VmaPageSize::Small,
147        )
148        .map_err(|_| SyscallError::OutOfMemory)?;
149
150    // Stack canary (issue #63): random per-image value at the top word.
151    let mut canary_bytes = [0u8; 8];
152    crate::entropy::fill_random(&mut canary_bytes);
153    let stack_canary = u64::from_le_bytes(canary_bytes) | 1; // never 0
154    write_bytes_to_as(&new_as_arc, stack_top - 8, &stack_canary.to_le_bytes())?;
155    current
156        .stack_canary
157        .store(stack_canary, core::sync::atomic::Ordering::Relaxed);
158    current
159        .stack_canary_addr
160        .store(stack_top - 8, core::sync::atomic::Ordering::Relaxed);
161
162    let sp = setup_user_stack(
163        &new_as_arc,
164        argv_ptr,
165        envp_ptr,
166        &load_info,
167        path_str.as_bytes(),
168        stack_base,
169        stack_top - 8, // data must stay below the canary slot
170    )?;
171
172    // TLS setup (Variant II) if the ELF has a PT_TLS segment.
173    let mut new_fs_base = 0u64;
174    if load_info.tls_memsz > 0 {
175        let tls_align = core::cmp::max(load_info.tls_align, 8).next_power_of_two();
176        let aligned_memsz = (load_info.tls_memsz + tls_align - 1) & !(tls_align - 1);
177        let total_size = aligned_memsz + 8;
178        let n_pages = ((total_size + 4095) / 4096) as usize;
179        let tls_flags = VmaFlags {
180            readable: true,
181            writable: true,
182            executable: false,
183            user_accessible: true,
184        };
185        let tls_base = new_as_arc
186            .find_free_vma_range(0x7FFF_E000_0000, n_pages, VmaPageSize::Small)
187            .ok_or(SyscallError::OutOfMemory)?;
188        new_as_arc
189            .map_region(
190                tls_base,
191                n_pages,
192                tls_flags,
193                VmaType::Anonymous,
194                VmaPageSize::Small,
195            )
196            .map_err(|_| SyscallError::OutOfMemory)?;
197        if load_info.tls_filesz > 0 && load_info.tls_vaddr != 0 {
198            let src_vaddr = load_info.tls_vaddr;
199            let mut off = 0u64;
200            let mut tmp = [0u8; 256];
201            while off < load_info.tls_filesz {
202                let chunk = core::cmp::min(256, (load_info.tls_filesz - off) as usize);
203                crate::process::elf::read_user_mapped_bytes_pub(
204                    &new_as_arc,
205                    src_vaddr + off,
206                    &mut tmp[..chunk],
207                )
208                .map_err(|_| SyscallError::Fault)?;
209                crate::process::elf::write_user_mapped_bytes_pub(
210                    &new_as_arc,
211                    tls_base + off,
212                    &tmp[..chunk],
213                )
214                .map_err(|_| SyscallError::Fault)?;
215                off += chunk as u64;
216            }
217        }
218        let tp = tls_base + aligned_memsz;
219        crate::process::elf::write_user_u64_pub(&new_as_arc, tp, tp)
220            .map_err(|_| SyscallError::Fault)?;
221        new_fs_base = tp;
222    }
223
224    // === EXECVE CLEANUP (POSIX semantics) ===
225    // Now that ELF is valid and loaded, perform cleanup before switching address space.
226
227    // 1. Close all file descriptors with CLOEXEC flag
228    unsafe {
229        let fd_table = &mut *current.process.fd_table.get();
230        fd_table.close_cloexec();
231    }
232
233    // 2. Reset all signal handlers to SIG_DFL
234    current.reset_signals();
235
236    // 2b. POSIX: exec disables the alternate signal stack for the new image.
237    unsafe {
238        *current.signal_stack.get() = None;
239    }
240
241    // 3. Clear thread-local storage address and TID pointer : POSIX exec semantics.
242    current
243        .clear_child_tid
244        .store(0, core::sync::atomic::Ordering::Relaxed);
245    current
246        .user_fs_base
247        .store(new_fs_base, core::sync::atomic::Ordering::Relaxed);
248
249    // 4. Reset memory layout: brk and mmap_hint belong to the old image.
250    current
251        .process
252        .brk
253        .store(0, core::sync::atomic::Ordering::Relaxed);
254    current.process.mmap_hint.store(
255        crate::kaslr::mmap_base(),
256        core::sync::atomic::Ordering::Relaxed,
257    );
258    // Set FS.base MSR for the new image TLS (or 0 if no PT_TLS).
259    unsafe {
260        let lo = new_fs_base as u32;
261        let hi = (new_fs_base >> 32) as u32;
262        core::arch::asm!(
263            "mov ecx, 0xC0000100", // MSR_FS_BASE
264            "wrmsr",
265            in("eax") lo,
266            in("edx") hi,
267            options(nostack, preserves_flags),
268        );
269    }
270
271    let old_as = current.process.replace_address_space(new_as_arc.clone());
272
273    unsafe {
274        current.process.address_space_arc().switch_to();
275    }
276
277    frame.iret_rip = load_info.runtime_entry;
278    frame.iret_rsp = sp;
279    frame.iret_rflags = 0x200; // IF=1, clean slate for the new image
280
281    frame.rdi = 0;
282    frame.rsi = 0;
283    frame.rdx = 0;
284    frame.rcx = 0;
285    frame.r8 = 0;
286    frame.r9 = 0;
287    frame.r10 = 0;
288    frame.r11 = 0;
289    frame.rbx = 0;
290    frame.rbp = 0;
291    frame.r12 = 0;
292    frame.r13 = 0;
293    frame.r14 = 0;
294    frame.r15 = 0;
295    frame.rax = 0;
296
297    // Safely drop the old address space now that the new CR3 is loaded
298    drop(old_as);
299
300    Ok(0)
301}
302
303/// Performs the setup user stack operation.
304fn setup_user_stack(
305    new_as: &AddressSpace,
306    argv_ptr: u64,
307    envp_ptr: u64,
308    elf_info: &LoadedElfInfo,
309    exec_path: &[u8],
310    stack_base: u64,
311    stack_top: u64,
312) -> Result<u64, SyscallError> {
313    let args = read_string_array(argv_ptr)?;
314    let envs = read_string_array(envp_ptr)?;
315
316    let mut sp = stack_top;
317    let mut str_ptrs: Vec<u64> = Vec::with_capacity(args.len()); // stores pointers to arguments
318    let mut env_ptrs: Vec<u64> = Vec::with_capacity(envs.len()); // stores pointers to env vars
319
320    // Push strings to stack (highest addresses)
321    // We push them in reverse order so they appear in memory roughly sequentially for cache locality?
322    // Actually standard is to put them at very top. Order doesn't strictly matter as long as pointers are correct.
323    // We'll push ENV strings first (highest), then ARG strings.
324
325    // Push ENV strings
326    for env in envs.iter().rev() {
327        let len = (env.len() + 1) as u64;
328        sp = sp
329            .checked_sub(len)
330            .ok_or(SyscallError::ArgumentListTooLong)?;
331        if sp < stack_base {
332            return Err(SyscallError::ArgumentListTooLong);
333        }
334        write_bytes_to_as(new_as, sp, env)?;
335        write_bytes_to_as(new_as, sp + env.len() as u64, &[0])?;
336        env_ptrs.push(sp);
337    }
338    // env_ptrs: [ptr_to_highest_env, ptr_to_second_highest...] which corresponds to [env[last], env[last-1]...]
339    // Userspace expects envp[0] to point to first env string.
340    // So we need to reverse env_ptrs to match original order.
341    env_ptrs.reverse();
342
343    // Push ARG strings
344    for arg in args.iter().rev() {
345        let len = (arg.len() + 1) as u64;
346        sp = sp
347            .checked_sub(len)
348            .ok_or(SyscallError::ArgumentListTooLong)?;
349        if sp < stack_base {
350            return Err(SyscallError::ArgumentListTooLong);
351        }
352        write_bytes_to_as(new_as, sp, arg)?;
353        write_bytes_to_as(new_as, sp + arg.len() as u64, &[0])?;
354        str_ptrs.push(sp);
355    }
356    str_ptrs.reverse();
357
358    // Push exec path (for AT_EXECFN).
359    let mut execfn_ptr = 0u64;
360    if !exec_path.is_empty() {
361        let len = (exec_path.len() + 1) as u64;
362        sp -= len;
363        write_bytes_to_as(new_as, sp, exec_path)?;
364        write_bytes_to_as(new_as, sp + exec_path.len() as u64, &[0])?;
365        execfn_ptr = sp;
366    }
367
368    // Push 16 bytes of random seed for AT_RANDOM (deterministic fallback source).
369    sp -= 16;
370    let rand_ptr = sp;
371    let seed = generate_aux_random_seed();
372    write_bytes_to_as(new_as, rand_ptr, &seed)?;
373
374    // Align SP to 16 bytes for System V ABI
375    sp &= !0xF;
376
377    // Phase 2: Push auxv, pointer arrays, then argc.
378    let size_ptr = 8u64;
379
380    // auxv entries end with AT_NULL.
381    let mut auxv: Vec<(u64, u64)> = Vec::with_capacity(10);
382    auxv.push((AT_PHDR, elf_info.phdr_vaddr));
383    auxv.push((AT_PHENT, elf_info.phent as u64));
384    auxv.push((AT_PHNUM, elf_info.phnum as u64));
385    auxv.push((AT_PAGESZ, 4096));
386    if let Some(base) = elf_info.interp_base {
387        auxv.push((AT_BASE, base));
388    }
389    auxv.push((AT_ENTRY, elf_info.program_entry));
390    auxv.push((AT_RANDOM, rand_ptr));
391    if execfn_ptr != 0 {
392        auxv.push((AT_EXECFN, execfn_ptr));
393    }
394
395    // Reserve padding above auxv so the final entry RSP still points at argc
396    // while satisfying the SysV x86_64 16-byte alignment requirement.
397    let stack_words = 1u64
398        + (str_ptrs.len() as u64 + 1)
399        + (env_ptrs.len() as u64 + 1)
400        + ((auxv.len() as u64 + 1) * 2);
401    let align_pad = (0u64.wrapping_sub(stack_words * size_ptr)) & 0xF;
402    sp -= align_pad;
403
404    // AT_NULL terminator.
405    sp -= size_ptr;
406    write_u64_to_as(new_as, sp, 0)?;
407    sp -= size_ptr;
408    write_u64_to_as(new_as, sp, AT_NULL)?;
409    for &(key, val) in auxv.iter().rev() {
410        sp -= size_ptr;
411        write_u64_to_as(new_as, sp, val)?;
412        sp -= size_ptr;
413        write_u64_to_as(new_as, sp, key)?;
414    }
415
416    // Push ENVP array
417    // [NULL]
418    // [envp[n]]
419    // ...
420    // [envp[0]]
421    sp -= size_ptr;
422    write_u64_to_as(new_as, sp, 0)?; // NULL terminator
423
424    for &ptr in env_ptrs.iter().rev() {
425        sp -= size_ptr;
426        write_u64_to_as(new_as, sp, ptr)?;
427    }
428    // Note: sp now points to envp[0]
429
430    // Push ARGV array
431    // [NULL]
432    // [argv[n]]
433    // ...
434    // [argv[0]]
435    sp -= size_ptr;
436    write_u64_to_as(new_as, sp, 0)?; // NULL terminator
437
438    for &ptr in str_ptrs.iter().rev() {
439        sp -= size_ptr;
440        write_u64_to_as(new_as, sp, ptr)?;
441    }
442    // Note: sp now points to argv[0]
443
444    // Push ARGC
445    sp -= size_ptr;
446    write_u64_to_as(new_as, sp, args.len() as u64)?;
447
448    debug_assert_eq!(sp & 0xF, 0);
449
450    Ok(sp)
451}
452
453/// Reads string array.
454fn read_string_array(ptr: u64) -> Result<Vec<Vec<u8>>, SyscallError> {
455    let mut res = Vec::new();
456    if ptr == 0 {
457        return Ok(res);
458    }
459
460    let mut arr_off = 0;
461    loop {
462        // Read string pointer from user memory (current AS)
463        let str_ptr = match UserSliceRead::new(ptr + arr_off, 8) {
464            Ok(slice) => match slice.read_u64(0) {
465                Ok(p) => p,
466                Err(_) => return Err(SyscallError::Fault),
467            },
468            Err(_) => return Err(SyscallError::Fault),
469        };
470
471        if str_ptr == 0 {
472            break;
473        }
474        if res.len() > 1024 {
475            return Err(SyscallError::ArgumentListTooLong);
476        }
477
478        let mut s = Vec::new();
479        let mut i = 0;
480        loop {
481            if i > 4096 {
482                return Err(SyscallError::ArgumentListTooLong);
483            }
484            let b = match UserSliceRead::new(str_ptr + i, 1) {
485                Ok(slice) => match slice.read_u8(0) {
486                    Ok(byte) => byte,
487                    Err(_) => return Err(SyscallError::Fault),
488                },
489                Err(_) => return Err(SyscallError::Fault),
490            };
491            if b == 0 {
492                break;
493            }
494            s.push(b);
495            i += 1;
496        }
497        res.push(s);
498        arr_off += 8;
499    }
500    Ok(res)
501}
502
503/// Writes bytes to as.
504fn write_bytes_to_as(as_ref: &AddressSpace, vaddr: u64, data: &[u8]) -> Result<(), SyscallError> {
505    use crate::arch::xshim::VirtAddr;
506    let mut written = 0;
507    // We assume data is small enough or we loop? Using unsafe pointer arithmetic.
508    // The `AddressSpace` methods like `translate` are needed.
509
510    // Since `load_elf_image` in `elf.rs` used `translate`, we should verify visibility.
511    // `AddressSpace` is usually public. `translate` is on `Mapper` trait?
512    // `AddressSpace` in `strat9` likely implements `Mapper` or has it.
513    // `elf.rs` used `user_as.translate(...)`.
514
515    // I need to import Translate? `AddressSpace` usually has `translate`.
516
517    while written < data.len() {
518        let curr_vaddr = vaddr + written as u64;
519        let page_offset = (curr_vaddr & 0xFFF) as usize;
520        let chunk_size = core::cmp::min(data.len() - written, 4096 - page_offset);
521
522        // translate might fail if page not mapped.
523        // `stack_base()`..`stack_top()` is mapped.
524        let phys = as_ref
525            .translate(VirtAddr::new(curr_vaddr))
526            .ok_or(SyscallError::Fault)?;
527        let virt = crate::memory::phys_to_virt(phys.as_u64()) as *mut u8;
528
529        unsafe {
530            core::ptr::copy_nonoverlapping(data.as_ptr().add(written), virt, chunk_size);
531        }
532        written += chunk_size;
533    }
534    Ok(())
535}
536
537/// Writes u64 to as.
538fn write_u64_to_as(as_ref: &AddressSpace, vaddr: u64, val: u64) -> Result<(), SyscallError> {
539    let bytes = val.to_ne_bytes();
540    write_bytes_to_as(as_ref, vaddr, &bytes)
541}
542
543/// Performs the generate aux random seed operation.
544fn generate_aux_random_seed() -> [u8; 16] {
545    let mut seed = [0u8; 16];
546    crate::entropy::fill_random(&mut seed);
547    seed
548}