跳转到内容

安全模型

OpenOS 的安全模型基于能力(Capability)系统,通过 Handle 和 Rights 实现细粒度的访问控制。

原则: 进程无法访问任何资源,除非它持有具有适当权限的 Handle。

实现:

  • 没有”当前目录”
  • 没有”环境变量”
  • 没有”默认搜索路径”
  • 每个资源访问都是显式的

好处:

  • 消除了环境权限带来的安全风险
  • 权限委托是显式的
  • 容易审计权限来源

原则: 每个进程只拥有完成其任务所需的最小权限。

实现:

  • Handle 权限是细粒度的(10 个独立的权限位)
  • 权限只能收窄,不能放大
  • 创建进程时显式授予权限

原则: 权限只能沿传递链减少,不能增加。

实现:

原始 Handle: READ | WRITE | TRANSFER | MAP
传输 Rights: READ | MAP
──────────────────────────────
接收方获得: READ | MAP (交集)

好处:

  • 消除了权限提升攻击
  • 权限委托是安全的
  • 容易追踪权限来源

原则: 一个组件的故障不应影响其他组件。

实现:

  • 用户空间驱动隔离
  • 独立的地址空间
  • 进程间通过 IPC 通信

好处:

  • 驱动崩溃不会导致内核崩溃
  • 可以独立重启服务
  • 更容易进行故障恢复

Handle 是 OpenOS 中的唯一访问控制机制:

struct Handle {
object_id: u32, // 内核对象 ID
rights: Rights, // 权限位掩码
generation: u32, // 防止关闭后使用
}
bit 0: READ - 从对象读取数据
bit 1: WRITE - 向对象写入数据
bit 2: EXECUTE - 执行(用于内存对象)
bit 3: TRANSFER - 通过 Channel 发送此 Handle
bit 4: DUPLICATE - 在同一进程内克隆此 Handle
bit 5: SIGNAL - 发送信号
bit 6: WAIT - 等待对象
bit 7: DESTROY - 关闭/销毁对象
bit 8: MAP - 映射到地址空间
bit 9: CONFIGURE - 修改对象属性
// 权限只能收窄,不能放大
fn handle_duplicate(handle: Handle, new_rights: Rights) -> Handle {
// new_rights 必须是原始权限的子集
let effective_rights = handle.rights & new_rights;
Handle {
object_id: handle.object_id,
rights: effective_rights,
generation: handle.generation + 1,
}
}
fn handle_transfer(handle: Handle, target: Handle, rights: Rights) -> Result<(), Error> {
// rights 是交集
let effective_rights = handle.rights & rights;
// 传输给接收方
// ...
}

OpenOS 没有全局的访问控制列表或权限表:

  • 没有 /etc/passwd
  • 没有全局文件描述符表
  • 没有环境变量
  • 每个进程独立的 Handle 表
// 启动进程(拥有所有初始 Handle)
let fs_handle = /* ... */;
let net_handle = /* ... */;
let console_handle = /* ... */;
// 委托给文件系统服务器(只读)
handle_transfer(fs_handle, fs_channel, READ);
// 委托给网络服务器(读写)
handle_transfer(net_handle, net_channel, READ | WRITE);
// 委托给用户 Shell(只写)
handle_transfer(console_handle, console_channel, WRITE);
// 撤回对资源的访问
handle_close(fs_handle);
// 如果这是最后一个 Handle,对象被销毁
// 所有映射的内存被释放
// 所有等待的线程被唤醒(返回错误)

TOCTOU(Time-of-Check to Time-of-Use)是常见的安全漏洞:

// 不安全的 POSIX 代码
if (access(file, W_OK) == 0) { // 检查
// 攻击者在这里替换文件
fd = open(file, O_WRONLY); // 使用
write(fd, data, len);
}

由于 Channel 通信是同步和原子的,不存在 TOCTOU 竞态:

// 安全的 OpenOS 代码
let reply = channel_call(fs_handle, ReadMsg { path, offset, len });
// 检查和操作是原子的

服务器处理请求并在一个事务中发送回复。没有时间窗口让攻击者介入。

OpenOS 使用 Rust 语言,编译期内存安全:

// 编译时捕获内存安全问题
fn example() {
let mut data = vec![1, 2, 3];
let reference = &data[0]; // 不可变借用
data.push(4); // 错误!不能在借用期间修改
println!("{}", reference);
}

unsafe 代码明确标记硬件交互点:

// 明确标记 unsafe
unsafe fn port_read(port: u16) -> u8 {
let mut port = Port::new(port);
port.read()
}
// 所有 unsafe 调用都是显式的
let value = unsafe { port_read(0x60) };
extern "x86-interrupt" fn handler(stack_frame: InterruptStackFrame) {
// 1. 不得阻塞
// 2. 必须发送 EOI
// 3. 避免死锁(不获取已持有的锁)
// 处理中断
// ...
// 发送 EOI
unsafe {
PICS.lock().notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
}
}
// 使用 without_interrupts 保护共享数据
fn print_something() {
x86_64::instructions::interrupts::without_interrupts(|| {
// 在此期间中断被禁用
// 安全地获取 VGA 锁
VGA.lock().write_str("Hello");
});
}
fn validate_message(msg: &[u8], handles: &[Handle]) -> Result<(), Error> {
// 检查消息大小
if msg.len() > MAX_MESSAGE_SIZE {
return Err(Error::TooBig);
}
// 检查 Handle 数量
if handles.len() > MAX_HANDLES_PER_MESSAGE {
return Err(Error::TooManyHandles);
}
// 验证每个 Handle
for handle in handles {
validate_handle(*handle)?;
}
Ok(())
}
fn check_send_permission(handle: Handle, port_id: u64) -> Result<(), Error> {
let entry = handle_table.lookup(handle)?;
// 检查是否有写权限
if !entry.rights.contains(WRITE) {
return Err(Error::PermissionDenied);
}
// 检查端口是否属于当前进程
if !port_belongs_to_process(port_id) {
return Err(Error::PermissionDenied);
}
Ok(())
}
方面POSIXOpenOS
访问控制UID/GID + 文件权限Handle + Rights
权限粒度粗粒度(读/写/执行)细粒度(10 个独立位)
环境权限全局(PATH, HOME 等)无环境权限
委托传递 FD 号通过 Channel 传递 Handle
撤回无法撤回关闭 Handle
TOCTOU存在竞态原子操作
方面SELinuxOpenOS
模型强制访问控制能力系统
配置复杂的策略文件显式 Handle 委托
性能开销较大原生性能
灵活性中等
// 只授予必要的权限
handle_transfer(resource, channel, READ); // 而不是 READ | WRITE
// 显式传递 Handle,而不是依赖环境
let (server, client) = channel_create(0);
handle_transfer(resource, server, READ);
process_start(child, thread, entry, stack, client);
// 不再需要时关闭 Handle
handle_close(resource_handle);
// 验证所有外部输入
fn process_request(msg: &[u8]) -> Result<Response, Error> {
if msg.len() < MIN_MESSAGE_SIZE {
return Err(Error::InvalidMessage);
}
// ...
}
功能状态说明
Handle 系统[WIP] 开发中基本实现
Rights 管理[WIP] 开发中基本实现
权限检查[WIP] 开发中基本实现
进程隔离[plan] 计划中需要完整的内存管理
安全审计[plan] 计划中日志和监控