From b12e247e7ec24ab8f96bad950579babdc6073185 Mon Sep 17 00:00:00 2001 From: Tulio A M Mendes Date: Mon, 9 Feb 2026 17:59:27 -0300 Subject: [PATCH] x86: add sigreturn trampoline and fix execve stack usage Implement SYSCALL_SIGRETURN and userland signal trampoline/sigframe so handlers can return safely. Fix x86 signal delivery stack layout to avoid clobbering sigframe/trampoline. Fix execve heap corruption by avoiding large on-stack argv/envp buffers and adding cleanup. Add init.elf smoke test for sigreturn. --- include/errno.h | 1 + include/syscall.h | 1 + src/arch/x86/idt.c | 69 ++++++++++++++++++++++++++++++++++---- src/kernel/syscall.c | 79 ++++++++++++++++++++++++++++++++++++-------- user/init.c | 39 ++++++++++++++++++++++ 5 files changed, 170 insertions(+), 19 deletions(-) diff --git a/include/errno.h b/include/errno.h index 44ba576..327200b 100644 --- a/include/errno.h +++ b/include/errno.h @@ -5,6 +5,7 @@ #define ENOENT 2 #define EIO 5 #define EINTR 4 +#define E2BIG 7 #define EBADF 9 #define ECHILD 10 #define EFAULT 14 diff --git a/include/syscall.h b/include/syscall.h index 8da9645..042cb1e 100644 --- a/include/syscall.h +++ b/include/syscall.h @@ -36,6 +36,7 @@ enum { SYSCALL_SIGACTION = 25, SYSCALL_SIGPROCMASK = 26, + SYSCALL_SIGRETURN = 27, }; #endif diff --git a/src/arch/x86/idt.c b/src/arch/x86/idt.c index b858cf5..e90c41f 100644 --- a/src/arch/x86/idt.c +++ b/src/arch/x86/idt.c @@ -4,10 +4,18 @@ #include "process.h" #include "spinlock.h" #include "uaccess.h" +#include "syscall.h" #include #define IDT_ENTRIES 256 +static const uint32_t SIGFRAME_MAGIC = 0x53494746U; // 'SIGF' + +struct sigframe { + uint32_t magic; + struct registers saved; +}; + struct idt_entry idt[IDT_ENTRIES]; struct idt_ptr idtp; @@ -77,12 +85,61 @@ static void deliver_signals_to_usermode(struct registers* regs) { return; } - uint32_t frame[2]; - frame[0] = regs->eip; - frame[1] = (uint32_t)sig; + // Build a sigframe + a tiny user trampoline that calls SYSCALL_SIGRETURN. + // Stack layout at handler entry (regs->useresp): + // [esp+0] return address -> trampoline + // [esp+4] int sig + // Below that: trampoline code bytes, below that: sigframe. + + struct sigframe f; + f.magic = SIGFRAME_MAGIC; + f.saved = *regs; + + const uint32_t tramp_size = 14U; + const uint32_t base = regs->useresp - (8U + tramp_size + (uint32_t)sizeof(f)); + const uint32_t retaddr_slot = base; + const uint32_t tramp_addr = base + 8U; + const uint32_t sigframe_addr = tramp_addr + tramp_size; + + // Trampoline bytes: + // mov eax, SYSCALL_SIGRETURN + // mov ebx, + // int 0x80 + // jmp . + uint8_t tramp[14]; + tramp[0] = 0xB8; + { + const uint32_t no = (uint32_t)SYSCALL_SIGRETURN; + tramp[1] = (uint8_t)(no & 0xFFU); + tramp[2] = (uint8_t)((no >> 8) & 0xFFU); + tramp[3] = (uint8_t)((no >> 16) & 0xFFU); + tramp[4] = (uint8_t)((no >> 24) & 0xFFU); + } + tramp[5] = 0xBB; + // tramp[6..9] patched with sigframe address below + tramp[10] = 0xCD; + tramp[11] = 0x80; + tramp[12] = 0xEB; + tramp[13] = 0xFE; + + tramp[6] = (uint8_t)(sigframe_addr & 0xFFU); + tramp[7] = (uint8_t)((sigframe_addr >> 8) & 0xFFU); + tramp[8] = (uint8_t)((sigframe_addr >> 16) & 0xFFU); + tramp[9] = (uint8_t)((sigframe_addr >> 24) & 0xFFU); + + if (copy_to_user((void*)(uintptr_t)sigframe_addr, &f, sizeof(f)) < 0 || + copy_to_user((void*)(uintptr_t)tramp_addr, tramp, sizeof(tramp)) < 0) { + const int SIG_SEGV = 11; + process_exit_notify(128 + SIG_SEGV); + __asm__ volatile("sti"); + schedule(); + for (;;) __asm__ volatile("hlt"); + } - const uint32_t new_esp = regs->useresp - (uint32_t)sizeof(frame); - if (copy_to_user((void*)(uintptr_t)new_esp, frame, sizeof(frame)) < 0) { + uint32_t callframe[2]; + callframe[0] = tramp_addr; + callframe[1] = (uint32_t)sig; + if (copy_to_user((void*)(uintptr_t)retaddr_slot, callframe, sizeof(callframe)) < 0) { const int SIG_SEGV = 11; process_exit_notify(128 + SIG_SEGV); __asm__ volatile("sti"); @@ -90,7 +147,7 @@ static void deliver_signals_to_usermode(struct registers* regs) { for (;;) __asm__ volatile("hlt"); } - regs->useresp = new_esp; + regs->useresp = retaddr_slot; regs->eip = (uint32_t)h; } diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index 4db8412..4af02cd 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -1,13 +1,15 @@ #include "syscall.h" #include "idt.h" #include "fs.h" -#include "heap.h" -#include "tty.h" #include "process.h" -#include "uart_console.h" +#include "spinlock.h" #include "uaccess.h" +#include "uart_console.h" #include "utils.h" +#include "heap.h" +#include "tty.h" + #include "errno.h" #include "elf.h" #include "stat.h" @@ -17,6 +19,14 @@ #include +#if defined(__i386__) +static const uint32_t SIGFRAME_MAGIC = 0x53494746U; // 'SIGF' +struct sigframe { + uint32_t magic; + struct registers saved; +}; +#endif + static int fd_alloc(struct file* f); static int fd_close(int fd); static struct file* fd_get(int fd); @@ -529,19 +539,25 @@ static int syscall_execve_impl(struct registers* regs, const char* user_path, co } // Snapshot argv/envp into kernel buffers (before switching addr_space). - char kargv[EXECVE_MAX_ARGC][EXECVE_MAX_STR]; - char kenvp[EXECVE_MAX_ENVC][EXECVE_MAX_STR]; + char (*kargv)[EXECVE_MAX_STR] = (char(*)[EXECVE_MAX_STR])kmalloc((size_t)EXECVE_MAX_ARGC * (size_t)EXECVE_MAX_STR); + char (*kenvp)[EXECVE_MAX_STR] = (char(*)[EXECVE_MAX_STR])kmalloc((size_t)EXECVE_MAX_ENVC * (size_t)EXECVE_MAX_STR); int argc = 0; int envc = 0; + int ret = 0; + + if (!kargv || !kenvp) { + ret = -ENOMEM; + goto out; + } if (user_argv) { for (int i = 0; i < EXECVE_MAX_ARGC; i++) { uintptr_t up = 0; int rc = execve_copy_user_ptr((const void* const*)&user_argv[i], &up); - if (rc < 0) return rc; + if (rc < 0) { ret = rc; goto out; } if (up == 0) break; rc = execve_copy_user_str(kargv[i], sizeof(kargv[i]), (const char*)up); - if (rc < 0) return rc; + if (rc < 0) { ret = rc; goto out; } argc++; } } @@ -550,29 +566,34 @@ static int syscall_execve_impl(struct registers* regs, const char* user_path, co for (int i = 0; i < EXECVE_MAX_ENVC; i++) { uintptr_t up = 0; int rc = execve_copy_user_ptr((const void* const*)&user_envp[i], &up); - if (rc < 0) return rc; + if (rc < 0) { ret = rc; goto out; } if (up == 0) break; rc = execve_copy_user_str(kenvp[i], sizeof(kenvp[i]), (const char*)up); - if (rc < 0) return rc; + if (rc < 0) { ret = rc; goto out; } envc++; } } // Distinguish ENOENT early. fs_node_t* node = vfs_lookup(path); - if (!node) return -ENOENT; + if (!node) { ret = -ENOENT; goto out; } uintptr_t entry = 0; uintptr_t user_sp = 0; uintptr_t new_as = 0; if (elf32_load_user_from_initrd(path, &entry, &user_sp, &new_as) != 0) { - return -EINVAL; + ret = -EINVAL; + goto out; } + const size_t user_stack_size = 0x1000U; + + if ((size_t)((argc + 1) + (envc + 1)) * sizeof(uintptr_t) + (size_t)argc * EXECVE_MAX_STR + (size_t)envc * EXECVE_MAX_STR + 64U > user_stack_size) { vmm_as_destroy(new_as); ret = -E2BIG; goto out; } uintptr_t old_as = current_process ? current_process->addr_space : 0; if (!current_process) { vmm_as_destroy(new_as); - return -EINVAL; + ret = -EINVAL; + goto out; } current_process->addr_space = new_as; @@ -628,7 +649,13 @@ static int syscall_execve_impl(struct registers* regs, const char* user_path, co regs->eip = (uint32_t)entry; regs->useresp = (uint32_t)sp; regs->eax = 0; - return 0; + ret = 0; + goto out; + +out: + if (kargv) kfree(kargv); + if (kenvp) kfree(kenvp); + return ret; } static int syscall_dup2_impl(int oldfd, int newfd) { @@ -908,6 +935,26 @@ static int syscall_sigprocmask_impl(uint32_t how, uint32_t mask, uint32_t* old_o return -EINVAL; } +static int syscall_sigreturn_impl(struct registers* regs, const struct sigframe* user_frame) { + if (!regs) return -EINVAL; + if (!current_process) return -EINVAL; + if ((regs->cs & 3U) != 3U) return -EPERM; + if (!user_frame) return -EFAULT; + + if (user_range_ok(user_frame, sizeof(*user_frame)) == 0) { return -EFAULT; } + + struct sigframe f; + if (copy_from_user(&f, user_frame, sizeof(f)) < 0) return -EFAULT; + if (f.magic != SIGFRAME_MAGIC) { return -EINVAL; } + + if ((f.saved.cs & 3U) != 3U) return -EPERM; + if ((f.saved.ss & 3U) != 3U) return -EPERM; + + // Restore the full saved trapframe. The interrupt stub will pop these regs and iret. + *regs = f.saved; + return 0; +} + static void syscall_handler(struct registers* regs) { uint32_t syscall_no = regs->eax; @@ -1122,6 +1169,12 @@ static void syscall_handler(struct registers* regs) { return; } + if (syscall_no == SYSCALL_SIGRETURN) { + const struct sigframe* user_frame = (const struct sigframe*)regs->ebx; + regs->eax = (uint32_t)syscall_sigreturn_impl(regs, user_frame); + return; + } + regs->eax = (uint32_t)-ENOSYS; } diff --git a/user/init.c b/user/init.c index d128756..6c9aa8c 100644 --- a/user/init.c +++ b/user/init.c @@ -28,6 +28,7 @@ enum { SYSCALL_SIGACTION = 25, SYSCALL_SIGPROCMASK = 26, + SYSCALL_SIGRETURN = 27, }; enum { @@ -395,6 +396,7 @@ __attribute__((noreturn)) static void sys_exit(int code) { } static volatile int got_usr1 = 0; +static volatile int got_usr1_ret = 0; static volatile int got_ttin = 0; static volatile int got_ttou = 0; @@ -405,6 +407,11 @@ static void usr1_handler(int sig) { (uint32_t)(sizeof("[init] SIGUSR1 handler OK\n") - 1)); } +static void usr1_ret_handler(int sig) { + (void)sig; + got_usr1_ret = 1; +} + static void ttin_handler(int sig) { (void)sig; got_ttin = 1; @@ -1025,6 +1032,38 @@ void _start(void) { (uint32_t)(sizeof("[init] sigaction/kill(SIGUSR1) OK\n") - 1)); } + // Verify that returning from a signal handler does not corrupt the user stack. + { + if (sys_sigaction(SIGUSR1, usr1_ret_handler, 0) < 0) { + sys_write(1, "[init] sigaction (sigreturn test) failed\n", + (uint32_t)(sizeof("[init] sigaction (sigreturn test) failed\n") - 1)); + sys_exit(1); + } + + volatile uint32_t canary = 0x11223344U; + int me = sys_getpid(); + if (sys_kill(me, SIGUSR1) < 0) { + sys_write(1, "[init] kill(SIGUSR1) (sigreturn test) failed\n", + (uint32_t)(sizeof("[init] kill(SIGUSR1) (sigreturn test) failed\n") - 1)); + sys_exit(1); + } + + if (!got_usr1_ret) { + sys_write(1, "[init] SIGUSR1 not delivered (sigreturn test)\n", + (uint32_t)(sizeof("[init] SIGUSR1 not delivered (sigreturn test)\n") - 1)); + sys_exit(1); + } + + if (canary != 0x11223344U) { + sys_write(1, "[init] sigreturn test stack corruption\n", + (uint32_t)(sizeof("[init] sigreturn test stack corruption\n") - 1)); + sys_exit(1); + } + + sys_write(1, "[init] sigreturn OK\n", + (uint32_t)(sizeof("[init] sigreturn OK\n") - 1)); + } + fd = sys_open("/tmp/hello.txt", 0); if (fd < 0) { sys_write(1, "[init] tmpfs open2 failed\n", -- 2.43.0