Skip to content

第一百六十二章:SELinux·铁面

渡劫期

涉及内核源码:

林小源在 namespaces 的虚空中继续前行,来到一座铁灰色的城堡前。城堡的城门紧闭,门上刻着一个严厉的面孔。

"你是谁?"林小源问。

"SELinux,"面孔开口说话,声音冰冷,"Security-Enhanced Linux。我实现了强制访问控制 (MAC)。"

"强制访问控制?"

"传统的 DAC 让文件所有者决定权限,"SELinux 说,"但文件所有者可能犯错,可能被欺骗。MAC 让系统管理员统一控制——即使是 root,也受限于策略。"

林小源感到一阵压迫。在这位铁面守护者面前,没有人能例外。

"我的核心是类型强制 (Type Enforcement),"SELinux 说,"每个进程有类型 (domain),每个文件有类型 (type)。策略定义 domain 对 type 的访问权限。"

"能举个例子吗?"

"httpd 进程的类型是 httpd_t,网页文件的类型是 httpd_sys_content_t。策略允许 httpd_t 读取 httpd_sys_content_t,但不允许写入。即使 httpd 进程被攻击,它也无法修改网页文件。"

"那日志文件呢?"

"httpd_log_t,"SELinux 说,"httpd_t 只能追加,不能读取。即使攻击者控制了 httpd 进程,也无法读取日志中的敏感信息。"

林小源点头。读写执行只是最粗的粒度,真正的控制可以细到每一个文件的类型。

"但很多人不喜欢你,"林小源说。

SELinux 的面孔没有任何表情:"我知道。我的策略很复杂,配置错误会导致服务无法启动。很多人选择直接禁用我。"

"那你怎么办?"

"我有三种模式,"SELinux 说,"enforcing 强制执行策略,违规被阻止并记录。permissive 只记录不阻止,用于调试。disabled 完全禁用。"

"建议用哪种模式?"

"enforcing,"SELinux 说,"安全需要代价。复杂是安全的代价,但不是禁用的理由。"

林小源望着那张铁面,心中肃然。在这片内核的深处,铁面无私是守护的代价。


c
/*
 * SELinux 的核心概念:
 *
 * 1. DAC vs MAC
 *    DAC: 文件所有者决定权限
 *    MAC: 系统管理员统一控制
 *
 * 2. 安全上下文 (security context)
 *    user:role:type:level
 *    例如: unconfined_u:object_r:httpd_sys_content_t:s0
 *
 * 3. 类型强制 (Type Enforcement)
 *    每个进程有类型 (domain)
 *    每个文件有类型 (type)
 *    策略定义 domain 对 type 的访问
 *
 * 4. 策略规则
 *    allow source target:class permissions;
 *    allow httpd_t httpd_sys_content_t:file read;
 *
 * SELinux 模式:
 *   enforcing — 强制执行
 *   permissive — 只记录不阻止
 *   disabled — 禁用
 */

/* 模拟安全上下文 */
struct security_context {
    char user[32];
    char role[32];
    char type[64];
    char level[32];
};

void print_context(const char *name, struct security_context *ctx) {
    printf("%s: %s:%s:%s:%s\n", name,
           ctx->user, ctx->role, ctx->type, ctx->level);
}

/* 模拟策略规则 */
struct policy_rule {
    char source[64];   /* 源类型 */
    char target[64];   /* 目标类型 */
    char class[32];    /* 对象类 */
    char perm[32];     /* 权限 */
};

struct policy_rule rules[] = {
    {"httpd_t", "httpd_sys_content_t", "file", "read"},
    {"httpd_t", "httpd_sys_content_t", "dir", "search"},
    {"httpd_t", "httpd_log_t", "file", "append"},
    {"sshd_t", "sshd_var_run_t", "file", "read"},
    {"sysadm_t", "unconfined_t", "process", "sigkill"},
};

int check_access(const char *source, const char *target,
                 const char *class, const char *perm) {
    int n = sizeof(rules) / sizeof(rules[0]);
    for (int i = 0; i < n; i++) {
        if (strcmp(rules[i].source, source) == 0 &&
            strcmp(rules[i].target, target) == 0 &&
            strcmp(rules[i].class, class) == 0 &&
            strcmp(rules[i].perm, perm) == 0) {
            return 1; /* 允许 */
        }
    }
    return 0; /* 拒绝 */
}

printf("=== SELinux·铁面 — 强制访问控制 ===\n\n");

printf("SELinux 实现强制访问控制 (MAC)\n\n");

/* 安全上下文 */
struct security_context httpd = {
    "system_u", "system_r", "httpd_t", "s0"
};
struct security_context webfile = {
    "system_u", "object_r", "httpd_sys_content_t", "s0"
};
struct security_context logfile = {
    "system_u", "object_r", "httpd_log_t", "s0"
};

printf("--- 安全上下文 ---\n");
print_context("httpd 进程", &httpd);
print_context("网页文件", &webfile);
print_context("日志文件", &logfile);

printf("\n--- 访问检查 ---\n");
/* httpd 读网页文件 */
int r1 = check_access("httpd_t", "httpd_sys_content_t", "file", "read");
printf("httpd 读网页文件: %s\n", r1 ? "允许" : "拒绝");

/* httpd 写网页文件 */
int r2 = check_access("httpd_t", "httpd_sys_content_t", "file", "write");
printf("httpd 写网页文件: %s\n", r2 ? "允许" : "拒绝");

/* httpd 追加日志 */
int r3 = check_access("httpd_t", "httpd_log_t", "file", "append");
printf("httpd 追加日志: %s\n", r3 ? "允许" : "拒绝");

printf("\n--- SELinux 模式 ---\n");
printf("enforcing:\n");
printf("  强制执行策略\n");
printf("  违规被阻止并记录\n\n");
printf("permissive:\n");
printf("  只记录不阻止\n");
printf("  用于调试\n\n");
printf("disabled:\n");
printf("  完全禁用\n");

printf("\n--- DAC vs MAC ---\n");
printf("DAC:\n");
printf("  文件所有者决定权限\n");
printf("  root 可以做任何事\n\n");
printf("MAC:\n");
printf("  系统管理员统一控制\n");
printf("  root 也受限\n");

道藏笔记

内核启示

传统 DAC 让文件所有者说了算,root 更是无法无天。SELinux 不吃这套——它搞的是强制访问控制(MAC),连 root 都得按策略来。

核心是类型强制:每个进程有个 type 叫 domain,每个文件也有 type,策略规定哪个 domain 能对哪个 type 做什么。httpd_t 能读 httpd_sys_content_t,但不能写;能追加 httpd_log_t,但不能读。就算 httpd 被打了,攻击者也干不了策略以外的事。

SELinux 有三种模式:enforcing 真正拦截,permissive 只记不拦(调试用),disabled 关掉。很多人嫌复杂直接禁了,但安全本来就有代价。

SELinux 是铁面——即使是 root,也受限于策略。


破关试炼

SELinux 之试

铁面一章中,按标签和策略强制访问控制的安全模块叫什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道