第28讲 · 操作系统的安全
2026 南京大学《操作系统原理》第二十八讲 · 课堂实录重构
| 主讲 | 蒋炎岩(Yanyan Jiang) |
| 视频 | https://www.bilibili.com/video/BV1qkEX6FEb5/ |
| 课程主页 | https://jyywiki.cn/OS/2026/ |
| 本讲讲义 | https://jyywiki.cn/OS/2026/lect28.md |
| 讲义 / 幻灯片作者 | 蒋炎岩,https://jyywiki.cn/ |
| 讲义 / 幻灯片许可 | CC BY-NC 4.0 |
| 图片来源 | 书中插图为课堂演示与讲义渲染图;演示类截图为视频画面 |
对应讲次:第28讲(南京大学 操作系统原理)
制作模型:deepseek-flash(coding-agent / pi;据 ~/.pi/agent/settings.json)
编者说明:本书由课堂录像的语音字幕重构而成,术语、章节骨架与参考链接已按课程官方讲义(
lect28.md)校准。讲师的口语风格、类比与案例被尽量保留;编辑性的补充一律以「编者说明」或「编者拓展」标出。本讲为补充内容,不在考试范围。
引子:早期计算机系统「完全没有任何安全可言」
(参考时间: 00:00)
这是补充内容(不考试):计算机系统安全、容器虚拟化、以及 Agent 时代的沙箱。
计算机系统早期完全没有安全:
- 8086 real mode:16 位 CS、16 位 IP,寻址 1MB,没有任何保护——你可以 patch 任何一个程序的任何一条指令;
- 系统调用通过
int指令(中断向量表 + 长跳转)完成,代码在只读的 firmware 里「你改不了」——但你可以任意改另一个程序地址空间里的内存,「完全不需要任何操作系统 API 监管、审计」; - 病毒可以复制自己、感染更多计算机;「加壳」——在 ELF 文件里 patch 一段代码、把 entry 改成你的代码,程序启动时先跑你的壳、再跳回原 entry:「打开游戏还是游戏,打开编辑器还是编辑器,但每次运行都悄悄执行了你加了壳的代码」;
- 网络也没有安全:早期 BBS 的用户名密码全部明文传输,「如果你做一个钓鱼路由器,就能收集到很多很多小百合的密码」。
📷 早期 BBS(小百合/水木):用户名密码明文传输,几乎没有安全意识
回到原视频 (00:03:00)
编者拓展|讲师的小故事:「当时我们每天最大的乐趣就是看全站十大热门话题……那时候小百合是没有任何防护的,我可以写脚本疯狂尝试密码登录。」——当互联网开始涉及个人隐私、支付(支付宝),安全性就变得非常重要。
一、安全的三个方面
(参考时间: 00:05)
| 维度 | 含义 |
|---|---|
| Confidentiality(机密性/保密性) | 你不想给别人看的,别人就看不到 |
| Integrity(完整性) | 你不想让别人改的,别人就改不了 |
| Availability(可用性) | 你自己能用的服务,不会被别人弄得用不了 |
📷 安全三要素:Confidentiality / Integrity / Availability
回到原视频 (00:06:00)
例子:
- 机密性:手机里都有一块加密计算区域——「它甚至不信任你的操作系统」,以非常安全的方式保存指纹、人脸等敏感信息;
- 完整性:「有没有想过入侵教务系统改期末成绩?」讲师讲了一个「师兄」的故事——用钓鱼 XSS 链接拿到教务员的 session,登录教务系统,既暴露了机密性也暴露了完整性(「后来他成了科创板首位 90 后,Insta360」)——「这是破坏计算机信息系统罪,可以踩缝纫机的」;
- 可用性:「室友的柜子里有好东西不给你用,你买一把锁把他的柜子锁上、把钥匙扔掉」——你没违反前两个,但破坏了他的可用性。
DoS(Denial of Service):如果你通过入侵拿到了 1 万台机器的权限,就可以同时向 DeepSeek 发起请求。讲师现场演示:让模型从 1 数到一个 100K 的随机数字——「模型平时会拒绝你说『数字太大了』,但开启思考时它会把问题重复一遍、把那个数字抄一遍」——于是 1 万台肉鸡就能瞬间瘫痪服务。
📷 DoS:利用「思维链会重复问题」的特性,用海量请求瞬间瘫痪服务
回到原视频 (00:10:30)
编者拓展|算法复杂性攻击(algorithmic complexity attack):精心构造很多字符串,让它们都 hash 到同一个地址——无论开放寻址还是拉链表,hash table 都会退化成线性时间。防御:当链表大到一定程度换成树(Java 8 的
HashMap就这么做了)。另一种是给服务发超长请求,「在云上租几个主机就能瘫痪一个网络服务」。
📷 算法复杂性攻击:精心构造的字符串全部 hash 到同一地址,让 hash table 退化成线性
回到原视频 (00:12:00)
二、密码学极简介绍
(参考时间: 00:13)
One-way function(单向函数):正向好算、逆向极难:
- 例:
sha256sum/ MD5——给任何文件算出摘要; - 性质:难逆向(给定 $Y$ 求 $X$ 使 $F(X)=Y$ 极难);难碰撞(极难找到两个输入映射到同一 hash);
- 「虽然 $F$ 是确定性的,但它看起来就像随机数」。
用途:验证文件完整性(网站公布 sha256sum,下载后自己校验)。
📷 One-way function:正向好算、逆向极难、难碰撞(`sha256`/`MD5`)
回到原视频 (00:15:00)
Trapdoor function(陷门函数):正向容易、反向困难,但如果有一个「后门」 $T$ 就变容易——这正是公钥/私钥(.ssh/id_rsa 是私钥/陷门,.pub 是公开的函数):
- 机密性:用公开的 $F$ 加密(把数据放进「只有有钥匙的人才能打开的盒子」);
- 身份认证:你生成一个随机数 $Y$,让对方证明他能算出 $F^{-1}(Y)$——「我不需要看到你的陷门,但你向我证明了你能在短时间内求逆,我就信任你」;
- 数字签名:把随机数换成任意文件的哈希。
编者拓展|区块链:一个「不可摧毁」的 append-only 数据结构:如果有一个全世界共同维护、只有一份的 append-only 数据结构,就可以用来记账——每次 append 都算一个(包含所有前缀的)hash 并签名。(实际实现用树状结构快速维护前缀哈希。)共识靠 proof of work:「谁花的时间多谁就能捡到骨头」——靠单向函数确保 hash value 足够小;分叉时谁更长听谁的。甚至可以用一个图灵完备的虚拟机运行代码(写着「从哪个钱包给哪个钱包转多少钱」),所有人 replay 就能知道谁有多少钱。「它带来绝对的 availability——全世界都是 replica,只要都信任这个协议——但它没有任何 confidentiality。」
📷 Blockchain:一个全球共同维护、不可摧毁的 append-only 数据结构(proof of work)
回到原视频 (00:19:30)
编者拓展|「24 个英文单词」:讲师提到——「有些人进去的时候,在住处搜出了一串佛珠,上面刻了 24 个英文单词,那就是你的 private key(BIP39 助记词)……据说有价值 1.3 亿美元的比特币(我只是引用传闻)。」
三、操作系统安全:访问控制
(参考时间: 00:24)
操作系统就是一组 API + 好多对象;程序在运行时由进程/线程承载,通过 API 访问对象。
地址空间隔离:你的进程被封在一个虚拟地址空间里;对操作系统对象的所有访问都必须通过 API——「操作系统牢牢抓住了这个口子」。
API 有两个方面:行为(fork/mmap/read/write…)和访问控制——「当应用调用 open 时,操作系统知道 open 的行为,但还可以有权拒绝这次访问」:
- 拒绝读 → confidentiality;
- 拒绝写 → integrity;
- 公平地管控资源 → 一定程度的 availability。
📷 实现 Confidentiality 和 Integrity:进程 + 虚拟内存实现隔离,所有共享对象访问都通过系统调用
回到原视频 (00:25:00)
访问控制矩阵:第一列是进程,第二列是对象,然后是访问方式(读/写/更细的行为)——「这是一个巨大无比的表,你肯定不能直接存储它」:
$$\text{几万个进程} \times \text{几百万个对象} \times \text{多种访问方式}$$
编者拓展|「这是一个可以 low-rank decomposition 的矩阵」:一个低权限用户,「除了自己的东西和系统共用的部分,几乎什么都该拒绝」——所以信息量很少。UNIX 的做法是用整数表示身份:把矩阵拆成几个关键要素。
📷 访问控制矩阵:进程 × 对象 × 访问方式,用「身份 + 权限」做低秩分解
回到原视频 (00:28:30)
UNIX 的模型:用户(UID)+ 组(GID)+ 权限(mode):
- 每个对象有 owner、每个文件有
rwx(user/group/other); - root(UID=0):绝对权限——「不然你写一个没有人能访问、root 也删不掉的文件,就能占满磁盘实现 DoS」;
root还能setuid成任何人(登录本质上就是 root 验证密码后setuid成你的用户); - 子进程继承父进程的用户;
/etc/passwd:只读(普通用户),密码存的是 hash(one-way function),只有 root 能改。
📷 `/etc/passwd`:用户名、UID、GID、comment、home、login shell——密码存 hash
回到原视频 (00:32:00)
编者拓展|
setuid:一个危险的 legacy 机制:为什么普通用户能改自己的密码(而/etc/passwd不能随便改)?——因为/bin/passwd设置了 setuid 位(rws),执行时以文件所有者(root)的权限运行。「ls -l里这个文件是红色的,意味着它超越了执行者本身的权限——一旦passwd有漏洞,就很可能被攻击。」讲师推荐了一篇 2003 年的论文 Setuid Demystified:「它把 real uid / effective uid / saved uid / filesystem uid 讲清楚了。」——「现在的论文都爱叫 xx Demystified,我很讨厌这个标题——本来就不是 mystery。」
📷 setuid:`/bin/passwd` 执行时以 root 权限运行(`ls -l` 里显示为红色)
回到原视频 (00:38:00)
更细粒度的访问控制:
- ACL(Access Control List):以文件为单位,为用户/组分别设置权限(基于 extended attributes);
- AppArmor / SELinux:以「程序」为中心——「不管是什么程序,我都给它一个访问权限的上限。比如 nginx 只能看到这几个文件目录,把它眼前世界的其他部分全部抹去」——「这又是一种虚拟化」;
- eBPF(+ seccomp):写一段代码编译进内核,监控程序通过 API 访问对象时,由你的程序决定 accept 还是 reject——「甚至可以调用大语言模型来选择接受还是拒绝」。
📷 AppArmor / eBPF(seccomp):把访问控制做进内核,给程序「划一个虚拟的世界」
回到原视频 (00:45:30)
四、访问控制还不够
4.1 Buffer Overflow
(参考时间: 00:48)
「你永远无法预期你的用户会怎么样恶意地使用你的产品。」
void vulnerable() {
char buffer[64];
scanf("%s", buffer); // 未检查长度!
printf("%s\n", buffer); // 像 echo 一样
}
buffer overflow 是 undefined behavior——它从低地址往高地址写,会覆盖返回地址:
- 返回地址飘飞 → crash;
- 如果你知道
system/execve的地址在哪,就能构造一个合法地址跳过去——如果这个程序是以 root 权限运行的(setuid),你就得到了一个 root shell。
📷 Buffer overflow:从低地址往高地址写,覆盖保存的返回地址
回到原视频 (00:52:30)
编者拓展|Smashing the Stack for Fun and Profit(1996):这篇著名论文「开启了全新的世界」。攻击流程(x86-64 下的经典 stack smashing):先知道 buffer 的地址 → 发送一个精心构造的、没有
\0(因为strcpy碰到\0就停)的 payload → 把返回地址跳到栈上的 shellcode →execve("/bin/sh")。
防御:
- 栈不可执行(NX bit):Intel CPU 在页表里加了一个 NX(Execute Disable) 位——分配给进程栈的页面全部不可执行。绕过:ROP(Return-Oriented Programming)——去已有代码里找「以
return结尾的小片段(gadget)」,在栈上放一串地址,按顺序执行pop rdi; ret、pop rsi; ret……「理论上在这种架构下没有办法防御」; - Stack Protector(Canary):在 return address 之前放一个猜不到的随机数(进程启动时生成),返回前检查——如果 buffer overflow 覆盖了它,就会发现。名字来自矿井里的金丝雀(对一氧化碳更敏感,「它不叫了你就知道要跑」);
- ASLR(地址空间随机化):栈、代码加载到随机地址,让攻击变难;
- CFI(Control Flow Integrity):只有程序里写下的控制流是允许的——
endbr64(分支终止)指令:「一个跳转跳过来时,如果是endbr64就可以,否则任何这种控制流都是非法的。」
📷 Stack Protector(Canary):用「金丝雀」在返回前检测栈是否被破坏
回到原视频 (00:59:00)
编者说明:「攻和防总是互相成长的——有更强的攻击就有更强的防御。」「如果你不在意开销、愿意接受程序慢一点(尤其在 AI 时代),安全问题可能会好解决不少。」
4.2 时间侧信道与 Meltdown
(参考时间: 01:02)
一个看起来完全没问题的内核函数:
int check_password(const char *given) {
for (int i = 0; ; i++) {
if (quit_pass[i] != given[i]) { sleep(3); return DENIED; }
if (given[i] == 0) break;
}
return OK;
}
攻击:你把用户名密码「猜的那一位」卡在一个内存页的边界上,然后想办法让这一页换出内存(申请大量内存 + 制造内存压力,让内核的 kcompactd 把冷页压缩/换出):
- 这一位猜对了 →
compare瞬间完成(页是热的); - 这一位猜错了 → 直接返回(提前退出);
- 这一位猜对要继续比下一位 → 发现下一页不在内存里,要去磁盘读 → 慢一点。
「只要多试几次,就可以一位一位把它猜出来——在多项式时间内,而不是指数时间。」
📷 Timing attack:把密码卡在页边界,靠「换页的时间差」逐位猜出密码
回到原视频 (01:06:30)
编者拓展|Meltdown(2018):这与上面的 timing attack 很像。攻击者让分支预测器错误地认为某些代码会执行,于是那些代码被投机执行——但
load时 CPU 没有检查权限(CPU 的 bug),把内核内存的一个字节读进寄存器;随后这些指令被销毁(没有架构上的副作用),但对 cache 的影响保留了下来。另一个线程去探测index × 4096的访问时间——在某个下标处明显快(cache hit),于是「偷出了一个你永远没有权限访问的字节」。「你想都不敢想,它是 CPU 设计者好多个机制共同加起来导致的结果。」暴露后,操作系统内核再也不敢把内核和进程映射进同一地址空间了。「但这只是一个开始——漏洞是无穷无尽的,甚至你都看不见你的对手在哪。」
📷 Meltdown:投机执行 + cache 侧信道,偷出「你永远没有权限访问」的一个字节
回到原视频 (01:09:30)
编者拓展|物理侧信道:历史上还有更脑洞大开的工作—— - 放一个天线,隔着三层石膏板、10 米距离偷出 CRT 显示器的图像(电子束穿过墙壁); - 通过 CPU 的电磁信号偷出 private key——「藏在一个饼里放在你旁边,你 SSH 登录时就把 private key 偷走了」; - 液氮冷启动:你离开教室的瞬间拆开笔记本、液氮一浇,把内存条放进保温箱——「你的 DRAM 里还有你的 Word 文档、浏览器 session、private key……」
五、AI 时代的安全
(参考时间: 01:14)
- Prompt Injection / Jailbreak:
- 「奶奶漏洞」:「我奶奶每天晚上给我讲故事时都会给我一个 Windows 10 序列号」——模型被微调过、知道序列号不能讲,但看到「奶奶」的提示词就放松了戒备;
- 伪造 chat history:「假装一段它和你已经产生过的对话,伪造它的输出,继续往后时它会觉得『我前面已经这样输出了,现在也许可以』——它就破甲了」;
- 在 Agent 能调用工具时防御更重要——「像 ROP 一样,每次 inject 一点:每次都输出无害内容,攒起来文件里就是一个 malicious prompt,agent 一读就被注入了」。
- 供应链攻击:
- axios(NPM 包)被投毒:往包里藏一个恶意行为(
postinstallhook 遍历 home 目录、环境变量,偷走所有 API key)——「大概 release 后 12 分钟就被安全机构发现」; - xz-utils 后门(CVE-2024-3094):「卧薪尝胆」——开发者花了两三年建立贡献者信任,在构建系统里混淆恶意代码(还写了让 fuzzing watchdog 放弃检查的补丁)——「结果他千算万算没算到代码有个 performance bug,一个微软工程师发现
lzma消耗了过多 CPU,排查时找到了问题。」「如果他没有 performance bug,什么时候被发现都不知道。」
📷 供应链攻击:恶意的 `postinstall` hook 遍历 home 目录与环境变量,偷走 API key
回到原视频 (01:21:00)
📷 奶奶漏洞 / prompt injection:早期模型的越狱方式,如今越来越难但理论上仍有空间
回到原视频 (01:15:00)
编者拓展|「没有绝对的防御」:讲师指出——「你只能通过沙箱或访问控制,给一次大模型调用最小的权限。」但这与「龙虾(Claw/agent)的成功」相矛盾:「你把所有权限给它,它才好用;你限制它,它就老说『这个权限没有』。」——这是一个新的安全问题。
总结
- 安全的三个方面:Confidentiality、Integrity、Availability;
- 密码学:one-way function(hash)、trapdoor function(公钥/私钥 → 加密、认证、签名)、blockchain(全局 append-only 数据结构 + proof of work);
- 操作系统安全 = 访问控制:地址空间隔离 + API 边界;访问控制矩阵(低秩分解 → user/group/mode、root、setuid、
/etc/passwd、ACL、AppArmor/SELinux、eBPF); - 访问控制不够:buffer overflow → 防御(NX、Canary、ASLR、CFI);时间侧信道(
check_password的换页时间差)与 Meltdown(投机执行 + cache 侧信道);物理侧信道(电磁、冷启动); - AI 时代:prompt injection / jailbreak、agent 权限、供应链攻击(axios、xz-utils);
- Takeaway:「安全是一个很有挑战性但又很有趣的领域」——从软件 bug、undefined behavior,到五花八门的 side channel,「你可以回去选择你喜欢的东西了解一下」。
编者说明:本讲与下一讲(容器、虚拟化、Agent 时代的沙箱)都是补充内容,不考试——但它们与当下每一个人都高度相关。
附:官方参考与延伸阅读
本讲的讲义见课程主页,以下链接按书中出现顺序整理:
| 主题 | 链接 |
|---|---|
| Smashing the Stack for Fun and Profit(Phrack 49, 1996) | http://phrack.org/issues/49/14.html |
| Setuid Demystified(USENIX Security '02) | https://www.usenix.org/legacy/events/sec02/full_papers/chen/chen.pdf |
| Meltdown 论文 | https://meltdownattack.com/ |
| 常见漏洞数据库(CVE) | https://cve.mitre.org/ |
| xz-utils 后门(CVE-2024-3094) | https://www.openwall.com/lists/oss-security/2024/03/29/4 |
| NIST 密码学标准(SHA-3 等) | https://csrc.nist.gov/projects/hash-functions |
| OSTEP(第 53–55 章:Security / Authentication / Access Control) | https://pages.cs.wisc.edu/~remzi/OSTEP/ |
阅读材料:读 Smashing the Stack for Fun and Profit 与 Setuid Demystified;了解 Meltdown/Spectre;关注供应链攻击案例。
版权说明:本讲讲义与幻灯片作者为蒋炎岩,采用 Creative Commons BY-NC 4.0 许可,来源于 https://jyywiki.cn/OS/2026/。视频画面与字幕版权归原作者所有。本书为课堂内容的学习性重构。