【论文分享】GPU Memory Exploitation for Fun and Profit 24‘USENIX

作者

目录

  • Abstract
  • Introduction
    • Responsible disclosure
  • Background
    • GPU Basics
      • GPU architecture
      • GPU virtual memory management
    • GPU Programming and Execution
      • GPU programming model
        • GPU kernel
        • Device function
      • NVIDIA PTX and SASS
      • SASS instruction encoding
    • GPU Memory Spaces
      • Global memory
      • Local memory
      • Shared memory
  • GPU Memory Safety
    • Prior Art
    • Our Goal
  • Demystifying GPU Memory
    • Buffer Overflows across Memory Spaces
      • Accesses to Global and Local Memory
        • Instructions
        • Memory layout
        • Addressing
      • Overflows across Global and Local Memory
        • OOB global memory references
          • Accessing a virtual PT address
          • Accessing a virtual local address
        • OOB local memory references
      • Overflows across Shared Memory and Local/Global Memory
      • Summary
    • Return Address Corruption
      • Stack Management
          • The role of R1
          • Stack commands
        • Return address
      • Return Address on the Stack
    • Code Injection
      • Executing data pages
      • Modifying code pages
    • CUDA ROP
      • CUDA library code
      • CUDA ROP gadgets
      • Generality
  • Case Study: Corruption Attacks on DNN
    • Threat Model
      • Victim
      • Attacker
    • Application Setups
      • Buffer overflow vulnerability
      • Triggering the buffer overflow vulnerability
    • Attack Methods and Results
      • Code Injection Attack
        • Step 1
        • Step 2
        • Step 3
      • ROP Attack
      • Address of the Malicious Code
      • Results
  • Discussion
    • BSYNC in CUDA ROP Gadgets
      • Usage of BSYNC
    • CUDA ASLR
    • CUDA JOP
    • Memory Errors in Real-World GPU Applications
    • CUDA Heap Exploitation
    • Countermeasures
      • OOB detection tools
      • Stack cookies
      • ASLR and PIE
      • WˆX policy
    • Related Work
  • Conclusion
  • A GPU List
  • B ROP Gadgets
  • C Shellcode
  • D System Specifications
  • E LLM Attacks
  • F JOP+ROP Attack

Abstract

现代应用程序越来越依赖 GPU 来加速计算,研究和理解 GPU 的安全影响变得非常重要。在这项工作中,我们对现代 GPU 上的缓冲区溢出进行了彻底检查。具体来说,我们证明,由于 GPU 独特的内存系统,与 CPU 程序相比,GPU 程序会遭受不同且更复杂的缓冲区溢出漏洞,这与先前研究的结论相矛盾。此外,尽管 GPU 在现代计算中发挥着关键作用,但 GPU 系统却缺少必要的内存保护机制。因此,当攻击者利用缓冲区溢出漏洞时,可能会导致代码注入攻击和代码重用攻击,包括面向返回编程(ROP)。我们的结果表明,这些攻击对现代 GPU 应用程序构成了重大的安全风险。

Introduction

图形处理单元 (GPU) 最初是为高质量图形渲染而设计和使用的。然而,在过去的十年中,它们已经发展成为通用计算平台。由于其高吞吐量能力,GPU 现在被用于各个领域,包括天气预报 [37]、加密货币挖掘 [28] 和生物信息学分析 [34]。此外,如今 GPU 已成为运行深度学习应用程序的事实上的标准选择 [9,17,24,29,30,47,52,55,56]。鉴于 GPU 的重要性日益增强,NVIDIA 最近发布了 Grace Hopper Superchip [42],该芯片专为大规模人工智能 (AI) 和高性能计算 (HPC) 应用而设计。该超级芯片使用高速互连 NVLink [43] 将 NVIDIA Grace CPU 和 Hopper GPU 结合在一起。 GPU 的广泛使用不可避免地要求对其安全影响进行彻底研究。

内存安全违规(内存错误)长期以来一直是计算系统的一个重大安全问题。这些违规行为是现代漏洞(攻击)的最常见根本原因。例如,缓冲区溢出可能允许攻击者覆盖返回地址,从而劫持程序的控制流,从而可能导致恶意代码的执行。事实上,Google 和 Microsoft 的报告显示,内存错误约占其产品中解决的所有安全问题的 70% [23, 39]。 CPU 程序的内存安全违规以及相关的利用技术已得到广泛研究。现代 CPU 甚至针对这些漏洞实施了某些内置防御机制(例如,[31,32,59])。然而,GPU程序中的漏洞并没有受到同样的关注。

CUDA [41] 由 NVIDIA 开发,是当今最流行的通用 GPU 编程语言之一。由于 CUDA 是从 C 和 C++(这两种语言以其内存不安全特性而闻名)扩展而来的,因此人们担心 CUDA 程序可能存在类似的内存安全漏洞。在本文中,我们深入探讨了这一问题,旨在回答以下问题:

在 NVIDIA GPU 上运行的 CUDA 程序会出现内存错误吗?如果是这样,这些错误会引发什么类型的攻击?

先前的几项研究已经探讨了 CUDA 程序中的内存安全漏洞 [19,38,48]。他们表明,内存安全违规,尤其是缓冲区溢出,也可能发生在 CUDA 程序中。然而,他们也认为传统的 CPU 内存利用技术,例如代码注入和代码重用,不适用于攻击 CUDA 程序。我们发现他们的调查有很大的局限性。

首先,这些研究的调查都是初步的,缺乏对GPU程序固有的内存安全漏洞的全面分析。例如,与 C 和 C++ 不同,CUDA 具有多个不同的内存空间,与 GPU 的专用内存层次结构保持一致。数据不同的内存空间有不同的作用域,并且以不同的方式访问。先前的研究仅表明缓冲区溢出可能发生在各个内存空间内。他们还没有探讨一个内存空间中的缓冲区溢出是否会直接影响另一内存空间中的数据。其次,这些研究是在早期的 NVIDIA GPU 上进行的,具有较旧的架构(Pascal 及更早版本)和 CUDA 计算功能(sm_60 及更早版本)。值得注意的是,从 2017 年发布的 Volta 架构(sm_70)开始,NVIDIA 对 GPU 系统进行了重大改变。因此,之前研究中得出的结论可能不适用于现代 GPU 架构。

在这项工作中,我们对现代 NVIDIA GPU 上的缓冲区溢出问题进行了彻底的检查。首先,我们对用于访问各种内存空间的机制进行逆向工程。具体来说,我们展示了 GPU 硬件如何识别对每个内存空间的内存引用,以及如何对这些内存空间进行地址转换。基于逆向工程结果,我们证明了对一个内存空间中的数据进行越界(OOB)操作可以影响另一内存空间中的数据,尽管不同的内存空间使用不同的指令进行访问。此外,我们还发现 OOB 操作可被利用来访问超出其合法范围的数据。例如,一个线程可以访问属于另一线程的本地内存。

然后,我们研究利用这些缓冲区溢出漏洞的潜在 GPU 攻击方法。我们发现现代 NVIDIA GPU 缺少基本的内存保护机制。因此,传统的内存利用技术(在 CPU 上已得到缓解)在 GPU 上仍然可行。例如,GPU 不区分代码页和数据页:数据页是可执行的,而代码页是可写的。

此外,我们还分析了现代 GPU 上函数调用和返回的机制。我们的调查表明,代码重用攻击,例如面向返回编程(ROP)[50],可以用于针对 CUDA 程序。我们进一步发现 CUDA 驱动程序 API 库链接为每个 CUDA 程序;在执行任何 CUDA 程序时,该库中的某些函数会加载到 GPU 内存中。重要的是,该库代码包含多个 ROP gadget,其中包括多个内存读/写 gadget,它们可以实现强大的基于 ROP 的攻击。

最后,我们证明上述内存利用技术可用于攻击现代 GPU 应用程序,例如深度神经网络 (DNN) 推理。例如,通过修改 DNN 权重,攻击者可以显着降低 DNN 推理精度,在最严重的情况下将其降低到与随机猜测相同的水平。

Responsible disclosure

我们于 2023 年 10 月向 NVIDIA 披露了我们的研究结果,NVIDIA 认可了我们的工作,并要求在结果公开后通知我们。

Background

在本节中,我们将概述 GPU 架构、编程模型和内存空间。请注意,虽然我们描述的概念对于 GPU 计算平台来说是通用的,但我们使用 NVIDIA 的术语进行描述。

GPU Basics

GPU architecture

Figure 1: GPU architecture overview.

