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.

@HeatCrab HeatCrab force-pushed the u-mode/basic-support branch from fe3574c to e31f722 Compare December 18, 2025 09:11
@HeatCrab HeatCrab force-pushed the u-mode/basic-support branch 2 times, most recently from 1930417 to a903be2 Compare December 27, 2025 09:08
@HeatCrab HeatCrab force-pushed the u-mode/basic-support branch 2 times, most recently from 98ec3ef to 6411a74 Compare January 4, 2026 09:44
@HeatCrab
Copy link
Collaborator Author

HeatCrab commented Jan 4, 2026

This PR now implements per-task kernel stack allocation for U-mode tasks. Previously, all U-mode tasks shared a single global kernel stack ( _stack ), which caused trap frame corruption when multiple U-mode tasks were scheduled concurrently.

Regarding validation, as umode.c had verified that syscalls work correctly with corrupted user SP, confirming the ISR correctly uses the kernel stack from mscratch. The multi-task isolation scenario will be validated by the PMP test suite in PR #32, which depends on this infrastructure.

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.

Each user mode task is allocated a dedicated 512-byte kernel stack to
ensure complete isolation between tasks and prevent stack overflow
attacks. The task control block is extended to track per-task kernel
stack allocations. A global pointer references the current task's kernel
stack and is updated during each context switch. The ISR loads this
pointer to access the appropriate per-task kernel stack through
mscratch, replacing the previous approach of using a single global
kernel stack shared by all user mode tasks.

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. For user mode tasks, the initial ISR frame is constructed on the
kernel stack rather than the user stack, ensuring the frame is protected
from user manipulation. 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 current
task's kernel stack address before returning to user mode, enabling the
next trap to use the correct per-task kernel stack.

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
per-task kernel stack base for user mode tasks. The main scheduler
initialization ensures the first task's kernel stack pointer is set
before entering the scheduling loop.

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 context switching
pattern, 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 updates include the calling convention guide's stack layout
section, which now distinguishes between machine mode and user mode task
stack organization with detailed diagrams of the dual-stack design. The
context switching guide's task initialization section reflects the
updated function signature for building initial interrupt frames with
per-task kernel stack parameters.

Testing validates that system calls succeed even when invoked with a
malicious stack pointer (0xDEADBEEF), confirming the ISR correctly uses
the per-task kernel stack from mscratch rather than the user-controlled
stack pointer.
@HeatCrab HeatCrab force-pushed the u-mode/basic-support branch from 6411a74 to 28dcddd Compare January 4, 2026 09:58
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