try ai
科普
编辑
分享
反馈
  • 时间侧信道攻击:原理、机制与应用

时间侧信道攻击:原理、机制与应用

SciencePedia玻尔百科
核心要点
  • 计算机程序的执行时间可能会无意中泄露秘密信息,因为它通常取决于正在处理的数据。
  • 时间漏洞存在于计算的各个层面,从高层软件逻辑和编译器优化,到底层硬件特性(如缓存和推测执行)。
  • 从处理器缓存到云基础设施,共享资源是时间侧信道的常见来源,导致了 Spectre 和 Meltdown 等臭名昭著的攻击。
  • 缓解策略主要包括强制执行常数时间以消除时间变化,或添加噪声以模糊泄露的信息。

引言

在网络安全的世界里,最巧妙的攻击往往是最不易察觉的。当我们通常想象黑客破解加密或利用软件漏洞时,一个更隐蔽的威胁却潜伏在背景中:通过时间本身泄露信息。这种现象被称为时间侧信道,它利用了一个简单的事实:计算机操作所需的时间会因其处理的秘密数据而异。这在对性能的不懈追求与对安全的关键需求之间造成了根本性的紧张关系,而攻击者可以巧妙地利用这一差距。

本文深入探讨了时间侧信道这个迷人而危险的世界,旨在解决性能与安全之间的这一根本冲突。我们将首先探索其基础的“原理与机制”,揭示这些泄露如何在软件以及我们硬件深层的微架构中显现。然后,在“应用与跨学科联系”部分,我们将拓宽视野,看看这些原理如何影响从编译器、操作系统到云架构的整个计算生态系统。让我们从剖析每个时间攻击核心的那个简单而深刻的原理开始。

原理与机制

想象一下,你正试图猜测朋友的密码。你看着他们输入,但他们的手被挡住了。在他们按下回车键后,你注意到了一个奇怪的现象:如果他们输入一个短密码,“无效密码”的消息几乎立刻出现;如果他们输入一个长密码,消息出现前会有一个微小到几乎无法察觉的延迟。你刚刚发现了一个​​时间侧信道​​。你没有破解加密,也没有读取他们的屏幕;你只是从一个副作用——系统响应所需的时间——中获取了信息。这就是时间侧信道攻击的本质:一个通过可观察到的计算持续时间来无意中泄露秘密信息的过程。

其基本原理惊人地简单:​​计算机的执行时间可以依赖于它正在处理的数据​​。在一个完美安全的世界里,涉及密钥的计算无论密钥的值是什么,都应该花费完全相同的时间。但在现实世界中,我们的计算机是为性能而生的。它们会走捷径,使用专用硬件,并根据遇到的数据遵循不同的路径。这些旨在让我们的计算机更快的优化措施,却可能使它们成为不知情的告密者。让我们层层剥茧,看看这种泄露是如何发生的,从我们编写的代码一直到处理器中的硅原子。

软件领域:两条路径的故事

在最高层次上,时间泄露通常始于代码本身。程序员喜欢编写高效的代码,而一个常见的优化是“提前退出”。如果你正在一个大文件中搜索错误,为什么在找到第一个错误后还要继续搜索呢?你应该立即停止并报告错误。虽然这对于性能来说完全合理,但它却创造了一个时间漏洞。

考虑一个旨在验证一个字节序列是否为有效 UTF-8(一种文本编码标准)的程序。一个简单的验证器可能会逐个字节地扫描。当它找到一个无效字节时,它会立即返回一个错误。攻击者可以向使用这种验证器的网络服务器发送各种字符串,并测量响应时间。快速响应意味着错误在字符串的早期被发现。较慢的响应意味着错误在后面被发现,或者字符串是有效的。通过精心构造输入字符串并测量服务器的响应时间,攻击者可以有效地描绘出验证器的内部逻辑,并可能提取与该字符串一同处理的敏感信息。

这不仅仅是关于错误检查。我们算法的逻辑本身就可能泄露信息。让我们来看一个计算机科学中最著名的算法之一:快速排序(Quicksort)。一个常见的实现使用了一种名为 Lomuto 的分区方案。它的工作原理是选择一个秘密的“主元”值,并重新排列一个数组,使得所有小于主元的元素都在一边,所有大于主元的元素都在另一边。该算法会遍历数组,每当找到一个小于主元的元素时,它就执行一次交换操作。