图 1 显示了典型 GPU 的架构概述。 GPU 中的基本处理单元称为流式多处理器 (SM)。每个 SM 都有一组简单的核心。借助这些内核,SM 可以以单指令多线程 (SIMT) 方式执行一组并行线程(称为 warp)。现代 GPU 通常有数十到数百个 SM。典型的扭曲大小为 32,GPU 可以同时运行数千个线程。

GPU中的每个SM都包含自己的寄存器文件,由通用寄存器和专用寄存器组成。通用寄存器在 SM 上运行的线程之间进行分区。例如,在 NVIDIA Ampere GPU 中,SM 中的每个线程都有自己的 256 个通用寄存器,标记为从 R0 到 R255 [12, 46]。这些寄存器临时存储线程需要立即访问的数据,例如变量或中间计算结果。另一方面,特殊寄存器具有不同的作用并用于专门的任务。例如,CLOCK 提供当前时钟周期计数。与通用寄存器不同,一些特殊寄存器在 SM 中的所有线程之间共享。

为了满足大量线程的内存带宽需求,GPU 有其专用的内存系统,如图 1 所示。每个 SM 都有自己的私有 L1 缓存和共享内存。 SM通过分层片上网络连接到共享L2缓存; L2 高速缓存还连接到与片外设备存储器接口的存储器控​​制器。与CPU侧的主机内存类似,设备内存也是基于DRAM。目前,GDDR6 和 HBM2 是应用最广泛的两种客户端和服务器 GPU的 DRAM 类型。请注意,CPU 和 GPU 的内存系统是相互独立的。在程序开始在 GPU 上运行之前,GPU 驱动程序会将相应的代码加载到设备内存中。同样,GPU程序所需的数据也必须传输到设备内存中(然后才能访问数据)。这通常是通过程序中的显式操作来完成的,尽管在某些情况下驱动程序隐式管理此数据传输 [1]。

GPU virtual memory management

现代 GPU 内存是虚拟化的,在分页系统上运行。当 SM 生成虚拟地址时,GPU 上的内存管理单元 (MMU) 使用 GPU 页表执行虚拟到物理地址转换。每个正在运行的 GPU 程序(即 GPU 上下文)都有一个页表。这些页表(来自不同的活动 GPU 上下文)存储在 GPU 内存中,并由 GPU 驱动程序调节 [58]【The GPU driver also maintains a copy of the page tables in host memory.】。与 CPU 页表类似,GPU 页表也有多个级别:给定虚拟内存地址,GPU MMU 遍历这些级别以查找包含所需转换信息的页表条目 (PTE)。之前的工作 [58] 表明,最近的 NVIDIA GPU 使用 5 级页表。在页表遍历期间,49 位虚拟地址被分段,其部分用于选择通过层次结构的遍历路径。

GPU Programming and Execution

GPU programming model

Listing 1: An example CUDA program that uses multiple GPU memory spaces.

GPU 最初设计用于加速图形和多媒体处理;它们只能使用某些 API(例如 OpenGL [53] 和 DirectX [36])进行编程,以支持 2D/3D 图形渲染。随着非图形计算任务中使用GPU的需求增加,各种通用GPU编程模型已经出现。其中,CUDA 可以说是最成功和最广泛使用的[41]。清单 1 给出了一个简单的 CUDA 程序。在 CUDA 程序的上下文中,有几个特定术语:

GPU kernel

内核(第 7 行)是在 GPU 上执行的函数,可以从主机 CPU 调用。 CUDA 程序可能由一个或多个内核组成。为了调用内核,GPU驱动程序向GPU发送相应的内核启动命令。它将首先创建一个线程块网格,每个块包含一定数量的线程。然后,这些线程块被调度到 GPU 上的可用 SM 上。启动内核时,主机代码需要指定所需的线程块和线程数。例如,清单 1 启动了一个具有 8 个线程块、每个块中有 32 个线程的内核(第 22 行)。

Device function

设备函数是只能从内核或其他设备函数调用的函数。它只能在 GPU 上执行(第 2 行)。

NVIDIA PTX and SASS

PTX 是 NVIDIA GPU 的中级指令集,在不同 GPU 代之间保持稳定。 CUDA 代码首先编译为 PTX,PTX 进一步编译为 SASS(NVIDIA GPU 的低级汇编语言)。 SASS 指令直接在 NVIDIA GPU 硬件上执行。这些指令是根据GPU的特定架构定制的;不同代的 GPU 可能使用不同的 SASS 指令。

SASS instruction encoding

Figure 2: The encoding of the MOV instruction on Volta GPUs

NVIDIA GPU 使用固定长度指令编码格式。最初,指令长度为8字节。从 Volta 一代(2017 年发布)开始,指令长度已扩展至 16 字节。与通常使用基于硬件的指令调度的 CPU 不同,NVIDIA GPU 将此调度任务委托给编译器。在 Volta 和更高版本的 GPU(具有 16 字节指令)上,调度代码嵌入到每个指令的较高位中,指定连续指令之间的最短等待时间以满足依赖性约束。图 2 显示了 Volta GPU 上 MOV 指令的编码。

GPU Memory Spaces

Table 1: The specification of the pointers in Listing 1.

大多数 GPU 编程模型允许在不同的内存空间中分配内存,每个内存空间都有其独特的行为。清单 1 显示了一个使用全局、本地内存和共享内存是 CUDA 程序中最常用的内存空间。这些内存空间的具体特征如表1所示。我们省略了对其他内存空间的讨论,例如纹理内存,因为它们与我们的研究无关。

Global memory

由 GPU 驱动程序管理。全局内存中的缓冲区只能由 CPU 代码(在内核启动之前)通过驱动程序 API 调用进行分配(清单 1 中的第 19 行)。全局内存驻留在GPU的片外设备内存中;它可以缓存在 L1 和 L2 缓存中。程序所有内核中的所有线程都可以访问全局内存中的缓冲区,直到该缓冲区被释放为止。全局存储器的加载和存储操作通常分别使用指令LDG和STG来执行。在清单 1 中,d_global 是全局内存中的一个缓冲区。

Local memory

对每个线程来说是私有的。它用于存储线程的堆栈,因此也称为堆栈内存。与全局内存类似,本地内存也驻留在设备内存(和缓存)中。然而,与全局内存不同,本地内存中的数据不会跨内核持久化(因为它们是线程私有的)。指令 LDL 和 STL 特别用于本地存储器。清单 1 中的 d_local 是存储在本地内存中的缓冲区。

Shared memory

是暂存器存储区域。它在同一线程块内的所有线程之间共享。如图 1 所示,共享存储器位于片内。开发者可以将同一个块中线程频繁访问的数据放置到共享内存中,以避免全局内存访问缓慢。共享内存中的数据不会备份到片外设备内存中。 LDS和STS用于共享内存操作。清单 1 中的 d_shared 驻留在共享内存中。

除了上述用于每个特定存储空间的指令外,还有通用加载和存储指令LD和ST,它们可用于访问所有存储空间。

GPU Memory Safety

Prior Art

由于 CUDA 是 C/C++ 的扩展,因此 CUDA 程序也可能存在与 C/C++ 程序中类似的内存漏洞。先前的几项研究[19,38,48]表明,CPU 程序中发现的一些内存错误(例如缓冲区溢出)也可能发生在 CUDA 程序中。然而,这些研究有一些局限性,我们将在下面解释。

首先,这些研究中提出的调查是基础性的,并没有深入研究 GPU 特有的问题。例如,如第 2.3 节所述,CUDA 具有多个不同的内存空间(与 C 和 C++ 不同)。先前的研究仅发现缓冲区溢出错误可能发生在特定的内存空间内。例如,一个本地内存缓冲区上的 OOB 操作可能会损害存储在同一本地内存中的其他数据。然而,他们还没有探索这样的OOB操作是否会影响不同内存空间中的数据。

其次,鉴于这些研究是在几年前进行的,他们只检查了较旧的 GPU(在 Volta 之前)。然而,自 Volta 架构以来,NVIDIA对其 GPU 进行了重大改变[25]。例如,引入了新的ISA,其中指令长度从8字节变为16字节。因此,这些研究的结论可能不适用于较新的 GPU 架构。例如,这些先前的研究有两个共同的结论。 1)利用缓冲区溢出来劫持CUDA中的控制流非常困难,因为返回地址存储在未公开的内存位置中,而不是在堆栈上。 2)传统的代码注入攻击不能针对CUDA程序,因为代码和数据在内存中是分离的。然而,通过我们的分析(第4节),我们发现他们的结论并不成立。

