bird/vgpu-unlock-blackwell
GitHub: bird/vgpu-unlock-blackwell
针对NVIDIA RTX 5090消费级GPU的vGPU解锁研究,通过19个二进制补丁成功绕过全部CPU端驱动验证,但最终受限于芯片上被硬件熔断关闭的VF PRIV寄存器,揭示了Blackwell架构vGPU由GSP固件直接驱动所带来的根本性障碍。
Stars: 0 | Forks: 0
# vgpu-unlock-blackwell
旨在消费级 NVIDIA Blackwell GPU (RTX 5090 / GB202) 上启用 vGPU 功能的研究项目。
**状态:受硬件限制。** 整个软件管线已可运行,但当访问在消费级芯片上被熔断关闭的硬件寄存器时,GSP 固件会崩溃。请参阅下文的[发现](#findings)。
## 这是什么
一组二进制补丁、内核模块修改和自动化脚本,可绕过 NVIDIA 595.58.03 驱动中所有 CPU 端的 vGPU 验证,从而实现:
- vGPU 类型注册(跨 3 种调度模式的 60 种 PRO 6000 配置文件)
- 通过 VFIO 创建 mdev 设备
- 客户机 VM PCI 可见性(QEMU 将 GPU 视为 10de:2b85)
- 设备 ID 欺骗(0x2b85 消费级 -> 0x2bb5 数据中心级)
- 向 GSP 固件发送具有完整且有效参数的 BOOTLOAD RPC
- GSP 固件接受并处理 BOOTLOAD 约 4 秒钟
GSP 在访问被熔断关闭的 VF PRIV 寄存器时崩溃。有关为什么在 Turing 上有效的 [vgpu_unlock](https://github.com/DualCoder/vgpu_unlock) 方法在这里行不通的原因,请参阅 [为什么 Blackwell 不同](#why-blackwell-is-different)。
## 要求
- NVIDIA RTX 5090 (GB202) 或类似的 Blackwell 消费级 GPU
- NVIDIA 驱动程序 595.58.03(kernel-open 源码)
- 支持 KVM、VFIO、mdev 的 Linux 内核 6.x
- `pci=realloc` 内核启动参数
## 文件
| 文件 | 描述 |
|------|-------------|
| `nvidia-vgpu-vfio-stub.c` | 内核模块:VFIO 存根,mdev 驱动,设备 ID 欺骗,BOOTLOAD 编排(约 1770 行) |
| `apply-binary-patches.py` | 针对 `nv-kernel.o_binary` 的 19 个二进制补丁(自验证,无专有代码) |
| `patch-kbuild.sh` | Kbuild 修改,用于将存根编译进 nvidia.ko |
| `vgpu-type-register.c` | 用户态工具,用于通过 NVA081 API 注册 vGPU 类型(约 590 行) |
| `setup-vgpu.sh` | 完整设置:加载模块,注册类型,创建 mdev |
| `teardown-vgpu.sh` | 干净卸载,禁用 SR-IOV 并卸载模块 |
| `build-vgpu.sh` | 构建自动化 |
| `test-vm-lspci.sh` | 启动 Alpine Linux VM 并扫描 PCI |
| `boot-vm.sh` | QEMU 启动脚本 |
## 设置
```
# 1. 下载 NVIDIA 595.58.03 驱动并解压 kernel-open
# 2. 将我们的文件复制到 kernel-open 目录中
# 3. 应用补丁:
python3 apply-binary-patches.py nvidia/nv-kernel.o_binary
bash patch-kbuild.sh
# 4. 在 conftest 中设置 NV_VGPU_KVM_BUILD=1:
sed -i 's/#undef NV_VGPU_KVM_BUILD/#define NV_VGPU_KVM_BUILD 1/' conftest/generic.h
# 5. 构建:
make modules -j$(nproc) NV_KERNEL_MODULES="nvidia" CONFIG_OBJTOOL=
# 6. 编译类型注册工具:
gcc -o vgpu-type-register vgpu-type-register.c
# 7. 运行 setup:
sudo ./setup-vgpu.sh
```
## 二进制补丁(共 19 个)
| # | 函数 | VA | 描述 |
|---|----------|-----|-------------|
| 1-4 | kvgpumgrPgpuAddVgpuType | 0x37caff+ | 跳过 GSP 错误,PCI ID,标志,初始化检查 |
| 5-7 | kvgpumgrCreateRequestVgpu | 0x384cb7+ | 跳过 PCI ID,标志,边界检查 |
| 8 | kvgpumgrSendAllVgpuTypesToGsp | 0x37c684 | 存根,返回 NV_OK |
| 9-10 | kvgpumgrGetPgpuIndex | 0x37c331+ | 跳过设备 ID 和标志检查 |
| 11-12 | BOOTLOAD 处理程序 | 0x37b1d7+ | 存根用于 pre/post-bootload VF 资源分配 |
| 13 | kfifoChidMgrFreeSystemChids | 0x1e5480 | 容忍 NULL 通道堆 |
| 14 | destroyKernelHostVgpuDeviceShare | 0x379d60 | 防止在卸载时 GSP SHUTDOWN 挂起 |
| 15-16 | BOOTLOAD 处理程序 | 0x37b068+ | 预分配 RPC 缓冲区,跳过 memset |
| 17-18 | BOOTLOAD 处理程序 | 0x37b0a6+ | 跳过 memGetByHandle 循环,边界检查 |
| 19 | BOOTLOAD 处理程序 | 0x37b0b8 | 加载 FB 基址,跳转至 BAR 填充 |
## 发现
### 什么有效
- **相同的二进制文件**:GRID 和消费级驱动共享相同的 `nv-kernel.o_binary` 和 `gsp_ga10x.bin`(已通过 MD5 验证)。每个消费级驱动中都存在 vGPU 代码。
- **CPU 端绕过**:所有 19 个补丁成功绕过了验证。设备 ID 欺骗(0x2b85 -> 0x2bb5)在每一层都被接受。
- **GSP RPC 交付**:BOOTLOAD RPC(命令 0x20804001)带着完整参数到达 GSP:dbdf、gfid、vgpuType、numChannels、FB 地址、段信息。
- **GSP 处理**:GSP 接受 RPC 并运行 4 秒钟——在撞上硬件墙之前完成了大量的初始化工作。
- **时间分片模式**:`SET_VGPU_MIG_TIMESLICE_MODE`(命令 0x2080400f)在消费级硬件上成功执行。
- **客户机可见性**:通过 QEMU 启动的 VM 在其 PCI 总线上看到具有正确 vendor/device/class 的 GPU。
### 阻碍因素
GSP 固件在访问位于 `0x111xxx` 的 `NV_PPRIV` 寄存器时崩溃(Xid 119):
```
REG 0x111424 via RM aperture: 0xbadf1002
REG 0x111424 via raw BAR0: 0xbadf1002
REG 0x000000 via RM aperture: 0x1b2000a1 (BOOT_0 -- works fine)
Error code: NV_PPRIV_SYS_PRI_ERROR_CODE_FECS_PRI_TIMEOUT
```
这些寄存器在消费级 GPU 上被**硬件熔断关闭**。通过读取 RM 的特权 IO 孔径(`osGpuReadReg032`)和原始 BAR0 ioremap 均证实了这一点——两者都返回完全一致的 BADF 值。一个正常工作的寄存器(位于 0x000000 的 `NV_PMC_BOOT_0`)通过这两种路径都返回了正确的值,证明读取机制是有效的,且这并不是 PRIV 防火墙问题。
### 已测试并排除的情况
| 假设 | 测试 | 结果 |
|-----------|------|--------|
| PRIV 防火墙(非熔断) | 通过 `osGpuReadReg032` 读取 vs 原始 BAR0 | 两者返回相同的 BADF |
| FB 地址冲突 | 在 4GB 和 16GB VRAM 偏移量处测试 | 崩溃情况相同 |
| SR-IOV 对比时间分片调度 | 测试标志为 0x008e, 0x0045, 0x0020 的类型 | 全部崩溃 |
| 缺少时间分片模式启用 | 调用命令 0x2080400f(返回 NV_OK) | 仍然崩溃 |
| GSP 中设备 ID 不匹配 | 两者均为 GB202(从 BOOT_0 获取的芯片 ID 0x1b2) | 芯片匹配 |
| VF 存在/不存在 | 使用和不使用 `pci_enable_sriov` | 均崩溃 |
| 不正确的 RPC 缓冲区 | 导出所有字段,验证完整 | 缓冲区有效 |
### 为什么 Blackwell 不同
由 [DualCoder](https://github.com/DualCoder) 发起的原始 [vgpu_unlock](https://github.com/DualCoder/vgpu_unlock) 项目成功在 Turing 消费级 GPU 上启用了 vGPU。由于根本性的架构变化,该方法**无法在 Blackwell 上奏效**:
| | Turing (vgpu_unlock 有效) | Blackwell (受阻) |
|---|---|---|
| vGPU 插件运行于 | **CPU**(内核模块) | **GSP**(RISC-V 固件) |
| 插件代码可修改 | 是——内核模块中的二进制补丁 | 否——WPR 内存 + FMC 签名验证 |
| 硬件寄存器访问 | 通过可打补丁的 OS 函数(`osGpuReadReg032`) | 来自 RISC-V 核心的直接 MMIO |
| 寄存器访问拦截 | 钩住读取函数,返回欺骗值 | 不可能——固件拥有原始硬件访问权限 |
| VF PRIV 寄存器 | 不需要(Turing vGPU 在 PF 上进行时间分片) | 在 BOOTLOAD 期间**总是被访问**,无论调度模式如何 |
| SR-IOV 要求 | 无(Turing 消费级没有 SR-IOV) | 消费级有 SR-IOV (totalvfs=1) 但 VF PRIV 寄存器被熔断关闭 |
在 Turing 上,vGPU 插件作为内核模块中的 CPU 代码运行。当它试图读取一个被熔断的寄存器时,`vgpu_unlock` 项目可以拦截该读取并返回一个有效值。在 Blackwell 上,vGPU 插件运行在具有直接内存映射硬件访问权限的专用 RISC-V 核心上的 GSP 固件内。没有可以拦截的软件层。
## 关键技术细节
- **OBJGPU 设备 ID**:偏移 +0x7f6(16 位 LE 字段)
- **PGPU 表**:`[g_pSys+0x1f8]`,每个条目步长 0xbbb80,PCI ID 位于 +0x48
- **sVgpuUsmTypes**:设备 0x2bb5 的 60 个条目(类型 1460-1620,3 个标志组)
- **GSP BOOTLOAD RPC**:命令 0x20804001,通过 `rpcRmApiControl_GSP` 传递 0x19d8 字节的参数
- **NVA084 BOOTLOAD ctrl**:命令 0xa084010d,参数 0x648 字节
- **SET_VGPU_MIG_TIMESLICE_MODE**:命令 0x2080400f(在消费级 GPU 上成功)
- **PCI 重置恢复**:`echo 1 > /sys/bus/pci/devices/.../reset`(在 GSP 崩溃后无需重启即可恢复 GPU)
- **熔断寄存器**:0x111424 (VF PRIV),0x009080 (PRI 超时),0x088000 (BIF)——通过 RM 孔径和原始 BAR0 读取均返回 0xbadf
## 贡献 / 后续步骤
软件层面已解决。剩下的挑战在于硬件。继续这项工作最具可操作性的路径是:
### FPGA 拦截器(最有希望)
放置在 GPU 和 PCIe 插槽之间的 FPGA 板可以拦截并仿真对被熔断寄存器的访问。当 GSP 通过 PRIV 环读取 `0x111424` 并遇到 PRI 超时时,一个监视寄存器总线的 FPGA 可以注入一个有效的响应。这将需要:
- 具备 PCIe Gen5 能力的 FPGA(或一个接入 PRIV 环信号的转接卡)
- 从数据中心 GPU(或从 GSP 固件的初始化代码)逆向工程预期的寄存器值
- 构建 GSP 在 BOOTLOAD 期间触及的所有熔断地址的寄存器映射
GSP 仅运行约 4 秒就崩溃了,表明需要仿真的寄存器访问次数相对较少。
### GSP 固件分析
GSP 崩溃缓冲区包含带时间戳的跟踪条目,如果了解 GSP 固件的运行时内存布局,这些条目就可以被解码。GSP 邮箱寄存器(`MAILBOX(0)` = 崩溃时的 PC?)可以映射到 `gsp_ga10x.bin` RISC-V ELF 中的特定函数,从而揭示究竟是哪个寄存器访问导致了崩溃以及 GSP 期望的值。
### 替代 GPU 架构
Ada Lovelace (RTX 4090) 使用类似的 GSP 架构,但可能有不同的熔丝编程。Hopper(H100 与消费级变体)是另一个候选者。本项目中的方法(二进制补丁,设备 ID 欺骗,RPC 注入)可以直接迁移——只需更改特定的补丁地址。
### 驱动版本探索
较旧的 Blackwell 驱动版本或测试版可能具有较少寄存器依赖的不同 GSP 固件。补丁脚本的自验证方法使测试变得简单:如果原始字节不匹配,补丁将干净地失败。
## 许可证
我们的原创代码(存根,补丁,脚本,工具)在 MIT 许可证下发布。
本项目不包含任何 NVIDIA 专有二进制文件。您必须单独获取 NVIDIA 驱动程序。二进制补丁在特定的字节偏移量处修改驱动程序——不分发任何专有代码。
## 致谢
本研究建立在 [DualCoder](https://github.com/DualCoder) 针对 Turing GPU 的 [vgpu_unlockhttps://github.com/DualCoder/vgpu_unlock) 项目以及更广泛的 GPU 虚拟化研究社区的基础之上。本 README 中的 Turing 与 Blackwell 架构比较直接阐明了为什么那种开创性的方法无法扩展到带有 GSP 固件的现代 NVIDIA GPU 上。
标签:Blackwell架构, GB202, GPU虚拟化, GSP固件, KVM, Linux内核, mdev, QEMU, RTX 5090, SR-IOV, VFIO, vGPU, Web报告查看器, 二进制补丁, 内联执行, 安全渗透, 客户端加密, 应用安全, 底层研究, 数据中心显卡, 消费级显卡, 硬件熔断寄存器, 硬件破解, 硬件解锁, 绕过验证, 虚拟化技术, 设备ID伪装, 身份验证强制, 逆向工具