os/arch/riscv/trap/
sum_guard.rs

1//! SUM (permit Supervisor User Memory access) 位保护器
2//!
3//! 提供 RAII 方式管理 sstatus 寄存器的 SUM 位,确保在访问用户空间内存时
4//! 正确设置和清除 SUM 位,即使发生 panic 也能正确恢复。
5
6use core::ops::Drop;
7use riscv::register::sstatus;
8
9/// SUM 位保护器,基于 RAII 实现用户空间内存访问保护。
10///
11/// 在创建时保存 sstatus.SUM 位的当前状态并设置为 1(允许内核访问用户空间内存);
12/// 在销毁时自动恢复之前保存的状态。
13///
14/// 这种设计允许安全的嵌套使用:
15/// - 如果外层已经设置了 SUM 位,内层 guard 不会重复设置,也不会在销毁时清除
16/// - 只有最外层的 guard 会在销毁时清除 SUM 位
17///
18/// # Safety
19///
20/// 此 guard 必须在访问用户空间内存之前创建,并在访问完成后立即销毁。
21/// 不应该长时间持有此 guard,因为它会降低内核的安全性。
22///
23/// # 使用示例
24///
25/// ```ignore
26/// // 读取用户空间指针
27/// let user_value = {
28///     let _guard = SumGuard::new();
29///     unsafe { core::ptr::read(user_ptr) }
30/// }; // 离开作用域,自动恢复 SUM 位
31/// ```
32///
33/// # 为什么需要此 guard
34///
35/// 手动调用 `sstatus::set_sum()` 和 `sstatus::clear_sum()` 存在安全隐患:
36/// 如果在两次调用之间发生 panic(例如,由于无效的用户指针导致缺页异常无法处理),
37/// `clear_sum()` 将不会被执行,导致 SUM 位保持置位状态。这会使内核在后续执行中
38/// 意外地允许访问用户空间内存,可能导致安全漏洞。
39///
40/// 使用此 RAII guard,即使发生 panic,Rust 的 drop 机制也会确保 SUM 位被正确恢复。
41pub struct SumGuard {
42    /// 创建 guard 前 SUM 位是否已设置
43    was_set: bool,
44}
45
46impl SumGuard {
47    /// 创建一个新的 SumGuard,保存当前 SUM 位状态并设置为 1。
48    ///
49    /// # Safety
50    ///
51    /// 调用者必须确保:
52    /// 1. 即将访问的用户空间地址是有效的
53    /// 2. 不会长时间持有此 guard
54    /// 3. 在 guard 生命周期内访问的所有用户空间指针都已经过验证
55    #[inline]
56    pub fn new() -> Self {
57        // 保存当前 SUM 位状态
58        let was_set = sstatus::read().sum();
59
60        // 如果 SUM 位尚未设置,则设置它
61        if !was_set {
62            // SAFETY: 设置 SUM 位以允许访问用户空间内存
63            unsafe { sstatus::set_sum() };
64        }
65
66        SumGuard { was_set }
67    }
68
69    /// 检查在创建此 guard 前,SUM 位是否已经被设置
70    #[allow(dead_code)]
71    pub fn was_set(&self) -> bool {
72        self.was_set
73    }
74}
75
76impl Drop for SumGuard {
77    /// 当 SumGuard 离开作用域时,恢复之前保存的 SUM 位状态。
78    #[inline]
79    fn drop(&mut self) {
80        // 只有在创建 guard 前 SUM 位未设置时,才清除它
81        // 这确保了嵌套使用的正确性
82        if !self.was_set {
83            // SAFETY: 恢复之前的状态
84            unsafe { sstatus::clear_sum() };
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::{kassert, println, test_case};
93
94    // 测试 SumGuard::new() 是否成功设置 SUM 位
95    test_case!(test_guard_sets_sum, {
96        println!("Testing: test_guard_sets_sum");
97
98        // 确保初始状态下 SUM 未设置
99        unsafe { sstatus::clear_sum() };
100        kassert!(!sstatus::read().sum());
101
102        {
103            let _guard = SumGuard::new();
104            // SUM 位应该已设置
105            kassert!(sstatus::read().sum());
106        }
107
108        // 离开作用域后 SUM 位应该被清除
109        kassert!(!sstatus::read().sum());
110    });
111
112    // 测试嵌套 SumGuard(现在应该能正确工作)
113    test_case!(test_guard_nested, {
114        println!("Testing: test_guard_nested");
115
116        unsafe { sstatus::clear_sum() };
117        kassert!(!sstatus::read().sum());
118
119        {
120            let guard1 = SumGuard::new();
121            kassert!(sstatus::read().sum());
122            kassert!(!guard1.was_set()); // 第一个 guard 设置了 SUM 位
123
124            {
125                let guard2 = SumGuard::new();
126                kassert!(sstatus::read().sum());
127                kassert!(guard2.was_set()); // 第二个 guard 发现 SUM 已设置
128            }
129
130            // 内层 guard 销毁后,SUM 位应该仍然为 1(因为外层还需要它)
131            kassert!(sstatus::read().sum());
132        }
133
134        // 外层 guard 销毁后,SUM 位才被清除
135        kassert!(!sstatus::read().sum());
136    });
137
138    // 测试 panic 时 SumGuard 是否能正确清理
139    // 注意:此测试需要 panic handler 支持
140    // test_case!(test_guard_cleans_on_panic, {
141    //     println!("Testing: test_guard_cleans_on_panic");
142    //     // 此测试需要特殊的 panic 处理机制
143    // });
144}