diff --git a/commands.c b/commands.c index 914d75f..0e1c87d 100644 --- a/commands.c +++ b/commands.c @@ -39,6 +39,7 @@ static void cmd_ps(BootInfo *Boot, CHAR16 *Args); static void cmd_spawn(BootInfo *Boot, CHAR16 *Args); static void cmd_memtest(BootInfo *Boot, CHAR16 *Args); static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args); +static void cmd_kusr(BootInfo *Boot, CHAR16 *Args); /* Small helper struct used to pass arguments into per-command tasks. */ typedef struct { @@ -75,48 +76,56 @@ static Command commands[] = { L"shutdown", L"Shutdown the system", L"Usage: shutdown\n\r Initiates a system shutdown using UEFI runtime services.", + TASK_PRIV_KERNEL, cmd_shutdown }, { L"help", L"Display available commands", L"Usage: help\n\r Lists all available commands with brief descriptions.", + TASK_PRIV_USER, cmd_help }, { L"man", L"Display manual page for a command", L"Usage: man \n\r Shows detailed help for the specified command.", + TASK_PRIV_USER, cmd_man }, { L"clear", L"Clear the screen", L"Usage: clear\n\r Clears the console screen.", + TASK_PRIV_USER, cmd_clear }, { L"about", L"Display system information", L"Usage: about\n\r Shows information about this operating system.", + TASK_PRIV_USER, cmd_about }, { L"mem", L"Display memory statistics", L"Usage: mem\n\r Shows physical memory, heap, and paging information.", + TASK_PRIV_KERNEL, cmd_mem }, { L"ps", L"List running tasks", L"Usage: ps\n\r Displays all active tasks with PID, state, and name.", + TASK_PRIV_DRIVER, cmd_ps }, { L"spawn", L"Spawn a demo background task", L"Usage: spawn [name]\n\r Creates a cooperative demo task.\n\r Optional argument sets the task name.", + TASK_PRIV_DRIVER, cmd_spawn }, { @@ -128,6 +137,7 @@ static Command commands[] = { L" 2) Heap free and coalescing verification via kfree()\n\r" L" 3) Single-page PMM allocate/free via pmm_alloc_page()/pmm_free_page()\n\r" L" 4) Multi-page (4-page) PMM allocate/free via pmm_alloc_pages()/pmm_free_pages()\n\r", + TASK_PRIV_KERNEL, cmd_memtest }, { @@ -138,9 +148,19 @@ static Command commands[] = { L" each printing three progress steps and yielding between them.\n\r" L" After the workers finish, prints the final task list to\n\r" L" demonstrate the cooperative round-robin scheduler.", + TASK_PRIV_DRIVER, cmd_tasktest }, - {NULL, NULL, NULL, NULL} /* sentinel */ + { + L"kusr", + L"Run a command with kernel privilege", + L"Usage: kusr [args...]\n\r" + L" Temporarily elevates the current task to kernel privilege,\n\r" + L" executes the given command, then restores the original level.", + TASK_PRIV_USER, + cmd_kusr + }, + {NULL, NULL, NULL, 0, NULL} /* sentinel */ }; /* ================================================================ @@ -149,10 +169,19 @@ static Command commands[] = { static void request_shutdown(BootInfo *Boot) { + Task *caller; + if (Boot == NULL) { return; } + /* Subsystem-level privilege enforcement: shutdown requires KERNEL. */ + caller = task_current(); + if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) { + SAFE_PRINT(Boot, L"Permission denied: shutdown requires kernel privilege.\n\r"); + return; + } + if (Boot->shutdown != NULL) { Boot->shutdown(); return; @@ -310,8 +339,16 @@ static void cmd_memtest(BootInfo *Boot, CHAR16 *Args) UINTN i; UINT64 page; UINTN h_total, h_used, h_free, h_blocks; + Task *caller; (void)Args; + /* Subsystem-level privilege enforcement: memtest requires KERNEL. */ + caller = task_current(); + if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) { + SAFE_PRINT(Boot, L"Permission denied: memtest requires kernel privilege.\n\r"); + return; + } + SAFE_PRINT(Boot, L"\n\r"); SAFE_PRINT(Boot, L"Memory Test\n\r"); SAFE_PRINT(Boot, L"================================================\n\r"); @@ -434,6 +471,40 @@ static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args) SAFE_PRINT(Boot, L"Task scheduler test completed.\n\r\n\r"); } +/* ---------------------------------------------------------------- + * kusr – run a command with escalated privilege + * ---------------------------------------------------------------- */ + +static void cmd_kusr(BootInfo *Boot, CHAR16 *Args) +{ + Task *self; + TaskPrivilege saved_priv; + + if (Args == NULL || Args[0] == L'\0') { + SAFE_PRINT(Boot, L"Usage: kusr [args...]\n\r"); + return; + } + + self = task_current(); + if (self == NULL) { + SAFE_PRINT(Boot, L"kusr: no task context available.\n\r"); + return; + } + + /* Elevate, dispatch, restore. */ + saved_priv = task_get_privilege(self); + task_set_privilege(self, TASK_PRIV_KERNEL); + + { + Task *cmd_task = execute_command(Boot, Args, TASK_PRIV_KERNEL); + if (cmd_task != NULL) { + task_wait(cmd_task); + } + } + + task_set_privilege(self, saved_priv); +} + /* ================================================================ * Public API * ================================================================ */ @@ -490,7 +561,7 @@ void show_help(BootInfo *Boot) * - Pointer to the spawned Task for the command, or NULL if the * command was not found or had to run synchronously. */ -Task *execute_command(BootInfo *Boot, CHAR16 *Input) +Task *execute_command(BootInfo *Boot, CHAR16 *Input, TaskPrivilege caller_priv) { CHAR16 *cmd_start = NULL; CHAR16 *args_start = NULL; @@ -543,7 +614,10 @@ Task *execute_command(BootInfo *Boot, CHAR16 *Input) ctx->handler = commands[i].handler; wstrcpy16_local(ctx->args, args_start, sizeof(ctx->args) / sizeof(ctx->args[0])); - t = task_create(commands[i].name, command_task_entry, ctx); + t = task_create_with_priv(commands[i].name, + command_task_entry, + ctx, + caller_priv); if (t == NULL) { SAFE_PRINT(Boot, L"Failed to create task for command '%s'; running in core thread.\n\r", commands[i].name); diff --git a/commands.h b/commands.h index f96348b..6cb847c 100644 --- a/commands.h +++ b/commands.h @@ -22,9 +22,10 @@ typedef void (*CommandHandlerFn)(BootInfo *Boot, CHAR16 *Args); */ typedef struct { const CHAR16 *name; /* command keyword (e.g. L"help") */ - const CHAR16 *description; /* one-line summary for `help` */ - const CHAR16 *usage; /* detailed text shown by `man` */ - CommandHandlerFn handler; /* function that executes the cmd */ + const CHAR16 *description; /* one-line summary for `help` */ + const CHAR16 *usage; /* detailed text shown by `man` */ + TaskPrivilege min_priv; /* minimum privilege required */ + CommandHandlerFn handler; /* function that executes the cmd */ } Command; /* Parse and dispatch a line of user input. @@ -33,7 +34,7 @@ typedef struct { * - Pointer to a Task representing the spawned command process, * or NULL if the command was not found or ran synchronously. */ -Task *execute_command(BootInfo *Boot, CHAR16 *Input); +Task *execute_command(BootInfo *Boot, CHAR16 *Input, TaskPrivilege caller_priv); /* Print a formatted list of all registered commands. */ void show_help(BootInfo *Boot); diff --git a/docs/commands-and-terminal.md b/docs/commands-and-terminal.md index a69aa85..b8341ff 100644 --- a/docs/commands-and-terminal.md +++ b/docs/commands-and-terminal.md @@ -3,7 +3,7 @@ User interaction with the kernel is mediated by the **Starling Terminal** and a command registry implemented in `kernel.c` and `commands.c`. - The **Starling Terminal** is a task that runs a read–eval–print loop, reading keystrokes via `BootInfo` services and dispatching commands. -- The **command subsystem** maintains a table of commands, each with a name, description, usage string, and handler function. +- The **command subsystem** maintains a table of commands, each with a name, description, usage string, minimum privilege level, and handler function. - Each command typically runs in its own task so that long-running work does not block the terminal. --- @@ -23,15 +23,18 @@ static void starling_terminal_task(void *arg) CHAR16 line[128]; UINTN len = 0; UINTN depth = 0; + TaskPrivilege shell_priv; if (ctx == NULL || ctx->Boot == NULL) { return; } - Boot = ctx->Boot; - depth = ctx->depth; + Boot = ctx->Boot; + depth = ctx->depth; + shell_priv = ctx->shell_priv; - SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d] ready.\n\r\n\r", depth); + SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d, priv %d] ready.\n\r\n\r", + depth, (INT32)shell_priv); SAFE_PRINT(Boot, L"starling> "); while (TRUE) { @@ -66,7 +69,7 @@ static void starling_terminal_task(void *arg) trim_spaces_inplace(line); ... } else { - Task *cmd_task = execute_command(Boot, line); + Task *cmd_task = execute_command(Boot, line, shell_priv); /* If a command task was spawned, wait for it to finish. */ if (cmd_task != NULL) { @@ -117,16 +120,21 @@ ctx = (StarlingContext *)kmalloc(sizeof(StarlingContext)); if (ctx == NULL) { SAFE_PRINT(Boot, L"Failed to allocate Starling Terminal context; starting inline.\n\r"); StarlingContext inline_ctx; - inline_ctx.Boot = Boot; - inline_ctx.depth = 0; + inline_ctx.Boot = Boot; + inline_ctx.depth = 0; + inline_ctx.shell_priv = TASK_PRIV_USER; starling_terminal_task(&inline_ctx); return; } -ctx->Boot = Boot; -ctx->depth = 0; +ctx->Boot = Boot; +ctx->depth = 0; +ctx->shell_priv = TASK_PRIV_USER; -terminal_task = task_create(L"starling-term", starling_terminal_task, ctx); +terminal_task = task_create_with_priv(L"starling-term", + starling_terminal_task, + ctx, + TASK_PRIV_USER); if (terminal_task == NULL) { SAFE_PRINT(Boot, L"Failed to start Starling Terminal task; falling back to kernel loop.\n\r"); ... @@ -153,69 +161,88 @@ This ensures that: The command registry is defined in `commands.c` as a static array: -```73:144:/home/lochlan/Documents/Coding/c/os/commands.c +```73:164:/home/lochlan/Documents/Coding/c/os/commands.c static Command commands[] = { { L"shutdown", L"Shutdown the system", L"Usage: shutdown\n\r Initiates a system shutdown using UEFI runtime services.", + TASK_PRIV_KERNEL, cmd_shutdown }, { L"help", L"Display available commands", L"Usage: help\n\r Lists all available commands with brief descriptions.", + TASK_PRIV_USER, cmd_help }, { L"man", L"Display manual page for a command", L"Usage: man \n\r Shows detailed help for the specified command.", + TASK_PRIV_USER, cmd_man }, { L"clear", L"Clear the screen", L"Usage: clear\n\r Clears the console screen.", + TASK_PRIV_USER, cmd_clear }, { L"about", L"Display system information", L"Usage: about\n\r Shows information about this operating system.", + TASK_PRIV_USER, cmd_about }, { L"mem", L"Display memory statistics", L"Usage: mem\n\r Shows physical memory, heap, and paging information.", + TASK_PRIV_KERNEL, cmd_mem }, { L"ps", L"List running tasks", L"Usage: ps\n\r Displays all active tasks with PID, state, and name.", + TASK_PRIV_DRIVER, cmd_ps }, { L"spawn", L"Spawn a demo background task", L"Usage: spawn [name]\n\r Creates a cooperative demo task.\n\r Optional argument sets the task name.", + TASK_PRIV_DRIVER, cmd_spawn }, { L"memtest", L"Test memory allocation and deallocation", ... + TASK_PRIV_KERNEL, cmd_memtest }, { L"tasktest", L"Test task scheduler with multiple tasks", ... + TASK_PRIV_DRIVER, cmd_tasktest }, - {NULL, NULL, NULL, NULL} /* sentinel */ + { + L"kusr", + L"Run a command with kernel privilege", + L"Usage: kusr [args...]\n\r" + L" Temporarily elevates the current task to kernel privilege,\n\r" + L" executes the given command, then restores the original level.", + TASK_PRIV_USER, + cmd_kusr + }, + {NULL, NULL, NULL, 0, NULL} /* sentinel */ }; ``` @@ -224,6 +251,7 @@ Each `Command` entry includes: - `name` – the token typed at the prompt. - `description` – a short summary used by `help`. - `usage` – a longer description and usage details for `man`. +- `min_priv` – the minimum `TaskPrivilege` required to run the command (see `task.h`). - `handler` – a function of type: ```14:19:/home/lochlan/Documents/Coding/c/os/commands.h @@ -246,7 +274,7 @@ To add a new command, follow the guide in the file header: The central function that processes a line of user input is `execute_command`: ```493:557:/home/lochlan/Documents/Coding/c/os/commands.c -Task *execute_command(BootInfo *Boot, CHAR16 *Input) +Task *execute_command(BootInfo *Boot, CHAR16 *Input, TaskPrivilege caller_priv) { CHAR16 *cmd_start = NULL; CHAR16 *args_start = NULL; @@ -299,7 +327,10 @@ Task *execute_command(BootInfo *Boot, CHAR16 *Input) ctx->handler = commands[i].handler; wstrcpy16_local(ctx->args, args_start, sizeof(ctx->args) / sizeof(ctx->args[0])); - t = task_create(commands[i].name, command_task_entry, ctx); + t = task_create_with_priv(commands[i].name, + command_task_entry, + ctx, + caller_priv); if (t == NULL) { SAFE_PRINT(Boot, L"Failed to create task for command '%s'; running in core thread.\n\r", commands[i].name); @@ -336,10 +367,11 @@ Pipeline stages: - `Boot` pointer. - Handler function. - A bounded copy of the argument string. - - A new task is created via `task_create` with: + - A new task is created via `task_create_with_priv` with: - Task name = command name. - Entry = `command_task_entry`. - Argument = pointer to the context. + - Privilege = `caller_priv` (inherited from the calling shell). - If task creation fails, the command handler is run synchronously in the current thread as a fallback. The terminal then optionally `task_wait`s on the returned `Task *`, serialising command execution from the user's perspective while still letting the scheduler run other tasks (e.g., background demos). @@ -396,19 +428,21 @@ This design gives each command: Some notable built-in handlers: - **System control**: - - `shutdown` → `cmd_shutdown` calls `Boot->shutdown` via `request_shutdown` to power off the machine. - - `clear` → `cmd_clear` uses `Boot->clear_screen` to wipe the console. + - `shutdown` (KERNEL) → `cmd_shutdown` calls `Boot->shutdown` via `request_shutdown` to power off the machine. `request_shutdown` enforces kernel privilege. + - `clear` (USER) → `cmd_clear` uses `Boot->clear_screen` to wipe the console. - **Information and help**: - - `help` → `cmd_help` calls `show_help` to print a formatted table of available commands. - - `man` → `cmd_man` prints the `usage` field for a specific command. - - `about` → `cmd_about` prints OS information and feature list. + - `help` (USER) → `cmd_help` calls `show_help` to print a formatted table of available commands. + - `man` (USER) → `cmd_man` prints the `usage` field for a specific command. + - `about` (USER) → `cmd_about` prints OS information and feature list. - **Diagnostics**: - - `mem` → `cmd_mem` calls `memory_print_stats` to show PMM and heap state. - - `ps` → `cmd_ps` calls `task_print_list` to show current tasks. - - `memtest` → `cmd_memtest` exercises heap and PMM allocations. - - `tasktest` → `cmd_tasktest` spawns multiple worker tasks to demonstrate cooperative scheduling. + - `mem` (KERNEL) → `cmd_mem` calls `memory_print_stats` to show PMM and heap state. The callee enforces kernel privilege. + - `ps` (DRIVER) → `cmd_ps` calls `task_print_list` to show current tasks. The callee enforces driver privilege. + - `memtest` (KERNEL) → `cmd_memtest` exercises heap and PMM allocations. The handler enforces kernel privilege. + - `tasktest` (DRIVER) → `cmd_tasktest` spawns multiple worker tasks to demonstrate cooperative scheduling. - **Tasking demo**: - - `spawn` → `cmd_spawn` creates a demonstration task using `demo_task_fn`, which yields in a loop and reports progress. + - `spawn` (DRIVER) → `cmd_spawn` creates a demonstration task using `demo_task_fn`, which yields in a loop and reports progress. +- **Privilege escalation**: + - `kusr` (USER) → `cmd_kusr` temporarily elevates the calling task to `TASK_PRIV_KERNEL`, dispatches the given sub-command, then restores the original privilege level. Examples: @@ -441,11 +475,12 @@ To add a new command `foo`: 1. **Declare the handler** near the top of `commands.c`: -```32:41:/home/lochlan/Documents/Coding/c/os/commands.c +```32:42:/home/lochlan/Documents/Coding/c/os/commands.c static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args); static void cmd_help(BootInfo *Boot, CHAR16 *Args); ... static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args); +static void cmd_kusr(BootInfo *Boot, CHAR16 *Args); /* Add: */ static void cmd_foo(BootInfo *Boot, CHAR16 *Args); ``` @@ -465,13 +500,14 @@ Use this as a template for `cmd_foo`, replacing the body with your logic and usi 3. **Register the command** in `commands[]` before the sentinel: -```140:143:/home/lochlan/Documents/Coding/c/os/commands.c +```140:164:/home/lochlan/Documents/Coding/c/os/commands.c { - L"tasktest", + L"kusr", ... - cmd_tasktest + TASK_PRIV_USER, + cmd_kusr }, - {NULL, NULL, NULL, NULL} /* sentinel */ + {NULL, NULL, NULL, 0, NULL} /* sentinel */ ``` Insert a new block above the sentinel: @@ -481,6 +517,7 @@ Insert a new block above the sentinel: L"foo", L"One-line description", L"Usage: foo [args]\n\r Detailed explanation...", + TASK_PRIV_USER, /* minimum privilege required */ cmd_foo }, ``` diff --git a/docs/interrupts-and-exceptions.md b/docs/interrupts-and-exceptions.md index 4302264..d3d5839 100644 --- a/docs/interrupts-and-exceptions.md +++ b/docs/interrupts-and-exceptions.md @@ -254,7 +254,7 @@ This approach preserves any firmware-installed handlers for higher interrupt vec The IDT/ISR subsystem interacts with other parts of the kernel in the following ways: - **BootInfo access**: - - `id_tinit` stores `Boot` in `gBoot` so that `isr_handler` can safely use `Boot->print` for diagnostics. + - `idt_init` stores `Boot` in `gBoot` so that `isr_handler` can safely use `Boot->print` for diagnostics. - **Memory subsystem**: - `isr_handler` reads CR2 for page faults; combined with `paging_get_phys` from `memory.c`, this can be used to inspect paging state. - **Tasks and scheduler**: diff --git a/docs/memory-and-allocation.md b/docs/memory-and-allocation.md index b508187..0cdf6ac 100644 --- a/docs/memory-and-allocation.md +++ b/docs/memory-and-allocation.md @@ -565,13 +565,21 @@ These statistics are surfaced to the user via the `mem` and `memtest` commands. ## Runtime memory diagnostics (`mem` and `memtest`) -The `mem` command (in `commands.c`) prints a snapshot of PMM and heap state by calling `memory_print_stats`: +The `mem` command (in `commands.c`) prints a snapshot of PMM and heap state by calling `memory_print_stats`. Access requires `TASK_PRIV_KERNEL`: -```525:562:/home/lochlan/Documents/Coding/c/os/memory.c +```525:572:/home/lochlan/Documents/Coding/c/os/memory.c void memory_print_stats(BootInfo *Boot) { UINTN h_total, h_used, h_free, h_blocks; UINTN p_total, p_free, p_used; + Task *caller; + + /* Subsystem-level privilege enforcement: memory stats require KERNEL. */ + caller = task_current(); + if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) { + SAFE_PRINT(Boot, L"Permission denied: memory stats require kernel privilege.\n\r"); + return; + } p_total = pmm_get_total_pages(); p_free = pmm_get_free_pages(); @@ -590,7 +598,7 @@ void memory_print_stats(BootInfo *Boot) } ``` -The `memtest` command runs a scripted set of tests that exercise heap allocation, heap free/coalescing, and PMM single- and multi-page allocation: +The `memtest` command runs a scripted set of tests that exercise heap allocation, heap free/coalescing, and PMM single- and multi-page allocation. It also enforces `TASK_PRIV_KERNEL`: ```306:379:/home/lochlan/Documents/Coding/c/os/commands.c static void cmd_memtest(BootInfo *Boot, CHAR16 *Args) @@ -600,8 +608,16 @@ static void cmd_memtest(BootInfo *Boot, CHAR16 *Args) UINTN i; UINT64 page; UINTN h_total, h_used, h_free, h_blocks; + Task *caller; (void)Args; + /* Subsystem-level privilege enforcement: memtest requires KERNEL. */ + caller = task_current(); + if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) { + SAFE_PRINT(Boot, L"Permission denied: memtest requires kernel privilege.\n\r"); + return; + } + SAFE_PRINT(Boot, L"\n\r"); SAFE_PRINT(Boot, L"Memory Test\n\r"); SAFE_PRINT(Boot, L"================================================\n\r"); diff --git a/docs/overview.md b/docs/overview.md index c0f2b83..f23a75e 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -157,7 +157,14 @@ void kmain(BootInfo *Boot) /* ---- Spawn Starling Terminal as its own task ---- */ ctx = (StarlingContext *)kmalloc(sizeof(StarlingContext)); ... - terminal_task = task_create(L"starling-term", starling_terminal_task, ctx); + ctx->Boot = Boot; + ctx->depth = 0; + ctx->shell_priv = TASK_PRIV_USER; + + terminal_task = task_create_with_priv(L"starling-term", + starling_terminal_task, + ctx, + TASK_PRIV_USER); if (terminal_task == NULL) { ... starling_terminal_task(Boot); @@ -180,7 +187,7 @@ void kmain(BootInfo *Boot) - `idt_init(Boot)` to install the kernel's Interrupt Descriptor Table and exception handlers. - `memory_init(Boot)` to bring up the physical allocator, paging helpers, and heap. - `task_init(Boot)` to bootstrap the cooperative scheduler and register the current thread as task 0. -3. **User interface** – prints a banner and spawns the Starling Terminal as a separate task via `task_create`, then turns the core thread into an idle loop that continuously `task_yield`s to allow other tasks to run. +3. **User interface** – prints a banner and spawns the Starling Terminal as a separate task via `task_create_with_priv` with `TASK_PRIV_USER` privilege, then turns the core thread into an idle loop that continuously `task_yield`s to allow other tasks to run. At this point, the system has: @@ -206,10 +213,12 @@ static void starling_terminal_task(void *arg) return; } - Boot = ctx->Boot; - depth = ctx->depth; + Boot = ctx->Boot; + depth = ctx->depth; + shell_priv = ctx->shell_priv; - SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d] ready.\n\r\n\r", depth); + SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d, priv %d] ready.\n\r\n\r", + depth, (INT32)shell_priv); SAFE_PRINT(Boot, L"starling> "); while (TRUE) { @@ -234,7 +243,7 @@ static void starling_terminal_task(void *arg) trim_spaces_inplace(line); ... } else { - Task *cmd_task = execute_command(Boot, line); + Task *cmd_task = execute_command(Boot, line, shell_priv); /* If a command task was spawned, wait for it to finish. */ if (cmd_task != NULL) { @@ -258,7 +267,7 @@ Key points: - **Non-blocking idle**: when `try_read_key` returns no key, the terminal calls `task_yield()` so other tasks can run while the user is idle. - **Line editing**: handles printable ASCII and backspace to maintain a simple line buffer (`line[128]`). -- **Command execution**: on Enter, the line is trimmed and passed to `execute_command(Boot, line)` in `commands.c`. If that function spawns a dedicated command task, the terminal waits for it via `task_wait`. +- **Command execution**: on Enter, the line is trimmed and passed to `execute_command(Boot, line, shell_priv)` in `commands.c`, propagating the shell's privilege level. If that function spawns a dedicated command task, the terminal waits for it via `task_wait`. - **Nested terminals**: entering `starling` recursively spawns another Starling Terminal task with increased `depth`, demonstrating multi-level shells. The command registry and dispatch path are documented in detail in `commands-and-terminal.md`. @@ -278,15 +287,17 @@ The kernel is organised into focused subsystems, each in its own translation uni - **Tasks and scheduler** (`task.c` + `task.h`): - Static process control block (PCB) pool. - Cooperative round-robin scheduler. + - Software privilege levels (`TASK_PRIV_USER`, `TASK_PRIV_DRIVER`, `TASK_PRIV_KERNEL`) for access control. - Stack management and context switch support (via an external `context_switch` assembly routine). - **Interrupts and exceptions** (`idt.c` + `idt.h`): - IDT mirroring of firmware entries. - Replacement of CPU exception vectors 0–31 with kernel stubs. - Central `isr_handler` that prints diagnostics and halts on unrecoverable faults. - **Commands and shell** (`commands.c` + `commands.h`): - - Command registry and help/man system. + - Command registry with per-command minimum privilege levels and help/man system. - System control commands (`shutdown`, `about`, `mem`, `ps`). - Test commands (`memtest`, `tasktest`, `spawn`) that exercise memory and scheduler subsystems in isolation. + - Privilege escalation command (`kusr`) for running commands with elevated privilege. Each of these subsystems is covered in a dedicated document: diff --git a/docs/tasks-and-scheduler.md b/docs/tasks-and-scheduler.md index 5516f4a..55ba6c4 100644 --- a/docs/tasks-and-scheduler.md +++ b/docs/tasks-and-scheduler.md @@ -36,6 +36,7 @@ void task_init(BootInfo *Boot) for (i = 0; i < TASK_MAX; i++) { tasks[i].state = TASK_STATE_FREE; tasks[i].pid = 0; + tasks[i].privilege = TASK_PRIV_USER; tasks[i].saved_rsp = 0; tasks[i].stack_base = 0; tasks[i].stack_pages = 0; @@ -51,9 +52,10 @@ void task_init(BootInfo *Boot) * allocate one. Its saved_rsp will be filled in during the * first context_switch call in task_yield(). */ - tasks[0].pid = next_pid++; - tasks[0].state = TASK_STATE_RUNNING; - tasks[0].switches = 1; + tasks[0].pid = next_pid++; + tasks[0].state = TASK_STATE_RUNNING; + tasks[0].privilege = TASK_PRIV_KERNEL; + tasks[0].switches = 1; wstrcpy16(tasks[0].name, L"core", TASK_NAME_LEN); current_task = &tasks[0]; @@ -66,22 +68,26 @@ void task_init(BootInfo *Boot) Important points: -- Task 0 represents the **kernel core thread**, which uses the boot-time stack provided by the loader. +- Task 0 represents the **kernel core thread**, which uses the boot-time stack provided by the loader. It receives `TASK_PRIV_KERNEL` privilege. - No stack is allocated for task 0; its `saved_rsp` is populated the first time a context switch occurs. -- All other PCBs begin in `TASK_STATE_FREE`. +- All other PCBs begin in `TASK_STATE_FREE` with `TASK_PRIV_USER`. --- ## Task creation and stack layout -New tasks are created via `task_create`, which: +New tasks are created via `task_create_with_priv` (or its wrapper `task_create`), which: -1. Finds a free PCB slot. -2. Allocates a stack from the PMM. -3. Sets up an initial stack frame so that `context_switch` can "return" into a C trampoline function. +1. Checks that the caller is not escalating privilege beyond its own level. +2. Finds a free PCB slot. +3. Allocates a stack from the PMM. +4. Sets up an initial stack frame so that `context_switch` can "return" into a C trampoline function. -```121:197:/home/lochlan/Documents/Coding/c/os/task.c -Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) +```121:211:/home/lochlan/Documents/Coding/c/os/task.c +Task *task_create_with_priv(const CHAR16 *name, + TaskEntryFn entry, + void *arg, + TaskPrivilege privilege) { Task *t = NULL; UINTN i; @@ -89,6 +95,14 @@ Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) UINT64 *sp; ... + /* Subsystem-level privilege enforcement: prevent privilege escalation. */ + { + Task *caller = task_current(); + if (caller != NULL && privilege > task_get_privilege(caller)) { + return NULL; + } + } + /* Find a free PCB slot */ for (i = 0; i < TASK_MAX; i++) { if (tasks[i].state == TASK_STATE_FREE) { @@ -107,6 +121,7 @@ Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) /* Fill in the PCB */ t->pid = next_pid++; t->state = TASK_STATE_READY; + t->privilege = privilege; t->entry = entry; t->arg = arg; t->switches = 0; @@ -154,6 +169,19 @@ Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) } ``` +The convenience wrapper `task_create` inherits the calling task's privilege level: + +```213:220:/home/lochlan/Documents/Coding/c/os/task.c +Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) +{ + /* Inherit privilege from the calling task (kernel if no task context). */ + Task *caller = task_current(); + TaskPrivilege priv = (caller != NULL) ? task_get_privilege(caller) + : TASK_PRIV_KERNEL; + return task_create_with_priv(name, entry, arg, priv); +} +``` + The effective stack layout (low to high addresses) after `task_create` is: - Saved `flags`, `r15`, `r14`, `r13`, `r12`, `rbx`, `rbp` (pushed by `context_switch` semantics). @@ -367,7 +395,7 @@ Because the scheduler is cooperative, this **busy-wait** loop is benign: it yiel Example usage from the Starling Terminal: ```135:140:/home/lochlan/Documents/Coding/c/os/kernel.c -Task *cmd_task = execute_command(Boot, line); +Task *cmd_task = execute_command(Boot, line, shell_priv); /* If a command task was spawned, wait for it to finish. */ if (cmd_task != NULL) { @@ -379,25 +407,34 @@ if (cmd_task != NULL) { ## Task inspection (`ps` and `tasktest`) -The `ps` command uses `task_print_list` to show current tasks: +The `ps` command uses `task_print_list` to show current tasks. Access requires at least `TASK_PRIV_DRIVER`: -```366:389:/home/lochlan/Documents/Coding/c/os/task.c +```407:439:/home/lochlan/Documents/Coding/c/os/task.c void task_print_list(BootInfo *Boot) { UINTN i; + Task *caller; + + /* Subsystem-level privilege enforcement: task list requires DRIVER. */ + caller = task_current(); + if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_DRIVER) { + SAFE_PRINT(Boot, L"Permission denied: task list requires driver privilege.\n\r"); + return; + } SAFE_PRINT(Boot, L"\n\r"); - SAFE_PRINT(Boot, L" PID STATE SWITCHES NAME\n\r"); - SAFE_PRINT(Boot, L" --- ---------- -------- ----\n\r"); + SAFE_PRINT(Boot, L" PID STATE PRIV SWITCHES NAME\n\r"); + SAFE_PRINT(Boot, L" --- ---------- ---- -------- ----\n\r"); for (i = 0; i < TASK_MAX; i++) { if (tasks[i].state == TASK_STATE_FREE) { continue; } - SAFE_PRINT(Boot, L" %3d %-10s %8d %s\n\r", + SAFE_PRINT(Boot, L" %3d %-10s %4d %8d %s\n\r", tasks[i].pid, state_str(tasks[i].state), + (INT32)tasks[i].privilege, tasks[i].switches, tasks[i].name); } @@ -444,3 +481,28 @@ Each worker task: This demonstrates how cooperative tasks interleave output and how `task_yield` drives scheduling. +--- + +## Privilege system + +Each task carries a `TaskPrivilege` level defined in `task.h`: + +```47:52:/home/lochlan/Documents/Coding/c/os/task.h +typedef enum { + TASK_PRIV_USER = 0, + TASK_PRIV_DRIVER = 1, + TASK_PRIV_KERNEL = 2, +} TaskPrivilege; +``` + +All tasks still execute in CPU ring 0; this is a **software-only** hierarchy used for access control decisions: + +- `task_create_with_priv` prevents a caller from creating a task with a higher privilege than its own. +- Subsystem functions like `memory_print_stats`, `task_print_list`, and `request_shutdown` check the calling task's privilege before proceeding. +- The `kusr` command (`commands.c`) temporarily elevates a task to `TASK_PRIV_KERNEL` to run a privileged sub-command, then restores the original level. + +Accessors: + +- `task_get_privilege(Task *t)` – returns the task's current privilege level. +- `task_set_privilege(Task *t, TaskPrivilege p)` – changes it (no enforcement; callers are responsible). + diff --git a/kernel.c b/kernel.c index d3db011..af8f607 100644 --- a/kernel.c +++ b/kernel.c @@ -36,8 +36,9 @@ /* Simple context passed to each Starling Terminal instance. */ typedef struct { - BootInfo *Boot; - UINTN depth; /* 0 = top-level, 1+ = nested shells */ + BootInfo *Boot; + UINTN depth; /* 0 = top-level, 1+ = nested shells */ + TaskPrivilege shell_priv; /* logical privilege for this shell */ } StarlingContext; /* ================================================================ @@ -54,15 +55,18 @@ static void starling_terminal_task(void *arg) CHAR16 line[128]; UINTN len = 0; UINTN depth = 0; + TaskPrivilege shell_priv; if (ctx == NULL || ctx->Boot == NULL) { return; } - Boot = ctx->Boot; - depth = ctx->depth; + Boot = ctx->Boot; + depth = ctx->depth; + shell_priv = ctx->shell_priv; - SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d] ready.\n\r\n\r", depth); + SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d, priv %d] ready.\n\r\n\r", + depth, (INT32)shell_priv); SAFE_PRINT(Boot, L"starling> "); while (TRUE) { @@ -114,10 +118,14 @@ static void starling_terminal_task(void *arg) SAFE_PRINT(Boot, L"Starling: failed to allocate nested terminal context.\n\r"); SAFE_PRINT(Boot, L"starling> "); } else { - child_ctx->Boot = Boot; - child_ctx->depth = depth + 1; + child_ctx->Boot = Boot; + child_ctx->depth = depth + 1; + child_ctx->shell_priv = shell_priv; - child_task = task_create(L"starling-term", starling_terminal_task, child_ctx); + child_task = task_create_with_priv(L"starling-term", + starling_terminal_task, + child_ctx, + shell_priv); if (child_task == NULL) { SAFE_PRINT(Boot, L"Starling: failed to spawn nested terminal.\n\r"); kfree(child_ctx); @@ -132,7 +140,7 @@ static void starling_terminal_task(void *arg) } } } else { - Task *cmd_task = execute_command(Boot, line); + Task *cmd_task = execute_command(Boot, line, shell_priv); /* If a command task was spawned, wait for it to finish. */ if (cmd_task != NULL) { @@ -223,16 +231,21 @@ void kmain(BootInfo *Boot) if (ctx == NULL) { SAFE_PRINT(Boot, L"Failed to allocate Starling Terminal context; starting inline.\n\r"); StarlingContext inline_ctx; - inline_ctx.Boot = Boot; - inline_ctx.depth = 0; + inline_ctx.Boot = Boot; + inline_ctx.depth = 0; + inline_ctx.shell_priv = TASK_PRIV_USER; starling_terminal_task(&inline_ctx); return; } - ctx->Boot = Boot; - ctx->depth = 0; + ctx->Boot = Boot; + ctx->depth = 0; + ctx->shell_priv = TASK_PRIV_USER; - terminal_task = task_create(L"starling-term", starling_terminal_task, ctx); + terminal_task = task_create_with_priv(L"starling-term", + starling_terminal_task, + ctx, + TASK_PRIV_USER); if (terminal_task == NULL) { SAFE_PRINT(Boot, L"Failed to start Starling Terminal task; falling back to kernel loop.\n\r"); diff --git a/kernel_types.h b/kernel_types.h index 4a917b9..1530628 100644 --- a/kernel_types.h +++ b/kernel_types.h @@ -18,6 +18,8 @@ typedef uint16_t UINT16; typedef uint32_t UINT32; typedef uint64_t UINT64; +typedef int32_t INT32; + typedef size_t UINTN; #ifndef BOOLEAN diff --git a/memory.c b/memory.c index 0a4dd46..cdbba2e 100644 --- a/memory.c +++ b/memory.c @@ -11,6 +11,7 @@ */ #include "memory.h" +#include "task.h" /* Null-safe print helper used throughout the kernel. */ #define SAFE_PRINT(Boot, ...) \ @@ -526,6 +527,14 @@ void memory_print_stats(BootInfo *Boot) { UINTN h_total, h_used, h_free, h_blocks; UINTN p_total, p_free, p_used; + Task *caller; + + /* Subsystem-level privilege enforcement: memory stats require KERNEL. */ + caller = task_current(); + if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) { + SAFE_PRINT(Boot, L"Permission denied: memory stats require kernel privilege.\n\r"); + return; + } p_total = pmm_get_total_pages(); p_free = pmm_get_free_pages(); diff --git a/task.c b/task.c index e4b00a2..e048210 100644 --- a/task.c +++ b/task.c @@ -69,6 +69,7 @@ void task_init(BootInfo *Boot) for (i = 0; i < TASK_MAX; i++) { tasks[i].state = TASK_STATE_FREE; tasks[i].pid = 0; + tasks[i].privilege = TASK_PRIV_USER; tasks[i].saved_rsp = 0; tasks[i].stack_base = 0; tasks[i].stack_pages = 0; @@ -84,9 +85,10 @@ void task_init(BootInfo *Boot) * allocate one. Its saved_rsp will be filled in during the * first context_switch call in task_yield(). */ - tasks[0].pid = next_pid++; - tasks[0].state = TASK_STATE_RUNNING; - tasks[0].switches = 1; + tasks[0].pid = next_pid++; + tasks[0].state = TASK_STATE_RUNNING; + tasks[0].privilege = TASK_PRIV_KERNEL; + tasks[0].switches = 1; wstrcpy16(tasks[0].name, L"core", TASK_NAME_LEN); current_task = &tasks[0]; @@ -119,7 +121,10 @@ static void task_trampoline(void) * Create a new task * ---------------------------------------------------------------- */ -Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) +Task *task_create_with_priv(const CHAR16 *name, + TaskEntryFn entry, + void *arg, + TaskPrivilege privilege) { Task *t = NULL; UINTN i; @@ -130,6 +135,14 @@ Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) return NULL; } + /* Subsystem-level privilege enforcement: prevent privilege escalation. */ + { + Task *caller = task_current(); + if (caller != NULL && privilege > task_get_privilege(caller)) { + return NULL; + } + } + /* Find a free PCB slot */ for (i = 0; i < TASK_MAX; i++) { if (tasks[i].state == TASK_STATE_FREE) { @@ -150,6 +163,7 @@ Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) /* Fill in the PCB */ t->pid = next_pid++; t->state = TASK_STATE_READY; + t->privilege = privilege; t->entry = entry; t->arg = arg; t->switches = 0; @@ -196,6 +210,15 @@ Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) return t; } +Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg) +{ + /* Inherit privilege from the calling task (kernel if no task context). */ + Task *caller = task_current(); + TaskPrivilege priv = (caller != NULL) ? task_get_privilege(caller) + : TASK_PRIV_KERNEL; + return task_create_with_priv(name, entry, arg, priv); +} + /* ---------------------------------------------------------------- * Schedule – pick the next READY task (round-robin) * ---------------------------------------------------------------- */ @@ -318,6 +341,22 @@ Task *task_current(void) return current_task; } +TaskPrivilege task_get_privilege(Task *t) +{ + if (t == NULL) { + return TASK_PRIV_KERNEL; + } + return t->privilege; +} + +void task_set_privilege(Task *t, TaskPrivilege privilege) +{ + if (t == NULL) { + return; + } + t->privilege = privilege; +} + UINTN task_count(void) { UINTN i, count = 0; @@ -366,19 +405,28 @@ static const CHAR16 *state_str(TaskState s) void task_print_list(BootInfo *Boot) { UINTN i; + Task *caller; + + /* Subsystem-level privilege enforcement: task list requires DRIVER. */ + caller = task_current(); + if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_DRIVER) { + SAFE_PRINT(Boot, L"Permission denied: task list requires driver privilege.\n\r"); + return; + } SAFE_PRINT(Boot, L"\n\r"); - SAFE_PRINT(Boot, L" PID STATE SWITCHES NAME\n\r"); - SAFE_PRINT(Boot, L" --- ---------- -------- ----\n\r"); + SAFE_PRINT(Boot, L" PID STATE PRIV SWITCHES NAME\n\r"); + SAFE_PRINT(Boot, L" --- ---------- ---- -------- ----\n\r"); for (i = 0; i < TASK_MAX; i++) { if (tasks[i].state == TASK_STATE_FREE) { continue; } - SAFE_PRINT(Boot, L" %3d %-10s %8d %s\n\r", + SAFE_PRINT(Boot, L" %3d %-10s %4d %8d %s\n\r", tasks[i].pid, state_str(tasks[i].state), + (INT32)tasks[i].privilege, tasks[i].switches, tasks[i].name); } diff --git a/task.h b/task.h index 0676cf2..0445012 100644 --- a/task.h +++ b/task.h @@ -27,11 +27,28 @@ typedef enum { TASK_STATE_FREE = 0, /* PCB slot is unused */ - TASK_STATE_READY, /* runnable, waiting to be scheduled */ - TASK_STATE_RUNNING, /* currently executing on the CPU */ - TASK_STATE_TERMINATED /* finished; slot will be recycled */ + TASK_STATE_READY, /* runnable, waiting to be scheduled */ + TASK_STATE_RUNNING, /* currently executing on the CPU */ + TASK_STATE_TERMINATED /* finished; slot will be recycled */ } TaskState; +/* + * Logical privilege level for a task. + * + * Higher numeric values are more privileged: + * USER (0) – least privileged + * DRIVER (1) – mid-level, can talk to hardware-facing subsystems + * KERNEL (2) – most privileged, core kernel and management threads + * + * All tasks still execute in ring 0 today; this is a software + * hierarchy used for access control decisions and future paging work. + */ +typedef enum { + TASK_PRIV_USER = 0, + TASK_PRIV_DRIVER = 1, + TASK_PRIV_KERNEL = 2, +} TaskPrivilege; + /* ================================================================ * Task entry function * ================================================================ */ @@ -44,9 +61,10 @@ typedef void (*TaskEntryFn)(void *arg); * ================================================================ */ typedef struct Task { - UINT32 pid; /* unique process ID */ - TaskState state; /* current lifecycle state */ - CHAR16 name[TASK_NAME_LEN]; /* human-readable label */ + UINT32 pid; /* unique process ID */ + TaskState state; /* current lifecycle state */ + TaskPrivilege privilege; /* logical privilege level */ + CHAR16 name[TASK_NAME_LEN]; /* human-readable label */ /* Context switch state */ UINT64 saved_rsp; /* RSP saved by context_switch() */ @@ -66,11 +84,21 @@ typedef struct Task { * ================================================================ */ void task_init(BootInfo *Boot); /* initialise scheduler */ -Task *task_create(const CHAR16 *name, /* spawn a new task */ + +/* Spawn a new task with a specific privilege level. */ +Task *task_create_with_priv(const CHAR16 *name, + TaskEntryFn entry, + void *arg, + TaskPrivilege privilege); + +/* Backwards-compatible helper: creates a kernel-privileged task. */ +Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg); void task_yield(void); /* voluntarily give up the CPU */ void task_exit(void); /* terminate the current task */ Task *task_current(void); /* return the running task's PCB */ +TaskPrivilege task_get_privilege(Task *t); +void task_set_privilege(Task *t, TaskPrivilege privilege); UINTN task_count(void); /* number of non-FREE tasks */ void task_print_list(BootInfo *Boot); /* print task table (for `ps`) */