6162 字
31 分钟
Cache Side-Channel Attacks

概述#

Cache Side-Channel Attacks(Cache 侧信道攻击)

  • Memory Hierarchy / Cache:利用局部性缩短访存时间;
  • Out-of-Order Execution 与 Speculative Execution:提前执行可能有用的指令,提高流水线利用率。

这些技术本来的目标都是提升性能,但它们会在处理器内部留下可以观察的 microarchitectural state(微架构状态)。攻击者虽然不能直接读取受保护的数据,却可以通过测量 Cache hit / miss 的时间差,推断受害者访问过哪些位置,进一步恢复机密信息。

  • 侧信道攻击不通过时间差推断 Cache 状态
  • 指令即使最终被丢弃、异常即使最终被处理,Cache 中产生的副作用仍可能保留
  • Meltdown 主要联系乱序执行与异常处理,破坏用户态与内核态之间的隔离;
  • Spectre 主要联系分支预测与推测执行,诱导受害者程序泄露自身可访问的数据;
  • 安全增强往往意味着性能开销,处理器设计需要在 performancesecurity 之间取舍。

目录#


为什么体系结构课程要讨论安全#

在前面的章节中,Cache、乱序执行和动态分支预测始终作为性能优化技术出现:

  • Cache 降低平均访存时间;
  • 乱序执行让不依赖的后续指令提前利用空闲功能部件;
  • 分支预测和推测执行减少控制相关造成的流水线停顿。

处理器为了性能而提前做出的动作,可能在软件还没有正式得到结果之前,就已经改变了 Cache 状态。

程序设计者通常只看到 ISA 层面的结果:

  • 一条非法 load 会触发异常;
  • 一条走错路径的推测指令最终不会提交;
  • 用户程序不能直接读内核空间。

但攻击者关注的是更底层的物理现象:

  • 某个 cache line 是否被加载过;
  • 访问某个位置花了多少周期;
  • 某个分支预测器是否被训练到特定状态。

因此,体系结构层的性能机制本身可以成为安全攻击的观测通道

  • 在 Meltdown / Spectre 之前,系统安全通常默认 CPU 硬件隔离可信;
  • 这些漏洞表明:如果微架构状态能够泄露信息,上层 OS、沙箱或虚拟机的隔离也可能被绕过;
  • 做安全防护通常需要付出一定性能代价,不存在“完全无代价地提高安全性”的通用方案。

Cache Side Channel 的基本原理#

Cache 为什么会泄露信息#

Cache 的正常作用是复用近期访问过的数据。对同一个地址而言:

  • 如果数据已经在 Cache 中,访问为 cache hit,延迟很短;
  • 如果数据不在 Cache 中,需要从更低层级取回,访问为 cache miss,延迟明显更长。

一个直观类比:

  • 桌面(desk) 相当于 Cache;
  • 手边桌面上已有的数据,可以立刻拿到;
  • 数据不在桌面上,就要去更远的位置查找,产生额外 penalty。

因此,只要攻击者能够精确计时,就可以判断某个地址对应的数据是否近期被加载进 Cache:

ThitTmissT_{hit} \ll T_{miss}

这就构成了一个侧信道:

受害者是否访问过某个地址
该地址对应 cache line 是否命中
攻击者测量访问延迟
推断受害者的访存行为

攻击者读取到的首先不是“秘密内容本身”,而是秘密影响下的访问轨迹。只要把不同秘密值映射到不同的可测 Cache 位置,就能从访问轨迹反推出秘密值。

共享 Cache 与隔离边界#

Cache 会被不同执行实体共享:

  • 同一核心上的不同进程或线程共享部分缓存资源;
  • 不同核心可能共享 LLC(Last-Level Cache);
  • 虚拟机、容器或云租户在底层也可能竞争同一套缓存结构。

共享本来是为了性能与资源利用率,但安全上会形成问题:

  • 一个进程的访问会改变 Cache 状态;
  • 另一个进程可以观察这种状态变化;
  • 即使两者在虚拟地址空间、权限或沙箱上相互隔离,也可能在 Cache 层面发生信息泄露。

所以,Cache 侧信道攻击的本质是:

利用共享微架构资源的可观察状态变化,绕开软件定义的安全隔离边界。

Architectural State 与 Microarchitectural State#

理解 Meltdown 与 Spectre,需要区分两类状态。

  1. Architectural State(体系结构状态)