现在,假设一个攻击者知道数组的内容,但不知道秘密的主元值。每次交换操作都会花费少量但可测量的时间。因此,分区的总时间与执行的交换次数成正比。通过测量总执行时间,攻击者可以精确计算出发生了多少次交换。这反过来又揭示了数组中有多少元素小于秘密主元。这一信息极大地缩小了主元的可能取值范围,通常将其限制在数组中两个已知值之间的一个小区间内。该算法在追求效率的过程中,背叛了自己的秘密。

深入兔子洞:微架构

如果我们把软件编写得完全“常数时间”会怎样?我们可以设计我们的 UTF-8 验证器,让它总是扫描整个字符串,只是在发现错误时设置一个标志,而不是提前返回。我们可以选择一个没有数据依赖时间的排序算法。这样就一定安全了吗?

不完全是。这个兔子洞更深。我们的代码所运行的硬件本身就是一个充满性能优化措施的复杂怪兽。即使一个程序由完全相同的指令序列组成,执行它们所需的时间也可能因其操作的数据值而异。

非规格化数的微妙之处

最显著的例子之一来自于计算机处理浮点数——即带小数点的数——的方式。管理浮点数运算的 IEEE 754 标准为典型范围定义了“规格化”数,并为那些极度接近零的值定义了“非规格化”(或非正规)数。可以把它想象成汽车的变速箱:你有用于日常驾驶的普通挡位,但也可能有一个特殊的、缓慢的“蠕动挡”,用于在非常棘手、低速的地形上行驶。挂上这个蠕动挡需要额外的时间。

在许多处理器上,涉及非规格化数的算术运算与此类似。“快速路径”硬件是为规格化数优化的。当计算产生一个非规格化结果时,处理器必须切换到一个更慢、更复杂的执行路径,这通常涉及特殊的微码。这造成了巨大的性能差异。一次正常的乘法可能只需要 444 个 CPU 周期,但一次产生非规格化数的结果可能需要 180180180 个周期甚至更多。

攻击者可以利用这一点。想象一个密码学函数计算 y=s/by = s/by=s/b,其中 sss 是一个密钥,bbb 是攻击者可以控制的输入。当一个浮点数的绝对值低于一个极小的阈值时(对于一个 64 位浮点数,这个阈值大约是 2−10222^{-1022}2−1022),它就会变成非规格化数。攻击者可以小心地选择输入 bbb 来检验关于 sss 的一个假设。通过选择一个非常大的 bbb,他们可以迫使结果 s/bs/bs/b 跨过阈值进入非规格化数的领域。如果除法突然花费了长得多的时间,攻击者就得知 ∣s/b∣<2−1022|s/b| \lt 2^{-1022}∣s/b∣<2−1022,这揭示了关于密钥 sss 量级的信息。这已经不是泄露的窃窃私语,而是震耳欲聋的呐喊。在循环中,时间差异可能非常大——达到毫秒级别——以至于即使在嘈杂的网络上也很容易测量。

资源之战

CPU 内部资源的竞争也会产生时间泄露。现代处理器是并行工程的奇迹,能够同时执行多条指令。然而,这些并行单元依赖于共享资源,例如​​寄存器文件​​——一个小型、极快的存储体,用于存放计算所需的即时数据。寄存器文件用于在单个时钟周期内读写数据的“端口”或访问点数量有限。

假设一个处理器每个周期可以发射两条指令,并且其寄存器文件有两个读端口和一个写端口。现在考虑两种类型的指令:B 型需要一次读取,而 A 型需要两次读取和一次写入。如果程序中的一个秘密比特导致它执行一长串 B 型指令,CPU 可以轻松地在每个周期发射两条指令(总共 2 次读取,0 次写入)。循环很快完成。但如果这个秘密比特导致程序执行一串 A 型指令,CPU 就会遇到瓶颈。它不能同时发射两条 A 型指令,因为这将需要四个读端口和两个写端口,超出了硬件的限制。它被迫逐一发射这些指令。循环所需的时间是原来的两倍。执行时间直接揭示了运行了哪种类型的指令,从而揭示了秘密比特的值。

作为神谕的缓存

也许最著名的微架构侧信道是​​缓存攻击​​。缓存是小型、快速的存储体,用于存储最近使用的数据或指令以加快访问速度。当 CPU 需要数据时,它首先检查缓存。如果数据在那里(​​缓存命中​​),访问速度非常快。如果不在(​​缓存未命中​​),CPU 必须从速度慢得多的主内存中获取它,这会带来显著的时间代价。