Our Goal

如今,GPU 已成为主要的计算组件,了解现代 GPU 中存在的安全问题非常重要。我们的目标是对这些计算设备上的缓冲区溢出漏洞进行深入分析,揭示多年来被忽视的隐藏威胁。

Demystifying GPU Memory

在本节中,我们将详细分析 GPU 缓冲区溢出,解决第 3 节中提到的限制。为了全面了解 GPU 内存模型,我们开发了一个使用直接内存访问 (DMA) 来转储设备内容的工具内存。我们进一步设法恢复存储在设备内存中的页表。 NVIDIA 已公开其驱动程序源代码。因此,从有关GPU页面管理的驱动代码部分[2](以及其他NVIDIA文档[45]),我们可以获得NVIDIA GPU上页表的整体格式。这使我们能够从提取的设备内存中识别并重建页表。注意,除非另有说明,本节中的所有实验均在系统上进行配备 NVIDIA GeForce RTX 3080 GPU、NVIDIA 驱动程序 470.63.01、CUDA 11.4 和 Ubuntu 20.04 操作系统。为了简化分析,我们关闭CUDA ASLR。

Buffer Overflows across Memory Spaces

这里我们研究CUDA程序中的缓冲区溢出问题。正如第 3 节中所解释的,先前的研究(例如,[38])已经表明缓冲区溢出可能会导致单个内存空间内的内存损坏。因此,我们更多地关注研究不同内存空间的缓冲区溢出的影响。具体来说,我们首先探讨本地内存和全局内存之间的问题,然后将研究扩展到这两者和共享内存之间的问题。

Accesses to Global and Local Memory

Listing 2: Code for accessing global memory. / Listing 3: Code for accessing local memory

全局内存访问相对简单。全局内存使用 49 位虚拟地址(存储为 64 位值)进行索引。访问全局内存有两种主要方法:使用专用 LDG/STG 指令或通用 LD/ST 指令(参见第 2.3 节)。这两对指令的操作方式类似:如清单 2 所示,指令的目标虚拟内存地址存储在 64 位寄存器中,该寄存器由两个 32 位寄存器组合而成。我们不知道使用这两对指令访问全局内存之间有任何根本区别。使用哪对的选择似乎取决于编译器的偏好。根据经验,我们观察到,当启用调试选项时,NVCC 编译器始终选择 LD/ST,否则优先选择 LDG/STG。

由于本地内存是各个线程私有的,因此本地内存访问比全局内存访问更复杂,如下所述。

Instructions

与全局内存非常相似,也有两组用于访问本地内存的指令:LDL/STL 和 LD/ST。然而,它们的工作方式却截然不同。如清单 3 所示,LDL/STL 使用存储在 32 位寄存器中的 24 位地址2。相反,如前所述,LD/ST 需要 49 位虚拟地址(来自 64 位寄存器)。事实上,我们发现当使用 LD/ST 访问本地内存时,49 位地址似乎是带有 25 位值前缀的 24 位地址(在我们的系统上为 0x7ffff2)。请注意,在我们的计算机上,属于其他内存空间的虚拟地址永远不会以此前缀值开头。与全局内存不同,编译器总是更喜欢使用 LDL/STL 访问本地内存,即使调试标志打开也是如此。

Memory layout

Listing 4: A simple CUDA program that allocates buffers in local memory.
我们使用清单 4 中的程序来解释本地内存布局:CUDA 内核 (local_arr) 启动时每个线程块有 32 个线程,总体上只有一个线程块。在此内核中,每个线程分配一个本地数组 (arr),总共有 32 个单独的数组。根据NVIDIA的规定,每个线程只能访问自己的数组,而不能访问属于其他线程的数组。
igure 3: The mapping details of local memory: (a) the translation between virtual local addresses and physical addresses; (b) the layout of local memory (in device memory); (c) the translation between virtual PT addresses and physical addresses.

为了了解本地内存如何存储在设备内存中,我们执行清单 4 中的程序并在第 6 行暂停。然后,我们转储设备内存内容并识别 arr 在其中的位置(通过数据模式)。图 3 (b) 显示了 arr 所在的转储设备内存段。从该图中,我们可以观察到不同线程的本地内存在设备内存中是交错出现的。具体来说,设备内存顺序存储来自所有线程的 arr[0],然后存储来自所有线程的 arr[1],依此类推。然后我们进行进一步的实验,调整总线程数和数组的数据类型。这些实验表明,warp 中线程(包含 32 个线程)的每 32 位本地内存始终连续存储在设备内存中。对于大于32位的变量,它们被分成32位段并单独存储。请注意,此布局信息可以帮助攻击者故意篡改本地内存数据,我们将在稍后展示。

Addressing

Figure 4: The output of line 6 in Listing 4.

鉴于每个线程都有自己的私有 arr,人们自然会期望每个 arr 都有一个不同的虚拟地址,这与 CPU 上的情况类似。然而,当我们如清单 4 第 6 行中所示打印 arr[0](或 ptr[0])的地址时,我们有两个有趣的观察结果,如图 4 所示。首先,不同线程的 arr[0] 实际上具有相同的虚拟地址。在我们的机器上,这个虚拟地址是 0x7ffff2fffd80(前缀+本地内存地址,参见清单 3)。其次,当使用该地址执行数据访问时,每个线程检索不同的数据。更具体地说,对于给定线程,检索到的数据对应于到该线程的 arr[0]。根据这些结果,我们相信本地内存的虚拟地址和物理地址之间的映射与图 3 (a) 和 (b) 中所示的映射一致:虚拟地址指向的物理地址可能会有所不同,具体取决于本地内存的 ID访问该虚拟地址的线程。

这里一个自然的问题是如何将相同的虚拟地址转换为不同的物理地址(对于不同的线程)。虽然 NVIDIA GPU 上的本地内存地址转换的具体细节尚未公开,但我们在这里给出一个猜想:本地内存可能存在一种独特的地址转换机制,该机制基于地址和线程 ID。此外,GPU硬件能够根据给定的虚拟地址或指令识别出给定的内存操作是针对本地内存(而不是其他内存空间)。具体来说,如果 1) 指令是 LDL/STL 或 2) 指令是 LD/ST 并且地址以特定模式(例如 0x7ffff2)开始,则将其视为本地内存操作。

在检查清单 4 中程序的页表后,我们得到了一个非常有趣的观察结果。根据页表,上述本地内存数据的虚拟地址(例如,arr[0] 的 0x7ffff2fffd80)未映射到任何有效的物理地址。【这也证实了本地内存地址存在特殊的地址转换路径。】 相反,对于本地内存中的每个数据块,都有一个不同的虚拟地址(例如,线程 0 的 arr[0] 为 0x7fffc4014000)看似与上面未映射的虚拟地址无关 - 是映射到该数据块的物理地址,如图3©所示。重要的是,当使用这个虚拟地址访问本地内存时,每个线程在访问相同的虚拟地址时都会获得相同的数据。例如,任何访问地址 0x7fffc4014000 的线程都将检索线程 0 的 arr[0] (0xdead0000) 的值,无论其线程 ID 是什么。类似地,使用地址0x7fffc4014004,每个线程都获取线程1的arr[0](0xdead0001)的值。

从以上结果,我们有两个结论。首先,对于本地内存中的每个物理地址,都有两个虚拟地址可以用来访问这个物理地址。其中只有一个在页表中有有效的映射;我们将该虚拟地址称为虚拟PT地址(例如图3(c)中的0x7fffc4014000),并将另一个虚拟地址称为虚拟本地地址(例如图3(a)中的0x7ffff2fffd80)。其次,如上所述,当访问虚拟本地地址时,GPU硬件可以识别出该地址是针对本地内存的,并为其触发特殊的地址转换例程。这个特殊例程考虑了线程 ID,从而确保每个线程只能访问自己的本地内存。然而,当访问虚拟PT地址时,GPU硬件不会将其识别为本地存储器访问,因此不使用这种特殊的转换例程。一旦知道该地址,一个线程就可以访问/修改程序中另一线程的本地内存。

为了进一步验证虚拟本地地址和虚拟PT地址都指向同一物理地址,我们进行了写后读实验。首先,我们使用虚拟 PT 地址写入线程 0 的 arr[0]。然后,我们使用虚拟本地地址读取线程0的arr[0]。我们发现,如果我们在写入和读取操作之间访问另一个大小至少为 128B 的缓冲区,则只能读出先前写入的值。鉴于我们 GPU 上的 L1 Dcache 为 128B,并且 L1 使用虚拟地址进行索引并使用物理地址进行标记,我们认为这些两个虚拟地址链接到同一个物理地址。

