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:
parent
58cddf7629
commit
d93301b512
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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过不了,暂时禁用"
|
||||
|
||||
|
|
|
|||
|
|
@ -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`指令序列
|
||||
|
||||
核心思想保持不变,只需调整汇编语法。
|
||||
|
|
@ -11,4 +11,5 @@
|
|||
|
||||
intro
|
||||
allocate-memory
|
||||
mmio
|
||||
mmio
|
||||
extable_safe_copy_design
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ SECTIONS
|
|||
|
||||
_etext = .;
|
||||
__etext = .;
|
||||
|
||||
/* 异常表段 */
|
||||
. = ALIGN(8);
|
||||
__start___ex_table = .;
|
||||
*(__ex_table)
|
||||
__stop___ex_table = .;
|
||||
}
|
||||
. = ALIGN(4096);
|
||||
data_start_pa = .;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取保护标志的映射表
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 虚拟地址范围
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue