From 8f22b81fc57be2cc6d895e649513ec1349a79eb4 Mon Sep 17 00:00:00 2001 From: WitcherTheWhite <1240749052@qq.com> Date: Thu, 9 Jul 2026 18:00:29 +0800 Subject: [PATCH 1/5] feat(kuaccess): add atomic user u32 cmpxchg for futex paths --- arch/kcpu/docs/security.md | 2 +- arch/kcpu/src/aarch64/atomic_user.S | 49 ++++++++++ arch/kcpu/src/aarch64/instrs.rs | 17 ++++ arch/kcpu/src/loongarch64/atomic_user.S | 41 ++++++++ arch/kcpu/src/loongarch64/instrs.rs | 17 ++++ arch/kcpu/src/riscv/atomic_user.S | 37 ++++++++ arch/kcpu/src/riscv/instrs.rs | 17 ++++ arch/kcpu/src/x86_64/atomic_user.S | 40 ++++++++ arch/kcpu/src/x86_64/instrs.rs | 17 ++++ core/ksyscall/src/sync/futex.rs | 12 ++- core/kuaccess/Cargo.toml | 1 + core/kuaccess/docs/design.md | 19 +++- core/kuaccess/docs/security.md | 8 +- core/kuaccess/src/lib.rs | 119 +++++++++++++++++++++++- posix/process/Cargo.toml | 1 + posix/process/src/runtime.rs | 20 ++++ 16 files changed, 404 insertions(+), 13 deletions(-) create mode 100644 arch/kcpu/src/aarch64/atomic_user.S create mode 100644 arch/kcpu/src/loongarch64/atomic_user.S create mode 100644 arch/kcpu/src/riscv/atomic_user.S create mode 100644 arch/kcpu/src/x86_64/atomic_user.S diff --git a/arch/kcpu/docs/security.md b/arch/kcpu/docs/security.md index a2aad2953..4a024572d 100644 --- a/arch/kcpu/docs/security.md +++ b/arch/kcpu/docs/security.md @@ -359,7 +359,7 @@ $f0–$f31 的寄存器编号。 - [ ] 修改 GDT 段选择子常量后验证 `UserContext::new` 中的 `cs`/`ss` 值。 - [ ] 新增异常向量处理时检查是否需要更新异常表或注册新 handler。 - [ ] LoongArch64 新增指令模拟时验证操作码掩码和寄存器编号范围。 -- [ ] 修改 `copy_user.S` 后确认每条访存指令均有对应的 `_asm_extable` 条目。 +- [ ] 修改 `copy_user.S` / `atomic_user.S` 后确认每条访存指令均有对应的 `_asm_extable` 条目。 - [ ] 新增 per-CPU 变量时验证初始化顺序(percpu init → `init_trap`)。 - [ ] 修改页表切换逻辑后验证 TLB 刷新语义是否正确。 - [ ] 修改 `fp-simd` / `tls` feature 门控代码后验证所有架构的一致性。 diff --git a/arch/kcpu/src/aarch64/atomic_user.S b/arch/kcpu/src/aarch64/atomic_user.S new file mode 100644 index 000000000..6ae8d0686 --- /dev/null +++ b/arch/kcpu/src/aarch64/atomic_user.S @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 KylinSoft Co., Ltd. +// See LICENSES for license details. +// +// Atomically compare-exchange a 32-bit word at a (possibly user) address. +// +// Prototype: +// size_t user_atomic_cmpxchg_u32(u32 *addr, u32 old, u32 new, u32 *old_out); +// Args: x0=addr, w1=old, w2=new, x3=old_out +// Returns: 0 if no fault occurred; non-zero if a data abort occurred. +// On success, *old_out receives the value observed at *addr. If that value +// equals `old`, `new` was stored. + +.macro _asm_extable, from, to + .pushsection __ex_table, "a" + .balign 8 + .quad \from + .quad \to + .popsection +.endm + +.section .text +.global user_atomic_cmpxchg_u32 +.type user_atomic_cmpxchg_u32, @function + +user_atomic_cmpxchg_u32: +.Lretry: +1: ldxr w4, [x0] + // old_out is a kernel pointer; no exception table needed. + str w4, [x3] + cmp w4, w1 + b.ne .Ldone +2: stxr w5, w2, [x0] + cbnz w5, .Lretry + dmb ish +.Ldone: + clrex + mov x0, #0 + ret + +.Lfault: + clrex + mov x0, #1 + ret + + _asm_extable 1b, .Lfault + _asm_extable 2b, .Lfault + +.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32 diff --git a/arch/kcpu/src/aarch64/instrs.rs b/arch/kcpu/src/aarch64/instrs.rs index 904501e4b..165e8ec7d 100644 --- a/arch/kcpu/src/aarch64/instrs.rs +++ b/arch/kcpu/src/aarch64/instrs.rs @@ -5,6 +5,7 @@ //! Wrapper functions for assembly instructions. core::arch::global_asm!(include_str!("copy_user.S")); +core::arch::global_asm!(include_str!("atomic_user.S")); unsafe extern "C" { /// Copies data from source to destination, where addresses may be in user @@ -17,6 +18,22 @@ unsafe extern "C" { /// Returns the number of bytes not copied. This means 0 indicates success, /// while a value > 0 indicates failure. pub fn raw_copy_from_user(dst: *mut u8, src: *const u8, size: usize) -> usize; + + /// Atomically compare-exchanges a 32-bit word at `addr`. + /// + /// On success (return `0`), writes the previous `*addr` value into + /// `old_out`. If that previous value equals `old`, stores `new`. + /// + /// # Safety + /// + /// `addr` must be a 4-byte-aligned address that is currently accessible in + /// the active address space under the user-access window. `old_out` must + /// point to writable kernel memory. + /// + /// # Returns + /// + /// `0` if no fault occurred; non-zero if a data abort occurred. + pub fn user_atomic_cmpxchg_u32(addr: *mut u32, old: u32, new: u32, old_out: *mut u32) -> usize; } /// Alias for compatibility with other architectures diff --git a/arch/kcpu/src/loongarch64/atomic_user.S b/arch/kcpu/src/loongarch64/atomic_user.S new file mode 100644 index 000000000..f89b310d2 --- /dev/null +++ b/arch/kcpu/src/loongarch64/atomic_user.S @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 KylinSoft Co., Ltd. +// See LICENSES for license details. +// +// Atomically compare-exchange a 32-bit word at a (possibly user) address. +// +// Prototype: +// size_t user_atomic_cmpxchg_u32(u32 *addr, u32 old, u32 new, u32 *old_out); +// Args: a0=addr, a1=old, a2=new, a3=old_out +// Returns: 0 if no fault occurred; non-zero if a fault occurred. +// On success, *old_out receives the value observed at *addr. If that value +// equals `old`, `new` was stored. +// +// LoongArch `sc.w rd, rj, si12` overwrites `rd` with 1 on success / 0 on fail, +// so the desired store value must be reloaded into `rd` on each retry. + +.section .text +.global user_atomic_cmpxchg_u32 +.type user_atomic_cmpxchg_u32, @function + +user_atomic_cmpxchg_u32: +.Lretry: +1: ll.w $t0, $a0, 0 + st.w $t0, $a3, 0 + bne $t0, $a1, .Ldone + move $t1, $a2 +2: sc.w $t1, $a0, 0 + beqz $t1, .Lretry + dbar 0 +.Ldone: + move $a0, $zero + jr $ra + +.Lfault: + ori $a0, $zero, 1 + jr $ra + + _asm_extable 1b, .Lfault + _asm_extable 2b, .Lfault + +.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32 diff --git a/arch/kcpu/src/loongarch64/instrs.rs b/arch/kcpu/src/loongarch64/instrs.rs index 5005f16f4..7bba6dc17 100644 --- a/arch/kcpu/src/loongarch64/instrs.rs +++ b/arch/kcpu/src/loongarch64/instrs.rs @@ -5,6 +5,7 @@ //! Wrapper functions for assembly instructions. core::arch::global_asm!(include_asm_macros!(), include_str!("copy_user.S")); +core::arch::global_asm!(include_asm_macros!(), include_str!("atomic_user.S")); unsafe extern "C" { /// Copies data from source to destination, where addresses may be in user @@ -17,4 +18,20 @@ unsafe extern "C" { /// Returns the number of bytes not copied. This means 0 indicates success, /// while a value > 0 indicates failure. pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize; + + /// Atomically compare-exchanges a 32-bit word at `addr`. + /// + /// On success (return `0`), writes the previous `*addr` value into + /// `old_out`. If that previous value equals `old`, stores `new`. + /// + /// # Safety + /// + /// `addr` must be a 4-byte-aligned address that is currently accessible in + /// the active address space under the user-access window. `old_out` must + /// point to writable kernel memory. + /// + /// # Returns + /// + /// `0` if no fault occurred; non-zero if a fault occurred. + pub fn user_atomic_cmpxchg_u32(addr: *mut u32, old: u32, new: u32, old_out: *mut u32) -> usize; } diff --git a/arch/kcpu/src/riscv/atomic_user.S b/arch/kcpu/src/riscv/atomic_user.S new file mode 100644 index 000000000..254ad5238 --- /dev/null +++ b/arch/kcpu/src/riscv/atomic_user.S @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 KylinSoft Co., Ltd. +// See LICENSES for license details. +// +// Atomically compare-exchange a 32-bit word at a (possibly user) address. +// +// Prototype: +// size_t user_atomic_cmpxchg_u32(u32 *addr, u32 old, u32 new, u32 *old_out); +// Args: a0=addr, a1=old, a2=new, a3=old_out +// Returns: 0 if no fault occurred; non-zero if a fault occurred. +// On success, *old_out receives the value observed at *addr. If that value +// equals `old`, `new` was stored. + +.section .text +.global user_atomic_cmpxchg_u32 +.type user_atomic_cmpxchg_u32, @function + +user_atomic_cmpxchg_u32: +.Lretry: +1: lr.w t0, (a0) + sw t0, 0(a3) + bne t0, a1, .Ldone +2: sc.w t1, a2, (a0) + bnez t1, .Lretry + fence rw, rw +.Ldone: + li a0, 0 + ret + +.Lfault: + li a0, 1 + ret + + _asm_extable 1b, .Lfault + _asm_extable 2b, .Lfault + +.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32 diff --git a/arch/kcpu/src/riscv/instrs.rs b/arch/kcpu/src/riscv/instrs.rs index 5005f16f4..7bba6dc17 100644 --- a/arch/kcpu/src/riscv/instrs.rs +++ b/arch/kcpu/src/riscv/instrs.rs @@ -5,6 +5,7 @@ //! Wrapper functions for assembly instructions. core::arch::global_asm!(include_asm_macros!(), include_str!("copy_user.S")); +core::arch::global_asm!(include_asm_macros!(), include_str!("atomic_user.S")); unsafe extern "C" { /// Copies data from source to destination, where addresses may be in user @@ -17,4 +18,20 @@ unsafe extern "C" { /// Returns the number of bytes not copied. This means 0 indicates success, /// while a value > 0 indicates failure. pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize; + + /// Atomically compare-exchanges a 32-bit word at `addr`. + /// + /// On success (return `0`), writes the previous `*addr` value into + /// `old_out`. If that previous value equals `old`, stores `new`. + /// + /// # Safety + /// + /// `addr` must be a 4-byte-aligned address that is currently accessible in + /// the active address space under the user-access window. `old_out` must + /// point to writable kernel memory. + /// + /// # Returns + /// + /// `0` if no fault occurred; non-zero if a fault occurred. + pub fn user_atomic_cmpxchg_u32(addr: *mut u32, old: u32, new: u32, old_out: *mut u32) -> usize; } diff --git a/arch/kcpu/src/x86_64/atomic_user.S b/arch/kcpu/src/x86_64/atomic_user.S new file mode 100644 index 000000000..fb3256172 --- /dev/null +++ b/arch/kcpu/src/x86_64/atomic_user.S @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 KylinSoft Co., Ltd. +// See LICENSES for license details. +// +// Atomically compare-exchange a 32-bit word at a (possibly user) address. +// +// Prototype: +// size_t user_atomic_cmpxchg_u32(u32 *addr, u32 old, u32 new, u32 *old_out); +// Args: rdi=addr, esi=old, edx=new, rcx=old_out +// Returns: 0 if no fault occurred; non-zero if a fault occurred. +// On success, *old_out receives the value observed at *addr. If that value +// equals `old`, `new` was stored. + +.macro _asm_extable, from, to + .pushsection __ex_table, "a" + .balign 8 + .quad \from + .quad \to + .popsection +.endm + +.section .text +.global user_atomic_cmpxchg_u32 +.type user_atomic_cmpxchg_u32, @function + +user_atomic_cmpxchg_u32: + mov eax, esi +1: lock cmpxchg dword ptr [rdi], edx + // After cmpxchg, eax always holds the previous *addr value. + mov dword ptr [rcx], eax + xor eax, eax + ret + +.Lfault: + mov eax, 1 + ret + + _asm_extable 1b, .Lfault + +.size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32 diff --git a/arch/kcpu/src/x86_64/instrs.rs b/arch/kcpu/src/x86_64/instrs.rs index 604c7088e..abab00964 100644 --- a/arch/kcpu/src/x86_64/instrs.rs +++ b/arch/kcpu/src/x86_64/instrs.rs @@ -5,6 +5,7 @@ //! Wrapper functions for assembly instructions. core::arch::global_asm!(include_str!("copy_user.S")); +core::arch::global_asm!(include_str!("atomic_user.S")); unsafe extern "C" { /// Copies data from source to destination, where addresses may be in user @@ -17,4 +18,20 @@ unsafe extern "C" { /// Returns the number of bytes not copied. This means 0 indicates success, /// while a value > 0 indicates failure. pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize; + + /// Atomically compare-exchanges a 32-bit word at `addr`. + /// + /// On success (return `0`), writes the previous `*addr` value into + /// `old_out`. If that previous value equals `old`, stores `new`. + /// + /// # Safety + /// + /// `addr` must be a 4-byte-aligned address that is currently accessible in + /// the active address space under the user-access window. `old_out` must + /// point to writable kernel memory. + /// + /// # Returns + /// + /// `0` if no fault occurred; non-zero if a fault occurred. + pub fn user_atomic_cmpxchg_u32(addr: *mut u32, old: u32, new: u32, old_out: *mut u32) -> usize; } diff --git a/core/ksyscall/src/sync/futex.rs b/core/ksyscall/src/sync/futex.rs index 1bb711a65..d427020c7 100644 --- a/core/ksyscall/src/sync/futex.rs +++ b/core/ksyscall/src/sync/futex.rs @@ -9,6 +9,7 @@ use core::{mem::size_of, sync::atomic::Ordering}; use kerrno::{KError, KResult, LinuxError}; use kfutex::FutexKey; use kprocess::{AsThread, current_futex_key}; +use kuaccess::atomic_u32_eq; use linux_raw_sys::general::{ FUTEX_CMD_MASK, FUTEX_CMP_REQUEUE, FUTEX_PRIVATE_FLAG, FUTEX_REQUEUE, FUTEX_WAIT, FUTEX_WAIT_BITSET, FUTEX_WAKE, FUTEX_WAKE_BITSET, robust_list_head, timespec, @@ -57,9 +58,10 @@ pub fn sys_futex( let futex_table = process.futex_state()?.table_for(&key); let command = futex_op & (FUTEX_CMD_MASK as u32); + let uaddr_usize = uaddr.as_ptr() as usize; match command { FUTEX_WAIT | FUTEX_WAIT_BITSET => { - if uaddr.read_vm()? != value { + if !atomic_u32_eq(uaddr_usize, value)? { return Err(KError::WouldBlock); } @@ -79,9 +81,9 @@ pub fn sys_futex( u32::MAX }; - let wait_result = futex - .wq - .wait_if(bitset, timeout, || uaddr.read_vm() == Ok(value)); + let wait_result = futex.wq.wait_if(bitset, timeout, || { + atomic_u32_eq(uaddr_usize, value).unwrap_or(false) + }); match wait_result { Ok(false) => { return Err(KError::WouldBlock); @@ -118,7 +120,7 @@ pub fn sys_futex( } FUTEX_REQUEUE | FUTEX_CMP_REQUEUE => { validate_non_negative(value)?; - if command == FUTEX_CMP_REQUEUE && uaddr.read_vm()? != value3 { + if command == FUTEX_CMP_REQUEUE && !atomic_u32_eq(uaddr_usize, value3)? { return Err(KError::WouldBlock); } let value2 = validate_non_negative(timeout_or_value2 as u32)?; diff --git a/core/kuaccess/Cargo.toml b/core/kuaccess/Cargo.toml index 40b089e0b..50fc65f19 100644 --- a/core/kuaccess/Cargo.toml +++ b/core/kuaccess/Cargo.toml @@ -21,3 +21,4 @@ memaddr.workspace = true memspace.workspace = true osvm.workspace = true unittest.workspace = true +unittest_support.workspace = true diff --git a/core/kuaccess/docs/design.md b/core/kuaccess/docs/design.md index 5f93c3a5e..ecff9c7b6 100644 --- a/core/kuaccess/docs/design.md +++ b/core/kuaccess/docs/design.md @@ -6,7 +6,8 @@ - 将 `osvm` 的通用虚拟内存访问入口接到当前线程地址空间; - 处理“内核在访问用户地址时允许页错误回填”的 trap 路径; -- 提供少量高频的用户态字符串装载辅助函数。 +- 提供少量高频的用户态字符串装载辅助函数; +- 提供用户态 32-bit 原子 cmpxchg 原语(供 futex 等路径使用)。 调用方包括 syscall 实现、用户线程 runtime,以及依赖 `osvm` 指针包装的其他 crate。 @@ -25,6 +26,7 @@ syscall / runtime kuaccess | \ | \-- vm_load_string* -> osvm load helpers + | \-- atomic_cmpxchg_u32 -> user_atomic_cmpxchg_u32 | \-- VirtMemIo(Vm) -> user_copy + current thread address space ``` @@ -47,9 +49,19 @@ syscall / runtime 5. `kuaccess` 消费 `MmSpace::handle_page_fault()` 返回的 typed fault outcome: `Resolved` / retry-class outcome 让 fault 指令重试;unmapped、permission、bus、 OOM、no-progress 和 generic failure 都返回 false,交给架构 exception-table - fixup 使 `user_copy` 返回失败。 + fixup 使 `user_copy` / `user_atomic_cmpxchg_u32` 返回失败。 6. 访问结束后恢复线程标志,并将失败映射为 `MemError` / `KError`。 +### 用户态原子 cmpxchg + +1. 校验 4 字节对齐与用户地址范围。 +2. 在 `IrqSave` 保护下、于 `access_user_memory()` 窗口内调用架构原语 + `user_atomic_cmpxchg_u32`。 +3. 成功时返回 `(exchanged, observed)`;fault 时由 `__ex_table` fixup 返回非零, + 映射为 `MemError::NoAccess`。 + +该原语供 futex / robust-list 等路径对用户态 futex word 做无 TOCTOU 的更新。 + ### 字符串装载 1. 从用户地址读取字节向量或 NUL 终止字节流。 @@ -58,7 +70,8 @@ syscall / runtime ## 并发模型 -- `Vm` 使用 `IrqSave` 建立访问窗口,避免访问期间的本地中断干扰。 +- `Vm` 与 `atomic_cmpxchg_u32` 使用 `IrqSave` 建立访问窗口,避免本地中断在 + exclusive/atomic 序列期间清除 monitor。 - 不维护全局共享状态;真正的并发控制由线程状态和地址空间锁负责。 ## 设计决策 diff --git a/core/kuaccess/docs/security.md b/core/kuaccess/docs/security.md index 75afd35c4..bb340857d 100644 --- a/core/kuaccess/docs/security.md +++ b/core/kuaccess/docs/security.md @@ -15,15 +15,18 @@ - `user_copy(...)`: 依赖调用前的用户地址范围检查,以及当前线程 `accessing_user_memory` 标志保证 trap handler 只在受控窗口内接管。 +- `user_atomic_cmpxchg_u32(...)`: + 依赖 4 字节对齐检查、用户地址范围检查,以及同一用户访问窗口与 exception-table fixup。 ## 内存安全不变量 - `check_access()` 只允许访问用户空间有效区间。 +- `atomic_cmpxchg_u32()` / `atomic_load_u32()` / `atomic_u32_eq()` 额外要求地址 4 字节对齐。 - `dispatch_irq_page_fault()` 仅在当前线程显式进入用户内存访问窗口时处理 fault。 - `kuaccess` 必须保留 `MmSpace::handle_page_fault()` 的 typed outcome 分类: 只有 `Resolved` 和 retry-class outcome 可以转换为 trap handled;unmapped、 permission denied、bus error、OOM、no-progress 和 generic failure 必须转换为 - user-copy failure,而不是继续重试。 + user-copy / atomic-user 失败,而不是继续重试。 - 字符串 helper 只在成功读取完整字节流后再做 UTF-8 解释。 ## 线程安全 @@ -52,4 +55,5 @@ ## 已知限制 -- 当前 helper 只覆盖字符串装载,复杂结构体解析仍由各调用方自行组织。 +- 当前 helper 只覆盖字符串装载与 `u32` 原子 cmpxchg;复杂结构体解析仍由各调用方自行组织。 +- 原子原语目前仅提供 32-bit cmpxchg;更大宽度或其它 RMW 操作按需再加。 diff --git a/core/kuaccess/src/lib.rs b/core/kuaccess/src/lib.rs index f2cc9a642..b3dc35a67 100644 --- a/core/kuaccess/src/lib.rs +++ b/core/kuaccess/src/lib.rs @@ -17,7 +17,7 @@ use extern_trait::extern_trait; use kaddr_layout::{USER_SPACE_BASE, USER_SPACE_SIZE}; use kerrno::{KError, KResult}; use khal::{ - asm::user_copy, + asm::{user_atomic_cmpxchg_u32, user_copy}, paging::MappingFlags, trap::{PAGE_FAULT, register_trap_handler}, }; @@ -102,6 +102,61 @@ pub fn vm_load_string_with_len(ptr: *const c_char, len: usize) -> KResult MemResult { + let (_, observed) = atomic_cmpxchg_u32(addr, 0, 0)?; + Ok(observed) +} + +/// Atomically tests whether `*addr == expected` without changing the word. +pub fn atomic_u32_eq(addr: usize, expected: u32) -> MemResult { + let (exchanged, _) = atomic_cmpxchg_u32(addr, expected, expected)?; + Ok(exchanged) +} + +/// Atomically compare-exchanges a 32-bit word in user memory. +/// +/// Returns `Ok((true, old))` when `*addr == old` and the store of `new` +/// succeeded. Returns `Ok((false, observed))` when the word differed from +/// `old` (no store). Returns `Err` on misalignment, out-of-range address, or +/// page fault that could not be resolved inside the user-access window. +/// +/// This is the primitive futex / robust-list paths need for race-free updates +/// of userspace futex words. +pub fn atomic_cmpxchg_u32(addr: usize, old: u32, new: u32) -> MemResult<(bool, u32)> { + if !addr.is_multiple_of(core::mem::align_of::()) { + return Err(MemError::InvalidAddr); + } + check_access(addr, core::mem::size_of::())?; + + // Match `VirtMemIo`: keep IRQs masked for the exclusive/atomic sequence so + // a local interrupt cannot clear the monitor between load and store. + let _irq = IrqSave::new(); + let mut observed = 0u32; + let failed = access_user_memory(|| { + // SAFETY: `check_access` validated the 4-byte user range, `addr` is + // 4-byte aligned, and `observed` is a live kernel stack slot. The + // call runs inside `access_user_memory`, so page faults are handled + // by the exception-table fixup on `user_atomic_cmpxchg_u32`. + unsafe { + user_atomic_cmpxchg_u32( + addr as *mut u32, + old, + new, + core::ptr::addr_of_mut!(observed), + ) + } + }); + if unlikely(failed != 0) { + Err(MemError::NoAccess) + } else { + Ok((observed == old, observed)) + } +} + #[extern_trait] // SAFETY: `Vm` validates the user range up front and performs raw copies only // inside the temporary user-access window established by `access_user_memory`. @@ -144,8 +199,12 @@ mod tests { use memspace::PageFaultOutcome; use osvm::MemError; use unittest::def_test; + use unittest_support::TestUserValue; - use super::{USER_SPACE_BASE, USER_SPACE_SIZE, check_access, fault_outcome_to_trap_result}; + use super::{ + USER_SPACE_BASE, USER_SPACE_SIZE, atomic_cmpxchg_u32, atomic_load_u32, atomic_u32_eq, + check_access, fault_outcome_to_trap_result, + }; #[def_test] fn test_check_access_valid() { @@ -177,6 +236,62 @@ mod tests { assert!(matches!(res, Err(MemError::NoAccess))); } + #[def_test] + fn test_atomic_cmpxchg_u32_rejects_misaligned() { + let res = atomic_cmpxchg_u32(USER_SPACE_BASE + 1, 0, 1); + assert!(matches!(res, Err(MemError::InvalidAddr))); + } + + #[def_test] + fn test_atomic_cmpxchg_u32_rejects_out_of_range() { + let res = atomic_cmpxchg_u32(USER_SPACE_BASE - 4, 0, 1); + assert!(matches!(res, Err(MemError::NoAccess))); + } + + #[def_test(custom)] + fn test_atomic_cmpxchg_u32_match_updates_user_word() { + let word = TestUserValue::::from_value(10).unwrap(); + let addr = word.as_user_ptr() as usize; + + let res = atomic_cmpxchg_u32(addr, 10, 20).unwrap(); + assert_eq!(res, (true, 10)); + assert_eq!(word.read(), 20); + } + + #[def_test(custom)] + fn test_atomic_cmpxchg_u32_mismatch_leaves_user_word() { + let word = TestUserValue::::from_value(10).unwrap(); + let addr = word.as_user_ptr() as usize; + + let res = atomic_cmpxchg_u32(addr, 5, 20).unwrap(); + assert_eq!(res, (false, 10)); + assert_eq!(word.read(), 10); + } + + #[def_test(custom)] + fn test_atomic_load_u32_reads_user_word() { + let word = TestUserValue::::from_value(42).unwrap(); + let addr = word.as_user_ptr() as usize; + + assert_eq!(atomic_load_u32(addr).unwrap(), 42); + } + + #[def_test(custom)] + fn test_atomic_u32_eq_matches_user_word() { + let word = TestUserValue::::from_value(7).unwrap(); + let addr = word.as_user_ptr() as usize; + + assert!(atomic_u32_eq(addr, 7).unwrap()); + assert!(!atomic_u32_eq(addr, 8).unwrap()); + } + + #[def_test(custom)] + fn test_atomic_cmpxchg_u32_faults_on_unmapped_user_addr() { + let unmapped = USER_SPACE_BASE + 0x1000; + let res = atomic_cmpxchg_u32(unmapped, 0, 1); + assert!(matches!(res, Err(MemError::NoAccess))); + } + #[def_test] fn test_check_access_rejects_far_above_user_space() { let res = check_access(USER_SPACE_BASE + USER_SPACE_SIZE + 0x1000, 1); diff --git a/posix/process/Cargo.toml b/posix/process/Cargo.toml index e3a54feef..8b2611e5a 100644 --- a/posix/process/Cargo.toml +++ b/posix/process/Cargo.toml @@ -23,6 +23,7 @@ khal.workspace = true kidentity.workspace = true klogger.workspace = true kprocess.workspace = true +kuaccess.workspace = true ksignal.workspace = true ksync.workspace = true ktask.workspace = true diff --git a/posix/process/src/runtime.rs b/posix/process/src/runtime.rs index 1c5e0393c..c2eb85c57 100644 --- a/posix/process/src/runtime.rs +++ b/posix/process/src/runtime.rs @@ -18,6 +18,7 @@ use kprocess::{ }; use ksignal::{SignalInfo, SignalOSAction, SignalSet, Signo}; use ktask::{TaskInner, current}; +use kuaccess::{atomic_cmpxchg_u32, atomic_load_u32}; use linux_raw_sys::general::ROBUST_LIST_LIMIT; use linux_sysno::Sysno; use memspace::PageFaultOutcome; @@ -155,11 +156,30 @@ struct RobustListHead { list_op_pending: *mut RobustList, } +/// Bits in the userspace futex word used by robust mutexes. +const FUTEX_OWNER_DIED: u32 = 0x0010_0000; +const FUTEX_WAITERS: u32 = 0x8000_0000; + +fn mark_futex_owner_died(address: usize) -> KResult<()> { + loop { + let observed = atomic_load_u32(address).map_err(KError::from)?; + if observed & FUTEX_OWNER_DIED != 0 { + return Ok(()); + } + let newval = (observed & FUTEX_WAITERS) | FUTEX_OWNER_DIED; + let (exchanged, _) = atomic_cmpxchg_u32(address, observed, newval).map_err(KError::from)?; + if exchanged { + return Ok(()); + } + } +} + fn dispatch_irq_futex_death(entry: *mut RobustList, offset: i64) -> KResult<()> { let address = (entry as u64) .checked_add_signed(offset) .ok_or(KError::InvalidInput)?; let address: usize = address.try_into().map_err(|_| KError::InvalidInput)?; + mark_futex_owner_died(address)?; let key = current_futex_key(address); let futex_state = current_user_process().futex_state()?; -- Gitee From 896162f3d603bd2f3826c96e57d2bf647580a347 Mon Sep 17 00:00:00 2001 From: WitcherTheWhite <1240749052@qq.com> Date: Mon, 13 Jul 2026 10:02:54 +0800 Subject: [PATCH 2/5] update --- arch/kcpu/src/riscv/atomic_user.S | 2 +- posix/process/src/runtime.rs | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/arch/kcpu/src/riscv/atomic_user.S b/arch/kcpu/src/riscv/atomic_user.S index 254ad5238..efcb37d79 100644 --- a/arch/kcpu/src/riscv/atomic_user.S +++ b/arch/kcpu/src/riscv/atomic_user.S @@ -22,8 +22,8 @@ user_atomic_cmpxchg_u32: bne t0, a1, .Ldone 2: sc.w t1, a2, (a0) bnez t1, .Lretry - fence rw, rw .Ldone: + fence rw, rw li a0, 0 ret diff --git a/posix/process/src/runtime.rs b/posix/process/src/runtime.rs index c2eb85c57..05d915b6d 100644 --- a/posix/process/src/runtime.rs +++ b/posix/process/src/runtime.rs @@ -13,8 +13,8 @@ use kerrno::{KError, KResult, LinuxError}; use khal::uspace::{ExceptionKind, ReturnReason, UserContext}; use kidentity::PidHandle; use kprocess::{ - CpuTimeState, Pid, Thread, UserThreadRuntimeAction, current_futex_key, current_user_process, - current_user_thread, poll_cpu_timers, process_exit, process_signals, + CpuTimeState, Pid, Thread, Tid, UserThreadRuntimeAction, current_futex_key, + current_user_process, current_user_thread, poll_cpu_timers, process_exit, process_signals, }; use ksignal::{SignalInfo, SignalOSAction, SignalSet, Signo}; use ktask::{TaskInner, current}; @@ -157,29 +157,35 @@ struct RobustListHead { } /// Bits in the userspace futex word used by robust mutexes. -const FUTEX_OWNER_DIED: u32 = 0x0010_0000; +const FUTEX_TID_MASK: u32 = 0x3fff_ffff; +const FUTEX_OWNER_DIED: u32 = 0x4000_0000; const FUTEX_WAITERS: u32 = 0x8000_0000; -fn mark_futex_owner_died(address: usize) -> KResult<()> { +fn mark_futex_owner_died(address: usize, tid: Tid) -> KResult { loop { let observed = atomic_load_u32(address).map_err(KError::from)?; + if (observed & FUTEX_TID_MASK) != tid { + return Ok(false); + } if observed & FUTEX_OWNER_DIED != 0 { - return Ok(()); + return Ok(true); } let newval = (observed & FUTEX_WAITERS) | FUTEX_OWNER_DIED; let (exchanged, _) = atomic_cmpxchg_u32(address, observed, newval).map_err(KError::from)?; if exchanged { - return Ok(()); + return Ok(true); } } } -fn dispatch_irq_futex_death(entry: *mut RobustList, offset: i64) -> KResult<()> { +fn dispatch_irq_futex_death(entry: *mut RobustList, offset: i64, tid: Tid) -> KResult<()> { let address = (entry as u64) .checked_add_signed(offset) .ok_or(KError::InvalidInput)?; let address: usize = address.try_into().map_err(|_| KError::InvalidInput)?; - mark_futex_owner_died(address)?; + if !mark_futex_owner_died(address, tid)? { + return Ok(()); + } let key = current_futex_key(address); let futex_state = current_user_process().futex_state()?; @@ -194,7 +200,7 @@ fn dispatch_irq_futex_death(entry: *mut RobustList, offset: i64) -> KResult<()> } /// Process robust futex list on thread exit and wake waiting threads. -fn exit_robust_list(head: *const RobustListHead) { +fn exit_robust_list(head: *const RobustListHead, tid: Tid) { let mut limit = ROBUST_LIST_LIMIT; // SAFETY: `head` comes from the task's registered robust-list head, and we @@ -211,7 +217,7 @@ fn exit_robust_list(head: *const RobustListHead) { let Some(next_entry) = entry.read_vm().map(|node| node.next).ok() else { return; }; - if entry != pending && dispatch_irq_futex_death(entry, offset).is_err() { + if entry != pending && dispatch_irq_futex_death(entry, offset, tid).is_err() { return; } entry = next_entry; @@ -224,7 +230,7 @@ fn exit_robust_list(head: *const RobustListHead) { } if !pending.is_null() { - let _ = dispatch_irq_futex_death(pending, offset); + let _ = dispatch_irq_futex_death(pending, offset, tid); } } @@ -249,7 +255,7 @@ pub fn do_exit(exit_code: i32, group_exit: bool) { let head = thr.robust_list_head() as *const RobustListHead; if !head.is_null() { - exit_robust_list(head); + exit_robust_list(head, thr.tid()); } // Per-thread TEE session cleanup when this thread holds a session context. -- Gitee From 57b4ecf113ebfd091f4dd52a8b6b3c330b6fdefb Mon Sep 17 00:00:00 2001 From: WitcherTheWhite <1240749052@qq.com> Date: Mon, 13 Jul 2026 10:14:42 +0800 Subject: [PATCH 3/5] update --- arch/kcpu/src/aarch64/atomic_user.S | 5 +++++ arch/kcpu/src/aarch64/copy_user.S | 5 +++++ arch/kcpu/src/loongarch64/instrs.rs | 7 +++++-- arch/kcpu/src/riscv/instrs.rs | 7 +++++-- arch/kcpu/src/riscv/macros.rs | 10 ++++++++++ arch/kcpu/src/x86_64/atomic_user.S | 5 +++++ arch/kcpu/src/x86_64/copy_user.S | 5 +++++ 7 files changed, 40 insertions(+), 4 deletions(-) diff --git a/arch/kcpu/src/aarch64/atomic_user.S b/arch/kcpu/src/aarch64/atomic_user.S index 6ae8d0686..44796df55 100644 --- a/arch/kcpu/src/aarch64/atomic_user.S +++ b/arch/kcpu/src/aarch64/atomic_user.S @@ -11,6 +11,9 @@ // On success, *old_out receives the value observed at *addr. If that value // equals `old`, `new` was stored. +.ifndef _ASM_EXTABLE_DEFINED +.equ _ASM_EXTABLE_DEFINED, 1 + .macro _asm_extable, from, to .pushsection __ex_table, "a" .balign 8 @@ -19,6 +22,8 @@ .popsection .endm +.endif + .section .text .global user_atomic_cmpxchg_u32 .type user_atomic_cmpxchg_u32, @function diff --git a/arch/kcpu/src/aarch64/copy_user.S b/arch/kcpu/src/aarch64/copy_user.S index b475c32d7..84b59edb8 100644 --- a/arch/kcpu/src/aarch64/copy_user.S +++ b/arch/kcpu/src/aarch64/copy_user.S @@ -3,6 +3,9 @@ // Arguments: x0=dst, x1=src, x2=size // Returns: 0 on success; remaining bytes (not copied) if a data abort occurs +.ifndef _ASM_EXTABLE_DEFINED +.equ _ASM_EXTABLE_DEFINED, 1 + .macro _asm_extable, from, to .pushsection __ex_table, "a" .balign 8 @@ -11,6 +14,8 @@ .popsection .endm +.endif + .section .text .global raw_copy_from_user diff --git a/arch/kcpu/src/loongarch64/instrs.rs b/arch/kcpu/src/loongarch64/instrs.rs index 7bba6dc17..8aa9b5a49 100644 --- a/arch/kcpu/src/loongarch64/instrs.rs +++ b/arch/kcpu/src/loongarch64/instrs.rs @@ -4,8 +4,11 @@ //! Wrapper functions for assembly instructions. -core::arch::global_asm!(include_asm_macros!(), include_str!("copy_user.S")); -core::arch::global_asm!(include_asm_macros!(), include_str!("atomic_user.S")); +core::arch::global_asm!( + include_asm_macros!(), + include_str!("copy_user.S"), + include_str!("atomic_user.S"), +); unsafe extern "C" { /// Copies data from source to destination, where addresses may be in user diff --git a/arch/kcpu/src/riscv/instrs.rs b/arch/kcpu/src/riscv/instrs.rs index 7bba6dc17..8aa9b5a49 100644 --- a/arch/kcpu/src/riscv/instrs.rs +++ b/arch/kcpu/src/riscv/instrs.rs @@ -4,8 +4,11 @@ //! Wrapper functions for assembly instructions. -core::arch::global_asm!(include_asm_macros!(), include_str!("copy_user.S")); -core::arch::global_asm!(include_asm_macros!(), include_str!("atomic_user.S")); +core::arch::global_asm!( + include_asm_macros!(), + include_str!("copy_user.S"), + include_str!("atomic_user.S"), +); unsafe extern "C" { /// Copies data from source to destination, where addresses may be in user diff --git a/arch/kcpu/src/riscv/macros.rs b/arch/kcpu/src/riscv/macros.rs index bce104610..12c00f92b 100644 --- a/arch/kcpu/src/riscv/macros.rs +++ b/arch/kcpu/src/riscv/macros.rs @@ -26,6 +26,11 @@ macro_rules! __asm_macros { sw \rs2, \off*XLENB(\rs1) .endm + .endif + + .ifndef ASM_EXTABLE_FLAG + .equ ASM_EXTABLE_FLAG, 1 + .macro _asm_extable, from, to .pushsection __ex_table, "a" .balign 4 @@ -52,6 +57,11 @@ macro_rules! __asm_macros { sd \rs2, \off*XLENB(\rs1) .endm + .endif + + .ifndef ASM_EXTABLE_FLAG + .equ ASM_EXTABLE_FLAG, 1 + .macro _asm_extable, from, to .pushsection __ex_table, "a" .balign 8 diff --git a/arch/kcpu/src/x86_64/atomic_user.S b/arch/kcpu/src/x86_64/atomic_user.S index fb3256172..f90829fde 100644 --- a/arch/kcpu/src/x86_64/atomic_user.S +++ b/arch/kcpu/src/x86_64/atomic_user.S @@ -11,6 +11,9 @@ // On success, *old_out receives the value observed at *addr. If that value // equals `old`, `new` was stored. +.ifndef _ASM_EXTABLE_DEFINED +.equ _ASM_EXTABLE_DEFINED, 1 + .macro _asm_extable, from, to .pushsection __ex_table, "a" .balign 8 @@ -19,6 +22,8 @@ .popsection .endm +.endif + .section .text .global user_atomic_cmpxchg_u32 .type user_atomic_cmpxchg_u32, @function diff --git a/arch/kcpu/src/x86_64/copy_user.S b/arch/kcpu/src/x86_64/copy_user.S index 6b748a75d..f85107ec5 100644 --- a/arch/kcpu/src/x86_64/copy_user.S +++ b/arch/kcpu/src/x86_64/copy_user.S @@ -1,3 +1,6 @@ +.ifndef _ASM_EXTABLE_DEFINED +.equ _ASM_EXTABLE_DEFINED, 1 + .macro _asm_extable, from, to .pushsection __ex_table, "a" .balign 8 @@ -6,6 +9,8 @@ .popsection .endm +.endif + .section .text .global user_copy user_copy: -- Gitee From 5e674b18fe9c97f6b496e53ba5022e5d22097786 Mon Sep 17 00:00:00 2001 From: WitcherTheWhite <1240749052@qq.com> Date: Mon, 13 Jul 2026 10:30:07 +0800 Subject: [PATCH 4/5] update --- arch/kcpu/src/aarch64/atomic_user.S | 27 ++++----------- arch/kcpu/src/aarch64/copy_user.S | 45 +++++++++---------------- arch/kcpu/src/aarch64/instrs.rs | 7 ++-- arch/kcpu/src/asm/extable.inc | 15 +++++++++ arch/kcpu/src/loongarch64/atomic_user.S | 14 ++++---- arch/kcpu/src/riscv/atomic_user.S | 14 ++++---- arch/kcpu/src/x86_64/atomic_user.S | 17 ++-------- arch/kcpu/src/x86_64/copy_user.S | 13 ------- arch/kcpu/src/x86_64/instrs.rs | 7 ++-- 9 files changed, 64 insertions(+), 95 deletions(-) create mode 100644 arch/kcpu/src/asm/extable.inc diff --git a/arch/kcpu/src/aarch64/atomic_user.S b/arch/kcpu/src/aarch64/atomic_user.S index 44796df55..59c90eb00 100644 --- a/arch/kcpu/src/aarch64/atomic_user.S +++ b/arch/kcpu/src/aarch64/atomic_user.S @@ -11,44 +11,31 @@ // On success, *old_out receives the value observed at *addr. If that value // equals `old`, `new` was stored. -.ifndef _ASM_EXTABLE_DEFINED -.equ _ASM_EXTABLE_DEFINED, 1 - -.macro _asm_extable, from, to - .pushsection __ex_table, "a" - .balign 8 - .quad \from - .quad \to - .popsection -.endm - -.endif - .section .text .global user_atomic_cmpxchg_u32 .type user_atomic_cmpxchg_u32, @function user_atomic_cmpxchg_u32: -.Lretry: +.Lcmpxchg_retry: 1: ldxr w4, [x0] // old_out is a kernel pointer; no exception table needed. str w4, [x3] cmp w4, w1 - b.ne .Ldone + b.ne .Lcmpxchg_done 2: stxr w5, w2, [x0] - cbnz w5, .Lretry + cbnz w5, .Lcmpxchg_retry dmb ish -.Ldone: +.Lcmpxchg_done: clrex mov x0, #0 ret -.Lfault: +.Lcmpxchg_fault: clrex mov x0, #1 ret - _asm_extable 1b, .Lfault - _asm_extable 2b, .Lfault + _asm_extable 1b, .Lcmpxchg_fault + _asm_extable 2b, .Lcmpxchg_fault .size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32 diff --git a/arch/kcpu/src/aarch64/copy_user.S b/arch/kcpu/src/aarch64/copy_user.S index 84b59edb8..99004e51a 100644 --- a/arch/kcpu/src/aarch64/copy_user.S +++ b/arch/kcpu/src/aarch64/copy_user.S @@ -3,19 +3,6 @@ // Arguments: x0=dst, x1=src, x2=size // Returns: 0 on success; remaining bytes (not copied) if a data abort occurs -.ifndef _ASM_EXTABLE_DEFINED -.equ _ASM_EXTABLE_DEFINED, 1 - -.macro _asm_extable, from, to - .pushsection __ex_table, "a" - .balign 8 - .quad \from - .quad \to - .popsection -.endm - -.endif - .section .text .global raw_copy_from_user @@ -25,7 +12,7 @@ // 3. Bulk copy 64 bytes per iteration using 8x LDP/STP pairs // 4. Copy remaining 8-byte chunks // 5. Tail copy bytes -// All load/store pairs are covered by exception table entries pointing to .Lfault +// All load/store pairs are covered by exception table entries pointing to .Lcopy_fault // so that on fault we compute remaining = original_end - current_dst. raw_copy_from_user: @@ -95,22 +82,22 @@ raw_copy_from_user: // Fault handler: x0 currently points just past last successfully written byte. // Remaining = dst_end - current_dst (x3 - x0) -.Lfault: +.Lcopy_fault: sub x0, x3, x0 ret // Exception table entries for every faultable memory access. - _asm_extable 1b, .Lfault - _asm_extable 2b, .Lfault - _asm_extable 3b, .Lfault - _asm_extable 4b, .Lfault - _asm_extable 5b, .Lfault - _asm_extable 6b, .Lfault - _asm_extable 7b, .Lfault - _asm_extable 8b, .Lfault - _asm_extable 9b, .Lfault - _asm_extable 10b, .Lfault - _asm_extable 11b, .Lfault - _asm_extable 12b, .Lfault - _asm_extable 13b, .Lfault - _asm_extable 14b, .Lfault + _asm_extable 1b, .Lcopy_fault + _asm_extable 2b, .Lcopy_fault + _asm_extable 3b, .Lcopy_fault + _asm_extable 4b, .Lcopy_fault + _asm_extable 5b, .Lcopy_fault + _asm_extable 6b, .Lcopy_fault + _asm_extable 7b, .Lcopy_fault + _asm_extable 8b, .Lcopy_fault + _asm_extable 9b, .Lcopy_fault + _asm_extable 10b, .Lcopy_fault + _asm_extable 11b, .Lcopy_fault + _asm_extable 12b, .Lcopy_fault + _asm_extable 13b, .Lcopy_fault + _asm_extable 14b, .Lcopy_fault diff --git a/arch/kcpu/src/aarch64/instrs.rs b/arch/kcpu/src/aarch64/instrs.rs index 165e8ec7d..bae9af526 100644 --- a/arch/kcpu/src/aarch64/instrs.rs +++ b/arch/kcpu/src/aarch64/instrs.rs @@ -4,8 +4,11 @@ //! Wrapper functions for assembly instructions. -core::arch::global_asm!(include_str!("copy_user.S")); -core::arch::global_asm!(include_str!("atomic_user.S")); +core::arch::global_asm!( + include_str!("../asm/extable.inc"), + include_str!("copy_user.S"), + include_str!("atomic_user.S"), +); unsafe extern "C" { /// Copies data from source to destination, where addresses may be in user diff --git a/arch/kcpu/src/asm/extable.inc b/arch/kcpu/src/asm/extable.inc new file mode 100644 index 000000000..e3965e799 --- /dev/null +++ b/arch/kcpu/src/asm/extable.inc @@ -0,0 +1,15 @@ +// Shared exception-table macro for user-access assembly helpers. +// Include once per global_asm! block before any .S file that uses _asm_extable. + +.ifndef _ASM_EXTABLE_DEFINED +.equ _ASM_EXTABLE_DEFINED, 1 + +.macro _asm_extable, from, to + .pushsection __ex_table, "a" + .balign 8 + .quad \from + .quad \to + .popsection +.endm + +.endif diff --git a/arch/kcpu/src/loongarch64/atomic_user.S b/arch/kcpu/src/loongarch64/atomic_user.S index f89b310d2..77dd5d5ee 100644 --- a/arch/kcpu/src/loongarch64/atomic_user.S +++ b/arch/kcpu/src/loongarch64/atomic_user.S @@ -19,23 +19,23 @@ .type user_atomic_cmpxchg_u32, @function user_atomic_cmpxchg_u32: -.Lretry: +.Lcmpxchg_retry: 1: ll.w $t0, $a0, 0 st.w $t0, $a3, 0 - bne $t0, $a1, .Ldone + bne $t0, $a1, .Lcmpxchg_done move $t1, $a2 2: sc.w $t1, $a0, 0 - beqz $t1, .Lretry + beqz $t1, .Lcmpxchg_retry dbar 0 -.Ldone: +.Lcmpxchg_done: move $a0, $zero jr $ra -.Lfault: +.Lcmpxchg_fault: ori $a0, $zero, 1 jr $ra - _asm_extable 1b, .Lfault - _asm_extable 2b, .Lfault + _asm_extable 1b, .Lcmpxchg_fault + _asm_extable 2b, .Lcmpxchg_fault .size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32 diff --git a/arch/kcpu/src/riscv/atomic_user.S b/arch/kcpu/src/riscv/atomic_user.S index efcb37d79..7e0a7a096 100644 --- a/arch/kcpu/src/riscv/atomic_user.S +++ b/arch/kcpu/src/riscv/atomic_user.S @@ -16,22 +16,22 @@ .type user_atomic_cmpxchg_u32, @function user_atomic_cmpxchg_u32: -.Lretry: +.Lcmpxchg_retry: 1: lr.w t0, (a0) sw t0, 0(a3) - bne t0, a1, .Ldone + bne t0, a1, .Lcmpxchg_done 2: sc.w t1, a2, (a0) - bnez t1, .Lretry -.Ldone: + bnez t1, .Lcmpxchg_retry +.Lcmpxchg_done: fence rw, rw li a0, 0 ret -.Lfault: +.Lcmpxchg_fault: li a0, 1 ret - _asm_extable 1b, .Lfault - _asm_extable 2b, .Lfault + _asm_extable 1b, .Lcmpxchg_fault + _asm_extable 2b, .Lcmpxchg_fault .size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32 diff --git a/arch/kcpu/src/x86_64/atomic_user.S b/arch/kcpu/src/x86_64/atomic_user.S index f90829fde..3d61900c8 100644 --- a/arch/kcpu/src/x86_64/atomic_user.S +++ b/arch/kcpu/src/x86_64/atomic_user.S @@ -11,19 +11,6 @@ // On success, *old_out receives the value observed at *addr. If that value // equals `old`, `new` was stored. -.ifndef _ASM_EXTABLE_DEFINED -.equ _ASM_EXTABLE_DEFINED, 1 - -.macro _asm_extable, from, to - .pushsection __ex_table, "a" - .balign 8 - .quad \from - .quad \to - .popsection -.endm - -.endif - .section .text .global user_atomic_cmpxchg_u32 .type user_atomic_cmpxchg_u32, @function @@ -36,10 +23,10 @@ user_atomic_cmpxchg_u32: xor eax, eax ret -.Lfault: +.Lcmpxchg_fault: mov eax, 1 ret - _asm_extable 1b, .Lfault + _asm_extable 1b, .Lcmpxchg_fault .size user_atomic_cmpxchg_u32, .-user_atomic_cmpxchg_u32 diff --git a/arch/kcpu/src/x86_64/copy_user.S b/arch/kcpu/src/x86_64/copy_user.S index f85107ec5..9df869d69 100644 --- a/arch/kcpu/src/x86_64/copy_user.S +++ b/arch/kcpu/src/x86_64/copy_user.S @@ -1,16 +1,3 @@ -.ifndef _ASM_EXTABLE_DEFINED -.equ _ASM_EXTABLE_DEFINED, 1 - -.macro _asm_extable, from, to - .pushsection __ex_table, "a" - .balign 8 - .quad \from - .quad \to - .popsection -.endm - -.endif - .section .text .global user_copy user_copy: diff --git a/arch/kcpu/src/x86_64/instrs.rs b/arch/kcpu/src/x86_64/instrs.rs index abab00964..4917debdd 100644 --- a/arch/kcpu/src/x86_64/instrs.rs +++ b/arch/kcpu/src/x86_64/instrs.rs @@ -4,8 +4,11 @@ //! Wrapper functions for assembly instructions. -core::arch::global_asm!(include_str!("copy_user.S")); -core::arch::global_asm!(include_str!("atomic_user.S")); +core::arch::global_asm!( + include_str!("../asm/extable.inc"), + include_str!("copy_user.S"), + include_str!("atomic_user.S"), +); unsafe extern "C" { /// Copies data from source to destination, where addresses may be in user -- Gitee From ac9439d4d45d8632ab1b89c96abf46d01d87d151 Mon Sep 17 00:00:00 2001 From: WitcherTheWhite <1240749052@qq.com> Date: Tue, 14 Jul 2026 10:27:47 +0800 Subject: [PATCH 5/5] update --- posix/process/src/runtime.rs | 47 ++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/posix/process/src/runtime.rs b/posix/process/src/runtime.rs index 05d915b6d..9c7119478 100644 --- a/posix/process/src/runtime.rs +++ b/posix/process/src/runtime.rs @@ -19,7 +19,7 @@ use kprocess::{ use ksignal::{SignalInfo, SignalOSAction, SignalSet, Signo}; use ktask::{TaskInner, current}; use kuaccess::{atomic_cmpxchg_u32, atomic_load_u32}; -use linux_raw_sys::general::ROBUST_LIST_LIMIT; +use linux_raw_sys::general::{FUTEX_OWNER_DIED, FUTEX_TID_MASK, FUTEX_WAITERS, ROBUST_LIST_LIMIT}; use linux_sysno::Sysno; use memspace::PageFaultOutcome; use osvm::{VirtMutPtr, VirtPtr}; @@ -156,22 +156,49 @@ struct RobustListHead { list_op_pending: *mut RobustList, } -/// Bits in the userspace futex word used by robust mutexes. -const FUTEX_TID_MASK: u32 = 0x3fff_ffff; -const FUTEX_OWNER_DIED: u32 = 0x4000_0000; -const FUTEX_WAITERS: u32 = 0x8000_0000; +/// Userspace robust-mutex lock word (`u32` futex value). +/// +/// Layout matches the Linux robust-futex ABI: TID in the low bits, plus +/// `FUTEX_OWNER_DIED` / `FUTEX_WAITERS` in the high bits. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +struct RobustFutexWord(u32); + +impl RobustFutexWord { + fn from_bits(bits: u32) -> Self { + Self(bits) + } + + fn bits(self) -> u32 { + self.0 + } + + fn tid(self) -> Tid { + self.0 & FUTEX_TID_MASK + } + + fn is_owner_died(self) -> bool { + self.0 & FUTEX_OWNER_DIED != 0 + } + + /// Preserve `WAITERS`, clear the TID, and set `OWNER_DIED`. + fn with_owner_died(self) -> Self { + Self((self.0 & FUTEX_WAITERS) | FUTEX_OWNER_DIED) + } +} fn mark_futex_owner_died(address: usize, tid: Tid) -> KResult { loop { - let observed = atomic_load_u32(address).map_err(KError::from)?; - if (observed & FUTEX_TID_MASK) != tid { + let observed = RobustFutexWord::from_bits(atomic_load_u32(address).map_err(KError::from)?); + if observed.tid() != tid { return Ok(false); } - if observed & FUTEX_OWNER_DIED != 0 { + if observed.is_owner_died() { return Ok(true); } - let newval = (observed & FUTEX_WAITERS) | FUTEX_OWNER_DIED; - let (exchanged, _) = atomic_cmpxchg_u32(address, observed, newval).map_err(KError::from)?; + let newval = observed.with_owner_died(); + let (exchanged, _) = + atomic_cmpxchg_u32(address, observed.bits(), newval.bits()).map_err(KError::from)?; if exchanged { return Ok(true); } -- Gitee