Skip to content

Commit 869c914

Browse files
authored
Merge pull request #62 from HeatCrab/u-mode/basic-support
Implement kernel stack isolation for U-mode tasks
2 parents f063a38 + a41d543 commit 869c914

File tree

11 files changed

+525
-140
lines changed

11 files changed

+525
-140
lines changed

Documentation/hal-calling-convention.md

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,14 @@ void hal_context_restore(jmp_buf env, int32_t val); /* Restore context + process
109109
The ISR in `boot.c` performs a complete context save of all registers:
110110

111111
```
112-
Stack Frame Layout (144 bytes, 33 words × 4 bytes, offsets from sp):
112+
Stack Frame Layout (144 bytes, 36 words × 4 bytes, offsets from sp):
113113
0: ra, 4: gp, 8: tp, 12: t0, 16: t1, 20: t2
114114
24: s0, 28: s1, 32: a0, 36: a1, 40: a2, 44: a3
115115
48: a4, 52: a5, 56: a6, 60: a7, 64: s2, 68: s3
116116
72: s4, 76: s5, 80: s6, 84: s7, 88: s8, 92: s9
117117
96: s10, 100:s11, 104:t3, 108: t4, 112: t5, 116: t6
118-
120: mcause, 124: mepc, 128: mstatus
119-
132-143: padding (12 bytes for 16-byte alignment)
118+
120: mcause, 124: mepc, 128: mstatus, 132: sp (for restore)
119+
136-143: padding (8 bytes for 16-byte alignment)
120120
```
121121

122122
Why full context save in ISR?
@@ -127,12 +127,14 @@ Why full context save in ISR?
127127

128128
### ISR Stack Requirements
129129

130-
Each task stack must reserve space for the ISR frame:
130+
Each task requires space for the ISR frame:
131131
```c
132-
#define ISR_STACK_FRAME_SIZE 144 /* 33 words × 4 bytes, 16-byte aligned */
132+
#define ISR_STACK_FRAME_SIZE 144 /* 36 words × 4 bytes, 16-byte aligned */
133133
```
134134
135-
This "red zone" is reserved at the top of every task stack to guarantee ISR safety.
135+
**M-mode tasks**: This "red zone" is reserved at the top of the task stack to guarantee ISR safety.
136+
137+
**U-mode tasks**: The ISR frame is allocated on the per-task kernel stack (1024 bytes), not on the user stack. This provides stack isolation and prevents user tasks from corrupting kernel trap handling state.
136138
137139
## Function Calling in Linmo
138140
@@ -200,7 +202,9 @@ void task_function(void) {
200202
201203
### Stack Layout
202204
203-
Each task has its own stack with this layout:
205+
#### Machine Mode Tasks
206+
207+
Each M-mode task has its own stack with this layout:
204208
205209
```
206210
High Address
@@ -216,6 +220,43 @@ High Address
216220
Low Address
217221
```
218222
223+
#### User Mode Tasks (Per-Task Kernel Stack)
224+
225+
U-mode tasks maintain separate user and kernel stacks for isolation:
226+
227+
**User Stack** (application execution):
228+
```
229+
High Address
230+
+------------------+ <- user_stack_base + user_stack_size
231+
| |
232+
| User Stack | <- Grows downward
233+
| (Dynamic) | <- Task executes here in U-mode
234+
| |
235+
+------------------+ <- user_stack_base
236+
Low Address
237+
```
238+
239+
**Kernel Stack** (trap handling):
240+
```
241+
High Address
242+
+------------------+ <- kernel_stack_base + kernel_stack_size (1024 bytes)
243+
| ISR Frame | <- 144 bytes for trap context
244+
| (144 bytes) | <- Traps switch to this stack
245+
+------------------+
246+
| Trap Handler | <- Kernel code execution during traps
247+
| Stack Space |
248+
+------------------+ <- kernel_stack_base
249+
Low Address
250+
```
251+
252+
When a U-mode task enters a trap (syscall, interrupt, exception):
253+
1. ISR swaps SP with `mscratch` via `csrrw` (mscratch contains kernel stack top)
254+
2. ISR saves full context to kernel stack
255+
3. Trap handler executes on kernel stack
256+
4. Return path restores user SP and switches back
257+
258+
This dual-stack design prevents user tasks from corrupting kernel state and provides strong isolation between privilege levels.
259+
219260
### Stack Alignment
220261
- 16-byte alignment: Required by RISC-V ABI for stack pointer
221262
- 4-byte alignment: Minimum for all memory accesses on RV32I

Documentation/hal-riscv-context-switch.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,26 @@ a complete interrupt service routine frame:
123123
```c
124124
void *hal_build_initial_frame(void *stack_top,
125125
void (*task_entry)(void),
126-
int user_mode)
126+
int user_mode,
127+
void *kernel_stack,
128+
size_t kernel_stack_size)
127129
{
128-
/* Place frame in stack with initial reserve below for proper startup */
129-
uint32_t *frame = (uint32_t *) ((uint8_t *) stack_top - 256 -
130-
ISR_STACK_FRAME_SIZE);
130+
/* For U-mode tasks, build frame on kernel stack for stack isolation.
131+
* For M-mode tasks, build frame on user stack as before.
132+
*/
133+
uint32_t *frame;
134+
if (user_mode && kernel_stack) {
135+
/* U-mode: Place frame on per-task kernel stack */
136+
void *kstack_top = (uint8_t *) kernel_stack + kernel_stack_size;
137+
frame = (uint32_t *) ((uint8_t *) kstack_top - ISR_STACK_FRAME_SIZE);
138+
} else {
139+
/* M-mode: Place frame on user stack with reserve below */
140+
frame = (uint32_t *) ((uint8_t *) stack_top - 256 -
141+
ISR_STACK_FRAME_SIZE);
142+
}
131143
132144
/* Initialize all general purpose registers to zero */
133-
for (int i = 0; i < 32; i++)
145+
for (int i = 0; i < 36; i++)
134146
frame[i] = 0;
135147
136148
/* Compute thread pointer: aligned to 64 bytes from _end */
@@ -152,6 +164,18 @@ void *hal_build_initial_frame(void *stack_top,
152164
/* Set entry point */
153165
frame[FRAME_EPC] = (uint32_t) task_entry;
154166
167+
/* SP value for when ISR returns (stored in frame[33]).
168+
* For U-mode: Set to user stack top.
169+
* For M-mode: Set to frame + ISR_STACK_FRAME_SIZE.
170+
*/
171+
if (user_mode && kernel_stack) {
172+
/* U-mode: frame[33] should contain user SP */
173+
frame[FRAME_SP] = (uint32_t) ((uint8_t *) stack_top - 256);
174+
} else {
175+
/* M-mode: frame[33] contains kernel SP after frame deallocation */
176+
frame[FRAME_SP] = (uint32_t) ((uint8_t *) frame + ISR_STACK_FRAME_SIZE);
177+
}
178+
155179
return frame; /* Return frame base as initial stack pointer */
156180
}
157181
```

app/umode.c

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,84 @@
11
#include <linmo.h>
22

3-
/* U-mode Validation Task
3+
/* Architecture-specific helper for SP manipulation testing.
4+
* Implemented in arch/riscv/entry.c as a naked function.
5+
*/
6+
extern uint32_t __switch_sp(uint32_t new_sp);
7+
8+
/* U-mode validation: syscall stability and privilege isolation.
49
*
5-
* Integrates two tests into a single task flow to ensure sequential execution:
6-
* 1. Phase 1: Mechanism Check - Verify syscalls work.
7-
* 2. Phase 2: Security Check - Verify privileged instructions trigger a trap.
10+
* 1. Verify syscalls work under various SP conditions (normal, malicious).
11+
* 2. Verify privileged instructions trap.
812
*/
913
void umode_validation_task(void)
1014
{
11-
/* --- Phase 1: Mechanism Check (Syscalls) --- */
12-
umode_printf("[umode] Phase 1: Testing Syscall Mechanism\n");
15+
/* --- Phase 1: Kernel Stack Isolation Test --- */
16+
umode_printf("Phase 1: Testing Kernel Stack Isolation\n");
17+
umode_printf("\n");
1318

14-
/* Test 1: sys_tid() - Simplest read-only syscall. */
19+
/* Test 1-1: Baseline - Syscall with normal SP */
20+
umode_printf("Test 1-1: sys_tid() with normal SP\n");
1521
int my_tid = sys_tid();
1622
if (my_tid > 0) {
17-
umode_printf("[umode] PASS: sys_tid() returned %d\n", my_tid);
23+
umode_printf("PASS: sys_tid() returned %d\n", my_tid);
1824
} else {
19-
umode_printf("[umode] FAIL: sys_tid() failed (ret=%d)\n", my_tid);
25+
umode_printf("FAIL: sys_tid() failed (ret=%d)\n", my_tid);
2026
}
27+
umode_printf("\n");
28+
29+
/* Test 1-2: Verify ISR uses mscratch, not malicious user SP */
30+
umode_printf("Test 1-2: sys_tid() with malicious SP\n");
2131

22-
/* Test 2: sys_uptime() - Verify value transmission is correct. */
32+
uint32_t saved_sp = __switch_sp(0xDEADBEEF);
33+
int my_tid_bad_sp = sys_tid();
34+
__switch_sp(saved_sp);
35+
36+
if (my_tid_bad_sp > 0) {
37+
umode_printf(
38+
"PASS: sys_tid() succeeded, ISR correctly used kernel "
39+
"stack\n");
40+
} else {
41+
umode_printf("FAIL: Syscall failed with malicious SP (ret=%d)\n",
42+
my_tid_bad_sp);
43+
}
44+
umode_printf("\n");
45+
46+
/* Test 1-3: Verify syscall functionality is still intact */
47+
umode_printf("Test 1-3: sys_uptime() with normal SP\n");
2348
int uptime = sys_uptime();
2449
if (uptime >= 0) {
25-
umode_printf("[umode] PASS: sys_uptime() returned %d\n", uptime);
50+
umode_printf("PASS: sys_uptime() returned %d\n", uptime);
2651
} else {
27-
umode_printf("[umode] FAIL: sys_uptime() failed (ret=%d)\n", uptime);
52+
umode_printf("FAIL: sys_uptime() failed (ret=%d)\n", uptime);
2853
}
54+
umode_printf("\n");
2955

30-
/* Note: Skipping sys_task_spawn for now, as kernel user pointer checks
31-
* might block function pointers in the .text segment, avoiding distraction.
32-
*/
56+
umode_printf("Phase 1 All tests passed.\n");
57+
umode_printf("\n");
3358

3459
/* --- Phase 2: Security Check (Privileged Access) --- */
35-
umode_printf("[umode] ========================================\n");
36-
umode_printf("[umode] Phase 2: Testing Security Isolation\n");
37-
umode_printf(
38-
"[umode] Action: Attempting to read 'mstatus' CSR from U-mode.\n");
39-
umode_printf("[umode] Expect: Kernel Panic with 'Illegal instruction'.\n");
40-
umode_printf("[umode] ========================================\n");
41-
42-
/* CRITICAL: Delay before suicide to ensure logs are flushed from
60+
umode_printf("========================================\n");
61+
umode_printf("\n");
62+
umode_printf("Phase 2: Testing Security Isolation\n");
63+
umode_printf("\n");
64+
umode_printf("Action: Attempting to read 'mstatus' CSR from U-mode.\n");
65+
umode_printf("Expect: Kernel Panic with 'Illegal instruction'.\n");
66+
umode_printf("\n");
67+
/* Delay before suicide to ensure logs are flushed from
4368
* buffer to UART.
4469
*/
4570
sys_tdelay(10);
4671

4772
/* Privileged Instruction Trigger */
73+
umode_printf("Result: \n");
4874
uint32_t mstatus;
4975
asm volatile("csrr %0, mstatus" : "=r"(mstatus));
5076

5177
/* If execution reaches here, U-mode isolation failed (still has
5278
* privileges).
5379
*/
54-
umode_printf(
55-
"[umode] FAIL: Privileged instruction executed! (mstatus=0x%lx)\n",
56-
(long) mstatus);
80+
umode_printf("FAIL: Privileged instruction executed! (mstatus=0x%lx)\n",
81+
(long) mstatus);
5782

5883
/* Spin loop to prevent further execution. */
5984
while (1)
@@ -62,7 +87,7 @@ void umode_validation_task(void)
6287

6388
int32_t app_main(void)
6489
{
65-
umode_printf("[Kernel] Spawning U-mode validation task...\n");
90+
umode_printf("Spawning U-mode validation task...\n");
6691

6792
/* app_main now runs in U-mode by default.
6893
* mo_task_spawn routes to sys_task_spawn syscall for U-mode apps,

0 commit comments

Comments
 (0)