程序能够直接看到的状态,例如:

  • 寄存器值;
  • 已提交的内存写入;
  • PC 与异常结果;
  • ISA 定义的可见行为。

如果推测执行方向错误,或指令最终触发异常,处理器必须保证 architectural state 看起来仍然正确。

  1. Microarchitectural State(微架构状态)

处理器为了实现高性能而维护的内部状态,例如:

  • Cache line 是否存在;
  • TLB 条目;
  • 分支预测器历史;
  • 预取器状态;
  • 执行缓冲区内部状态。

问题在于:

  • 错误路径上的指令可以被撤销,不提交寄存器结果;
  • 非法访问最终可以触发异常,不让程序直接拿到非法数据;
  • 但已经加载进 Cache 的 line 往往不会随之恢复原状。

因此,攻击的核心并非让非法结果正式提交,而是让非法或推测性的访问在 Cache 中留下痕迹

TIP

最终的权限检查或异常处理会阻止非法结果提交,但瞬态执行产生的 Cache footprint 可能保留。不同处理器对权限检查与数据返回的具体实现时刻有所差异,architectural rollback 不等于 microarchitectural rollback


CPU Cache 缺陷与瞬态执行#

课件以一组依赖关系说明处理器为何会留下漏洞窗口:

指令 3:读取某个最终会被判定为非法的地址
指令 4:依赖指令 3 的值计算新的访问位置

在支持推测或乱序执行的处理器中,处理器可能在指令 3 的异常正式提交之前,让后续依赖访问进入执行过程:

  1. 指令 3 尝试读取受保护数据;
  2. 后续指令把这个数据转换成某个数组索引;
  3. 对应数组位置所在的 cache line 被载入;
  4. 之后处理器发现前面访问非法,丢弃错误路径上的 architectural results;
  5. 但第 3 步形成的 Cache 状态变化仍然可以被计时观察。

可用下面的抽象流程理解:

非法/推测读取 secret
secret 参与计算 probe[secret × stride]
某个 probe cache line 被加载
异常或预测错误导致指令结果作废
攻击者逐项计时 probe 数组
命中最快的位置暴露 secret

这套机制把前面章节中的三个知识点串在了一起:

机制正常目的在攻击中的作用
Cache hit / miss latency降低平均访存时间提供可计时的观测通道
Out-of-order execution隐藏长延迟、提高 ILP在异常正式处理前执行泄露操作
Speculative execution / BPU降低 branch penalty被训练后沿错误路径产生 Cache footprint

Meltdown:利用乱序执行泄露内核数据#

漏洞原理#

Meltdown 的关键对象是用户态与内核态之间的内存隔离。

正常情况下:

  • 用户程序无权读取内核地址;
  • 对内核地址的非法 load 应触发异常;
  • 提交阶段之后,程序不可能从 architectural state 中得到内核数据。

Meltdown 利用的是乱序执行与异常处理之间的窗口:

  • 非法 load 最终会被识别并触发异常;
  • 在异常真正生效、相关指令退休之前,后续指令可能已经根据非法读出的值进行了瞬态执行;
  • 后续指令将秘密值编码到 Cache 状态中;
  • 即使处理器丢弃寄存器计算结果,攻击者仍能利用 Cache timing 恢复该值。

课堂讲述把它与动态调度部分直接联系起来:

  • 指令先进入执行缓冲区 / reservation stations;
  • 可执行的操作在结果正式按序提交前已经发生;
  • 最后通过按序 retirement / commit 维持程序可见结果正确;
  • 安全问题来自:被丢弃指令产生的 Cache 副作用并没有同时丢弃

Example:probe array 编码秘密值#

示例代码逻辑如下:

; rcx = kernel address
; rbx = probe array
mov al, byte [rcx] ; 读取内核地址中的一个字节 secret
shl rax, 0xc ; rax = secret × 4096
mov rbx, qword [rbx+rax] ; 访问 probe_array[secret × 4096]

其中:

  • rcx 指向攻击者想读取的内核地址;
  • al 保存瞬态读取到的一个字节;
  • probe_array 是攻击者可访问、可测时的数组;
  • secret × 4096 让不同字节值对应到不同内存页,从而在 Cache 中留下容易区分的痕迹。

攻击过程可以分为四步。

第一步:准备指令与探测数组#

攻击者准备一个 probe_array,使不同秘密值能够映射到不同探测位置。

