-
Notifications
You must be signed in to change notification settings - Fork 29
Implement kernel stack isolation for U-mode tasks #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No issues found across 5 files
9801050 to
afcfd14
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 issue found across 7 files
Prompt for AI agents (all 1 issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="arch/riscv/boot.c">
<violation number="1" location="arch/riscv/boot.c:333">
P3: Comment claims `ISR_CONTEXT_SIZE` is "used inline" but the macro is not actually used - the value `144` is hardcoded. Consider either using the macro via extended asm operands (as the original code did) or updating the comment to reflect the actual implementation.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
afcfd14 to
fe3574c
Compare
arch/riscv/hal.c
Outdated
| * This value is used by the ISR epilogue to restore mscratch | ||
| * when returning to U-mode tasks. _stack is the kernel stack base. | ||
| */ | ||
| _kernel_stack_ptr = (uint32_t) &_stack; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where in the code is _kernel_stack_ptr actually used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After checking the code, the _kernel_stack_ptr variable was intended to mirror this value but is not actually read by any consumer. I've removed it to eliminate the dead code. Thanks !
| /* Output directly character by character. U-mode tasks need strict | ||
| * ordering, which the async logger queue cannot guarantee. */ | ||
| for (const char *p = str; *p; p++) | ||
| _putchar(*p); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, if an interrupt occurs in the middle of the loop, would this cause output interleaving between kernel space and user space, or between two different user-space tasks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. To address this, I have wrapped the output loop within a critical section.
However, while tracing the relevant I/O code, I also noticed similar unprotected output loops in other parts of the codebase. To keep this PR focused on its primary objective, I decided to only fix the specific instance you pointed out. I‘ll open a separate issue to address the similar problems in the rest of the code.
app/umode.c
Outdated
| asm volatile( | ||
| "mv %0, sp \n" | ||
| "li sp, 0xDEADBEEF \n" | ||
| : "=r"(saved_sp)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should consider encapsulating this logic into a user library helper in the future. This would prevent code duplication and eliminate the risk of compiler reordering breaking the inline assembly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Inspired by the Linux kernel's __switch_to mechanism, I've refactored the code by introducing a helper function __switch_sp(uint32_t new_sp) in arch/riscv/entry.c using __attribute__((naked)).As the function signature suggests, it is designed to accept the new stack pointer as an argument rather than relying on hardcoded values. This makes it a reusable utility for any future tests that need to validate different stack scenarios.
The test case in app/umode.c now cleanly calls this helper instead of using raw inline assembly, ensuring the compiler does not generate any prologue/epilogue code or reorder instructions that could interfere with the swap.
User mode tasks require kernel stack isolation to prevent malicious or corrupted user stack pointers from compromising kernel memory during interrupt handling. Without this protection, a user task could set its stack pointer to an invalid or controlled address, causing the ISR to write trap frames to arbitrary memory locations. This commit implements stack isolation using the mscratch register as a discriminator between machine mode and user mode execution contexts. The ISR entry performs a blind swap with mscratch: for machine mode tasks (mscratch=0), the swap is immediately undone to restore the kernel stack pointer. For user mode tasks (mscratch=kernel_stack), the swap provides the kernel stack while preserving the user stack pointer in mscratch. The interrupt frame structure is extended to include dedicated storage for the stack pointer. Task initialization zeroes the entire frame and correctly sets the initial stack pointer to support the new restoration path. Enumeration constants replace magic number usage for improved code clarity and consistency. The ISR implementation now includes separate entry and restoration paths for each privilege mode. The M-mode path maintains mscratch=0 throughout execution. The U-mode path saves the user stack pointer from mscratch immediately after frame allocation and restores mscratch to the kernel stack address before returning to user mode. Task initialization was updated to configure mscratch appropriately during the first dispatch. The dispatcher checks the current privilege level and sets mscratch to zero for machine mode tasks or to the kernel stack base for user mode tasks. The user mode output system call was modified to bypass the asynchronous logger queue and implement task-level synchronization. Direct output ensures strict FIFO ordering for test output clarity, while preventing task preemption during character transmission avoids interleaving when multiple user tasks print concurrently. This ensures each string is output atomically with respect to other tasks. A test helper function was added to support stack pointer manipulation during validation. Following the Linux kernel's __switch_to pattern for context switching, this provides precise control over stack operations without compiler interference. The validation harness uses this to verify syscall stability under corrupted stack pointer conditions. Documentation has been updated to reflect the new interrupt frame layout and initialization logic. Testing validates that system calls succeed even when invoked with a malicious stack pointer (0xDEADBEEF), confirming the ISR correctly uses the kernel stack from mscratch rather than the user-controlled stack pointer.
fe3574c to
e31f722
Compare
User mode tasks require kernel stack isolation to prevent malicious or corrupted user stack pointers from compromising kernel memory during interrupt handling. Without this protection, a user task could set its stack pointer to an invalid or controlled address, causing the ISR to write trap frames to arbitrary memory locations.
Stack isolation is implemented by using the
mscratchregister as a discriminator between machine mode and user mode execution contexts. The ISR entry performs a blind swap withmscratch: for machine mode tasks (mscratch=0), the swap is immediately undone to restore the kernel stack pointer. For user mode tasks, the swap provides the kernel stack while preserving the user stack pointer inmscratch. The interrupt frame structure is extended to 36 words withframe[33]dedicated to stack pointer storage. Task initialization configuresmscratchappropriately during the first dispatch by checking theMPPfield in mstatus.Test code and the outputs validates that system calls succeed even when invoked with a malicious stack pointer (
0xDEADBEEF), confirming the ISR correctly uses the kernel stack frommscratchrather than the user-controlled stack pointer. All existing tests continue to pass, demonstrating that the isolation mechanism does not affect machine mode task execution.Related to #53