转译后备缓冲器(TLB)是一种特殊的缓存,它存储虚拟内存地址到物理内存地址的近期转换。像任何其他缓存一样,TLB 命中是快速的,而 TLB 未命中是缓慢的。在许多系统上,TLB 在同一 CPU 核心上运行的不同程序或线程之间是共享的。这种共享为间谍活动创造了机会。

一个间谍程序可以使用“Flush+Reload”技术。首先,它从共享缓存中“刷新”一个特定的 TLB 条目。然后,它等待片刻,让受害者程序运行。最后,间谍程序“重新加载”同一个内存地址,并计时所需的时间。如果访问速度快(命中),间谍就知道受害者一定在间隔时间内访问了该地址,从而将其带回了缓存。如果访问速度慢(未命中),间谍就知道受害者没有访问该地址。通过对不同地址重复此过程,间谍可以了解受害者的内存访问模式,这可用于破解密码学实现并泄露大量数据。这就是臭名昭著的 Meltdown 和 Spectre 攻击背后的基本原理。

静默的艺术:缓解措施

如果时间泄露已经融入我们硬件和软件的结构之中,我们该如何防御它们呢?答案在于两大策略:让执行时间恒定,或将信号淹没在噪声中。

最稳健的防御是努力实现​​常数时间执行​​:重写代码和设计硬件,使得执行时间与秘密值无关。

  • ​​在软件中​​:我们的 UTF-8 验证器可以通过始终处理整个字符串来修复,无论错误发生在哪里。可以教导编译器自动均衡条件语句(例如 if/else 块)不同分支的执行时间,方法是在较快的分支中填充空操作指令,直到其运行时间与较慢的分支相匹配。
  • ​​在硬件中​​:问题一直延伸到底层。如果不同的指令需要不同的时间来解码,处理器本身就会泄露信息。一个安全的设计可能会强制所有指令花费相同的时间,例如通过填充较快的解码以匹配最慢的解码。一个更优雅的解决方案是重新构建流水线,或许可以将复杂的解码阶段分解为几个更小、更均衡的阶段,这样指令就能以统一的速率流过,从而同时实现安全和高性能。为了对抗非规格化数泄露,处理器可以被置于“刷新至零”模式,该模式将所有非规格化数视为零,从而完全绕过慢速路径,但代价是牺牲一些数值精度。为了防止缓存攻击,操作系统可以使用地址空间标识符(ASID)等硬件特性来标记缓存条目,确保一个进程只能访问自己的条目,从而在共享缓存中有效地建立一堵墙。

当真正的常数时间执行成本过高或不切实际时,第二种策略是添加噪声并降低攻击者的测量精度。操作系统可以将其提供给程序的系统时钟变得更粗糙。它可能提供一个每微秒才滴答一次的时钟,而不是每纳秒滴答一次。这使得攻击者更难测量构成泄露的微小时间差异。这里存在一个直接而优雅的权衡。如果时钟的精度是 ϵ\epsilonϵ 且时间泄露是 Δ<ϵ\Delta \lt \epsilonΔ<ϵ 的差异,攻击者在单次尝试中检测到它的概率就是 Δϵ\frac{\Delta}{\epsilon}ϵΔ​。通过增加 ϵ\epsilonϵ,操作系统使攻击者的工作变得更困难。然而,这也损害了依赖精确计时的合法应用程序,它们成功测量一个短时间间隔 d<ϵd \lt \epsilond<ϵ 的概率同样降低到 dϵ\frac{d}{\epsilon}ϵd​。

时间侧信道的世界揭示了计算机设计中一种深刻而美妙的张力——性能与安全之间永恒的斗争。每一个捷径,每一次优化,每一个旨在让计算机更快的巧妙技巧,都有可能留下一串时间的痕迹供攻击者追踪。理解时间与信息之间这种无声的舞蹈,是构建能够真正保守秘密的系统的第一步。

应用与跨学科联系

我们已经花时间理解了时间侧信道的基本原理,看到了计算的持续时间如何背叛其处理的秘密。现在,我们将踏上一段旅程,去看看这些机器中的幽灵究竟栖身何处。这并非局限于实验室的深奥奇谈;它是一个深刻而实际的现实,触及现代计算的每一个层面。时间信道的故事,就是抽象的信息世界如何不可避免地与硅和电的物理世界相互作用的故事。就像侦探追踪沙滩上的脚印一样,攻击者可以追踪计算的时间足迹,以揭示其隐藏的路径。

