Skip to content

第一百六十一章:隔离之法

渡劫期

涉及内核源码:

林小源离开沙箱平原,来到一片奇异的空间。这里没有天空,没有地面,只有无数透明的泡泡漂浮在虚空中。

"这是什么地方?"林小源问。

"namespaces,"nsproxy.c 说,他是一位空间编织者,手中握着一根银色的丝线。"每个泡泡都是一个独立的世界——独立的 PID、网络、文件系统。进程在泡泡里看到的一切,都是隔离的。"

林小源走近一个泡泡,看到里面有一个完整的系统:进程在运行,网络在通信,文件系统在挂载。但从外面看,这一切都是虚幻的。

"这就是容器的基础?"

"是的,"编织者说,"容器 = namespaces + cgroups + seccomp。namespaces 隔离视图,cgroups 限制资源,seccomp 限制系统调用。三层防御,层层叠加。"

"USER namespace 特别重要,"编织者说,他指向一个泡泡,"看,里面有一个 root 用户,uid=0。但在宿主机上,它映射为 uid=1000——一个普通用户。"

"这意味着什么?"

"即使容器逃逸,攻击者也只是普通用户,"编织者说,"USER namespace 让容器内的 root 不等于宿主机的 root。这大大降低了风险。"

林小源望着那个泡泡,忽然明白了一件事——隔离不只是看不见,是真的没有那个权限。

"但容器不是绝对安全的,"编织者的声音变得严肃,"内核漏洞、配置错误、特权容器——都可能导致容器逃逸。"

"那怎么防御?"

"非特权容器、最小权限、内核更新,"编织者说,"每一层都有可能被突破,但突破所有层很难。这就是纵深防御的意义。"

林小源望着虚空中无数的泡泡,心中暗下决心。在这片内核的深处,隔离是安全的基石。


c
/*
 * Linux namespaces:
 *
 * PID namespace — 进程 ID 隔离
 *   进程有自己的 PID 空间
 *   PID 1 是 init 进程
 *
 * NET namespace — 网络隔离
 *   独立的网络接口、路由表
 *   独立的端口空间
 *
 * MNT namespace — 挂载点隔离
 *   独立的文件系统视图
 *
 * UTS namespace — 主机名隔离
 *   独立的 hostname
 *
 * IPC namespace — 进程间通信隔离
 *   独立的共享内存、信号量
 *
 * USER namespace — 用户隔离
 *   容器内 root ≠ 宿主机 root
 *
 * Cgroup namespace — cgroup 隔离
 *   隐藏 cgroup 层级
 *
 * Time namespace — 时间隔离
 *   独立的时钟偏移
 */

/* 模拟 namespace 信息 */
struct ns_info {
    const char *name;
    const char *purpose;
    int isolated;
};

struct ns_info namespaces[] = {
    {"PID", "进程 ID 隔离", 1},
    {"NET", "网络隔离", 1},
    {"MNT", "挂载点隔离", 1},
    {"UTS", "主机名隔离", 1},
    {"IPC", "进程间通信隔离", 1},
    {"USER", "用户隔离", 1},
    {"Cgroup", "cgroup 隔离", 1},
    {"Time", "时间隔离", 1},
};

printf("=== 隔离之法 — namespaces 安全 ===\n\n");

printf("namespaces 提供进程隔离:\n\n");

int n = sizeof(namespaces) / sizeof(namespaces[0]);
for (int i = 0; i < n; i++) {
    printf("%s namespace:\n", namespaces[i].name);
    printf("  %s\n", namespaces[i].purpose);
    printf("  隔离: %s\n", namespaces[i].isolated ? "是" : "否");
}

printf("\n--- PID namespace ---\n");
printf("容器内:\n");
printf("  PID 1: 容器 init\n");
printf("  PID 2: 容器进程\n\n");
printf("宿主机:\n");
printf("  PID 12345: 容器 init\n");
printf("  PID 12346: 容器进程\n\n");
printf("容器内 kill 1:\n");
printf("  只影响容器\n");

printf("\n--- NET namespace ---\n");
printf("容器有独立的:\n");
printf("  网络接口 (eth0)\n");
printf("  IP 地址\n");
printf("  路由表\n");
printf("  端口空间\n\n");
printf("容器监听 80:\n");
printf("  不影响宿主机\n");

printf("\n--- USER namespace ---\n");
printf("容器内 root (uid=0):\n");
printf("  宿主机映射为 uid=1000\n");
printf("  只有容器内权限\n\n");
printf("安全价值:\n");
printf("  容器逃逸 ≠ 宿主机 root\n");

printf("\n--- 容器安全 ---\n");
printf("容器 = namespaces + cgroups + seccomp\n\n");
printf("namespaces:\n");
printf("  隔离视图\n");
printf("cgroups:\n");
printf("  限制资源\n");
printf("seccomp:\n");
printf("  限制系统调用\n\n");

printf("--- 容器逃逸 ---\n");
printf("风险:\n");
printf("  内核漏洞\n");
printf("  配置错误\n");
printf("  特权容器\n\n");
printf("防御:\n");
printf("  非特权容器\n");
printf("  最小权限\n");
printf("  内核更新\n");

道藏笔记

内核启示

namespaces 就是给进程造泡泡——泡泡里看到的世界跟外面完全不同。PID namespace 让容器觉得自己有 PID 1,NET namespace 让它有自己的网卡和端口,MNT namespace 让它有自己的文件系统。USER namespace 更狠,容器里的 root 映射到宿主机上只是个普通用户,逃逸了也没啥大用。

容器安全靠三层防线叠在一起:namespaces 管隔离(你看到啥),cgroups 管限制(你能用多少),seccomp 管调用(你能干啥)。单独一层都可能被突破,但三层全破就很难了。

namespaces 是隔离——让容器成为独立的世界。


破关试炼

隔离之试

隔离之法中,进程看到的资源视图被拆开的基础机制英文名是什么?

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

以修仙之名,悟内核之道