例如:

secret = 0x41
访问位置 = probe_array[0x41 × 4096]

第二步:瞬态执行秘密相关访问#

mov al, byte [rcx] 属于无权限访问,最终一定会失败。
但在异常正式提交前,处理器可能已经执行:

mov rbx, qword [rbx+rax]

于是与 al 对应的探测页进入 Cache。

第三步:异常处理恢复体系结构状态#

处理器发现用户态读取内核地址非法:

  • 丢弃相关指令的可见执行结果;
  • 恢复 architectural state;
  • 触发异常或终止非法执行流。

但 Cache 中被加载的页仍然存在。

第四步:通过计时恢复秘密#

攻击者遍历所有候选探测页:

probe_array[0 × 4096]
probe_array[1 × 4096]
...
probe_array[255 × 4096]

哪一项访问延迟明显更小,说明哪一页已经在 Cache 中,从而得到:

最快命中页的编号 = secret 的字节值

因此,Meltdown 的真正泄露链路是:

内核字节 → 数组索引 → Cache page → timing measurement → 恢复内核字节

漏洞复现与缓解#

这一部分具体参考slides

Meltdown 缓解思路#

KAISER / KPTI(Kernel Page-Table Isolation) 方向:

  • 用户态运行期间,尽量不再把完整内核映射暴露在同一页表视图下;
  • 攻击者即使触发瞬态执行,也缺乏可直接读取的内核映射;
  • 这能够显著缓解 Meltdown 类型的攻击。

局限性在于:

  • x86 某些机制仍要求保留少量必要映射;
  • 切换页表和隔离维护会引入性能成本;
  • 该类防护主要针对 Meltdown,并不能直接消除 Spectre。

Spectre:利用预测执行诱导受害程序泄密#

漏洞原理#

Spectre 的核心不是用户程序直接越权读取内核空间,而是:

攻击者操纵受害程序的分支预测行为,使受害程序在错误预测路径上瞬态访问自身可访问但不应泄露的数据,再通过 Cache 侧信道将信息带出。

它与前面动态分支预测的知识直接对应:

  • BPU(Branch Prediction Unit)记录过去分支的行为;
  • 处理器依据预测结果提前取指和执行;
  • 如果预测正确,性能提升;
  • 如果预测错误,错误路径上的 architectural results 被丢弃;
  • 但错误路径产生的 Cache footprint 仍可能保留。

因此,Spectre 的攻击者不一定需要突破硬件权限检查,而是让目标程序在推测状态下自己访问并编码秘密

Spectre 的三个阶段#

Spectre 攻击划分为三个阶段:

  1. 训练分支预测单元

    攻击者使用大量合法输入,使 BPU 习惯于预测某个边界检查会通过。

  2. 触发错误推测执行

    随后传入恶意越界索引。即使最终检查会失败,处理器仍可能根据训练结果暂时沿“检查通过”的路径执行。

  3. 通过 Cache timing 解码秘密

    推测路径中的秘密相关访问将某个探测位置加载进 Cache。攻击者重新计时访问探测数组,即可推断秘密内容。

Example:越界检查绕过#

典型代码为:

if (x < array1_size) {
y = array2[array1[x] * 256];
// do something using y that is
// observable when speculatively executed
}

正常语义下:

  • 只有 x < array1_size 时才能读取 array1[x]
  • 越界的 x 应被条件判断拦截。

攻击过程中:

  1. 攻击者多次提供合法 x,让 BPU 预测该条件通常为真;
  2. 再提供一个恶意越界 x,使 array1[x] 指向受害者地址空间中的敏感数据;
  3. 条件判断尚未真正完成时,预测执行临时进入分支体;
  4. array1[x] 的秘密字节决定 array2[...] 中哪个位置被访问;
  5. 预测错误后,y 等可见结果会被丢弃,但对应 array2 位置的 Cache 状态已改变;
  6. 攻击者遍历并计时 array2,恢复秘密字节。

其中:

array2[array1[x] * 256]

起到的作用与 Meltdown 中的 probe array 类似:

秘密值 → 某个可区分的 Cache 位置

Spectre 缓解措施#