要点 1:在 NVIDIA GPU 上,本地内存中的每个数据块都链接到两个虚拟地址;这些地址之一允许 CUDA 线程访问/修改其他线程的本地内存。

Overflows across Global and Local Memory

OOB global memory references

回想一下,无论使用什么指令,全局内存操作始终使用 64 位地址。因此,攻击者可以利用全局内存中的缓冲区溢出漏洞来影响设备内存中的任何位置,包括本地内存。具体来说,当使用 OOB 索引访问全局内存缓冲区时,攻击者可以操纵索引将目标地址(基地址 + 索引)定向到 1)虚拟 PT 地址或 2)数据中的虚拟本地地址。本地内存。下面我们讨论一下这两种方法的可行性:

Accessing a virtual PT address

前面提到,全局内存引用有两组指令,LD/ST 和 LDG/STG。根据我们的实验,为这些指令中的任何一个提供虚拟 PT 地址都会使它们按预期执行,从而使用给定地址访问本地内存(任何线程)中的数据。

Accessing a virtual local address

当向 LDG/STG 提供虚拟本地地址时,会提示运行时错误,引用非法内存访问。相反,当向 LD/ST 提供这样的地址时,指令执行时不会出现任何错误。然而,正如前面所解释的,使用虚拟本地地址可以防止我们修改或访问属于其他线程的数据。我们讨论这种方法只是为了完整性。在现实场景中,攻击者可能会选择前一种方法。

简而言之,全局内存中的缓冲区溢出错误可能允许攻击者瞄准虚拟 PT 地址,从而使攻击者能够访问或修改程序中任何活动线程的本地内存数据。

OOB local memory references

我们发现(本地内存的)虚拟本地地址和全局内存的虚拟地址之间存在很大的差距。在测试的系统上,这些地址之间的最小差异是 0x10000000。由于这种差异,本地内存上的 OOB 操作是否会影响全局内存实际上取决于处理该操作的特定指令:当使用 LDL/STL 时,它使用 24 位地址(虚拟本地地址的低 24 位)进行操作,本地内存上的OOB操作不能影响全局内存。相反,如果使用采用完整 64 位虚拟本地地址的 LD/ST,则本地内存上的 OOB 操作有可能获取/修改全局内存中的数据。

Overflows across Shared Memory and Local/Global Memory

共享内存中的数据的访问方式与本地内存中的数据类似。具体来说,可以使用 1) 使用 24 位地址的专用指令 LDS/STS,或 2) 使用 49 位地址的通用指令 LD/ST 来访问共享存储器。同样,49 位地址是通过在 24 位地址上添加前缀而形成的,在测试系统上为 0x7ffff4。因此,当使用 LD/ST 指令时,对共享内存中的数据进行 OOB 操作可能会影响全局或本地内存中的数据。此外,对全局或本地内存(使用 LD/ST)中的数据进行 OOB 操作可能会影响共享内存中的数据。请注意,与本地内存不同,共享内存没有虚拟 PT 地址,因为它不是设备内存的一部分。这意味着,对共享内存的访问仍然局限于其合法范围(在线程块内)。我们不能利用虚拟 PT 地址来执行超出范围的共享内存访问(如对本地内存所做的那样)。

Summary

Table 2: Summary of the buffer overflow problem in CUDA; ✓ means the OOB operation can affect this memory space, while ✗ means it cannot.

我们在表2中对CUDA中的溢出问题进行了全面的总结。首先,关于内存空间,当使用通用内存指令LD/ST时,该问题可能发生在单个内存空间内或跨不同空间。相反,当使用专用指令(例如 LDL/STL)时,问题仅限于单个内存空间。一个例外是,带有 LDG/STG 的 OOB 全局内存引用会影响本地内存。

其次,就内存范围(即可见性)而言,当针对本地内存时,溢出错误可能会导致访问超出预期范围的过程。这是由于使用了虚拟 PT 地址。在其他场景下,内存访问总是被限制在合法范围内。

请注意,本节中提出的结论,特别是与如何管理 GPU 内存访问相关的结论,是基于广泛的逆向工程工作。虽然它们得到了彻底实验的支持,但我们不能绝对肯定地声称我们的发现完全准确。然而,值得注意的是,我们逆向工程分析的主要目标不是完美地重建 GPU 内存访问功能,而是调查 CUDA 程序中缓冲区溢出漏洞的可能性。尽管我们的逆向工程结果存在任何潜在的不准确之处,但我们已经最终证明,GPU 上的缓冲区溢出可被利用来跨不同内存空间和超出合法范围访问/修改数据(表 2)。

Return Address Corruption

返回地址损坏是一种严重的安全威胁,因为它允许攻击者劫持程序的控制流,可能导致任意代码执行。在 CPU 上,攻击者可以利用堆栈缓冲区溢出漏洞来覆盖堆栈上的返回地址。然而,先前的研究 [38, 48] 表明这种利用在 GPU 上是不可行的:他们声称在 GPU 上,返回地址存储在设备内存中的未公开位置,而不是堆栈(本地内存)上。我们在本节中重新审视这一主张。

Stack Management

Figure 5: Assembly code when entering/leaving the device function; the 64B local array in the device function is stored in [R1] to [R1+0x3c].

为了了解 GPU 返回地址的管理,我们启动了一个简单的 CUDA 内核,其唯一任务是调用设备函数。设备函数分配一个64B的本地数组并用0xdeadbeef填充它。图 5 显示了该设备函数的汇编代码片段,说明了函数开头和结尾处的堆栈管理。当函数启动时,函数想要的某些寄存器要修改的内容被推送到堆栈,以便稍后在函数完成时恢复。相反,函数完成后,这些寄存器将从堆栈中弹出并恢复。这些代码片段提供了有关 CUDA 堆栈管理的两个关键见解:

The role of R1

在 CUDA 中,R1 是通用寄存器,而不是特殊寄存器 [44]。然而,上面的代码暗示R1可以用作堆栈指针。事实上,通过进一步检查libcudnn等常见的CUDA库,我们发现R1是唯一被用作堆栈指针的寄存器。值得注意的是,NVIDIA 的特殊寄存器列表不包含任何堆栈指针寄存器 [44]。

Stack commands

与 RISC-V 架构类似,NVIDIA GPU 没有专用的入栈/出栈指令。相反,推送操作是通过本地内存写入和堆栈指针递减来实现的。相反,出栈操作是通过本地内存读取和堆栈指针的递增来实现的。

Return address

Figure 6: Part of the local memory in the dumped device memory; the local array and R20.64 (i.e., R21||R20) are highlighted; “*” means the data is the same with above.

在图5中,返回指令(RET)使用R20作为操作数。该寄存器在进入函数时被压入堆栈,并在 RET 指令之前检索。直观上看,R20中的值应该与返回地址有关。为了验证这一点,我们在 CUDAGDB 中运行程序,发现 R20.64 的值(即 R21||R20)[虽然指令中没有明确指定,但 RET 似乎总是从 R20.64 而不仅仅是 R20 检索返回地址。] 与预期返回地址相同(在我们的具体情况下为 0x7fffd6fad8e0)。为了更好地理解这一点,我们提取设备内存(在执行 RET 之前)并在图 6 中显示本地内存部分。我们可以看到 R20.64 的值位于本地内存中,靠近本地数组(填充为0xdeadbeef)。这一观察结果证实了由 R20.64 表示的返回地址与局部变量一起存储在堆栈上,这与之前的研究结论相矛盾。

我们进一步对本地数组执行 OOB 操作以覆盖返回地址,将其指向程序中的另一个函数。正如预期的那样,当函数返回时,它将继续处理被覆盖的地址,执行位于那里的代码,而不是返回到原始调用者地址。当我们对全局内存(或共享内存)执行 OOB 操作以覆盖返回地址时,会发生类似的行为(参见表 2)。

要点 2:在 CUDA 中,利用缓冲区溢出漏洞允许攻击者修改本地内存(堆栈)中存储的返回地址,从而重定向程序的控制流。

Return Address on the Stack

