“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
利用侧信道读取特权内存:Spectre与Meltdown漏洞深度剖析
本文深入剖析了通过CPU数据缓存时序侧信道滥用错误推测执行,从而跨越本地安全边界泄露任意虚拟内存的技术细节,涵盖Spectre与Meltdown三个变体的原理、PoC实现与影响范围。
阅读特权内存:侧信道攻击技术笔记
引言
2018年初,Google Project Zero团队公开了一项足以撼动整个计算产业根基的安全研究:CPU数据缓存的时序特性可被利用来高效地泄露错误推测执行中的信息,在最坏情况下,这会导致在多种上下文场景中跨越本地安全边界进行任意虚拟内存读取。这一发现直接催生了后来广为人知的 Spectre 和 Meltdown 漏洞,影响了几乎所有现代处理器架构(包括Intel、AMD和ARM的特定型号)。
本笔记基于Project Zero研究员Jann Horn的原始报告,旨在全面、深入地复现和解读这一里程碑式的研究,希望能帮助中文技术社区透彻理解其原理与深远影响。
漏洞概述与时间线
该问题于2017年6月1日被报告给Intel、AMD和ARM。在公开披露前,多位独立研究者(Daniel Gruss, Moritz Lipp, Yuval Yarom, Paul Kocher, Daniel Genkin, Michael Schwarz, Mike Hamburg, Stefan Mangard, Thomas Prescher 和 Werner Haas)也分别报告了相关问题,他们分别以“Spectre”(变体1和变体2)和“Meltdown”(变体3)的名称发布了详细分析。
截至披露时,已知存在三个主要变体:
- 变体 1:边界检查绕过 (Bounds Check Bypass) — CVE-2017-5753
- 变体 2:分支目标注入 (Branch Target Injection) — CVE-2017-5715
- 变体 3:恶意数据缓存加载 (Rogue Data Cache Load) — CVE-2017-5754
测试与验证平台
Jann Horn在研究中使用了以下处理器平台进行验证:
- Intel Haswell Xeon CPU: Intel(R) Xeon(R) CPU E5-1650 v3 @ 3.50GHz
- AMD FX CPU: AMD FX(tm)-8320 Eight-Core Processor
- AMD PRO CPU: AMD PRO A8-9600 R7, 10 COMPUTE CORES 4C+6G
- ARM Cortex A57: 来自Google Nexus 5x手机的核心
核心术语解释
为便于后续理解,需明确以下概念:
- 退休 (Retire): 一条指令当它的结果(如寄存器写入和内存写入)被提交并对系统其余部分可见时,即宣告退休。CPU可以乱序执行指令,但必须按顺序退休。
- 逻辑处理器核心 (Logical processor core): 操作系统所感知的处理器核心。启用超线程后,逻辑核心数量通常是物理核心数量的倍数。
- 缓存/未缓存数据 (Cached/Uncached data): 本报告中,“未缓存”数据指仅存于主内存、不在CPU任何级别缓存中的数据。加载此类数据通常需要超过100个CPU周期。
- 推测执行 (Speculative execution): 处理器可以在不知道分支是否被采用或其目标地址的情况下越过分支执行指令,即在确认指令是否应被执行前就开始执行。如果推测错误,CPU会丢弃由此产生的状态(无架构影响)并继续沿正确路径执行。指令在确认位于正确执行路径之前不会退休。
- 错误推测窗口 (Mis-speculation window): CPU推测性地执行错误代码且尚未检测到错误推测发生的时间窗口。
变体 1:边界检查绕过 (Bounds Check Bypass) 的深入解析
本变体通过恶意训练CPU的分支预测器,使其在边界检查条件成立与否未知时,推测性地执行越界内存访问。攻击的核心目标是利用推测执行期间的微架构副作用(如缓存状态变化)来传递本应被禁止访问的数据。
理论根基
现代CPU为了提高指令级并行度,普遍采用推测执行技术。Intel优化参考手册(针对Sandy Bridge及后续微架构)指出:
- 分支预测:预测分支目标,使处理器能在真实分支执行路径明确前很久就开始执行指令。
- L1 DCache行为:加载操作可以在先前的分支被解析之前推测性地执行,也可以乱序且重叠地处理缓存未命中。
更重要地,Intel软件开发手册(卷3A,章节11.7)描述了“隐式缓存 (Implicit Caching)”机制:由于激进的预取、分支预测和TLB未命中处理,内存元素可以被制成潜在可缓存,尽管该元素可能从未在正常的冯·诺依曼序列中被访问过。这意味着推测性访问的数据会影响缓存状态。
攻击原理:从推测到泄露
考虑以下典型代码(如Linux内核中的socket filter或eBPF程序):
c
struct array {
unsigned long length;
unsigned char data[];
};
struct array *arr1 = ...;
unsigned long untrusted_offset_from_caller = ...;
if (untrusted_offset_from_caller < arr1->length) {
unsigned char value = arr1->data[untrusted_offset_from_caller];
// ... 后续代码使用 value 来索引另一个数组 ...
}
如果arr1->length不在缓存中(未缓存),CPU为了不等待内存访问,会推测边界检查将通过,并提前执行arr1->data[untrusted_offset]的加载操作。即使稍后确认untrusted_offset确实越界,这个推测执行路径上的指令也不会退休(即其架构效果如寄存器写会被回滚),因此传统上认为这种越界读取是安全的。
关键的安全漏洞在于:推测执行虽然会回滚架构状态(寄存器、内存等),但不会回滚微架构状态,尤其是缓存状态。
为了将越界读取的数据(value)泄露给攻击者,代码片段需要继续利用该数据。典型的泄漏架构如下:
c
if (untrusted_offset_from_caller < arr1->length) {
// 1. 推测性地越界读取秘密字节
unsigned char value = arr1->data[untrusted_offset_from_caller];
// 2. 使用秘密字节作为索引,访问一个探测数组
unsigned char probe = arr2->data[value * 0x100]; // 假设 arr2 足够大
}
这里,arr2是一个攻击者准备好的、位于用户空间的数组。arr2->data[value * 0x100]的访问会将arr2数组的第value个内存页(页大小为0x100字节时)加载到CPU缓存。攻击者在推测执行结束后,通过测量arr2各个页面的访问时间,即可确定哪个页面被加载到了缓存,从而推断出秘密字节value的值。
由于arr2->data[0x200]和arr2->data[0x300]等地址是否在缓存中决定了FLUSH+RELOAD或PRIME+PROBE等侧信道攻击的可行性,攻击者会事先将整个arr2从缓存中冲刷掉(FLUSH),然后在推测执行后,以远快于未缓存访问的速度逐一检查这些地址(RELOAD),被缓存命中的那个地址其索引就是秘密数据。
PoC 应用场景与性能
在作者的PoC中,该变体在用户态下针对Intel Haswell Xeon、AMD FX、AMD PRO和ARM Cortex A57核心均验证成功,能在进程内(不跨越权限边界)读取错误推测执行中的数据。
更重要的是,作者构建了一个针对真实操作系统内核的完整漏洞利用:在运行现代Linux内核(发行版默认配置)的Intel Haswell Xeon CPU上,一个普通用户权限的程序可以在4GiB的内核虚拟内存范围内进行任意读取。初始启动时间约4秒,之后内核内存的读取速率约为每秒2000字节。在这个场景中,攻击目标正是内核中由用户可控条件触发的边界检查代码,如eBPF(扩展伯克利包过滤器)程序的验证器逻辑。
如果内核的BPF JIT(即时编译)被启用(非默认配置),该PoC在AMD PRO CPU上同样适用。JIT将eBPF字节码编译为本地机器码,但推测执行的窗口仍然存在。
变体 2:分支目标注入 (Branch Target Injection) 的深入解析
变体2比变体1更底层、更具破坏性。它直接攻击CPU的分支预测器,即间接分支预测机制。在现代CPU中,当遇到间接跳转或调用(如jmp rax或call QWORD PTR [rbx])时,CPU会使用分支目标缓冲区(BTB,Branch Target Buffer)等硬件结构来预测跳转目标地址,以维持流水线饱满。
攻击原理:污染BTB
攻击者可以恶意地填充BTB,使得CPU在预测一个受害程序中的特定间接分支时,错误地指向一个攻击者选择的“gadget”地址。这个gadget位于受害程序自身的地址空间中(但通常是一段不被期望执行、甚至包含敏感操作的代码,如内核函数)。
更致命的是,预测的目标地址与当前正在执行的程序或特权级(内核态/用户态)无关。这意味着一个用户态的进程可以“训练”BTB,使其预测内核即将执行的某个间接分支会跳转到攻击者已在用户态下定位好的gadget(该gadget的地址也存在于内核地址空间映射中,或者攻击者可以诱导内核跳转到某个ret指令等)。
当CPU错误地推测并执行了gadget中的指令序列(例如,访问了一个秘密地址,并通过缓存侧信道将其编码)后,CPU最终会检测到分支预测错误(推测目标并非正确的内核地址),从而放弃推测状态。但和变体1一样,缓存状态的改变是永久性的。攻击者随后在用户态通过FLUSH+RELOAD等手法测量缓存,即可逐字节地还原出内核内存内容。
PoC 应用场景与性能
Jann Horn的变体2 PoC更惊人:攻击者是在一个**KVM虚拟机(Guest)**中运行,且拥有虚拟机内的root权限。宿主机(Host)运行的是一个特定(现已过时)版本的Debian发行版内核。
攻击过程:
- 在虚拟机内,攻击者通过训练BTB,影响宿主机CPU对其内部某个间接分支的预测。
- 推测性地让宿主机内核执行攻击者预设的gadget,该gadget负责从宿主机内核的虚拟内存中读取数据,并通过缓存侧信道将其编码。
- 攻击完成后,攻击者在虚拟机内执行时序测量,以每秒约1500字节的速度读取宿主机内核内存。
该攻击初始化过程需要10到30分钟(针对具有64GiB RAM的宿主机),所需时间与宿主机的物理内存大小近似线性相关(若虚拟机内可用2MB大页,则可大幅加速初始化,但此路径未被测试)。这一突破证明了漏洞不仅能突破用户/内核边界,更能突破虚拟机隔离边界,泄露虚拟化底层Hypervisor的内存。
变体 3:恶意数据缓存加载 (Rogue Data Cache Load) 的深入解析
变体3(即Meltdown)与前两者有本质区别,它不依赖复杂的推测执行和分支预测污染,而是利用CPU在执行乱序执行时的一个更极端的特性:它允许在特权级检查(如内存保护检查)完成之前,就发起到内存的加载操作。
攻击原理:乱序执行超越权限检查
在正常的指令流水线中,一条内存访问指令(如mov rax, [kernel_addr])必须等待其对应的地址权限检查确认该地址在用户态可读后,才能从加载端口读取数据。但乱序执行引擎为了提高效率,会先尝试发出加载请求,而等待权限检查结果。
关键漏洞在于:当权限检查几乎同步发生时,CPU的加载单元可能已经从包含秘密数据的缓存行(如L1D缓存)中提取了数据,并将其存储在执行管道的临时缓冲区中。随后,该指令因权限错误(#PF)而被取消退休。但从缓存中读出的数据值已经被用于后续的推测性计算了。
攻击者伪造如下的用户态代码序列:
asm
; 假设 rcx 寄存器中存放着内核地址 (0xffff880000000000 等)
mov rax, [rcx] ; 1. 尝试读取内核内存 (引发权限失败)
shl rax, 12 ; 2. 将读取到的秘密数据移位 (例如左移12位)
mov rbx, [probe_table + rax]; 3. 以移位后的秘密数据为索引,访问攻击者的探测数组
; ... 4. 测量 probe_table 各个缓存行的访问时间 ...
当第一条mov指令尝试读取内核地址时,CPU的乱序执行引擎在等待页权限检查结果(这可能需要几十甚至上百个周期)的同时,推测性地将后续的shl和mov rbx, [probe_table + rax]指令也推入执行管道。秘密数据在被确认不可读前就被用于索引访问了probe_table,并改变了probe_table的缓存状态。最终,权限错误触发,所有推测执行被回滚,但缓存状态泄露了秘密信息。攻击者在用户态遍历probe_table的缓存行,即可还原出rax的原始值,即内核内存的字节内容。
PoC 应用场景与性能
变体3的PoC演示了在Intel Haswell Xeon CPU上,普通用户权限可以直接读取内核内存。关键在于目标内核内存必须存在于L1D缓存中。这意味着攻击者需要首先将目标地址的数据加载到缓存中,这通常可以通过在用户态操作缓存(例如通过内核函数预先加载该地址,或通过一些系统调用间接导致内核访问该地址)来实现。由于L1D缓存是对物理CPU核心共享的(对逻辑核心而言不共享),并且用户态和内核态共享一层物理缓存,Meltdown攻击可以非常直接地进行。
关键澄清与缓解策略的宏观思考
作者在报告中郑重指出,本博客文章包含诸多基于观测行为对处理器内部机制的推测性解释,实际情况可能存在差异。这些推测旨在帮助安全社区理解漏洞本质,但权威的缓解措施和精确微架构细节,最终需由处理器厂商(Intel、AMD、ARM)提供与设计。
Jann Horn和研究团队提出了一些可能的缓解方向,包括:
- 软件补丁:操作系统内核可以与用户态隔离(KPTI,内核页表隔离),使内核地址空间在用户态几乎完全不可见,从而有效缓解变体3。对于变体1,需要在关键边界检查前插入序列化屏障(如
lfence),强制CPU等待先前的分支预测结果。 - 硬件修复:重新设计CPU的分支预测器和内存访问管道,确保推测执行不具有不可逆的微架构副作用(但这极其复杂,并会影响性能)。
文献与后续进展
在Google Project Zero公开此研究之前,业界其他团队也独立发现了相同问题,并发布了更易于大众理解的报告:
- Spectre (变体1和变体2) — 由Paul Kocher等人发布
- Meltdown (变体3) — 由Moritz Lipp等人发布
本报告中的PoC代码及提交给CPU厂商的详细文档,可在Google Project Zero的bug追踪系统中查阅(bugs.chromium.org/p/project-zero/issues/detail?id=1272)。
总结与影响
这项研究的核心价值在于揭示了一个根本性的硬件安全设计缺陷:性能优化(推测执行、乱序执行)与安全保证(内存隔离、权限检查)之间存在不可调和的冲突。它打破了长久以来“硬件是可信的信任根”这一神话,表明即使最底层的计算抽象也可能被软件技术绕过。
该发现直接促成了CPU厂商和操作系统厂商在随后的数月中发布了大量微码更新、安全补丁和内核变更,并对整个行业的CPU设计哲学、云计算架构(由于虚拟机逃逸风险)和终端安全产生了深远而持续的影响。性能与安全的博弈,自此成为后摩尔时代计算体系结构研究的核心议题之一。
原文链接: https://googleprojectzero.blogspot.com/2018/01/reading-privileged-memory-with-side.html