三类缓解手段。

  1. 序列化 / 内存屏障指令

    在敏感路径上限制推测执行,例如使用类似 LFENCE 的指令,确保边界检查完成后再继续依赖访问。

    问题:

    • 依赖处理器与系统配置支持;
    • 插入过多会显著破坏指令级并行性与性能。
  2. 在条件分支附近插入阻止推测执行的处理

    通过编译器或程序改写,在关键分支及目标位置限制瞬态执行泄露。

    问题:

    • 需要识别所有危险代码位置;
    • 对热点代码的性能影响较大。
  3. 微代码与处理器修复

    对分支预测或推测加载行为增加控制能力。

    问题:

    • 修复范围受具体微架构限制;
    • 防御越保守,性能代价通常越明显;
    • 新变种仍可能绕过既有策略。

Spectre 的困难在于它利用的是处理器广泛依赖的预测执行机制。完全关闭或大幅限制推测执行,会直接牺牲长期积累的性能收益。


Meltdown 与 Spectre 的比较#

比较维度MeltdownSpectre
主要联系的体系结构机制乱序执行、异常处理、按序 retirement分支预测、推测执行、BPU 训练
攻击目标用户态读取原本受保护的内核数据诱导受害程序泄露自身可访问的数据
隔离边界用户程序与操作系统内核之间不同应用、沙箱或受害程序内部的逻辑边界
关键动作非法访问在异常处理前留下 Cache 痕迹错误预测路径留下 Cache 痕迹
是否依赖训练 BPU通常不以训练分支预测为核心
典型探测方式probe array + timingarray2 / probe array + timing
影响范围特征早期典型情形与许多 Intel 处理器密切相关影响范围更广,涉及 Intel、AMD、ARM 等
典型软件缓解KAISER / KPTI屏障、retpoline、编译器与微代码缓解
防护难点隔离内核映射会影响性能预测执行用途广,通用彻底防御困难

共同点是:

最终程序结果看起来可以被恢复正确
但瞬态执行留下的 Cache footprint 会暴露秘密

区别在于:

  • Meltdown 强调越过权限隔离读取内核
  • Spectre 强调诱导受害者沿错误预测路径访问秘密

更广泛的 Cache 攻击面#

Meltdown 与 Spectre 只是最具代表性的案例。slides 与课堂讲述还强调了若干更广泛的攻击方向。

攻击所面临的现实约束#

真实攻击环境中会出现许多噪声和限制:

  • 受害程序的运行窗口很短;
  • 系统并发访问会扰动 Cache 状态;
  • 硬件预取可能干扰探测结果;
  • 补丁与操作系统实现会改变攻击成功率。

因此,后续攻击研究往往追求三个指标:

指标含义
攻击速度单位时间内能恢复多少信息
攻击精度是否能在噪声下稳定辨认 Cache 痕迹
信息泄露量能恢复的秘密范围与带宽

TEE 与新的执行环境#

某些商用 TEE(Trusted Execution Environment) 在 Cache 侧信道面前并不天然安全。原因仍然是:

  • 不同安全域可能共享底层 Cache;
  • 软件或权限上的隔离并未等价为缓存资源隔离。

课堂还将这一问题延伸到现代执行场景,例如:

  • 云服务与多租户环境;
  • serverless 等共享基础设施;
  • AI inference 等对性能高度敏感、资源复用密集的场景。

这些场景下,性能优化越积极,共享资源越多,越需要关注微架构侧信道。

不同硬件平台的攻击分布#

  • Intel、AMD、ARM 等主流商用平台都存在不同类型的缓存侧信道风险;
  • 一部分攻击利用 Cache 共享、命中时延等通用性质,因此跨平台;
  • 一部分新型攻击依赖具体的查找逻辑、替换策略、目录、一致性协议、预取机制或瞬态执行,因此平台相关性更强;
  • 现有商用处理器仍难以对所有 Cache 侧信道攻击提供完全防护。

Cache 安全增强方案#

商用处理器缓解方式#

Intel 与 ARM 面向已披露瞬态执行漏洞的部分缓解方式:

攻击类型Intel 采用的防御方法ARM 采用的防御方法
边界检查绕过内存屏障指令 LFENCE新内存屏障指令 CSDB
分支目标注入返回跳板 retpoline无通用缓解方法
数据缓存恶意加载内核页表隔离技术 KPTI-
系统寄存器恶意加载更新微代码非必要
推测存储绕过LFENCE;特殊模块寄存器 MSRSSBD 标识位SSBBPSSBB