在 4.2.1 节中,我们重点关注在函数执行期间将返回地址寄存器(例如图 5 中的 R20)压入堆栈的场景。然而,NVCC 编译器并不总是选择这种方法。编译器通常避免在整个函数中使用该寄存器,而不是将返回地址寄存器压入堆栈。仅当难以(或不可行)确保该寄存器在函数中保持不变时,它才会选择压入该寄存器。下面是返回地址寄存器被压入堆栈的一些场景。

  1. 设备函数是递归的。
  2. 设备函数有大量局部变量,导致寄存器不足(即寄存器溢出)。

Code Injection

Executing data pages

通过覆盖返回地址的能力,攻击者可以将执行重定向到他们已填充 shellcode 的数据页。然后,当函数返回时,执行会转向该恶意代码,从而导致代码注入攻击。此类攻击已经在 CPU 上得到缓解:例如,大多数 CPU 系统都实施了 WˆX 策略,该策略要求每个内存页面都可以写入或可执行,但不能同时两者兼而有之。这会阻止 shellcode 被直接执行。然而,我们发现这个策略并没有在现代 GPU 上实现。

我们的结果表明,通过操纵返回地址,我们可以将控制流重定向到全局内存地址并在那里执行数据(作为代码)。我们还可以更改控制流以指向本地内存地址并执行堆栈上的数据。[控制流无法重定向到共享内存地址,这意味着我们无法像执行代码一样执行共享内存中存储的数据。]这些发现表明 GPU 确实不使可写数据页不可执行。我们还发现,根据NVIDIA发布的页表格式[2, 45],PTE中没有“可执行位”(也没有“脏位”)。这意味着 GPU 在执行某个地址的内容之前不会检查该地址是否是合法的代码地址。请注意,先前的研究声称在 GPU 上执行数据缓冲区是不可行的;他们认为这要么是因为代码和数据地址是分开的,要么是因为数据页不可执行 [38, 48]。我们发现这些假设都不准确。

Modifying code pages

鉴于数据页是可执行的,自然会出现一个问题:代码页是否可修改。事实上,之前的工作已经表明可以修改旧 GPU 上的代码页。我们通过检查写入代码页之前和之后的设备内存,在现代 GPU 上进一步验证了这一点。此外,在检查 GPU 页表格式时,我们注意到 PTE 中有一个“只读位”。然而,在分析页表(从设备内存中提取)后,我们发现该位始终保持未设置状态,即使对于代码页也是如此。

要点 3:NVIDIA GPU 不区分代码页和数据页。

CUDA ROP

返回地址损坏还可能导致代码重用攻击,其中 ROP 就是一个主要示例。事实证明,ROP 在 CPU 上非常有效,在常用的库代码(例如 libc)中可以找到许多 ROP 小工具。这里我们研究ROP在现代GPU上的可行性

CUDA library code

在执行 CUDA 程序期间检查设备内存的内容时,我们发现除了应用程序特定的代码之外,还有其他代码加载到设备内存中。此附加代码对于每个 CUDA 程序都是相同的。我们将这段代码与常见CUDA库的机器码进行比较,发现这段代码是libcuda的一部分。 NVIDIA 将 libcuda 描述为 CUDA 驱动程序 API 库,它处理与 GPU 直接交互相关的任务,例如内存管理、错误处理和流管理。该库中的函数包括(但不限于)printf、cuMemcpy 和 cuMemFree。此外,我们的实验表明,将 CUDA 程序的控制流重定向到该驱动程序 API 代码中的地址后,我们可以执行该代码而不会触发任何错误。

CUDA ROP gadgets

Figure 7: CUDA ROP gadgets.

我们检查此驱动程序 API 代码并发现 190 个返回指令 (RET)。在这些 RET 指令中,只有 52 条指令附带弹出返回地址的指令。这意味着,此驱动器 API 代码中只有 52 个可能的 ROP 小工具,其中包括 7 个内存损坏小工具和其他 45 个小工具。清单 7 显示了两个示例小工具:第一个将数据从寄存器写入存储器一个地址,而第二个从一个内存地址读取数据,然后将其写入另一个地址。不幸的是,我们的分析表明这个 CUDA 小工具集不是图灵完备的。然而,稍后在第 5 节中,我们将展示即使使用这些有限的小工具,攻击者也可以显着降低 GPU 上基于 DNN 的应用程序的性能。此外,使用内存损坏gadgets,我们或许可以修改CUDA代码(未写保护)来创建一个图灵完备的gadgets集合,从而实现任意计算。

请注意,很难出现意外的 ROP 小工具,因为所有 GPU 指令都必须是 8B 对齐的。此外,包括其他常见的CUDA库,例如libcublas和libcudnn,并没有真正带来更多的ROP小工具:这些库经过如此优化,以至于返回地址几乎从未被推送到堆栈;它通常存储在寄存器中。

要点 4:ROP 可用于读取或写入 NVIDIA GPU 上的内存。

Generality

在本节中,我们讨论并介绍使用本节开头指定的平台进行的调查。然而,从这些调查中得出的结论,包括跨内存空间内存损坏的可行性(参见表 2),以及代码注入和代码重用攻击的可能性,也适用于其他现代 NVIDIA GPU。【具体细节(例如 ROP gadget 的数量)可能会因 CUDA 版本的不同而略有不同。】 我们已经验证了这些漏洞存在于多个 NVIDIA GPU 上,涵盖多个最新架构,包括 Volta、Turing、Ampere 和 Ada Lovelace;我们使用从版本 470.63(于 2021 年 7 月发布)到版本 550.67(于 2024 年 3 月发布)的各种 NVIDIA 驱动程序以及从版本11.2 至版本 12.4。结果一致表明,无论使用什么驱动程序/CUDA 版本,所有测试的 GPU 中都存在这些漏洞。附录 A 中提供了经过测试的 GPU 的完整列表。

Case Study: Corruption Attacks on DNN

在本节中,我们将演示第 4 节中讨论的 GPU 内存损坏漏洞如何对基于 DNN 的应用程序(最常见的 GPU 应用程序之一)造成重大安全风险。

Threat Model

Victim

受害者是一个基于 DNN 的应用程序,运行在配备现代 NVIDIA GPU 的服务器上。该应用程序接收来自远程用户的请求,使用 DNN 模型处理这些请求,然后发回响应。我们假设DNN推理过程中涉及的一些CUDA内核存在内存损坏漏洞(漏洞示例将在稍后的5.2节中讨论)。作为一种常见的做法,模型参数(例如权重)会在应用程序初始化期间加载到设备内存中。为了最大限度地减少响应延迟,这些参数在用户请求期间保留在内存中,而不是为每个新请求重新加载或在处理请求后删除。

Attacker

攻击者是可以向受害应用程序发送请求的远程用户。通过制作恶意请求(第 5.2 节中详细介绍),攻击者能够利用受害应用程序使用的 GPU 内核中的缓冲区溢出漏洞。攻击者的主要目标是通过此漏洞更改模型参数,例如权重。因此,对其他用户未来请求的推断将受到影响。我们假设攻击者了解受害者 DNN 的布局模型权重。

Application Setups

在上述威胁模型下,我们选择四种广泛使用的视觉模型作为潜在受害者进行评估:ResNet-18 [26]、ResNet-50 [26]、VGGNet [54] 和 Vision Transformer (ViT) [35].7我们在云环境中使用流行的 DNN 框架(例如 PyTorch [49])来实现这些模型。

我们将 DNN 推理应用程序(即受害者应用程序)托管在配备服务器级 GPU 的云系统上的虚拟机中,这与第 4 节中实验使用的系统不同。我们利用 NVIDIA 的虚拟 GPU(vGPU) )虚拟化 GPU 的技术[13]。这是云环境中 DNN 推理的常见配置 [7, 8]。该系统的详细规格可以在附录D中找到。注意vGPU不支持CUDA ASLR;对于 vGPU,即使激活 ASLR,地址也不会随机化。我们在配置中保持 CUDA ASLR 处于激活状态,因为它是默认设置。然而,它没有任何效果。我们稍后将在 6.2 节中讨论非虚拟化环境中的 CUDA ASLR。

之前的工作 [18] 表明现代 DNN 框架可能容易受到 GPU 缓冲区溢出问题的影响,但尚未披露任何具体实例。同样,我们还没有发现这些框架中存在任何溢出漏洞。然而,本文的目的不是发现此类漏洞;而是要发现这些漏洞。我们把这个任务留给未来的研究。相反,为了我们的评估目的,我们故意将缓冲区溢出漏洞引入 DNN 框架中。

Buffer overflow vulnerability

Listing 5: The device function for matrix-vector mulpiclication with a buffer overflow vulnerability.

我们在受害者应用程序中包含了一个用于矩阵向量乘法(具有溢出漏洞)的 CUDA 设备函数,如清单 5 所示。矩阵向量乘法在 DNN 推理中非常重要且常用。此代码对矩阵中的每一行使用多个 CUDA 线程来计算部分和,并且每个线程在计算之前将向量的必要部分从全局内存传输到其本地内存。我们故意在这个内核中引入一个漏洞:它缺乏适当的检查来确保每个线程处理的向量部分的大小不超过线程本地数组的容量。因此,当用户控制的向量大小(如下所述)大于预期时,可能会发生堆栈溢出。

Triggering the buffer overflow vulnerability

为了触发清单 5 中的漏洞,我们假设向量 (n) 的大小由用户控制。这种情况的一个例子发生在数据预处理阶段。具体来说,用户提供的输入数据的大小可能并不总是与 DNN 模型所需的输入大小匹配。例如,用户可能提供尺寸为 512×1024 像素的图像,而 DNN 模型设计为仅处理 256×512 像素的图像。为了处理此类差异,可以使用卷积层对用户输入进行预处理,然后再将其输入 DNN 模型。该卷积层通常采用矩阵向量乘法来进行性能优化[16]。在此预处理卷积层中,矩阵和向量(m 和 n)的维度由用户输入的大小确定。如果输入大小明显大于 DNN 模型预期的大小,这可能允许用户触发清单 5 中的漏洞。

Attack Methods and Results

在本节中,我们将研究攻击者可以用来修改权重的两种策略:代码注入和 ROP 攻击。我们讨论攻击者发起这些攻击必须采取的具体步骤以及由此产生的结果。

Code Injection Attack

我们将代码注入攻击实现为受控权重攻击:攻击者可以控制写入内存的数据,并且可以将权重修改为任何所需的值。具体来说,攻击者使用 shellcode 准备一个数据缓冲区,将特定值写入给定地址,并使用堆栈溢出漏洞将控制流重定向到该缓冲区(参见清单 5)。 shellcode的详细信息可以在附录C中找到。攻击过程分为三个步骤:

Step 1

攻击者准备一个数据缓冲区,其大小足够大,当该缓冲区发送到受害者进行 DNN 推理时,会触发受害者的缓冲区溢出错误(参见清单 5)。

Step 2

攻击者操纵缓冲区中的数据来实现两个关键目标:1)在将该缓冲区复制到本地内存后,每个线程的本地数组(清单 5 中的 arr_local)将填充预期的 shellcode; 2) 数据复制后,每个线程的返回地址都会被这个本地数组(shellcode 所在的位置)的地址覆盖。这些准备工作对于确保触发缓冲区溢出时导致预期的代码注入攻击至关重要。

Step 3

攻击者使用该数据缓冲区作为输入发起 DNN 推理请求。

ROP Attack

我们将 ROP 攻击实现为不受控制的权重修改攻击:攻击者重复执行单个内存写入小工具来修改权重。攻击者控制提供写操作中使用的地址的寄存器,但不控制提供要写入的数据的寄存器。该小工具的详细信息在附录B中。与5.3.1节中介绍的代码注入攻击类似,该ROP攻击也分为三个步骤,与代码注入攻击中的步骤非常相似。但是,第二步(准备数据缓冲区)的目标略有不同。具体来说,攻击者需要操作输入缓冲区中的数据,以确保在数据复制后,1)堆栈上的返回地址被 ROP gadget 的地址覆盖,2)某些寄存器将被使用通过 ROP 小工具,从堆栈中弹出时接收预期值。

Address of the Malicious Code

在上述两种类型的攻击中,为了修改 DNN 权重,攻击者需要知道恶意代码(ROP gadget 或 shellcode)的地址。如第 5.2 节所述,CUDA ASLR 在使用 vGPU 时没有任何影响,从而使该地址在执行过程中可预测且稳定。因此,攻击者可以相当简单地确定 shellcode/ROP 小工具的地址。例如,一旦攻击者分析了一个 NVIDIA GPU 的内存并识别了 ROP gadget 的地址,它就可以对同一代并使用相同 CUDA 工具包的所有 NVIDIA GPU 发起 ROP 攻击,因为这些 gadget 加载在固定地址。我们在6.2节讨论ASLR生效的场景(在没有虚拟机的原生环境中)。

Results

Table 3: The DNN inference accuracy with the weight modification attacks (only for weights in the first layer).

我们使用5.3.1节和5.3.2节中的攻击方法,在修改每个模型第一层的权重后测试模型的准确性。表3显示了修改10%、20%、50%和100%后的准确率结果权重。在代码注入攻击中,攻击者可以指定所需的权重值,我们为每个权重选择一个较大的值。这是因为 DNN 模型中的大多数权重值都非常小;使用较大的值预计会对模型性能产生重大影响。如表所示,这种方法显着降低了所有测试模型的准确性。 ResNet-18 和 ViT 尤其受到影响,其准确性几乎反映了随机猜测。相比之下,在攻击者无法控制修改后的权重值的 ROP 攻击中,准确性基本上不受影响。这是因为攻击中使用的 ROP gadget 恰好将权重更改为一个较小的值,而不是一个接近权重原始值的大值。

Discussion

BSYNC in CUDA ROP Gadgets

Usage of BSYNC

CUDA提供了不同级别的同步机制。 BSYNC 指令专门用于 Warp 内同步。尽管 warp 中的线程旨在同时执行相同的指令,但某些情况(例如条件分支)可能会导致线程发散。 BSYNC 和 BSSY 用于管理此类情况:BSSY 向硬件发出信号以准备发散并指定重新收敛的地址 [25]。 BSYNC 充当同步屏障:当 warp 中的线程到达 BSYNC 时,它会等待 warp 中的其他线程。

我们发现 BSYNC 和 BSSY 总是一起(成对)出现在 CUDA 二进制文件中。但是,这种配对可能不会在 ROP 小工具中保留。例如,图 7 中的小工具仅包含 BSYNC,但不包含 BSSY。这意味着,BSYNC执行时没有指定重新收敛地址,这可能会导致错误。有趣的是,我们的分析表明,如果 warp 中的线程不发散,BSYNC 不会影响执行。因此,只要线程在执行 ROP 小工具时保持同步,小工具就会按预期运行,而不会受到 BSYNC 的影响。这是一个可行的条件,特别是对于基于 DNN 的应用程序来说,线程分歧很少发生。

CUDA ASLR

在非虚拟化环境中,CUDA 支持数据和代码的 ASLR。另外,激活CUDA ASLR取决于操作系统中的 ASLR 设置。如果在操作系统中启用了 ASLR,则 CUDA ASLR 也会启用(由 GPU 驱动程序),反映与操作系统中类似的随机化级别(例如,无随机化、保守随机化或完全随机化 [11])。请注意,如第 5.2 节所述,CUDA ASLR 无法在使用 vGPU 技术的虚拟化环境中运行 [13]。

CUDA ASLR 在运行时使攻击者更难获取 shellcode 的地址,从而有助于减轻代码注入攻击。然而,攻击者可能能够通过 GPU 侧通道绕过 CUDA ASLR(例如,[58])。此外,我们发现 4.4 节中提到的 CUDA 驱动程序 API 库始终加载在固定地址,即使应用完全随机化也是如此。因此,我们不能依靠 CUDA ASLR 来完全阻止攻击者利用该库中的 ROP gadget。

CUDA JOP

与ROP类似,面向跳转编程(JOP)[15]也是一种先进的代码重用攻击技术。 JOP 使用以间接跳转结束的小工具,而不是链接以返回结束的小工具。这些跳转使用寄存器来确定目标地址。为了链接 JOP 小工具,我们需要一个“调度程序”(也称为“调度程序小工具”)。它的作用是将下一个gadget的地址加载到适当的寄存器中,然后跳转到它。

在 NVIDIA GPU 上,间接跳转指令是 BRX(例如“BRX R3, 0xa0”)。通过该指令,可以在 GPU 上形成 JOP。正如 4.4 节中提到的,我们在 libcudnn 和 libcublas 等常见 CUDA 库中没有找到任何 ROP gadget。然而,我们发现其中一些库确实使用了 BRX,因此包含 JOP 小工具。不幸的是,在分析这些小工具后,我们发现它们只能执行有限的功能。例如,libcuda.so.470.63.01 库包含 156 个 JOP 小工具,但其中 151 个仅用于移位寄存器值。其他常见 CUDA 库(例如 libcudnn_cnn_infer.so.8.2.2)也进行了类似的观察。但是,我们可以结合 ROP 和 JOP 小工具来实现更多功能。更多详细信息请参见附录 F。

Memory Errors in Real-World GPU Applications

现有 GPU 应用程序中已发现内存错误,尤其是缓冲区溢出错误。首先,之前的一项研究[22]分析了16个基准测试套件中的175个常用GPU程序,发现其中7个程序存在13个缓冲区溢出错误;其中一些错误与我们在机器学习攻击中假设的错误非常相似。其次,在利用 GPU 加速渲染任务的网络浏览器中也发现了缓冲区溢出错误。例如,2022 年,研究人员发现 Chrome 中存在缓冲区溢出错误这种情况发生在 CPU 和 GPU 内存之间的数据传输过程中 [3]。这允许远程攻击者通过精心设计的 HTML 页面逃离沙箱。此外,在 2023 年,事实证明,在 GPU 上运行的 WebGL 程序中也可能发生缓冲区溢出,导致浏览器崩溃 [4]。

此外,之前关于模糊 ML 框架的研究(例如,[18])已经发现其 GPU 内核中存在重大错误。其中包括导致结果不准确的计算错误和可能导致整个应用程序崩溃的崩溃错误。然而,这些初步的模糊研究尚未揭示这些框架中任何可利用的内存错误,我们将其留给未来的工作。请注意,考虑到 CUDA 和 C/C++ 之间的相似编程模型,我们认为这些框架中可能存在可利用的内存错误。

CUDA Heap Exploitation

CUDA 支持动态内存分配:在 CUDA 内核中动态分配的缓冲区(使用 malloc() 函数)驻留在 GPU 的堆内存中。【堆内存与全局内存不同。全局内存缓冲区只能由 CPU 代码分配(在内核启动之前,参见第 2.3 节),而堆缓冲区可以在 CUDA 内核执行期间由 GPU 代码分配】堆内存在 GPU 进程的生命周期内是持久的,并在该进程中的内核之间共享。 NVIDIA尚未发布CUDA中使用的具体内存分配器。然而,我们的实验结果表明,该分配器遵循与 CPU 内存分配器(例如 ptmalloc [14])类似的策略。因此,CUDA 程序也可能容易受到空间/时间堆漏洞的攻击,类似于 CPU 程序中的漏洞。然而,由于其显着的性能开销,通常不鼓励在 GPU 上使用动态内存分配 [33]。在分析了常见的 CUDA 应用程序和基准测试后,我们没有发现任何使用动态缓冲区分配的场景。因此,我们认为与静态分配的内存(本地/全局/共享内存)中的溢出相比,这个问题要小得多。

Countermeasures

OOB detection tools

有很多工具可以静态/动态检测 GPU 程序中的缓冲区溢出错误。首先,NVIDIA Compute Sanitizer [40] 是一种 GPU 内存安全检查工具,它基于动态二进制检测:它在执行之前拦截运行时的每条程序指令。虽然有效,但这种方法会带来相当大的性能开销。其次,cuCatch [57] 是一个编译时工具,可以帮助检测 CUDA 程序执行期间的空间和时间内存错误。它存储内存安全所需的元数据检查一张表,每个分配有一个条目。给定一个指针,相应的表项索引可以嵌入到指针的高位中(如果可能),也可以存储在影子内存中。与 NVIDIA Compute Sanitizer 相比,cuCatch 引入的性能开销要少得多。但需要注意的是,这些工具都无法达到 100% 的检测率。

Stack cookies

堆栈金丝雀或堆栈 cookie 是放置在堆栈上的缓冲区和控制数据之间的秘密值,用于监视堆栈溢出。不幸的是,NVIDIA 并没有在他们的 GPU 上采用这种技术。此外,值得注意的是,之前的研究已经表明,攻击者可能能够确定堆栈cookie的值,从而绕过检测机制(例如[51])。

事实上,研究人员已经提出了几种基于金丝雀的检测工具(针对 GPU 缓冲区溢出),例如 GMOD [21]、GMODx [20] 和 clARMOR [22]。他们在每个 GPU 缓冲区(不仅仅是堆栈缓冲区)之前和之后插入一个金丝雀,并通过定期验证这些金丝雀的完整性来检测缓冲区溢出。这些工具的性能开销最小。但是,它们无法检测 OOB 读操作或非相邻 OOB 读/写操作。

ASLR and PIE

NVIDIA GPU 支持位置无关可执行文件 (PIE) 和 ASLR。然而,我们发现包含强大 ROP 小工具的 CUDA 驱动程序 API 库并未编译为 PIE,并且即使在激活 CUDA ASLR 的情况下也会在固定地址加载(参见第 6.2 节)。因此,ASLR 无法阻止对 GPU 的 ROP 攻击。目前尚不清楚 NVIDIA 为何选择这种设计方法。为了有效防止ROP攻击,NVIDIA将该库编译为PIE并确保其受ASLR约束至关重要。另外,ASLR使得攻击者发起代码注入攻击变得更加困难。然而,攻击者可能能够通过旁路攻击绕过 ASLR(参见第 6.2 节)。

WˆX policy

区分代码页和数据页对于抵御代码注入攻击至关重要。正如第 4 节中提到的,PTE 中已经有一个只读位。为了了解该位是否生效,我们在CUDA程序执行过程中通过IOMMU修改了该位,发现设置该位有效地禁止了对页面的任何修改。因此,GPU驱动程序可以为代码页设置该位以防止代码修改。此外,还需要包含一个可执行位以防止执行数据页。

Related Work

对 GPU 内存漏洞的研究非常有限。首先,2016年,Miele对CUDA中的缓冲区溢出漏洞进行了初步探索[38]。这项研究表明,在 GTX TITAN Black GPU(带 sm_30 的开普勒架构)上,可以利用堆栈溢出会覆盖函数指针,从而重定向执行流程。它进一步表明这些 GPU 上的函数调用和返回机制基于 PRET 指令,最后跟随 RET 指令。 PRET 指令将返回地址存储在未知位置,使得传统 ROP 在这些 GPU 上不切实际。此外,该研究得出的结论是,代码和数据地址空间是分开的,因此不可能执行数据缓冲区。请注意,这项工作还证实了 CUDA 堆溢出的可行性,尽管堆很少在 CUDA 程序中使用[33]。

其次,同年,Di 等人。在 GeForce GTX 750Ti GPU(带有 sm_50 的 Maxwell 架构)上进行了类似的实验 [19],得出的结论与 Miele 的结论一致。然而,与 Miele 的研究相比,这项工作对 GPU 堆溢出提供了更详细的分析。

第三,2021 年晚些时候,Park 等人。对 GPU 内存利用技术进行了更深入的分析。他们首次提出了对基于 GPU 内存操作的 DNN 框架的攻击 [48]。这项工作中的实验是在 GTX 950 GPU(带有 sm_50 的 Maxwell 架构)和 GTX 1050 GPU(带有 sm_60 的 Pascal 架构)上进行的。这项工作得出了一些与之前的工作相同的结论,例如隐藏的返回地址。不过,它也有了一个新的发现,即代码页在 NVIDIA GPU 上是可写的。

Conclusion

在本文中,我们对 CUDA 程序中的缓冲区溢出问题进行了全面的研究。首先,我们对用于访问各种 GPU 内存空间的机制进行了逆向工程,证明缓冲区溢出错误可能会导致跨内存空间的内存损坏并超出数据的合法范围。其次,我们探讨了 GPU 上的代码和数据管理策略,揭示了传统的代码注入攻击在 GPU 上仍然有效。我们还分析了函数返回的机制并证明了CUDA ROP的可行性。最后,我们证明了本文中发现的漏洞对 GPU 上运行的 DNN 应用程序构成了重大安全风险。 CUDA 代码注入和 CUDA ROP 的概念验证可从 https://github.com/SecureArch/gpu_mem_attack 获取。

A GPU List

Table 4: The complete list of tested GPUs.

表 4 列出了我们验证了第 4 节中提出的结论的所有 GPU。

B ROP Gadgets

Listing 6: An example ROP gadget.

清单 6 显示了第 5 节中的 ROP 小工具的示例。该小工具的每次执行都会修改一个权重值,我们重复执行它以修改多个权重。具体来说,我们准备堆栈,以便每次执行gadget时,都满足两个条件:1)gadget中第一条指令的地址存储在[R1 + 0x18]中,从而加载到R20中; 2) 下一个权重的地址存储在[R1+0x38]中,并因此被加载到R28中。因此,该gadget的每次执行都会首先使用ST指令修改权重值,然后将R28更新为下一个权重的地址,最后返回到gadget的开头以修改后续权重。如果有大量权重需要修改,并且堆栈大小不足以支持该小工具的重复执行,我们会使用多个恶意用户请求(从而导致多个 ROP 攻击)来完成任务。

