Skip to content

Conversation

@HeatCrab
Copy link
Collaborator

@HeatCrab HeatCrab commented Dec 13, 2025

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 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, the swap provides the kernel stack while preserving the user stack pointer in mscratch. The interrupt frame structure is extended to 36 words with frame[33] dedicated to stack pointer storage. Task initialization configures mscratch appropriately during the first dispatch by checking the MPP field in mstatus.

[umode] Phase 1: Testing Kernel Stack Isolation

[umode] Test 1a: sys_tid() with normal SP
[umode] PASS: sys_tid() returned 2

[umode] Test 1b: sys_tid() with malicious SP
[umode] PASS: sys_tid() succeeded, ISR correctly used kernel stack

[umode] Test 1c: sys_uptime() with normal SP
[umode] PASS: sys_uptime() returned 4

[umode] Phase 1 Complete: Kernel stack isolation validated

[umode] ========================================

[umode] Phase 2: Testing Security Isolation

[umode] Action: Attempting to read 'mstatus' CSR from U-mode.
[umode] Expect: Kernel Panic with 'Illegal instruction'.

[EXCEPTION] Illegal instruction epc=0x800002F0

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 from mscratch rather 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

Copy link

@cubic-dev-ai cubic-dev-ai bot left a 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

@HeatCrab HeatCrab marked this pull request as draft December 13, 2025 13:28
@HeatCrab HeatCrab force-pushed the u-mode/basic-support branch 2 times, most recently from 9801050 to afcfd14 Compare December 13, 2025 14:15
@HeatCrab HeatCrab marked this pull request as ready for review December 13, 2025 14:21
Copy link

@cubic-dev-ai cubic-dev-ai bot left a 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 &quot;used inline&quot; 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

@HeatCrab HeatCrab force-pushed the u-mode/basic-support branch from afcfd14 to fe3574c Compare December 13, 2025 14:42
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;
Copy link
Collaborator

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?

Copy link
Collaborator Author

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);
Copy link
Collaborator

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?

Copy link
Collaborator Author

@HeatCrab HeatCrab Dec 18, 2025

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));
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants