feat: 实现基于异常表的安全用户空间内存访问机制 (#1383)

* feat: 实现基于异常表的安全用户空间内存访问机制

- 新增异常表机制,在系统调用中安全处理用户空间内存访问错误
- 实现带异常表保护的memcpy和memset函数,防止无效用户地址导致内核在内存拷贝处以及pagefault处理程序之间反复横跳
- 重构用户空间访问API,提供安全的UserBuffer包装类型
- 更新页错误处理程序,支持异常表修复路径
- 添加异常表测试程序,验证各种边界情况
- 更新内存管理文档,详细说明异常表设计原理和使用场景

Signed-off-by: longjin <longjin@DragonOS.org>

* chore: 更新应用黑名单配置

- 添加 test_ebpf_new, test_ebpf_tp 到黑名单,原因:aya上游发版问题导致CI失败

Signed-off-by: longjin <longjin@DragonOS.org>

---------

Signed-off-by: longjin <longjin@DragonOS.org>
This commit is contained in:
LoGin 2025-11-18 21:02:07 +08:00 committed by GitHub
parent 58cddf7629
commit d93301b512
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1203 additions and 50 deletions

View File

@ -25,6 +25,12 @@ jobs:
- name: Checkout DragonOS code
uses: actions/checkout@v4
- name: Build DragonOS
shell: bash -ileo pipefail {0}
run: |
make clean
make -j$(nproc) all
- name: Run syscall test
shell: bash -ileo pipefail {0}
continue-on-error: true

View File

@ -65,6 +65,16 @@ log_skipped = true
[[blocked_apps]]
name = "gvisor syscall tests"
reason = "由于文件较大,因此屏蔽。如果要允许系统调用测试,则把这几行取消注释即可"
[[blocked_apps]]
name = "NovaShell"
reason = "工具链版本有问题应为nightly-2025-8-10"
[[blocked_apps]]
name = "test_ebpf_new"
reason = "2025.11.17,aya上游发版有问题导致ci过不了暂时禁用"
[[blocked_apps]]
name = "test_ebpf_tp"
reason = "2025.11.17,aya上游发版有问题导致ci过不了暂时禁用"

View File

@ -0,0 +1,279 @@
# 异常表安全内存拷贝方案设计
:::{note}
本文作者:龙进 <longjin@dragonos.org>
:::
## 概述
本文档描述DragonOS中基于异常表(Exception Table)机制的安全内存拷贝方案的核心设计思想。该方案解决内核在系统调用上下文中安全访问用户空间内存的问题,防止因访问无效用户地址而导致的内核panic。
## 设计背景与动机
### 问题定义
在系统调用处理中,内核需要访问用户空间传入的指针(如路径字符串、参数结构体等)。这些访问可能失败:
1. **地址未映射**: 用户传入的地址没有对应的VMA(Virtual Memory Area)
2. **权限不足**: 页面存在但缺少所需权限
3. **恶意输入**: 用户故意传入非法地址
### 传统方案的局限
**预检查方案的TOCTTOU问题:**
- 检查时地址有效,使用时可能已被其他线程修改
- 存在竞态窗口
**直接访问的困境:**
- 无法区分"正常缺页"和"非法访问"
- 页错误处理器无法判断是内核bug还是用户错误
## 异常表机制原理
### 核心思想
异常表机制通过**编译期标记 + 运行时查找**实现安全的用户空间访问:
1. **编译期**: 在可能触发页错误的指令处生成异常表条目
2. **运行时**: 页错误发生时,查找异常表并跳转到修复代码
3. **零开销**: 正常路径无性能损失
### 架构示意图
```
┌─────────────────────────────────────────────────────────────┐
│ 系统调用执行流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户空间 内核空间 │
│ ┌──────┐ ┌──────────────────────────────┐ │
│ │0x1000│ │ 1. 系统调用入口 │ │
│ │(未映射)─────────→ 2. 拷贝用户数据(带标记) │ │
│ └──────┘ │ ├─ 正常完成 ──→ 返回成功 │ │
│ │ └─ 触发#PF │ │
│ │ ↓ │ │
│ │ 3. 页错误处理器 │ │
│ │ ├─ 查找异常表 │ │
│ │ └─ 找到修复代码地址 │ │
│ │ ↓ │ │
│ │ 4. 修改指令指针(RIP) │ │
│ │ ↓ │ │
│ │ 5. 执行修复代码 │ │
│ │ └─ 设置错误码(-1) │ │
│ │ ↓ │ │
│ │ 6. 返回EFAULT给用户 │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 核心数据结构
**异常表条目 (8字节对齐):**
```
┌─────────────────┬──────────────────┐
│ 指令相对偏移 │ 修复代码相对偏移 │
│ (4 bytes) │ (4 bytes) │
└─────────────────┴──────────────────┘
```
**设计要点:**
- 使用相对偏移支持ASLR(地址空间布局随机化)
- 8字节对齐提高缓存性能
- 存储于只读段防止篡改
### 工作流程
```
编译期:
源码 ──→ 带标记的指令 ──→ 生成异常表条目 ──→ 链接到内核镜像
(rep movsb) (insn→fixup)
运行期:
执行拷贝 ──→ 触发页错误? ─否→ 正常返回
查找异常表 ──→ 找到? ─否→ 内核panic
修改RIP到修复代码 ──→ 返回错误码
```
## 典型执行场景
### 场景: 系统调用传入无效地址
以`open()`系统调用为例,展示异常表的工作过程:
```
用户程序: open(0x1000, O_RDONLY) // 0x1000未映射
┌────────────────────────────────┐
│ 1. 进入系统调用 │
│ ├─ 解析路径字符串 │
│ └─ 逐字节拷贝直到'\0' │
└────────────────────────────────┘
┌────────────────────────────────┐
│ 2. 拷贝第一个字节时触发页错误 │
│ (地址0x1000不在VMA中) │
└────────────────────────────────┘
┌────────────────────────────────┐
│ 3. 页错误处理器 │
│ ├─ 检测到访问用户地址 │
│ ├─ 查找异常表 │
│ └─ 找到对应的修复代码 │
└────────────────────────────────┘
┌────────────────────────────────┐
│ 4. 修改指令指针到修复代码 │
│ └─ 设置返回值为错误码 │
└────────────────────────────────┘
┌────────────────────────────────┐
│ 5. 系统调用返回EFAULT │
└────────────────────────────────┘
用户程序: fd = -1, errno = EFAULT
```
**关键点:**
- 无需预检查地址有效性
- 页错误自动转换为错误码
- 内核不会panic,用户程序收到明确的错误信息
## 使用场景分析
### ✅ 适合使用异常表保护的场景
#### 1. 小数据的系统调用参数
**特征:**
- 数据量小 (通常 < 4KB)
- 一次性拷贝
- 无法预知数据长度(如字符串)
**典型应用:**
- 路径字符串: `open()`, `stat()`, `execve()`
- 固定大小结构体: `sigaction`, `timespec`, `stat`
- 小型数组: `iovec[]`, `pollfd[]`
**优势:**
- **避免TOCTTOU竞态**: 无需预检查
- **高鲁棒性**: 用户错误不会导致内核panic
- **性能可接受**: 数据量小,即使多拷贝一次也影响不大
#### 2. 不确定地址有效性的场景
当无法通过其他方式验证地址时,异常表是最安全的选择:
- 用户直接传入的原始指针
- 多线程环境下可能被并发修改的地址
- 需要原子性保证的操作
### ❌ 不适合使用异常表保护的场景
#### 1. 大数据传输
**反模式: read/write系统调用中双重缓冲**
```
用户缓冲区 → 内核临时缓冲区 → 用户缓冲区 ❌
```
**问题:**
- 内存浪费: 需要额外的内核缓冲区
- 双重拷贝: 数据被拷贝两次
- OOM风险: 大量并发读写耗尽内存
**正确方案: 零拷贝**
- 预先验证地址在有效VMA中
- 直接在用户缓冲区上操作
- 页错误触发正常的缺页处理(非错误)
#### 2. 已验证的VMA内地址
如果地址已通过VMA检查,异常表是多余的:
- `mmap()`后的立即访问
- DMA缓冲区
- 共享内存区域
在这些场景下,页错误是**正常的缺页处理**(如COW),不是错误。
#### 3. 性能敏感的热路径
避免在循环中频繁调用带异常表保护的函数:
- **批量处理**: 一次拷贝整个数组,而非逐元素拷贝
- **提前验证**: 在循环外验证地址,循环内直接访问
### 决策矩阵
| 场景特征 | 数据量 | 推荐方案 | 核心考虑 |
|---------|--------|----------|---------|
| 系统调用小参数 | < 4KB | 异常表保护 | 避免TOCTTOU,提高鲁棒性 |
| 文件读写 | 可变(MB级) | 零拷贝 | 性能优先,避免双重缓冲 |
| mmap后访问 | 任意 | 直接访问 | VMA已验证,正常缺页 |
| 批量小数据 | 累计KB级 | 批量拷贝 | 减少系统调用次数 |
| 字符串解析 | 未知 | 异常表保护 | 逐字节扫描,需要健壮性 |
## 安全性分析
### 防御能力
异常表机制可以防御:
1. **空指针解引用**: 返回EFAULT而非段错误
2. **内核地址注入**: 用户传入内核地址被安全拒绝
3. **竞态攻击**: TOCTTOU窗口被消除
4. **越界访问**: 访问VMA外地址被捕获
### 安全边界
异常表**不能**防御:
1. **内核自身bug**: 如野指针解引用
2. **硬件故障**: 内存物理损坏
3. **其他异常类型**: 仅处理页错误
### 多层防御
异常表是纵深防御的一部分:
```
┌─────────────────────────────────────┐
│ 用户空间权限检查 (SELinux/AppArmor) │ ← 权限层
├─────────────────────────────────────┤
│ 系统调用参数验证 │ ← 逻辑层
├─────────────────────────────────────┤
│ 异常表机制 │ ← 内存安全层
├─────────────────────────────────────┤
│ 硬件页保护 (MMU) │ ← 硬件层
└─────────────────────────────────────┘
```
## 实现要点
### 关键技术
1. **相对偏移编码**: 支持地址随机化(ASLR)
2. **二分查找**: O(log n)时间复杂度快速定位
3. **内联汇编**: 精确控制指令和异常表生成
4. **零开销抽象**: 正常路径无性能损失
### 架构移植
异常表机制可移植到其他架构:
- **x86_64**: 使用`rep movsb`指令
- **ARM64**: 使用`ldp/stp`指令序列
- **RISC-V**: 使用`ld/sd`指令序列
核心思想保持不变,只需调整汇编语法。

View File

@ -11,4 +11,5 @@
intro
allocate-memory
mmio
mmio
extable_safe_copy_design

View File

@ -39,6 +39,12 @@ SECTIONS
_etext = .;
__etext = .;
/* 异常表段 */
. = ALIGN(8);
__start___ex_table = .;
*(__ex_table)
__stop___ex_table = .;
}
. = ALIGN(4096);
data_start_pa = .;

View File

@ -13,7 +13,7 @@ use crate::{
mm::{MemoryManagementArch, X86_64MMArch},
CurrentIrqArch, MMArch,
},
exception::InterruptArch,
exception::{extable::ExceptionTableManager, InterruptArch},
ipc::signal_types::{SigCode, SigInfo, SigType},
mm::{
fault::{FaultFlags, PageFaultHandler, PageFaultMessage},
@ -153,6 +153,12 @@ impl X86_64MMArch {
error_code: X86PfErrorCode,
address: VirtAddr,
) {
// 尝试异常表修复
if Self::try_fixup_exception(regs, error_code, address) {
// 成功修复,直接返回
return;
}
unsafe { crate::debug::traceback::lookup_kallsyms(regs.rip, 0xff) };
let pcb = crate::process::ProcessManager::current_pcb();
let kstack_guard_addr = pcb.kernel_stack().guard_page_address();
@ -184,6 +190,42 @@ impl X86_64MMArch {
//TODO https://code.dragonos.org.cn/xref/linux-6.6.21/arch/x86/mm/fault.c#do_kern_addr_fault
}
/// 尝试使用异常表修复页错误
///
/// ## 返回值
/// - `true`: 成功修复,可以继续执行
/// - `false`: 无法修复,是真正的内核错误
fn try_fixup_exception(
regs: &'static TrapFrame,
_error_code: X86PfErrorCode,
address: VirtAddr,
) -> bool {
// 只处理用户空间地址的访问错误
if !address.check_user() {
return false;
}
// 在异常表中查找修复代码
if let Some(fixup_addr) = ExceptionTableManager::search_exception_table(regs.rip as usize) {
log::debug!(
"Page fault at {:#x} accessing user address {:#x}, fixed up to {:#x}",
regs.rip,
address.data(),
fixup_addr
);
// 修改trap frame的RIP到修复代码
unsafe {
let regs_mut = regs as *const TrapFrame as *mut TrapFrame;
(*regs_mut).rip = fixup_addr as u64;
}
return true;
}
false
}
/// 用户态缺页异常处理
/// ## 参数
///
@ -255,6 +297,25 @@ impl X86_64MMArch {
.expect("failed to send SIGSEGV to process");
};
// 辅助函数:处理内核访问用户地址失败的情况
let handle_kernel_access_failed = || {
// 如果是内核代码访问用户地址,尝试异常表修复
if !regs.is_from_user() {
if Self::try_fixup_exception(regs, error_code, address) {
return true; // 成功修复
}
// 如果异常表中没有说明是bug
error!(
"Kernel code at {:#x} illegally accessed user address {:#x} \
without exception table entry",
regs.rip,
address.data()
);
panic!("Illegal user space access from kernel");
}
false // 不是内核访问,继续正常流程
};
let current_address_space: Arc<AddressSpace> = AddressSpace::current().unwrap();
let mut space_guard = current_address_space.write_irqsave();
let mut fault;
@ -270,6 +331,12 @@ impl X86_64MMArch {
address.data(),
regs.rip,
);
// VMA不存在检查是否需要异常表修复
if handle_kernel_access_failed() {
return; // 已通过异常表修复
}
send_segv();
return;
}
@ -289,6 +356,12 @@ impl X86_64MMArch {
error_code,
address.data(),
);
// 栈溢出,检查是否需要异常表修复
if handle_kernel_access_failed() {
return; // 已通过异常表修复
}
send_segv();
return;
}
@ -312,6 +385,11 @@ impl X86_64MMArch {
);
log::error!("fault rip: {:#x}", regs.rip);
// 地址不在VMA范围内检查是否需要异常表修复
if handle_kernel_access_failed() {
return; // 已通过异常表修复
}
send_segv();
return;
}
@ -323,6 +401,12 @@ impl X86_64MMArch {
error_code,
address.data(),
);
// VMA权限错误检查是否需要异常表修复
if handle_kernel_access_failed() {
return; // 已通过异常表修复
}
send_segv();
}
let mapper = &mut space_guard.user_mapper.utable;

View File

@ -393,6 +393,98 @@ impl MemoryManagementArch for X86_64MMArch {
// log::debug!("CR0.WP bit disabled for kernel write protection");
}
}
/// 带异常表保护的内存拷贝
#[inline(always)]
unsafe fn copy_with_exception_table(dst: *mut u8, src: *const u8, len: usize) -> i32 {
let mut result: i32;
core::arch::asm!(
// 保存原始值
"xor {result:e}, {result:e}",
// 标记为可能出错的访问点
"2:",
// 执行拷贝
"rep movsb",
// 正常完成,跳过错误处理
"jmp 3f",
// 错误处理: 设置返回值为-1
"4:",
"mov {result:e}, -1",
"3:",
// 添加异常表条目
".pushsection __ex_table, \"a\"",
".balign 8",
".quad 2b - .",
".quad 4b - .",
".popsection",
result = out(reg) result,
inout("rdi") dst => _,
inout("rsi") src => _,
inout("rcx") len => _,
options(att_syntax, nostack)
);
result
}
/// 带异常表保护的内存设置memset
///
/// 参考 Asterinas 的实现和 Linux 内核的 memset_64.S
///
/// ## 参数
/// - `dst`: 目标地址rdi
/// - `value`: 要设置的字节值
/// - `len`: 要设置的字节数
///
/// ## 返回值
/// - 0: 成功
/// - -1: 发生页错误
#[inline(always)]
unsafe fn memset_with_exception_table(dst: *mut u8, value: u8, len: usize) -> i32 {
let mut result: i32;
core::arch::asm!(
// 初始化返回值为0
"xor {result:e}, {result:e}",
// 标记为可能出错的访问点
"2:",
// 执行内存设置,将 al 的值写入 [rdi] 并重复 rcx 次
// rep stosb 使用: al=value, rdi=dst, rcx=count
"rep stosb",
// 正常完成,跳过错误处理
"jmp 3f",
// 错误处理: 设置返回值为-1
"4:",
"mov {result:e}, -1",
"3:",
// 添加异常表条目
".pushsection __ex_table, \"a\"",
".balign 8",
".quad 2b - .",
".quad 4b - .",
".popsection",
result = out(reg) result,
inout("rdi") dst => _,
inout("rcx") len => _,
inout("al") value => _,
options(att_syntax, nostack)
);
result
}
}
/// 获取保护标志的映射表

View File

@ -0,0 +1,85 @@
//! 内核异常表(Exception Table)
//! 用于处理在系统调用上下文中访问用户空间内存时的页错误
/// 异常表条目
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ExceptionTableEntry {
/// 可能触发异常的指令地址(相对于表项地址的偏移)
pub insn_offset: i32,
/// 修复代码地址(相对于表项地址的偏移)
pub fixup_offset: i32,
}
impl ExceptionTableEntry {
/// 获取指令的绝对地址
pub fn insn_addr(&self) -> usize {
let self_addr = self as *const Self as usize;
(self_addr as i64 + self.insn_offset as i64) as usize
}
/// 获取修复代码的绝对地址
pub fn fixup_addr(&self) -> usize {
let self_addr = self as *const Self as usize;
(self_addr as i64 + self.fixup_offset as i64) as usize
}
}
extern "C" {
// 链接器脚本中定义的异常表边界
static __start___ex_table: ExceptionTableEntry;
static __stop___ex_table: ExceptionTableEntry;
}
/// 异常表管理器
pub struct ExceptionTableManager;
impl ExceptionTableManager {
/// 在异常表中搜索给定地址的修复代码
///
/// ## 参数
/// - `fault_addr`: 触发异常的指令地址
///
/// ## 返回值
/// - `Some(fixup_addr)`: 找到对应的修复地址
/// - `None`: 未找到(说明不是预期的用户空间访问错误)
pub fn search_exception_table(fault_addr: usize) -> Option<usize> {
unsafe {
let start = &__start___ex_table as *const ExceptionTableEntry;
let end = &__stop___ex_table as *const ExceptionTableEntry;
let count =
((end as usize) - (start as usize)) / core::mem::size_of::<ExceptionTableEntry>();
if count == 0 {
return None;
}
let table = core::slice::from_raw_parts(start, count);
// 二分查找(表在编译时已排序)
Self::binary_search(table, fault_addr)
}
}
fn binary_search(table: &[ExceptionTableEntry], fault_addr: usize) -> Option<usize> {
let mut left = 0;
let mut right = table.len();
while left < right {
let mid = left + (right - left) / 2;
let entry = &table[mid];
let insn_addr = entry.insn_addr();
if insn_addr == fault_addr {
return Some(entry.fixup_addr());
} else if insn_addr < fault_addr {
left = mid + 1;
} else {
right = mid;
}
}
None
}
}

View File

@ -8,6 +8,7 @@ pub mod debug;
pub mod dummychip;
pub mod ebreak;
pub mod entry;
pub mod extable;
pub mod handle;
pub mod init;
pub mod ipi;

View File

@ -270,7 +270,7 @@ impl Sigaction {
/// 用户态传入的sigaction结构体符合posix规范
/// 请注意我们会在sys_sigaction函数里面将其转换成内核使用的sigaction结构体
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Default)]
pub struct UserSigaction {
pub handler: *mut core::ffi::c_void,
pub flags: SigFlags,

View File

@ -35,20 +35,18 @@ pub(super) fn do_kernel_sigaction(
old_act: usize,
from_user: bool,
) -> Result<usize, SystemError> {
// 请注意用户态传进来的user_sigaction结构体类型请注意这个结构体与内核实际的不一样
let act: *mut UserSigaction = new_act as *mut UserSigaction;
let old_act = old_act as *mut UserSigaction;
let mut new_ka: Sigaction = Default::default();
let mut old_sigaction: Sigaction = Default::default();
// 如果传入的新的sigaction不为空
if !act.is_null() {
// 如果参数的范围不在用户空间,则返回错误
let r = UserBufferWriter::new(act, core::mem::size_of::<Sigaction>(), from_user);
if r.is_err() {
return Err(SystemError::EFAULT);
}
let mask: SigSet = unsafe { (*act).mask };
let input_sighandler = unsafe { (*act).handler as u64 };
if new_act != 0 {
let mut act_writer = UserBufferWriter::new(
new_act as *mut UserSigaction,
core::mem::size_of::<UserSigaction>(),
from_user,
)?;
let act: UserSigaction = act_writer.buffer_protected(0)?.read_one(0)?;
let mask: SigSet = act.mask;
let input_sighandler = act.handler as u64;
let sig = Signal::from(sig);
// Linux 只判断 sig 是否合法
@ -60,13 +58,13 @@ pub(super) fn do_kernel_sigaction(
match input_sighandler {
USER_SIG_DFL => {
new_ka = Sigaction::DEFAULT_SIGACTION;
*new_ka.flags_mut() = unsafe { (*act).flags };
*new_ka.flags_mut() = act.flags;
new_ka.set_restorer(None);
}
USER_SIG_IGN => {
new_ka = Sigaction::DEFAULT_SIGACTION_IGNORE;
*new_ka.flags_mut() = unsafe { (*act).flags };
*new_ka.flags_mut() = act.flags;
new_ka.set_restorer(None);
}
@ -74,12 +72,12 @@ pub(super) fn do_kernel_sigaction(
// 从用户空间获得sigaction结构体
// TODO mask是default还是用户空间传入
new_ka = Sigaction::new(
SigactionType::SaHandler(SaHandlerType::Customized(unsafe {
VirtAddr::new((*act).handler as usize)
})),
unsafe { (*act).flags },
SigactionType::SaHandler(SaHandlerType::Customized(VirtAddr::new(
input_sighandler as usize,
))),
act.flags,
SigSet::default(),
unsafe { Some(VirtAddr::new((*act).restorer as usize)) },
Some(VirtAddr::new(act.restorer as usize)),
);
}
}
@ -107,23 +105,24 @@ pub(super) fn do_kernel_sigaction(
let retval = super::super::sighand::do_sigaction(
sig,
if act.is_null() {
if new_act == 0 {
None
} else {
Some(&mut new_ka)
},
if old_act.is_null() {
if old_act == 0 {
None
} else {
Some(&mut old_sigaction)
},
);
if (retval == Ok(())) && (!old_act.is_null()) {
let r = UserBufferWriter::new(old_act, core::mem::size_of::<UserSigaction>(), from_user);
if r.is_err() {
return Err(SystemError::EFAULT);
}
if (retval == Ok(())) && (old_act != 0) {
let mut old_act_writer = UserBufferWriter::new(
old_act as *mut UserSigaction,
core::mem::size_of::<UserSigaction>(),
from_user,
)?;
let sigaction_handler = match old_sigaction.action() {
SigactionType::SaHandler(handler) => {
@ -142,15 +141,17 @@ pub(super) fn do_kernel_sigaction(
VirtAddr::new(USER_SIG_DFL as usize)
}
};
let mut old_act_buf = old_act_writer.buffer_protected(0)?;
unsafe {
(*old_act).handler = sigaction_handler.data() as *mut c_void;
(*old_act).flags = old_sigaction.flags();
(*old_act).mask = old_sigaction.mask();
if old_sigaction.restorer().is_some() {
(*old_act).restorer = old_sigaction.restorer().unwrap().data() as *mut c_void;
}
let mut old_act: UserSigaction = old_act_buf.read_one::<UserSigaction>(0)?;
old_act.handler = sigaction_handler.data() as *mut c_void;
old_act.flags = old_sigaction.flags();
old_act.mask = old_sigaction.mask();
if old_sigaction.restorer().is_some() {
old_act.restorer = old_sigaction.restorer().unwrap().data() as *mut c_void;
}
old_act_buf.write_one(0, &old_act)?;
}
compiler_fence(Ordering::SeqCst);
return retval.map(|_| 0);

View File

@ -706,6 +706,40 @@ pub trait MemoryManagementArch: Clone + Copy + Debug {
/// 禁用 内核态的 Write Protect
fn disable_kernel_wp();
/// 带异常表保护的内存拷贝
///
/// 这个函数使用内联汇编实现,并在可能出错的指令处添加异常表条目。
/// 当发生页错误时异常表会将RIP修复到错误处理代码。
///
/// ## 返回值
/// - 0: 成功
/// - -1: 发生页错误
unsafe fn copy_with_exception_table(dst: *mut u8, src: *const u8, len: usize) -> i32 {
// 对于不支持异常表的架构,直接使用普通的内存拷贝
ptr::copy_nonoverlapping(src, dst, len);
0
}
/// 带异常表保护的内存设置memset
///
/// 将 `len` 字节的内存设置为指定的值 `value`。
/// 这个函数使用内联汇编实现,并在可能出错的指令处添加异常表条目。
/// 当发生页错误时异常表会将RIP修复到错误处理代码。
///
/// ## 参数
/// - `dst`: 目标地址
/// - `value`: 要设置的字节值
/// - `len`: 要设置的字节数
///
/// ## 返回值
/// - 0: 成功
/// - -1: 发生页错误
unsafe fn memset_with_exception_table(dst: *mut u8, value: u8, len: usize) -> i32 {
// 对于不支持异常表的架构,直接使用普通的内存设置
ptr::write_bytes(dst, value, len);
0
}
}
/// @brief 虚拟地址范围

View File

@ -65,7 +65,7 @@ use crate::{
cpu::{AtomicProcessorId, ProcessorId},
kick_cpu,
},
syscall::user_access::clear_user,
syscall::user_access::clear_user_protected,
};
use timer::AlarmTimer;
@ -461,7 +461,10 @@ impl ProcessManager {
if let Some(addr) = thread.clear_child_tid {
// 按 Linux 语义:先清零 userland 的 *clear_child_tid再 futex_wake(addr)
unsafe { clear_user(addr, core::mem::size_of::<i32>()).expect("clear tid failed") };
unsafe {
clear_user_protected(addr, core::mem::size_of::<i32>())
.expect("clear tid failed")
};
if Arc::strong_count(&pcb.basic().user_vm().expect("User VM Not found")) > 1 {
// Linux 使用 FUTEX_SHARED 标志来唤醒 clear_child_tid
// 这允许跨进程/线程的同步(例如 pthread_join

View File

@ -27,6 +27,7 @@ use self::{misc::SysInfo, user_access::UserBufferWriter};
pub mod misc;
pub mod table;
pub mod user_access;
pub mod user_buffer;
// 与linux不一致的调用在linux基础上累加
pub const SYS_PUT_STRING: usize = 100000;

View File

@ -16,7 +16,30 @@ use crate::{
process::ProcessManager,
};
use super::SystemError;
use super::{user_buffer::UserBuffer, SystemError};
/// Clear data in the specified range of user space
///
/// ## Arguments
///
/// - `dest`: Destination address in user space
/// - `len`: Length of data to clear
///
/// ## Returns
///
/// Returns the length of cleared data
///
/// ## Errors
///
/// - `EFAULT`: Destination address is invalid
pub unsafe fn clear_user_protected(dest: VirtAddr, len: usize) -> Result<usize, SystemError> {
let mut writer = UserBufferWriter::new(dest.data() as *mut u8, len, true)?;
// Clear user space data
writer.buffer_protected(0)?.clear()?;
compiler_fence(Ordering::SeqCst);
return Ok(len);
}
/// Clear data in the specified range of user space
///
@ -55,16 +78,6 @@ pub unsafe fn copy_to_user(dest: VirtAddr, src: &[u8]) -> Result<usize, SystemEr
return Ok(src.len());
}
/// Copy data from user space to kernel space
pub unsafe fn copy_from_user(dst: &mut [u8], src: VirtAddr) -> Result<usize, SystemError> {
verify_area(src, dst.len()).map_err(|_| SystemError::EFAULT)?;
let src: &[u8] = core::slice::from_raw_parts(src.data() as *const u8, dst.len());
// 拷贝数据
dst.copy_from_slice(src);
return Ok(dst.len());
}
/// Check and copy a C string from user space.
///
/// Returns an error when encountering an invalid address.
@ -99,9 +112,12 @@ pub fn check_and_clone_cstr(
let addr = unsafe { user.add(i) };
let mut c = [0u8; 1];
// 使用受异常表保护的版本,如果用户地址无效会安全返回错误
unsafe {
copy_from_user(&mut c, VirtAddr::new(addr as usize))?;
copy_from_user_protected(&mut c, VirtAddr::new(addr as usize))?;
}
if c[0] == 0 {
break;
}
@ -140,7 +156,10 @@ pub fn check_and_clone_cstr_array(user: *const *const u8) -> Result<Vec<CString>
unsafe {
let dst = [0usize; 1];
let mut dst = core::mem::transmute::<[usize; 1], [u8; size_of::<usize>()]>(dst);
copy_from_user(&mut dst, VirtAddr::new(addr as usize))?;
// 使用受异常表保护的版本
copy_from_user_protected(&mut dst, VirtAddr::new(addr as usize))?;
let dst = core::mem::transmute::<[u8; size_of::<usize>()], [usize; 1]>(dst);
str_ptr = dst[0] as *const u8;
@ -357,6 +376,28 @@ impl UserBufferReader<'_> {
.map_err(|_| SystemError::EINVAL)
}
/// 返回一个受异常表保护的用户缓冲区包装
///
/// 与 `buffer()` 不同,此方法返回的 `UserBuffer` 类型会在所有读写操作中
/// 使用异常表保护的拷贝函数确保访问无效用户地址时能安全返回错误而不是panic
///
/// # 参数
/// - `offset`: 字节偏移量
///
/// # 返回值
/// - `Ok(UserBuffer)`: 受保护的用户缓冲区包装
/// - `Err(SystemError)`: 偏移量无效
pub fn buffer_protected(&'_ self, offset: usize) -> Result<UserBuffer<'_>, SystemError> {
if offset > self.buffer.len() {
return Err(SystemError::EINVAL);
}
let addr = VirtAddr::new(self.buffer.as_ptr() as usize + offset);
let len = self.buffer.len() - offset;
Ok(unsafe { UserBuffer::new(addr, len) })
}
/// Convert user space data to a slice of specified type with page mapping and permission verification
///
/// This function verifies that the pages are mapped AND have the required permissions,
@ -443,6 +484,42 @@ impl UserBufferReader<'_> {
}
}
impl UserBufferReader<'_> {
/// 使用异常保护的方式从用户空间拷贝数据到内核空间
///
/// 此方法使用异常表机制,即使在页错误时也能安全返回错误,
/// 而不是panic或无限循环。
///
/// # Arguments
/// * `dst` - 目标缓冲区(内核空间)
/// * `offset` - 用户缓冲区的字节偏移
///
/// # Returns
/// * `Ok(len)` - 成功拷贝的字节数
/// * `Err(SystemError::EFAULT)` - 访问失败
pub fn copy_from_user_protected(
&self,
dst: &mut [u8],
offset: usize,
) -> Result<usize, SystemError> {
if offset >= self.buffer.len() {
return Err(SystemError::EINVAL);
}
let src_slice = &self.buffer[offset..];
let copy_len = core::cmp::min(dst.len(), src_slice.len());
if copy_len == 0 {
return Ok(0);
}
unsafe {
copy_from_user_protected(
&mut dst[..copy_len],
VirtAddr::new(src_slice.as_ptr() as usize),
)
}
}
}
#[allow(dead_code)]
impl<'a> UserBufferWriter<'a> {
/// Construct a BufferWriter pointing to a user space location
@ -565,6 +642,36 @@ impl<'a> UserBufferWriter<'a> {
Self::convert_with_offset::<T>(self.buffer, offset).map_err(|_| SystemError::EINVAL)
}
/// 返回一个受异常表保护的用户缓冲区包装
///
/// 与 `buffer()` 不同,此方法返回的 `UserBuffer` 类型会在所有读写操作中
/// 使用异常表保护的拷贝函数确保访问无效用户地址时能安全返回错误而不是panic
///
/// # 参数
/// - `offset`: 字节偏移量
///
/// # 返回值
/// - `Ok(UserBuffer)`: 受保护的用户缓冲区包装
/// - `Err(SystemError)`: 偏移量无效
///
/// # 示例
/// ```rust
/// let mut writer = UserBufferWriter::new(user_ptr, len, true)?;
/// let mut buffer = writer.buffer_protected(0)?;
/// // 这个写入是安全的,即使地址无效也会返回 EFAULT 而不是 panic
/// buffer.write_to_user(0, kernel_data)?;
/// ```
pub fn buffer_protected(&'a mut self, offset: usize) -> Result<UserBuffer<'a>, SystemError> {
if offset > self.buffer.len() {
return Err(SystemError::EINVAL);
}
let addr = VirtAddr::new(self.buffer.as_ptr() as usize + offset);
let len = self.buffer.len() - offset;
Ok(unsafe { UserBuffer::new(addr, len) })
}
/// Convert to a mutable slice of specified type with page mapping and permission verification
///
/// This function verifies that the pages are mapped AND have the required permissions,
@ -648,6 +755,43 @@ impl<'a> UserBufferWriter<'a> {
}
}
impl<'a> UserBufferWriter<'a> {
/// 使用异常保护的方式从内核空间拷贝数据到用户空间
///
/// 此方法使用异常表机制,即使在页错误时也能安全返回错误,
/// 而不是panic或无限循环。
///
/// # Arguments
/// * `src` - 源缓冲区(内核空间)
/// * `offset` - 用户缓冲区的字节偏移
///
/// # Returns
/// * `Ok(len)` - 成功拷贝的字节数
/// * `Err(SystemError::EFAULT)` - 访问失败
#[allow(dead_code)]
pub fn copy_to_user_protected(
&'a mut self,
src: &[u8],
offset: usize,
) -> Result<usize, SystemError> {
if offset >= self.buffer.len() {
return Err(SystemError::EINVAL);
}
let dst_slice = &mut self.buffer[offset..];
let copy_len = core::cmp::min(src.len(), dst_slice.len());
if copy_len == 0 {
return Ok(0);
}
unsafe {
copy_to_user_protected(
VirtAddr::new(dst_slice.as_mut_ptr() as usize),
&src[..copy_len],
)
}
}
}
/// Check user access by page table - verifies both page mapping AND permissions
///
/// This function checks if pages are mapped in the page table AND verifies
@ -704,3 +848,74 @@ fn check_user_access_by_page_table(addr: VirtAddr, size: usize, check_write: boo
return true;
}
/// 带异常保护的用户空间数据拷贝
///
/// 与`copy_from_user`不同,此函数使用异常表机制,
/// 即使在页错误时也能安全返回错误
///
/// ## 参数
/// - `dst`: 目标缓冲区(内核空间)
/// - `src`: 源地址(用户空间)
///
/// ## 返回值
/// - `Ok(len)`: 成功拷贝的字节数
/// - `Err(SystemError::EFAULT)`: 访问失败
pub unsafe fn copy_from_user_protected(
dst: &mut [u8],
src: VirtAddr,
) -> Result<usize, SystemError> {
let len = dst.len();
if len == 0 {
return Ok(0);
}
let dst_ptr = dst.as_mut_ptr();
let src_ptr = src.data() as *const u8;
// 快速路径: 页表检查
if !check_user_access_by_page_table(src, len, false) {
return Err(SystemError::EFAULT);
}
// 执行实际拷贝,使用异常表保护
let result = MMArch::copy_with_exception_table(dst_ptr, src_ptr, len);
match result {
0 => Ok(len),
_ => Err(SystemError::EFAULT),
}
}
/// 带异常保护的用户空间写入
///
/// ## 参数
/// - `dest`: 目标地址(用户空间)
/// - `src`: 源缓冲区(内核空间)
///
/// ## 返回值
/// - `Ok(len)`: 成功写入的字节数
/// - `Err(SystemError::EFAULT)`: 访问失败
pub unsafe fn copy_to_user_protected(dest: VirtAddr, src: &[u8]) -> Result<usize, SystemError> {
let len = src.len();
if len == 0 {
return Ok(0);
}
let dst_ptr = dest.data() as *mut u8;
let src_ptr = src.as_ptr();
// 快速路径: 页表检查
if !check_user_access_by_page_table(dest, len, true) {
return Err(SystemError::EFAULT);
}
MMArch::disable_kernel_wp();
let result = MMArch::copy_with_exception_table(dst_ptr, src_ptr, len);
MMArch::enable_kernel_wp();
match result {
0 => Ok(len),
_ => Err(SystemError::EFAULT),
}
}

View File

@ -0,0 +1,249 @@
//! 用户缓冲区包装类型
//!
//! 提供对用户空间缓冲区的安全访问接口,所有操作都通过异常表保护
use crate::mm::VirtAddr;
use system_error::SystemError;
/// 用户空间缓冲区的安全包装
///
/// 这个类型封装了对用户空间内存的访问,确保所有读写操作
/// 都通过异常表机制处理可能的页错误
pub struct UserBuffer<'a> {
/// 用户空间地址
user_addr: VirtAddr,
/// 缓冲区长度
len: usize,
/// 生命周期标记
_phantom: core::marker::PhantomData<&'a ()>,
}
impl<'a> UserBuffer<'a> {
/// 创建一个新的用户缓冲区包装
///
/// # 参数
/// - `addr`: 用户空间地址
/// - `len`: 缓冲区长度
///
/// # 安全性
/// 调用者必须确保地址和长度是有效的用户空间范围
pub(super) unsafe fn new(addr: VirtAddr, len: usize) -> Self {
Self {
user_addr: addr,
len,
_phantom: core::marker::PhantomData,
}
}
/// 获取缓冲区长度
pub fn len(&self) -> usize {
self.len
}
/// 检查缓冲区是否为空
pub fn is_empty(&self) -> bool {
self.len == 0
}
/// 从用户缓冲区读取数据到内核缓冲区
///
/// # 参数
/// - `offset`: 用户缓冲区内的偏移量
/// - `dst`: 目标内核缓冲区
///
/// # 返回值
/// - `Ok(len)`: 成功读取的字节数
/// - `Err(SystemError)`: 读取失败如地址不在VMA中
pub fn read_from_user(&self, offset: usize, dst: &mut [u8]) -> Result<usize, SystemError> {
if offset >= self.len {
return Err(SystemError::EINVAL);
}
let available = self.len - offset;
let copy_len = core::cmp::min(dst.len(), available);
if copy_len == 0 {
return Ok(0);
}
let src_addr = VirtAddr::new(self.user_addr.data() + offset);
unsafe {
crate::syscall::user_access::copy_from_user_protected(&mut dst[..copy_len], src_addr)
}
}
/// 将内核缓冲区数据写入用户缓冲区
///
/// # 参数
/// - `offset`: 用户缓冲区内的偏移量
/// - `src`: 源内核缓冲区
///
/// # 返回值
/// - `Ok(len)`: 成功写入的字节数
/// - `Err(SystemError)`: 写入失败如地址不在VMA中
pub fn write_to_user(&mut self, offset: usize, src: &[u8]) -> Result<usize, SystemError> {
if offset >= self.len {
return Err(SystemError::EINVAL);
}
let available = self.len - offset;
let copy_len = core::cmp::min(src.len(), available);
if copy_len == 0 {
return Ok(0);
}
let dst_addr = VirtAddr::new(self.user_addr.data() + offset);
unsafe { crate::syscall::user_access::copy_to_user_protected(dst_addr, &src[..copy_len]) }
}
/// 从用户缓冲区读取单个值
///
/// # 类型参数
/// - `T`: 要读取的类型
///
/// # 参数
/// - `offset`: 用户缓冲区内的偏移量
///
/// # 返回值
/// - `Ok(value)`: 成功读取的值
/// - `Err(SystemError)`: 读取失败
pub fn read_one<T>(&self, offset: usize) -> Result<T, SystemError> {
let size = core::mem::size_of::<T>();
if offset + size > self.len {
return Err(SystemError::EINVAL);
}
let mut value = core::mem::MaybeUninit::<T>::uninit();
let dst_slice =
unsafe { core::slice::from_raw_parts_mut(value.as_mut_ptr() as *mut u8, size) };
self.read_from_user(offset, dst_slice)?;
Ok(unsafe { value.assume_init() })
}
/// 向用户缓冲区写入单个值
///
/// # 类型参数
/// - `T`: 要写入的类型
///
/// # 参数
/// - `offset`: 用户缓冲区内的偏移量
/// - `value`: 要写入的值
///
/// # 返回值
/// - `Ok(())`: 成功
/// - `Err(SystemError)`: 写入失败
pub fn write_one<T>(&mut self, offset: usize, value: &T) -> Result<(), SystemError> {
let size = core::mem::size_of::<T>();
if offset + size > self.len {
return Err(SystemError::EINVAL);
}
let src_slice =
unsafe { core::slice::from_raw_parts(value as *const T as *const u8, size) };
self.write_to_user(offset, src_slice)?;
Ok(())
}
/// 读取整个用户缓冲区的内容到一个新的Vec
///
/// # 返回值
/// - `Ok(Vec<u8>)`: 包含所有数据的向量
/// - `Err(SystemError)`: 读取失败
pub fn read_all(&self) -> Result<alloc::vec::Vec<u8>, SystemError> {
let mut buffer = vec![0; self.len];
let read_len = self.read_from_user(0, &mut buffer)?;
// 如果读取的长度小于分配的长度,调整
if read_len < self.len {
buffer.truncate(read_len);
}
Ok(buffer)
}
/// 将数据写入整个用户缓冲区
///
/// # 参数
/// - `data`: 要写入的数据
///
/// # 返回值
/// - `Ok(len)`: 成功写入的字节数
/// - `Err(SystemError)`: 写入失败
pub fn write_all(&mut self, data: &[u8]) -> Result<usize, SystemError> {
self.write_to_user(0, data)
}
/// 将用户缓冲区的指定范围清零
///
/// 这个方法使用带异常表保护的 memset 实现,直接将用户空间内存设置为零。
/// 如果访问的地址不在有效的 VMA 中,会安全地返回错误而不会导致内核崩溃。
///
/// # 参数
/// - `offset`: 要清零的起始偏移量
/// - `len`: 要清零的字节数
///
/// # 返回值
/// - `Ok(())`: 成功清零
/// - `Err(SystemError)`: 清零失败如地址不在VMA中
///
/// # 示例
/// ```rust
/// let mut buffer = old_act_writer.buffer_protected(0)?;
/// buffer.clear_range(0, 64)?; // 清零前64字节
/// ```
pub fn clear_range(&mut self, offset: usize, len: usize) -> Result<(), SystemError> {
use crate::arch::MMArch;
use crate::mm::MemoryManagementArch;
if len == 0 {
return Ok(());
}
if offset >= self.len {
return Err(SystemError::EINVAL);
}
let available = self.len - offset;
let clear_len = core::cmp::min(len, available);
if clear_len == 0 {
return Ok(());
}
let dst_addr = (self.user_addr.data() + offset) as *mut u8;
// 使用架构相关的带异常表保护的 memset
let result = unsafe { MMArch::memset_with_exception_table(dst_addr, 0, clear_len) };
if result == 0 {
Ok(())
} else {
Err(SystemError::EFAULT)
}
}
/// 将整个用户缓冲区清零
///
/// 这个方法是 `clear_range(0, self.len())` 的便捷封装。
/// 使用带异常表保护的 memset 实现,高效且安全。
///
/// # 返回值
/// - `Ok(())`: 成功清零
/// - `Err(SystemError)`: 清零失败
///
/// # 示例
/// ```rust
/// let mut buffer = old_act_writer.buffer_protected(0)?;
/// buffer.clear()?; // 清零整个缓冲区
/// ```
pub fn clear(&mut self) -> Result<(), SystemError> {
self.clear_range(0, self.len)
}
}