我们的旅程将从处理器的核心开始,穿过编译器和操作系统的迷宫逻辑,延伸到现代数据中心和云的广阔共享景观。在每一步,我们都将看到,对性能和效率的追求——计算进步的引擎本身——如何无意中制造了这些微妙的信息泄露。

架构蓝图:设计“诚实”的硬件

有人可能认为安全始于巧妙的软件,但其根基必须更深,深入到处理器自身的蓝图——指令集架构(ISA)。如果处理器能理解的基本命令本身就是泄露的,那么建立在其上的所有软件都将基于一个有缺陷的基础。

考虑密码学中的一个常见任务:模加法,即计算 (x+y)(modM)(x+y) \pmod M(x+y)(modM)。一个简单的实现可能会检查 x+yx+yx+y 是否大于 MMM,如果是,则执行一次减法。这个“如果”在执行流中创造了一个分支。所需的时间将取决于遵循哪条路径,而这又取决于 xxx 和 yyy 的值。如果 xxx 或 yyy 是秘密的,我们就有了泄露。

那么,我们如何设计一个“诚实”的指令来执行这个任务,而不泄露其输入呢?秘诀在于避免提问。一个安全的指令必须遵循一条单一、不变的路径,而不是“如果-那么-否则”的结构。这一原则的一个优美范例是无条件地计算两种可能的结果,然后在没有分支的情况下选择正确的一个。一条指令可以同时计算 t=x+yt = x+yt=x+y 和 u=x+y−Mu = x+y-Mu=x+y−M。然后,它通过检查减法产生的借位——这个比特通常由算术逻辑单元免费计算出来——来确定哪个结果是正确的。如果需要借位(意味着 x+y<Mx+y \lt Mx+y<M),它选择 ttt;否则,它选择 uuu。这种选择不是通过条件分支完成的,而是通过位运算逻辑,类似于硬件中的多路复用器。整个操作——加法、减法和选择——总是发生,因此其时间是恒定的,与输入值无关。这是设计的安全性,将密码学原理嵌入到硅片之中。

推测执行的双刃剑

现代处理器是性能工程的奇迹,它们使用各种技巧来比顺序读取代码所暗示的更快地执行代码。其中最强大的技巧之一是*推测执行*。当处理器到达一个岔路口(一个条件分支)时,它不会等待找出该走哪条路。它会做出猜测并向前冲,推测性地执行指令。如果猜对了,就节省了时间。如果猜错了,它就丢弃结果然后返回,损失很小。

但是,“丢弃结果”意味着什么呢?虽然架构状态(寄存器和内存的官方内容)被回滚了,但*微架构*状态——机器内部微妙的物理状态——通常不会。最著名的例子是缓存。一次访问依赖于秘密的内存位置的推测执行,会将该数据带入处理器的缓存中。即使推测是错误的并且指令被“退役”,数据仍然留在缓存中,就像泥地里留下的脚印。攻击者随后可以计时自己的内存访问,以查看缓存的哪些部分是“热”的,从而揭示处理器推测性探索的路径。

这就是臭名昭著的 Spectre 和 Meltdown 攻击背后的原理。泄露不是洪流,而是耳语。它需要统计分析才能从噪声中提取信号。在一个简化但富有洞察力的模型中,我们可以想象推测性的、依赖于秘密的数据访问与攻击者指令的时间之间存在微小的“交叉耦合”。即使这种耦合很小,通过进行多次测量——成百上千次——攻击者也可以平均掉随机噪声,并以非常高的概率可靠地区分出秘密比特‘0’和‘1’。与这些漏洞的斗争是一场深刻的博弈,它将推测执行带来的性能增益与保持沉默的安全要求对立起来。

编译器:一个不知情的同谋

在程序员的意图和硬件的执行之间是编译器。这个复杂的工具将人类可读的代码翻译成处理器的母语。在其对优化的不懈追求中,编译器可能会无意中成为制造侧信道的同谋。

考虑一种“常数时间”编程策略,其中密码学家小心翼翼地编写代码,使其对所有输入都具有完全相同的操作序列。一个编译器,不了解这种精妙的安全舞蹈,可能会看到像 x⊕xx \oplus xx⊕x 这样的表达式。知道这总是零,它会将其“优化”掉。但这样做,它改变了指令的数量和时间,可能会破坏常数时间的属性,并重新引入数据依赖的时间变化。一个具有安全意识的编译器必须被教导要更加小心,或许可以用一个公共操作(如 0⊕00 \oplus 00⊕0)替换依赖于秘密的操作,以打破数据依赖性,同时保持代码的时间结构。