总体结论是:

  • 商用处理器已经针对 Meltdown / Spectre 等公开漏洞部署了一系列缓解方案;
  • 这些方案通常具有针对性,未必覆盖全部 Cache 侧信道变种;
  • 安全增强会带来不同程度的性能开销。

基于软件的防御#

软件防御可以从三个层次展开。

1. 漏洞检测方法#

通过静态或动态程序分析,定位可能存在秘密相关访存或危险预测路径的代码。

目标是:

  • 找出机密数据是否参与了可观察的访存索引;
  • 找出边界检查之后可能发生的瞬态泄露路径;
  • 为编译器插入防护提供位置依据。

2. 用户级防御措施#

在应用程序中减少 timing leakage,例如:

  • 让敏感运算尽可能保持恒定执行时间;
  • 避免根据秘密值选择可观测的访存位置;
  • 在必要位置清理或扰动 Cache 状态;
  • 对关键代码插入屏障或采用安全编程模式。

3. 系统级防御措施#

由操作系统或虚拟机管理程序增强隔离,例如:

  • 完善页表隔离;
  • 限制攻击者与受保护任务共享关键 Cache 资源;
  • 管理进程调度与缓存分配;
  • 对高风险执行环境实施更严格的隔离策略。

软件方案的特点:

  • 优点:部署灵活,可以通过补丁、编译器或 OS 更新快速推广;
  • 缺点:防御效果依赖底层微架构;若要完整防护,往往需要硬件能力配合,且会引入运行时开销。

基于硬件的防御#

缓存分组 / 分区方法#

核心思想是:

把 Cache 的不同区域分配给不同安全域,减少攻击者与受害者共享同一缓存位置的机会。

例如按 way 划分:

Way 0 Way 1 Way 2 Way 3
└── 受保护进程 ──┘ └── 其他进程 ──┘

优点:

  • 从共享资源的根源上削弱侧信道;
  • 隔离逻辑直观,安全语义清晰。

缺点:

  • 可用缓存容量被切分,命中率可能下降;
  • 硬件实现和资源管理复杂;
  • 性能损失可能较明显。

缓存随机化方法#

传统 Cache 映射具有固定关系:

address → index → cache set

只要攻击者掌握映射规则,就可以构造与受害者冲突或用于探测的地址。
缓存随机化的目标是:

消除特定地址与缓存位置之间对攻击者稳定可预测的固定映射。

两类实现思路:

  1. 基于表的随机映射

    • 把随机映射关系保存在表中;
    • 每次通过查表获得实际 cache index。

    特点:

    • 映射灵活;
    • 需要额外表存储与查找开销。
  2. 基于计算的随机映射

    • 对地址进行加密或哈希式计算;
    • 根据计算结果确定实际映射位置。

    特点:

    • 附加存储较少;
    • 可以用较小开销实现随机化;
    • 仍不能从原理上完全消除所有侧信道。

结论是:

  • 随机化通常具有较小的性能开销;
  • 对多类缓存探测攻击有明显缓解效果;
  • 但它主要提升攻击难度,并不能保证绝对安全;
  • 商用处理器在采用强防护时仍会谨慎评估性能损失。

软件硬件协同#

除了分区和随机化,slides 还给出了两类继续发展的方向。

针对目录结构的防御#

对于目录或一致性相关侧信道,可以从两个角度处理:

  1. 从源头减少冲突

    • 增加目录项数量;
    • 减少不同请求竞争同一目录项造成的可观测现象。
  2. 从攻击结果进行修复

    • 检测跨核心缓存行替换;
    • 在发现异常替换后,将被替换行重新加载至 L2 等结构中。

重新定义软硬件接口#

仅在微架构内部“打补丁”难以长期覆盖所有新攻击,因此可以重新考虑 ISA 与程序语义:

  • 通过 Data-Oblivious Programming(数据遗忘编程),消除机密信息与访存模式或分支执行时延之间的关联;
  • 通过优化基本块执行,减少敏感代码对分支预测的依赖;
  • 通过新的 ISA 支持,让软件明确表达敏感执行区域或禁止泄露的访问模式。

这一方向的核心思想是:

安全不能永远依赖处理器事后猜测哪些瞬态执行可能危险;软件与硬件需要共同表达并维护安全边界。


Cache Side-Channel Attacks
https://www.lazysheep2031.top/posts/ca/chapter4_extention/
作者
Lazysheep
发布于
2026-05-26
许可协议
CC BY-NC-SA 4.0

评论