View File

@ -0,0 +1,86 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
int main() {
printf("=== DragonOS Exception Table Test ===\n\n");
// 测试1: open() 使用未映射的路径指针
printf("Test 1: open() with unmapped path pointer\n");
char *bad_path = (char *)0x1000; // 未映射地址
int fd = open(bad_path, O_RDONLY);
if (fd == -1 && errno == EFAULT) {
printf(" ✓ PASS: open returned -1 with EFAULT\n");
} else {
printf(" ✗ FAIL: open returned %d, errno=%d\n", fd, errno);
if (fd >= 0) close(fd);
}
// 测试2: open() 使用已释放的内存作为路径
printf("\nTest 2: open() with freed memory path\n");
void *mem = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mem == MAP_FAILED) {
printf(" ✗ FAIL: mmap failed\n");
return 1;
}
strcpy(mem, "/tmp/test");
munmap(mem, 4096);
fd = open(mem, O_RDONLY);
if (fd == -1 && errno == EFAULT) {
printf(" ✓ PASS: open returned -1 with EFAULT after munmap\n");
} else {
printf(" ✗ FAIL: open returned %d, errno=%d\n", fd, errno);
if (fd >= 0) close(fd);
}
// 测试3: stat() 使用无效路径指针
printf("\nTest 3: stat() with invalid path pointer\n");
struct stat st;
int ret = stat(bad_path, &st);
if (ret == -1 && errno == EFAULT) {
printf(" ✓ PASS: stat returned -1 with EFAULT\n");
} else {
printf(" ✗ FAIL: stat returned %d, errno=%d\n", ret, errno);
}
// 测试4: access() 使用无效路径指针
printf("\nTest 4: access() with invalid path pointer\n");
ret = access(bad_path, F_OK);
if (ret == -1 && errno == EFAULT) {
printf(" ✓ PASS: access returned -1 with EFAULT\n");
} else {
printf(" ✗ FAIL: access returned %d, errno=%d\n", ret, errno);
}
// 测试5: 正常的 open 应该工作
printf("\nTest 5: normal open should work\n");
fd = open("/", O_RDONLY);
if (fd >= 0) {
printf(" ✓ PASS: normal open succeeded (fd=%d)\n", fd);
close(fd);
} else {
printf(" ✗ FAIL: normal open failed\n");
}
// 测试6: execve 使用无效的程序路径
printf("\nTest 6: execve() with invalid path pointer\n");
char *argv[] = { NULL };
char *envp[] = { NULL };
ret = execve(bad_path, argv, envp);
if (ret == -1 && errno == EFAULT) {
printf(" ✓ PASS: execve returned -1 with EFAULT\n");
} else {
printf(" ✗ FAIL: execve returned %d, errno=%d (should not reach here)\n", ret, errno);
}
printf("\n=== All tests completed ===\n");
return 0;
}