C Shellcode

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码注入攻击中使用的 shellcode 示例如表 7 所示。它将值 0xffffffff 写入从 0x7fffdeadbeef 到 0x7fffdeadbeef+0xaaaa 的一系列内存地址。

D System Specifications

Table 5: Platform details.

第 5 节中实验使用的云系统在表 5 中指定。

E LLM Attacks

我们在三个 LLM(Flan-T5Small、Flan-T5-Base 和 Flan-T5-Large)上测试了内存损坏攻击,包括代码注入攻击和 ROP 攻击 [5]。我们假设数据预处理阶段存在缓冲区溢出漏洞(例如,用于噪声消除[6]),类似于第 5 节中的假设。
Table 6: The LLM inference accuracy (with MMLU [27]) after the weight modification attacks.

与对视觉模型的攻击类似(参见第 5 节),代码注入攻击(具有受控写入值)可以将 LLM 的准确性降低到与随机猜测相同的水平。然而,如表6所示,ROP攻击(其中写入的值不受攻击者控制)对LLM的准确性的影响有限。

F JOP+ROP Attack

Figure 8: An example of combining JOP and ROP, assuming that R4 contains the address of the helper gadget.

图 8 显示了组合 JOP 和 ROP gadget 的示例。这里我们假设 R4 包含辅助 gadget 的地址,该地址用于链接 JOP gadget(与 ROP gadget)。每次执行 JOP gadget 后,它都会跳转到 helper gadget,然后从堆栈中弹出下一个 gadget 的地址并返回到它。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/417195.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据库】MySQL表的基本查询

关于表的增删查改主要分为CRUD:Create(创建), Retrieve(读取),Update(更新),Delete(删除) 目录 1.Creat(增加内容) 1.1指定列插入 1.2全列插入 1.3多行插入 1.4插入冲突更新 1.5替换 2.R…

【算法每日一练及解题思路】判断字符串是否包含数字

【算法每日一练及解题思路】判断字符串是否含数字 一、题目:给定一个字符串,找出其中不含重复字符的最长子串的长度 二、举例: 比如"abcdefgh",不含数字;比如"1",含数字;比如"a1s",含…

fl studio24.1.1.4285中文版怎么破解?FL Studio 2024安装破解使用图文教程

fl studio24.1.1.4285中文破解版是一款功能强大的编曲软件,也就是众所熟知的水果软件。它可以编曲、剪辑、录音、混音,让您的计算机成为全功能录音室。除此之外,这款软件功能非常强大,为用户提供了许多音频处理工具,包…

单点登录问题【拼多多0905一面】

说一些今晚情况,7点腾讯音乐笔试,因为8点拼多多一面,哪个都拒不了。硬着头皮50分钟写了1.2题然后去面试。刚开始状态真的很差,大脑思考不动,面试中2个手撕,做出来一个,两个项目问题,…

notepad++将换行替换成空

将多行里的换行置为一行,例如将下面的6行置为3行 crrlH打开替换框, 替换目标为【,\r\n】,替换成空,勾选循环查找和 正则表达式,全部替换即可。 替换后的效果

【Git】本地仓库操作

Part1 基础概念 git作用:管理代码版本,记录,切换,合并代码 git仓库:记录文件状态内容和历史记录的地方(.git文件夹) git的三个区域:1)工作区:实际开发时的文…

IP 协议详解

一、认识 IP 地址与网络层的职责 网络层是OSI七层模型中的第三层,也是TCP/IP四层模型中的网络接入层。在这一层,数据包被封装并加上IP层的头部信息,以便在网络之间传输。网络层的主要功能包括路由选择、分段与重组、拥塞控制以及IP地址管理等…

视频技术未来展望:EasyCVR如何引领汇聚融合平台新趋势

随着科技的飞速发展,视频技术已成为现代社会不可或缺的一部分,广泛应用于安防监控、娱乐传播、在线教育、电商直播等多个领域。本文将探讨视频技术的未来发展趋势,并深入分析TSINGSEE青犀EasyCVR视频汇聚融合平台的技术优势,展现其…

ArcGIS展线/投线教程

1 制作CSV文件 必要字段:起始经度,起始纬度,终止经度,终止纬度4列,其他列可以选填。 2 加载表格数据 点击号在当前地图加载表格。 3 使用工具箱 找到工具箱 - 数据管理工具 - 要素 - XY转线。 填空即可。当然&…

16 C语言连接

使用c语言连接mysql,需要使用mysql官网提供的库,可以在官网下载 准备工作: 保证mysql服务有效 官网下载合适的mysql connect库 也可以直接安装mysql服务 yum install -y mysql-devel Connector/C使用 库格式如下: [hbMiWiFi-R1…

监控平台之nodejs模拟后端接口

github:可以下载进行实验 https://github.com/Mr-Shi-root/sdk-platform/tree/master 1.配置node环境,安装express cors body-parser babel/cors body-parser - node.js 中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。cookie-parse…

VR虚拟驾驶未来发展_vr自动驾驶汽车所带来的改变

在自动驾驶汽车的基础上,VR虚拟现实技术的应用也让自动驾驶汽车更加智能化,能够实现更高级的驾驶体验,今天这篇文章就和大家一起探讨一下 VR虚拟驾驶未来发展的趋势,以及虚拟现实自动驾驶汽车所带来的几个改变。 一、VR 虚拟驾驶未…

UnityShaderGraph 卡通水面效果

效果预览: 原理: 使用三张噪声贴图,结合UV偏移制作水面波纹混合的假象效果 噪声图1: 噪声图2: 噪声图3: 三次采样都是同样的方法,使用step函数来二分噪声 三张噪声采样结果相乘得到最终的波纹…

深度学习与大模型第1课环境搭建

文章目录 深度学习与大模型第1课环境搭建1. 安装 Anaconda2. 修改环境变量2.1 修改 .condarc 文件2.2 使用 Anaconda Prompt 修改环境变量 3. 新建 .ipynb 文件 机器学习基础编程:常见问题: 深度学习与大模型第1课 环境搭建 1. 安装 Anaconda 首先&am…

Ai Illustrator 取消吸附到像素点,鼠标拖动的时候只能到像素点

Ai Illustrator 取消吸附到像素点,鼠标拖动的时候只能到像素点 在做图的时候无意间变成吸附到像素点了,导致无法更细致的移动点。 像这样: 关闭的方法是打开上面菜单中的 【视图】取消勾选【对齐像素】 即可。 结果就是:

新160个crackme - 048-monkeycrackme1

运行分析 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/8e7c9973721b4df1997cc9a83e0ef2b6.png 500x) 点击注册无反应 PE分析 Delphi程序,32位,无壳 静态分析&动态调试 使用DeDeDark进行分析,发现Register按钮事件地址入口…

容性负载箱如何测量电容器的容量、电压、泄漏电流和ESR等参数?

容性负载箱是用于测量电容器参数的重要设备。它的主要功能是通过向电容器施加不同的负载,从而测量电容器的容量、电压响应、损耗等关键参数。 具体来说,容性负载箱可以通过以下方式测量电容器的各项参数: 1. 测量电容器的容量:容…

Java项目:139 springboot基于SpringBoot的论坛系统设计与实现

项目介绍 论坛系统设置的角色有管理员,版主,用户等。 管理员管理论坛,主要是对论坛帖子增删改查以及对论坛帖子回复进行查看,对版主和用户进行管理,管理新闻等。 版主可以发布帖子,可以查询论坛帖子&…

【Ubuntu20.04】配置深度学习环境

参考 Ubuntu20.04配置深度学习环境(全网最细最全) NVIDIA显卡驱动安装安装CUDA 通过终端nvidia-smi查看自己能安装的最高CUDA版本,在官方网址下载需要的版本。安装cuDNN 在官方网址选择适配于自己安装的CUDA版本的cuDNN安装Anaconda &#x…

Elastic Stack-ES集群常用的API

前言:本博客仅作记录学习使用,部分图片出自网络,如有侵犯您的权益,请联系删除 学习B站博主教程笔记: 最新版适合自学的ElasticStack全套视频(Elk零基础入门到精通教程)Linux运维必备—Elastic…