更复杂的优化带来了更大的风险。循环去开关化是一种将循环不变条件从循环中提出来的技术,以避免在每次迭代中都检查它。如果这个条件是一个公共的配置标志,这是一个绝佳的优化。但如果这个条件是秘密的,并且产生的两条代码路径具有不同的时间,那么这个优化并不能消除泄露;它实际上可以通过移除分支指令本身的“噪声”来使泄露更响亮、更清晰,从而增加攻击者的信噪比。类似地,迹调度会激进地重排指令以优化最可能的执行路径。这可能导致来自“冷”路径(处理秘密数据的路径)的指令被推测性地执行,即使只有“热”的、非秘密的路径被采用,从而创造出典型的推测执行漏洞。

操作系统:共享领域的守护者

操作系统(OS)是资源管理的大师。它调度进程、管理内存并仲裁对硬件的访问。因为它处于所有共享活动的中心,所以它在时间信道的世界中扮演着至关重要的角色。

攻击者无需十分复杂就能探测操作系统。一个简单的用户程序可以触发一个页错误——当访问一个它不应该访问的内存区域时发生的错误——并计时操作系统内核处理该错误并返回错误所需的时间。内核处理该错误的执行路径可能取决于其自身的内部状态、系统负载或最近的活动。通过反复触发错误,攻击者可以测量这些时间变化,并构建出内核内部工作方式的地图。操作系统层面的缓解措施非常有趣。操作系统可以为已知的坏内存访问实现一个特殊的“常数时间”快速路径。或者,它可以像夜总会的保镖一样,使用“令牌桶”来限制单个进程产生错误的频率,从而节流信息信道的带宽。

操作系统还有责任帮助应用程序保护自己。一个在用户空间运行的密码学服务受制于操作系统调度器。在敏感计算中间发生一次抢占式上下文切换,会引入巨大的随机延迟,使其自身的时间变得嘈杂。虽然这种噪声可以阻碍攻击者,但对于能够平均多次测量的攻击者来说,它并不能消除泄露。一个更稳健的解决方案是让操作系统提供一种新型服务:一个“时间隔离”的执行环境。应用程序可以请求在一个专用核心上非抢占式地运行一个代码块,并且所有可观察的时间源(如时钟和事件通知)都被量化到一个粗糙的粒度。这种空间和时间的隔离组合有效地蒙蔽了攻击者,提供了作为服务的常数时间执行。

大都会:片上系统与云

放大来看,现代片上系统(SoC)是一个由专用核心和共享资源组成的繁华都市。运行受信任和不受信任代码的核心可能是分开的,但它们都通过可以承载信息的共享基础设施连接起来。对共享的末级缓存(LLC)、片上网络(NoC)、DRAM 控制器和 DMA 引擎的争用都可能造成跨核心的时间信道。一个不受信任的应用程序仅仅通过观察自己向内存发出的请求需要多长时间,就可以了解到受信任应用程序的秘密。这里主要的防御策略是分区和随机化。我们可以通过“着色”内存页或锁定缓存路来分区共享缓存,为每个租户提供自己的私有部分。我们可以使用严格的 TDMA 调度来划分网络时间,为每个核心提供一个保留的时隙。我们可以划分 DRAM 存储体以防止干扰。在无法严格分区的地方,我们可以注入随机性来模糊信号。

这个问题在云环境中被放大了,在云中,来自不同、互不信任的租户的整个虚拟机(VM)在同一物理硬件上运行。在这里,即使是“虚拟”硬件也可能成为泄露的来源。共享一个半虚拟化网络设备的两个虚拟机可以创建一个隐蔽信道。一个发送方虚拟机可以通过调节其向共享队列发送数据包的速率来制造争用。同一主机上的接收方虚拟机可以通过测量自己的网络延迟来检测这种争用。通过仔细的统计分析,一个可靠的、高带宽的通信信道就可以在管理虚拟机的软件 Hypervisor 的眼皮底下建立起来。

从单个指令的设计到全球云网络的架构,原理保持不变。时间是计算的一个维度,任何与共享资源的交互,无论多么短暂,都会留下时间的痕迹。理解这一点是构建不仅快速高效,而且值得信赖和安全的系统的第一步。