diff --git a/arch/kcpu/docs/design.md b/arch/kcpu/docs/design.md index d1312ae4043375ed2823bb3909e9bcef2c54e74f..b645e778c7cc47d4fcbfe2bc439d219449a48dbc 100644 --- a/arch/kcpu/docs/design.md +++ b/arch/kcpu/docs/design.md @@ -135,6 +135,14 @@ TaskContext (每架构定义,callee-saved 寄存器用于上下文切换 每个架构在该函数中完成异常表排序、硬件描述符表加载、trap 向量基址设置。 - **trap handler**(如 `x86_trap_handler`):运行在中断关闭上下文中, 不可睡眠或阻塞。由汇编入口直接调用,调用时栈上已保存完整 trap frame。 +- **riscv64 `gp` 不变量**:`gp`(x3)是每 CPU 的 percpu 基址,启动时由 + `init_percpu_reg` 设置一次,S-mode trap 期间恒定,故**不**由 + `PUSH_POP_GENERAL_REGS` 保存/恢复——绝不放入可迁移的每任务 trapframe。 + U-mode 用户 `gp` 经 slot-3(`UserContext.regs.gp`)中转:`.Lexit_user` + 入口保存,`.Ltrap_return` 仅在返回 U-mode(SPP==0)时恢复;S-mode 返回 + 保持本 hart 的 `gp`。原因:handler 若在 trap 途中阻塞(如 page-fault + 后端),任务会在 PUSH 与 POP 之间迁移到另一 hart,此时从 trapframe 恢复 + 旧 hart 的基址会污染 `current()`/`this_cpu_id()`。 - **`TaskContext::switch_to()`**:必须在中断关闭上下文调用。 涉及页表切换、TLS 更新和 FP 状态保存/恢复。 - **`UserContext::run()`**:禁用本地 IRQ 后进入用户态,返回后重新启用。 diff --git a/arch/kcpu/docs/security.md b/arch/kcpu/docs/security.md index 20546253f4302726a02537118c56b5bb090dcd5c..a2aad29535f52148a00bc349a244c3b4b52f788f 100644 --- a/arch/kcpu/docs/security.md +++ b/arch/kcpu/docs/security.md @@ -46,6 +46,12 @@ - **用户态寄存器状态**:`UserContext::run()` 切换到用户态后,用户可控制 所有通用寄存器内容。trap handler 需正确处理任意寄存器值。 +- **riscv64 `gp`(percpu 基址)**:`gp` 是每 CPU 不变量,绝不从每任务 + trapframe 恢复。trap 途中任务可能因 handler 阻塞而迁移到其他 hart;若从 + 可迁移的 trapframe 恢复 `gp`,会把另一 hart 的 percpu 基址装入本 hart, + 使 `current()`/`this_cpu_id()` 指向错误的 CPU 与任务,构成跨任务指针泄漏。 + 当前实现:S-mode trap 返回不触碰 `gp`;仅 U-mode 返回从 slot-3 恢复用户 + `gp`。 - **中断/异常输入**:硬件中断和异常向量触发 trap 入口。IRQ 编号来自硬件, 不完全可信。 diff --git a/arch/kcpu/src/riscv/excp.S b/arch/kcpu/src/riscv/excp.S index 7e9001f9de7b98a7edc327ff6e0164c71e0653a0..c5cb32f84be5c3637ef5957e813d50a54d7579c8 100644 --- a/arch/kcpu/src/riscv/excp.S +++ b/arch/kcpu/src/riscv/excp.S @@ -33,6 +33,14 @@ trap_vector_base: j riscv_trap_handler .Lexit_user: + // Trapped from U-mode: gp currently holds the USER's gp. The kernel + // percpu `gp` (per-hart, never carried in a trapframe) is restored from + // the callee frame at slot-13 below. Stash the user gp into the + // trapframe's slot-3 (UserContext.regs.gp) here -- before `LDR sp, sp, 0` + // switches `sp` to the callee frame -- so `.Ltrap_return` can reload it on + // return to U-mode. PUSH_POP_GENERAL_REGS no longer touches gp, so this is + // the sole write of slot-3 on the U-mode entry path. + STR gp, sp, 3 LDR sp, sp, 0 LDR s0, sp, 0 LDR s1, sp, 1 @@ -80,7 +88,15 @@ enter_user: LDR t1, sp, 33 csrw sepc, t0 csrw sstatus, t1 - + // `gp` is this hart's per-CPU base -- per-hart-invariant, so it is never + // restored from the (per-task, migratable) trapframe. For an S-mode trap + // return (SPP==1) leave `gp` as this hart's own base. For a U-mode return + // (SPP==0) restore the user's gp from slot-3 (saved by `.Lexit_user` on + // entry). t0/t1 are caller-saved and restored by POP_GENERAL_REGS below. + andi t0, t1, 1 << 8 // SPP (from saved sstatus in t1) + bnez t0, .Lgp_skip // S-mode: keep this hart's gp + LDR gp, sp, 3 // U-mode: restore user gp +.Lgp_skip: POP_GENERAL_REGS LDR sp, sp, 2 // restore sp diff --git a/arch/kcpu/src/riscv/macros.rs b/arch/kcpu/src/riscv/macros.rs index 7e19d59eb533a5a9e42933fe9f362d6f7b26fe50..bce1046102b76f91ec45865dec49e39c29cc5f29 100644 --- a/arch/kcpu/src/riscv/macros.rs +++ b/arch/kcpu/src/riscv/macros.rs @@ -168,7 +168,16 @@ macro_rules! include_asm_macros { .macro PUSH_POP_GENERAL_REGS, op \op ra, sp, 1 - \op gp, sp, 3 + // gp (x3, slot-3) is intentionally NOT saved/restored here. + // On RISC-V `gp` holds this hart's per-CPU base (percpu + // crate); it is per-hart-invariant and set once at boot, so it + // must never be stored in a per-task trapframe that can + // migrate between harts between PUSH and POP -- doing so + // restored a stale foreign base into `gp` and corrupted + // `current()`. slot-3 is instead used to carry the *user* gp + // across a U-mode trap: written by `.Lexit_user` on entry and + // loaded by `.Ltrap_return` on U-mode return only. An S-mode + // trap return leaves `gp` untouched. \op tp, sp, 4 \op t0, sp, 5 \op t1, sp, 6 diff --git a/arch/khal/src/percpu.rs b/arch/khal/src/percpu.rs index 588cf195b37461402513de33564ebd00a7f23aba..80bcff97457dd6cc5b108e4badaf36b9a361b139 100644 --- a/arch/khal/src/percpu.rs +++ b/arch/khal/src/percpu.rs @@ -33,7 +33,7 @@ pub fn current_task_ptr() -> *const T { unsafe { // on RISC-V and LA64, reading `CURRENT_TASK_PTR` requires multiple instruction, so we disable local IRQs. let _guard = kspin::IrqSave::new(); - CURRENT_TASK_PTR.read_current_raw() as _ + CURRENT_TASK_PTR.read_current_raw() as *const T } } diff --git a/platforms/riscv64-qemu-virt/defconfig b/platforms/riscv64-qemu-virt/defconfig index 50495cee079cd36d80dbb4e92b4a9e976e1f9b40..92afd06da2a79fec2e9023913309d781336dfb94 100644 --- a/platforms/riscv64-qemu-virt/defconfig +++ b/platforms/riscv64-qemu-virt/defconfig @@ -9,7 +9,7 @@ ARCH_RISCV64=y BOOT_CONSOLE_ADDR=0x10000000 BOOT_STACK_SIZE=0x40000 # BUILD_TYPE_DEBUG is not set -NR_CPUS=1 +NR_CPUS=4 KERNEL_STACK_SIZE=0x40000 # KFEAT_ALLOC_BUDDY is not set # KFEAT_ALLOC_TLSF is not set