C++ / a working model

159 / 163   ·   C11   ·   约 8 分钟

访问控制

先记住这句话

本章介绍操作系统如何将既定安全策略转化为对每次资源请求的允许或拒绝判定,重点讨论参考监视器的职责、主体与客体模型,以及访问控制列表和能力两种实现路径在效率与灵活性上的差异。

本篇内容
  1. 访问判定的核心挑战
  2. 主体、客体与授权
  3. 列表与令牌:两种经典机制
  4. 虚拟化带来的廉价特例
  5. 运行示例
  6. 动手练习

官方章节 PDF

访问判定的核心挑战

身份一旦确认,内核必须立即根据策略判断该次请求是否合法。判定发生在系统调用陷入之后、真正执行之前,由一段被称为参考监视器的代码完成。监视器必须既正确又足够快,否则要么放过非法操作,要么让整个系统变得不可用。

主体、客体与授权

主体通常是代表某用户运行的进程,客体是文件、设备或内存区域,访问模式则是读、写或执行。授权就是检查“这个主体能否以这种模式接触这个客体”的三元组是否被策略允许。任何遗漏都会破坏完全中介原则。

列表与令牌:两种经典机制

访问控制列表把允许的主体名单贴在客体上,像夜店门口的宾客名单。能力则是发给主体的不可伪造令牌,像一把只能开特定门的钥匙。列表便于集中撤销,令牌便于分散持有;操作系统必须在这两种数据结构之间做出选择。

虚拟化带来的廉价特例

一旦进程获得了专属的虚拟内存或虚拟设备,后续每次访问都不必再经过软件监视器,硬件页表或设备映射已经完成隔离。这种“先授权、后自由使用”的模式把检查成本摊到了映射建立的那一次,从而在不牺牲安全的前提下大幅降低开销。

常见误区

  • 只在打开时检查而忽略后续读写会留下 TOCTOU 窗口。
  • 能力被复制后几乎无法精确撤销。
  • 过粗的 ACL 会让管理员无法表达细粒度策略。

运行一个例子

最低标准 C11 · 完整程序 · 下载 .c

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

bool check_access(const char *subject, const char *object, char access) {
    if (strcmp(object, "/home/report") == 0) {
        if (strcmp(subject, "alice") == 0) return (access == 'r' || access == 'w');
        if (strcmp(subject, "bob") == 0) return (access == 'r');
        return false;
    }
    if (strcmp(object, "/etc/config") == 0) {
        return (strcmp(subject, "root") == 0);
    }
    return false;
}

int main(void) {
    struct {
        const char *user;
        const char *file;
        char mode;
    } tests[] = {
        {"alice", "/home/report", 'w'},
        {"bob", "/home/report", 'w'},
        {"bob", "/home/report", 'r'},
        {"root", "/etc/config", 'r'},
        {"alice", "/etc/config", 'r'}
    };
    int n = sizeof(tests) / sizeof(tests[0]);
    for (int i = 0; i < n; i++) {
        bool ok = check_access(tests[i].user, tests[i].file, tests[i].mode);
        printf("%s %c %s -> %s\n", tests[i].user, tests[i].mode, tests[i].file, ok ? "yes" : "no");
    }
    return 0;
}

在本地编译

gcc -std=c11 -Wall -Wextra -Wpedantic -Werror ostep-55-access-control.c -o example && ./example

预期结果

alice w /home/report -> yes
bob w /home/report -> no
bob r /home/report -> yes
root r /etc/config -> yes
alice r /etc/config -> no

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

操作系统为何通常只在 open 时做一次访问检查,而不是每次 read/write 都检查?

查看参考答案

打开成功后返回的文件描述符本身就充当了一种能力,后续操作直接使用该描述符即可,既保持了策略约束又避免了重复的内核陷入开销。

继续查证

标准草案与官方章节会更新;版本标记只说明示例最低要求。

回到目录