]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
x86: add sigreturn trampoline and fix execve stack usage
authorTulio A M Mendes <[email protected]>
Mon, 9 Feb 2026 20:59:27 +0000 (17:59 -0300)
committerTulio A M Mendes <[email protected]>
Mon, 9 Feb 2026 20:59:27 +0000 (17:59 -0300)
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
include/syscall.h
src/arch/x86/idt.c
src/kernel/syscall.c
user/init.c

index 44ba57621e80ff67b9f17b5936d66f1873f8f4fc..327200b5c8ecc5858e3b0a38cfa472c0746a314e 100644 (file)
@@ -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
index 8da9645d2299b6eab8d204371086a2814f6723ab..042cb1ee45cb35fd7687cd85a74badd58611b1cf 100644 (file)
@@ -36,6 +36,7 @@ enum {
 
     SYSCALL_SIGACTION = 25,
     SYSCALL_SIGPROCMASK = 26,
+    SYSCALL_SIGRETURN = 27,
 };
 
 #endif
index b858cf5e94e624937f2b8f43c95a7e110081cbe8..e90c41f3a7410185b809c6b502efc6848a6d1d02 100644 (file)
@@ -4,10 +4,18 @@
 #include "process.h"
 #include "spinlock.h"
 #include "uaccess.h"
+#include "syscall.h"
 #include <stddef.h>
 
 #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, <sigframe_addr>
+    //   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;
 }
 
index 4db8412cc41fdd70c0bf07dd8aa4fb366da65989..4af02cda8cb73b6ae02095ba6271d4eccdddd01f 100644 (file)
@@ -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"
 
 #include <stddef.h>
 
+#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;
 }
 
index d128756e5dee5954c8ef68a31ac1e2b5b9b14e64..6c9aa8c60259b17e8fe38c48814e635c99f5a736 100644 (file)
@@ -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",