From f61d8ac8e6bf8e34df02996f6baf7ff907e07811 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Wed, 28 Jan 2026 12:32:02 +0800 Subject: [PATCH] Refine task_spawn with defense-in-depth security This splits mo_task_spawn into two kernel-internal functions: - mo_task_spawn_kernel(): Creates M-mode tasks (used by logger) - mo_task_spawn_user(): Creates U-mode tasks (used by main, syscall) The public mo_task_spawn() macro routes automatically based on CONFIG_PRIVILEGED build flag, maintaining backward compatibility. Security model (three-layer defense-in-depth): 1. Compile-time: private/task.h guard rejects non-kernel builds 2. Runtime: in_syscall_context check rejects syscall-context calls 3. Hardware: read_csr(mstatus) traps immediately from U-mode --- Documentation/hal-calling-convention.md | 25 ++++++++-- app/cond.c | 8 ++-- app/coop.c | 6 +-- app/cpubench.c | 2 +- app/echo.c | 4 +- app/hello.c | 6 +-- app/mqueues.c | 10 ++-- app/mutex.c | 8 ++-- app/pipes.c | 8 ++-- app/pipes_small.c | 6 +-- app/pipes_struct.c | 4 +- app/prodcons.c | 6 +-- app/progress.c | 4 +- app/rtsched.c | 13 +++--- app/suspend.c | 6 +-- app/test64.c | 2 +- app/timer.c | 2 +- app/timer_kill.c | 8 ++-- app/umode.c | 9 ++-- include/private/error.h | 5 ++ include/private/task.h | 44 ++++++++++++++++++ include/sys/syscall.h | 2 +- include/sys/task.h | 20 ++++++-- kernel/logger.c | 4 +- kernel/main.c | 3 +- kernel/syscall.c | 27 +++++++++-- kernel/task.c | 62 +++++++++++++++++++++---- 27 files changed, 224 insertions(+), 80 deletions(-) create mode 100644 include/private/task.h diff --git a/Documentation/hal-calling-convention.md b/Documentation/hal-calling-convention.md index 00d68a52..3c2ef433 100644 --- a/Documentation/hal-calling-convention.md +++ b/Documentation/hal-calling-convention.md @@ -141,11 +141,30 @@ This "red zone" is reserved at the top of every task stack to guarantee ISR safe Standard RISC-V calling convention applies: ```c -/* Example: mo_task_spawn(entry, stack_size, mode) */ -/* a0 = entry, a1 = stack_size, a2 = mode, return value in a0 */ -int32_t result = mo_task_spawn(task_function, 2048, TASK_MODE_M); +/* Application API - the only interface apps should use: */ +int32_t result = mo_task_spawn(task_function, 2048); ``` +The `mo_task_spawn()` macro routes automatically based on build configuration: +- `CONFIG_PRIVILEGED`: Direct kernel call → M-mode task +- Otherwise: Syscall (`sys_task_spawn()`) → U-mode task + +**Internal Architecture** (kernel developers only): + +The implementation uses two internal functions declared in `include/private/task.h`: +- `mo_task_spawn_kernel()`: Creates M-mode tasks (used by logger.c) +- `mo_task_spawn_user()`: Creates U-mode tasks (used by main.c, syscall.c) + +These are not exposed to applications. The public header only exposes the +`mo_task_spawn()` macro, which provides the correct behavior transparently. + +**Security Model**: +- `mo_task_spawn_kernel()`: Protected by defense-in-depth: + - Runtime check rejects calls from syscall context (returns -1) + - Hardware protection: CRITICAL_ENTER traps if called from U-mode +- `mo_task_spawn_user()`: No privilege restrictions (creates restricted tasks) +- `mo_task_spawn()`: Safe for applications - routes to appropriate implementation + ### System Call Interface Linmo provides system calls through the RISC-V trap mechanism for privilege diff --git a/app/cond.c b/app/cond.c index 7b63f997..ace81ef0 100644 --- a/app/cond.c +++ b/app/cond.c @@ -110,10 +110,10 @@ int32_t app_main(void) mo_mutex_init(&m); mo_cond_init(&cv); - mo_task_spawn(producer, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(consumer, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(mutex_tester, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(idle_task, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(producer, DEFAULT_STACK_SIZE); + mo_task_spawn(consumer, DEFAULT_STACK_SIZE); + mo_task_spawn(mutex_tester, DEFAULT_STACK_SIZE); + mo_task_spawn(idle_task, DEFAULT_STACK_SIZE); /* preemptive mode */ return 1; diff --git a/app/coop.c b/app/coop.c index c1f8b8ce..7921d82c 100644 --- a/app/coop.c +++ b/app/coop.c @@ -34,9 +34,9 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task0, DEFAULT_STACK_SIZE); + mo_task_spawn(task1, DEFAULT_STACK_SIZE); + mo_task_spawn(task2, DEFAULT_STACK_SIZE); /* cooperative mode */ return 0; diff --git a/app/cpubench.c b/app/cpubench.c index 83342261..6330d7e5 100644 --- a/app/cpubench.c +++ b/app/cpubench.c @@ -62,6 +62,6 @@ int32_t app_main(void) printf("Result: a=%d, b=%d, c=%d\n", a, b, c); printf("Elapsed time: %lu.%03lus\n", elapsed / 1000, elapsed % 1000); - mo_task_spawn(idle, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(idle, DEFAULT_STACK_SIZE); return 1; } diff --git a/app/echo.c b/app/echo.c index 9d1aa50e..e5e9db9a 100644 --- a/app/echo.c +++ b/app/echo.c @@ -55,8 +55,8 @@ int32_t app_main(void) { pipe = mo_pipe_create(PIPE_CAP); - mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task0, DEFAULT_STACK_SIZE); + mo_task_spawn(task1, DEFAULT_STACK_SIZE); /* preemptive mode */ return 1; diff --git a/app/hello.c b/app/hello.c index 7739b9d3..d6e785be 100644 --- a/app/hello.c +++ b/app/hello.c @@ -37,9 +37,9 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task0, DEFAULT_STACK_SIZE); + mo_task_spawn(task1, DEFAULT_STACK_SIZE); + mo_task_spawn(task2, DEFAULT_STACK_SIZE); mo_task_priority(2, TASK_PRIO_LOW); diff --git a/app/mqueues.c b/app/mqueues.c index 5dea26e4..5816a1ce 100644 --- a/app/mqueues.c +++ b/app/mqueues.c @@ -162,11 +162,11 @@ int32_t app_main(void) /* Spawn all the tasks. The idle task (task 0) is typically spawned first * to ensure it's available when other tasks yield or block. */ - mo_task_spawn(idle, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task3, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task4, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(idle, DEFAULT_STACK_SIZE); + mo_task_spawn(task1, DEFAULT_STACK_SIZE); + mo_task_spawn(task2, DEFAULT_STACK_SIZE); + mo_task_spawn(task3, DEFAULT_STACK_SIZE); + mo_task_spawn(task4, DEFAULT_STACK_SIZE); /* Create the message queues with a capacity of 8 items each. */ mq1 = mo_mq_create(8); /* Queue for signaling task1. */ diff --git a/app/mutex.c b/app/mutex.c index 6550027e..c332687d 100644 --- a/app/mutex.c +++ b/app/mutex.c @@ -191,10 +191,10 @@ int32_t app_main(void) printf("Binary semaphore created successfully\n"); /* Create tasks */ - int32_t task_a_id = mo_task_spawn(task_a, 1024, TASK_MODE_M); - int32_t task_b_id = mo_task_spawn(task_b, 1024, TASK_MODE_M); - int32_t monitor_id = mo_task_spawn(monitor_task, 1024, TASK_MODE_M); - int32_t idle_id = mo_task_spawn(idle_task, 512, TASK_MODE_M); + int32_t task_a_id = mo_task_spawn(task_a, 1024); + int32_t task_b_id = mo_task_spawn(task_b, 1024); + int32_t monitor_id = mo_task_spawn(monitor_task, 1024); + int32_t idle_id = mo_task_spawn(idle_task, 512); if (task_a_id < 0 || task_b_id < 0 || monitor_id < 0 || idle_id < 0) { printf("FATAL: Failed to create tasks\n"); diff --git a/app/pipes.c b/app/pipes.c index 3bd35eb7..242bda9b 100644 --- a/app/pipes.c +++ b/app/pipes.c @@ -57,10 +57,10 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task3, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task0, DEFAULT_STACK_SIZE); + mo_task_spawn(task1, DEFAULT_STACK_SIZE); + mo_task_spawn(task2, DEFAULT_STACK_SIZE); + mo_task_spawn(task3, DEFAULT_STACK_SIZE); pipe1 = mo_pipe_create( 128); /* pipe buffer, 128 bytes (allocated on the heap) */ diff --git a/app/pipes_small.c b/app/pipes_small.c index 512829e5..a8cdea70 100644 --- a/app/pipes_small.c +++ b/app/pipes_small.c @@ -42,9 +42,9 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task0, DEFAULT_STACK_SIZE); + mo_task_spawn(task1, DEFAULT_STACK_SIZE); + mo_task_spawn(task2, DEFAULT_STACK_SIZE); pipe1 = mo_pipe_create( 64); /* pipe buffer, 64 bytes (allocated from the heap) */ diff --git a/app/pipes_struct.c b/app/pipes_struct.c index 5d04ee15..645ae91f 100644 --- a/app/pipes_struct.c +++ b/app/pipes_struct.c @@ -44,8 +44,8 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task0, DEFAULT_STACK_SIZE); + mo_task_spawn(task1, DEFAULT_STACK_SIZE); pipe1 = mo_pipe_create(64); /* pipe buffer, 64 bytes */ diff --git a/app/prodcons.c b/app/prodcons.c index 3a8bd946..f590b95a 100644 --- a/app/prodcons.c +++ b/app/prodcons.c @@ -38,9 +38,9 @@ static void consumer(void) int32_t app_main(void) { - mo_task_spawn(producer, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(consumer, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(consumer, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(producer, DEFAULT_STACK_SIZE); + mo_task_spawn(consumer, DEFAULT_STACK_SIZE); + mo_task_spawn(consumer, DEFAULT_STACK_SIZE); empty = mo_sem_create(3, N); full = mo_sem_create(3, 0); diff --git a/app/progress.c b/app/progress.c index 9565eeff..57ea1775 100644 --- a/app/progress.c +++ b/app/progress.c @@ -36,9 +36,9 @@ int32_t app_main(void) int32_t i; for (i = 0; i < N_TASKS; i++) - mo_task_spawn(task, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task, DEFAULT_STACK_SIZE); // add logger task - mo_task_spawn(logger, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(logger, DEFAULT_STACK_SIZE); /* preemptive mode */ return 1; diff --git a/app/rtsched.c b/app/rtsched.c index 25c8c7f1..56402814 100644 --- a/app/rtsched.c +++ b/app/rtsched.c @@ -452,18 +452,17 @@ int32_t app_main(void) /* test_start_time will be initialized by first task that runs */ /* Spawn all 5 RT/background tasks first */ - int32_t tid0 = mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); - int32_t tid1 = mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); - int32_t tid2 = mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); - (void) mo_task_spawn(task3, DEFAULT_STACK_SIZE, - TASK_MODE_M); /* Non-RT task 3 */ + int32_t tid0 = mo_task_spawn(task0, DEFAULT_STACK_SIZE); + int32_t tid1 = mo_task_spawn(task1, DEFAULT_STACK_SIZE); + int32_t tid2 = mo_task_spawn(task2, DEFAULT_STACK_SIZE); + (void) mo_task_spawn(task3, DEFAULT_STACK_SIZE); /* Non-RT task 3 */ /* Non-RT task 4 - displays stats */ - (void) mo_task_spawn(task4, DEFAULT_STACK_SIZE, TASK_MODE_M); + (void) mo_task_spawn(task4, DEFAULT_STACK_SIZE); /* Spawn IDLE task LAST so it's at end of round-robin list. * This ensures other ready tasks get scheduled before IDLE. */ - (void) mo_task_spawn(idle_task, DEFAULT_STACK_SIZE, TASK_MODE_M); + (void) mo_task_spawn(idle_task, DEFAULT_STACK_SIZE); /* Configure EDF priorities for RT tasks 0-2 with deadlines relative to * current time */ diff --git a/app/suspend.c b/app/suspend.c index 6153bcd4..653ef68f 100644 --- a/app/suspend.c +++ b/app/suspend.c @@ -54,9 +54,9 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task0, DEFAULT_STACK_SIZE); + mo_task_spawn(task1, DEFAULT_STACK_SIZE); + mo_task_spawn(task2, DEFAULT_STACK_SIZE); /* preemptive mode */ return 1; diff --git a/app/test64.c b/app/test64.c index 9c6d4b3b..6ed98aa8 100644 --- a/app/test64.c +++ b/app/test64.c @@ -86,7 +86,7 @@ void task(void) int32_t app_main() { - mo_task_spawn(task, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task, DEFAULT_STACK_SIZE); return 1; } diff --git a/app/timer.c b/app/timer.c index 9db0e520..19f53466 100644 --- a/app/timer.c +++ b/app/timer.c @@ -63,7 +63,7 @@ int32_t app_main(void) mo_timer_start(0x6002, TIMER_AUTORELOAD); /* Spawn a single idle task to keep the kernel running. */ - mo_task_spawn(idle_task, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(idle_task, DEFAULT_STACK_SIZE); /* preemptive mode */ return 1; diff --git a/app/timer_kill.c b/app/timer_kill.c index c1f9c82e..3ab3c837 100644 --- a/app/timer_kill.c +++ b/app/timer_kill.c @@ -38,10 +38,10 @@ void idle(void) int32_t app_main(void) { - mo_task_spawn(timer1, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(timer2, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(timer3, DEFAULT_STACK_SIZE, TASK_MODE_M); - mo_task_spawn(idle, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(timer1, DEFAULT_STACK_SIZE); + mo_task_spawn(timer2, DEFAULT_STACK_SIZE); + mo_task_spawn(timer3, DEFAULT_STACK_SIZE); + mo_task_spawn(idle, DEFAULT_STACK_SIZE); /* preemptive mode */ return 1; diff --git a/app/umode.c b/app/umode.c index 7b7c54d4..2fc339eb 100644 --- a/app/umode.c +++ b/app/umode.c @@ -27,8 +27,8 @@ void umode_validation_task(void) umode_printf("[umode] FAIL: sys_uptime() failed (ret=%d)\n", uptime); } - /* Note: Skipping sys_tadd for now, as kernel user pointer checks might - * block function pointers in the .text segment, avoiding distraction. + /* Note: Skipping sys_task_spawn for now, as kernel user pointer checks + * might block function pointers in the .text segment, avoiding distraction. */ /* --- Phase 2: Security Check (Privileged Access) --- */ @@ -65,9 +65,10 @@ int32_t app_main(void) umode_printf("[Kernel] Spawning U-mode validation task...\n"); /* app_main now runs in U-mode by default. - * Use sys_tadd syscall to create additional U-mode tasks. + * mo_task_spawn routes to sys_task_spawn syscall for U-mode apps, + * ensuring consistent API usage across the codebase. */ - sys_tadd(umode_validation_task, DEFAULT_STACK_SIZE); + mo_task_spawn(umode_validation_task, DEFAULT_STACK_SIZE); /* Return 1 to enable preemptive scheduler */ return 1; diff --git a/include/private/error.h b/include/private/error.h index 5589087b..fc1646c8 100644 --- a/include/private/error.h +++ b/include/private/error.h @@ -1,5 +1,10 @@ #pragma once +/* Kernel-only marker: defined when compiling kernel code. + * Used by private headers to enforce kernel-only usage at compile time. + */ +#define __LINMO_KERNEL 1 + /* Centralizes all error codes used throughout the kernel. Error codes use * automatic enumeration starting from -16383 to avoid conflicts with POSIX * errno values. Each subsystem has its own logical grouping for easier diff --git a/include/private/task.h b/include/private/task.h new file mode 100644 index 00000000..2a8a7b4a --- /dev/null +++ b/include/private/task.h @@ -0,0 +1,44 @@ +#pragma once + +/* Kernel-internal task management APIs. + * + * These functions are for kernel use only (logger, main, syscall handlers). + * Applications should use the mo_task_spawn() macro from . + * + * Note: This header must be included after or other headers + * that define int32_t and uint16_t (project uses arch-specific types). + */ + +/* Include error.h to get __LINMO_KERNEL marker for compile-time guard */ +#include "private/error.h" + +/* Compile-time guard: reject non-kernel builds */ +#ifndef __LINMO_KERNEL +#error \ + "private/task.h is for kernel use only. Applications must use mo_task_spawn() from " +#endif + +/* Creates and starts a new task in kernel (machine) mode. + * @task_entry : Pointer to the task's entry function (void func(void)) + * @stack_size : The desired stack size in bytes (minimum is enforced) + * + * Returns the new task's ID on success, -1 on privilege violation, + * or panics on memory allocation failure. + * + * Security: Protected by defense-in-depth: + * - Runtime check rejects calls from syscall context (returns -1) + * - Hardware protection: read_csr(mstatus) traps immediately if called + * from U-mode (illegal instruction exception) + */ +int32_t mo_task_spawn_kernel(void *task_entry, uint16_t stack_size); + +/* Creates and starts a new task in user mode. + * @task_entry : Pointer to the task's entry function (void func(void)) + * @stack_size : The desired stack size in bytes (minimum is enforced) + * + * Returns the new task's ID on success. Panics on memory allocation failure. + * + * Used by kernel bootstrap (main.c) and syscall handlers. + * U-mode tasks run with restricted privileges and must use syscalls. + */ +int32_t mo_task_spawn_user(void *task_entry, uint16_t stack_size); diff --git a/include/sys/syscall.h b/include/sys/syscall.h index 218e4b66..0da62598 100644 --- a/include/sys/syscall.h +++ b/include/sys/syscall.h @@ -37,7 +37,7 @@ /* 21-31 reserved for future POSIX extensions */ \ \ /* Linmo-specific system calls (32+) */ \ - _(tadd, 32, int, (void *task, int stack_sz)) \ + _(task_spawn, 32, int, (void *task, int stack_sz)) \ _(tcancel, 33, int, (int id)) \ _(tyield, 34, int, (void) ) \ _(tdelay, 35, int, (int ticks)) \ diff --git a/include/sys/task.h b/include/sys/task.h index 4e030e69..ac8db01c 100644 --- a/include/sys/task.h +++ b/include/sys/task.h @@ -85,7 +85,7 @@ typedef struct tcb { uint16_t delay; /* Ticks remaining for task in TASK_BLOCKED state */ uint16_t id; /* Unique task ID, assigned by kernel upon creation */ uint8_t state; /* Current lifecycle state (e.g., TASK_READY) */ - uint8_t flags; /* Task flags for future extensions (reserved) */ + task_mode_t mode; /* Privilege mode: TASK_MODE_M or TASK_MODE_U */ /* Real-time Scheduling Support */ void *rt_prio; /* Opaque pointer for custom real-time scheduler hook */ @@ -193,14 +193,26 @@ void _yield(void); /* Task Lifecycle Management */ -/* Creates and starts a new task. +/* Application task creation macro. * @task_entry : Pointer to the task's entry function (void func(void)) * @stack_size : The desired stack size in bytes (minimum is enforced) - * @mode : Privilege mode (TASK_MODE_M or TASK_MODE_U) * * Returns the new task's ID on success. Panics on memory allocation failure. + * + * Routes to the correct implementation based on build configuration: + * - CONFIG_PRIVILEGED: Direct kernel call (M-mode task creation) + * - Otherwise: Syscall wrapper (U-mode task creation, hardware enforced) */ -int32_t mo_task_spawn(void *task_entry, uint16_t stack_size, task_mode_t mode); +#ifdef CONFIG_PRIVILEGED +/* Include private header for kernel-internal function (not exposed to apps) */ +#include "private/task.h" +#define mo_task_spawn(task_entry, stack_size) \ + mo_task_spawn_kernel((task_entry), (stack_size)) +#else +int sys_task_spawn(void *task, int stack_size); +#define mo_task_spawn(task_entry, stack_size) \ + sys_task_spawn((task_entry), (stack_size)) +#endif /* Cancels and removes a task from the system. A task cannot cancel itself. * @id : The ID of the task to cancel diff --git a/kernel/logger.c b/kernel/logger.c index cdb7a79b..4174af8b 100644 --- a/kernel/logger.c +++ b/kernel/logger.c @@ -13,6 +13,7 @@ #include #include "private/error.h" +#include "private/task.h" /* Ring buffer entry: fixed-size for O(1) enqueue/dequeue */ typedef struct { @@ -82,7 +83,8 @@ int32_t mo_logger_init(void) return ERR_FAIL; /* 1024B stack: space for log_entry_t (130B) + ISR frame (128B) + calls */ - logger.task_id = mo_task_spawn(logger_task, 1024, TASK_MODE_M); + /* Kernel internal: always use direct API for kernel-mode task */ + logger.task_id = mo_task_spawn_kernel(logger_task, 1024); if (logger.task_id < 0) { mo_mutex_destroy(&logger.lock); return ERR_FAIL; diff --git a/kernel/main.c b/kernel/main.c index 2b983187..348dc5e5 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -5,6 +5,7 @@ #include #include "private/error.h" +#include "private/task.h" #ifndef CONFIG_PRIVILEGED /* Wrapper to run app_main() in U-mode. @@ -64,7 +65,7 @@ int32_t main(void) #else /* U-mode: preemptive mode fixed, app_main() runs as user task */ kcb->preemptive = true; - mo_task_spawn(app_main_wrapper, DEFAULT_STACK_SIZE, TASK_MODE_U); + mo_task_spawn_user(app_main_wrapper, DEFAULT_STACK_SIZE); #endif printf("Scheduler mode: %s\n", diff --git a/kernel/syscall.c b/kernel/syscall.c index af41f4c2..fe6e0433 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -4,8 +4,16 @@ #include #include "private/stdio.h" +#include "private/task.h" #include "private/utils.h" +/* Syscall context flag for privilege enforcement. + * When set, kernel APIs that check privilege will reject M-mode requests. + * This prevents privilege escalation if U-mode code somehow calls kernel + * functions directly (defense-in-depth alongside hardware protection). + */ +volatile bool in_syscall_context = false; + /* syscall wrappers */ #define _(name, num, rettype, arglist) static rettype _##name arglist; SYSCALL_TABLE @@ -19,6 +27,9 @@ static const void *syscall_table[SYS_COUNT] = {SYSCALL_TABLE}; /* Core syscall execution via direct table lookup. * Called by trap handlers to invoke syscall implementations without * triggering privilege transitions. User space must not call this directly. + * + * Sets in_syscall_context flag during execution to enable runtime privilege + * checks in kernel APIs (defense-in-depth for mo_task_spawn_kernel). */ int do_syscall(int num, void *a1, void *a2, void *a3) { @@ -28,7 +39,13 @@ int do_syscall(int num, void *a1, void *a2, void *a3) if (unlikely(!syscall_table[num])) return -ENOSYS; - return ((int (*)(void *, void *, void *)) syscall_table[num])(a1, a2, a3); + /* Mark syscall context for privilege enforcement */ + in_syscall_context = true; + int result = + ((int (*)(void *, void *, void *)) syscall_table[num])(a1, a2, a3); + in_syscall_context = false; + + return result; } /* Generic user-space syscall interface. @@ -242,18 +259,18 @@ static int _link(char *old, char *new) /* Linmo syscalls (wrapper implementation) */ -static int _tadd(void *task, int stack_size) +static int _task_spawn(void *task, int stack_size) { if (unlikely(!task || stack_size <= 0)) return -EINVAL; /* Syscall always creates U-mode tasks for security */ - return mo_task_spawn(task, stack_size, TASK_MODE_U); + return mo_task_spawn_user(task, stack_size); } -int sys_tadd(void *task, int stack_size) +int sys_task_spawn(void *task, int stack_size) { - return syscall(SYS_tadd, task, (void *) stack_size, 0); + return syscall(SYS_task_spawn, task, (void *) stack_size, 0); } static int _tcancel(int id) diff --git a/kernel/task.c b/kernel/task.c index 7c4fc4e8..746d139e 100644 --- a/kernel/task.c +++ b/kernel/task.c @@ -13,6 +13,11 @@ #include "private/error.h" #include "private/utils.h" +/* Syscall context flag from syscall.c for runtime privilege enforcement. + * When true, M-mode task creation requests are rejected (defense-in-depth). + */ +extern volatile bool in_syscall_context; + static int32_t noop_rtsched(void); void _timer_tick_handler(void); @@ -71,6 +76,12 @@ static const uint8_t priority_timeslices[TASK_PRIORITY_LEVELS] = { TASK_TIMESLICE_IDLE /* Priority 7: Idle */ }; +/* Task mode to display character mapping (extensible for future modes) */ +static const char task_mode_chars[] = { + [TASK_MODE_M] = 'M', /* Machine mode */ + [TASK_MODE_U] = 'U', /* User mode */ +}; + /* Mark task as ready (state-based) */ static void sched_enqueue_task(tcb_t *task); @@ -712,9 +723,12 @@ static bool init_task_stack(tcb_t *tcb, size_t stack_size) /* Task Management API */ -int32_t mo_task_spawn(void *task_entry, - uint16_t stack_size_req, - task_mode_t mode) +/* Internal task spawn implementation. + * Centralizes task creation logic, called by public M-mode and U-mode wrappers. + */ +static int32_t task_spawn_internal(void *task_entry, + uint16_t stack_size_req, + bool user_mode) { if (!task_entry) panic(ERR_TCB_ALLOC); @@ -734,7 +748,7 @@ int32_t mo_task_spawn(void *task_entry, tcb->delay = 0; tcb->rt_prio = NULL; tcb->state = TASK_STOPPED; - tcb->flags = 0; + tcb->mode = user_mode ? TASK_MODE_U : TASK_MODE_M; /* Set default priority with proper scheduler fields */ tcb->prio = TASK_PRIO_NORMAL; @@ -778,19 +792,18 @@ int32_t mo_task_spawn(void *task_entry, CRITICAL_LEAVE(); /* Initialize execution context outside critical section. */ - int user_mode = (mode == TASK_MODE_U); hal_context_init(&tcb->context, (size_t) tcb->stack, new_stack_size, - (size_t) task_entry, user_mode); + (size_t) task_entry, user_mode ? 1 : 0); /* Initialize SP for preemptive mode. * Build initial ISR frame on stack with mepc pointing to task entry. */ void *stack_top = (void *) ((uint8_t *) tcb->stack + new_stack_size); - tcb->sp = hal_build_initial_frame(stack_top, task_entry, user_mode); + tcb->sp = hal_build_initial_frame(stack_top, task_entry, user_mode ? 1 : 0); - printf("task %u: entry=%p stack=%p size=%u prio_level=%u time_slice=%u\n", + printf("task %u: entry=%p stack=%p size=%u mode=%c prio=%u slice=%u\n", tcb->id, task_entry, tcb->stack, (unsigned int) new_stack_size, - tcb->prio_level, tcb->time_slice); + task_mode_chars[tcb->mode], tcb->prio_level, tcb->time_slice); /* Add to cache and mark ready */ cache_task(tcb->id, tcb); @@ -799,6 +812,37 @@ int32_t mo_task_spawn(void *task_entry, return tcb->id; } +/* Creates and starts a new task in kernel (machine) mode. + * Used by kernel code (logger) and privileged applications. + * + * Security (defense-in-depth with multiple layers): + * 1. Compile-time: private/task.h guard rejects non-kernel builds + * 2. Runtime: in_syscall_context check rejects calls from syscall handlers + * 3. Hardware: read_csr(mstatus) traps immediately if called from U-mode + */ +int32_t mo_task_spawn_kernel(void *task_entry, uint16_t stack_size) +{ + /* Explicit privilege check: reading mstatus from U-mode causes trap. + * This provides immediate hardware-enforced security before any other code + * runs. The volatile prevents compiler from optimizing away the check. + */ + volatile uint32_t mstatus_check __attribute__((unused)) = read_csr(mstatus); + + /* Defense-in-depth: reject M-mode task creation from syscall context */ + if (in_syscall_context) + return -1; + + return task_spawn_internal(task_entry, stack_size, false); +} + +/* Creates and starts a new task in user mode. + * Used by kernel bootstrap (main.c) and syscall handlers. + */ +int32_t mo_task_spawn_user(void *task_entry, uint16_t stack_size) +{ + return task_spawn_internal(task_entry, stack_size, true); +} + int32_t mo_task_cancel(uint16_t id) { if (id == 0 || id == mo_task_id())