今天分享的论文《GPU Memory Exploitation for Fun and Profit》来自2024年USENIX Security。在本文中,作者团队对 CUDA 程序中的缓冲区溢出问题进行了全面的研究:
(1)对用于访问各种 GPU 内存空间的机制进行了逆向工程,证明缓冲区溢出错误可能会导致跨内存空间的内存损坏并超出数据的合法范围。
(2)探讨了 GPU 上的代码和数据管理策略,揭示了传统的代码注入攻击在 GPU 上仍然有效。作者团队还分析了函数返回的机制并证明了CUDA ROP的可行性。
(3)证明了本文中发现的漏洞对 GPU 上运行的 DNN 应用程序构成了重大安全风险。
CUDA 代码注入和 CUDA ROP攻击的PoC可从 https://github.com/SecureArch/gpu_mem_attack 获取。
Background & Motivation
GPU内存安全问题:
(1)CUDA由 NVIDIA 开发,是当今最流行的通用 GPU 编程语言之一。由于 CUDA 是从 C 和 C++(这两种语言以其内存不安全特性而闻名)扩展而来的,因此人们担心 CUDA 程序可能存在类似的内存安全漏洞。
(2)先前工作已经探讨了 CUDA 程序中的内存安全漏洞。他们表明,内存安全违规,尤其是缓冲区溢出,也可能发生在 CUDA 程序中。
相关工作的局限性:
(1)首先,这些研究中提出的调查是基础性的,并没有深入研究 GPU 特有的问题。例如,CUDA 具有多个不同的内存空间(与 C 和 C++ 不同)。先前的研究仅发现缓冲区溢出错误可能发生在特定的内存空间内。例如,一个本地内存缓冲区上的 out-of-bounds(OOB) 操作可能会损害存储在同一本地内存中的其他数据。然而,他们还没有探索这样的OOB操作是否会影响不同内存空间中的数据。
(2)其次,鉴于这些研究是在几年前进行的,他们只检查了较旧的 GPU(在 Volta 之前)。然而,自 Volta 架构以来,NVIDIA对其 GPU 进行了重大改变。例如,引入了新的ISA,其中指令长度从8字节变为16字节。因此,这些研究的结论可能不适用于较新的 GPU 架构。例如,这些先前的研究有两个共同的结论。 首先,利用缓冲区溢出来劫持CUDA中的控制流非常困难,因为返回地址存储在未公开的内存位置中,而不是在堆栈上。 其次,传统的代码注入攻击不能针对CUDA程序,因为代码和数据在内存中是分离的。 然而,通过作者团队的分析,之前的结论并不成立。
由此,作者团队旨在深入探讨并回答以下问题:
在 NVIDIA GPU 上运行的 CUDA 程序会出现内存错误吗?如果是这样,这些错误会引发什么类型的攻击?
Overview
CUDA 程序会出现内存错误吗?
(1)跨内存空间的缓冲区溢出。 先前的研究已经表明缓冲区溢出可能会导致单个内存空间内的内存损坏。 因此,作者团队更多地关注研究不同内存空间的缓冲区溢出的影响。具体来说,作者团队首先探讨本地内存和全局内存之间的问题,然后将研究扩展到这两者和共享内存之间的问题。
Takeaway:在 NVIDIA GPU 上,本地内存中的每个数据块都链接到两个虚拟地址;这些地址之一允许 CUDA 线程访问/修改其他线程的本地内存。
(2)返回地址覆盖。在 CPU 上,攻击者可以利用堆栈缓冲区溢出漏洞来覆盖堆栈上的返回地址。然而,先前的研究表明这种利用在 GPU 上是不可行的:他们声称在 GPU 上,返回地址存储在设备内存中的未公开位置,而不是堆栈(本地内存)上。作者团队重新审视这一主张。
Takeaway:在 CUDA 中,利用缓冲区溢出漏洞允许攻击者修改本地内存(堆栈)中存储的返回地址,从而重定向程序的控制流。
这些错误会引发什么类型的攻击?
(1)代码注入攻击。通过覆盖返回地址的能力,攻击者可以将执行重定向到他们已填充 shellcode 的数据页。然后,当函数返回时,执行会转向该恶意代码,从而导致代码注入攻击。此类攻击已经在 CPU 上得到缓解:例如,大多数 CPU 系统都实施了 WˆX 策略,该策略要求每个内存页面都可以写入或可执行,但不能同时两者兼而有之。这会阻止 shellcode 被直接执行。然而,作者团队发现这个策略并没有在现代 GPU 上实现。请注意,先前的研究声称在 GPU 上执行数据缓冲区是不可行的;他们认为这要么是因为代码和数据地址是分开的,要么是因为数据页不可执行。 作者团队发现这些假设都不准确。
Takeaway:NVIDIA GPU 不区分代码页和数据页。
(2)ROP攻击。返回地址覆盖还可能导致代码重用攻击,其中 ROP 就是一个主要示例。事实证明,ROP 在 CPU 上非常有效,在常用的库代码(例如 libc)中可以找到许多 ROP 小工具。这里作者团队研究ROP在现代GPU上的可行性。
Takeaway:ROP 可用于读取或写入 NVIDIA GPU 上的内存。
表 4 列出了作者验证结论的所有 GPU。
Case Study
作者团队演示GPU 内存损坏漏洞如何对基于 DNN 的应用程序(最常见的 GPU 应用程序之一)造成重大安全风险。
实验使用的云系统在表 5 中指定。
威胁模型:
(1)受害者:一个在配备现代NVIDIA GPU的服务器上运行的DNN应用。这个应用接收远程用户的请求,使用DNN模型处理这些请求,并将响应返回给用户。
(2)攻击者:一个可以向受害者应用发送请求的远程用户。攻击者通过精心构造的恶意请求,利用GPU内核中的缓冲区溢出漏洞来实现攻击。
漏洞:
作者团队在受害者应用程序中包含了一个用于矩阵向量乘法(具有溢出漏洞)的 CUDA 设备函数,如清单 5 所示。作者团队故意在这个内核中引入一个漏洞:它缺乏适当的检查来确保每个线程处理的向量部分的大小不超过线程本地数组的容量。因此,当用户控制的向量大小(如下所述)大于预期时,可能会发生堆栈溢出。为了触发漏洞,假设向量 (n) 的大小由用户控制。在数据预处理阶段,用户提供的输入数据的大小可能并不总是与 DNN 模型所需的输入大小匹配。例如,用户可能提供尺寸为 512×1024 像素的图像,而 DNN 模型设计为仅处理 256×512 像素的图像。为了处理此类差异,可以使用卷积层对用户输入进行预处理,然后再将其输入 DNN 模型。该卷积层通常采用矩阵向量乘法来进行性能优化。在此预处理卷积层中,矩阵和向量(m 和 n)的维度由用户输入的大小确定。如果输入大小明显大于 DNN 模型预期的大小,这可能允许用户触发清单 5 中的漏洞。
- 代码注入攻击:
作者团队将代码注入攻击实现为受控权重修改攻击:攻击者可以控制写入内存的数据,并且可以将权重修改为任何所需的值。具体来说,攻击者使用 shellcode 准备一个数据缓冲区,将特定值写入给定地址,并使用堆栈溢出漏洞将控制流重定向到该缓冲区(参见清单 5)。 攻击过程分为三个步骤:
(1)攻击者准备一个数据缓冲区,其大小足够大,当该缓冲区发送到受害者进行 DNN 推理时,会触发受害者的缓冲区溢出错误(参见清单 5)。
(2)攻击者操纵缓冲区中的数据来实现两个关键目标:1)在将该缓冲区复制到本地内存后,每个线程的本地数组(清单 5 中的 arr_local)将填充预期的 shellcode; 2) 数据复制后,每个线程的返回地址都会被这个本地数组(shellcode 所在的位置)的地址覆盖。这些准备工作对于确保触发缓冲区溢出时导致预期的代码注入攻击至关重要。
(3)攻击者使用该数据缓冲区作为输入发起 DNN 推理请求。
- ROP攻击:
作者团队将 ROP 攻击实现为不受控权重修改攻击:攻击者重复执行单个内存写入小工具来修改权重。攻击者控制提供写操作中使用的地址的寄存器,但不控制提供要写入的数据的寄存器。该ROP攻击也分为三个步骤,与代码注入攻击中的步骤非常相似。但是,第二步(准备数据缓冲区)的目标略有不同。具体来说,攻击者需要操作输入缓冲区中的数据,以确保在数据复制后,
1)堆栈上的返回地址被 ROP gadget 的地址覆盖,
2)某些寄存器将被使用通过 ROP 小工具,从堆栈中弹出时接收预期值。
两种攻击的结果
作者团队使用前两种攻击方法,在修改每个模型第一层的权重后测试模型的准确性。表3显示了修改10%、20%、50%和100%后的准确率结果权重。在代码注入攻击中,攻击者可以指定所需的权重值,作者团队为每个权重选择一个较大的值。这是因为 DNN 模型中的大多数权重值都非常小;使用较大的值预计会对模型性能产生重大影响。如表所示,这种方法显着降低了所有测试模型的准确性。 ResNet-18 和 ViT 尤其受到影响,其准确性几乎反映了随机猜测。相比之下,在攻击者无法控制修改后的权重值的 ROP 攻击中,准确性基本上不受影响。这是因为攻击中使用的 ROP gadget 恰好将权重更改为一个较小的值,而不是一个接近权重原始值的大值。
Countermeasures
-
OOB检测工具:
- 介绍了两种检测GPU程序中缓冲区溢出错误工具:NVIDIA Compute Sanitizer和cuCatch。
- NVIDIA Compute Sanitizer通过动态二进制 instrumentation在运行时截取每个程序指令,但这种方法会带来显著的性能开销。
- cuCatch是一个编译时工具,可以在CUDA程序执行期间检测空间和时间上的内存错误,性能开销较小。
-
堆栈Cookie(Stack cookies):
- 提到了堆栈Cookie技术,这是一种在堆栈上放置秘密值以监控堆栈溢出的方法。
- 指出NVIDIA尚未在其GPU上采用这种技术,并且攻击者可能会通过确定堆栈Cookie的值来绕过检测机制。
-
ASLR和PIE:
- 讨论了地址空间布局随机化(ASLR)和位置无关可执行文件(PIE)在NVIDIA GPU上的支持情况。
- 指出CUDA驱动程序API库即使在启用了CUDA ASLR的情况下也未编译为PIE,并且总是被加载在固定地址,这限制了ASLR在防御ROP攻击中的有效性。
-
W^X策略:
- 讨论了区分代码和数据页面对于对抗代码注入攻击的重要性。
- 建议GPU驱动程序应该为代码页面设置只读位,以防止代码被修改,并引入可执行位以防止数据页面被执行。