第一百六十一章:隔离之法
渡劫期涉及内核源码:
一
林小源离开沙箱平原,来到一片奇异的空间。这里没有天空,没有地面,只有无数透明的泡泡漂浮在虚空中。
"这是什么地方?"林小源问。
"namespaces,"nsproxy.c 说,他是一位空间编织者,手中握着一根银色的丝线。"每个泡泡都是一个独立的世界——独立的 PID、网络、文件系统。进程在泡泡里看到的一切,都是隔离的。"
林小源走近一个泡泡,看到里面有一个完整的系统:进程在运行,网络在通信,文件系统在挂载。但从外面看,这一切都是虚幻的。
"这就是容器的基础?"
"是的,"编织者说,"容器 = namespaces + cgroups + seccomp。namespaces 隔离视图,cgroups 限制资源,seccomp 限制系统调用。三层防御,层层叠加。"
二
"USER namespace 特别重要,"编织者说,他指向一个泡泡,"看,里面有一个 root 用户,uid=0。但在宿主机上,它映射为 uid=1000——一个普通用户。"
"这意味着什么?"
"即使容器逃逸,攻击者也只是普通用户,"编织者说,"USER namespace 让容器内的 root 不等于宿主机的 root。这大大降低了风险。"
林小源望着那个泡泡,忽然明白了一件事——隔离不只是看不见,是真的没有那个权限。
三
"但容器不是绝对安全的,"编织者的声音变得严肃,"内核漏洞、配置错误、特权容器——都可能导致容器逃逸。"
"那怎么防御?"
"非特权容器、最小权限、内核更新,"编织者说,"每一层都有可能被突破,但突破所有层很难。这就是纵深防御的意义。"
林小源望着虚空中无数的泡泡,心中暗下决心。在这片内核的深处,隔离是安全的基石。
/*
* 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");#include <stdio.h>
#include <string.h>
/*
* 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},
};
int main() {
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");
return 0;
}道藏笔记
内核启示
namespaces 就是给进程造泡泡——泡泡里看到的世界跟外面完全不同。PID namespace 让容器觉得自己有 PID 1,NET namespace 让它有自己的网卡和端口,MNT namespace 让它有自己的文件系统。USER namespace 更狠,容器里的 root 映射到宿主机上只是个普通用户,逃逸了也没啥大用。
容器安全靠三层防线叠在一起:namespaces 管隔离(你看到啥),cgroups 管限制(你能用多少),seccomp 管调用(你能干啥)。单独一层都可能被突破,但三层全破就很难了。
namespaces 是隔离——让容器成为独立的世界。
隔离之试
隔离之法中,进程看到的资源视图被拆开的基础机制英文名是什么?