CVE-2025-23340
An out-of-bounds write vulnerability exists in the RELA section parsing functionality of NVIDIA nvdisasm 12.8.90. A specially crafted ELF file can lead to code execution. An attacker can provide a malicious file to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
NVIDIA nvdisasm 12.8.90
nvdisasm - https://docs.nvidia.com/cuda/cuda-binary-utilities/index.html
7.8 - CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-131 - Incorrect Calculation of Buffer Size
The nvdisasm
tool provided by the Nvidia CUDA Toolkit is used to display information about CUDA ELF files. Apart from the disassembly of CUDA compiled binary code, it is capable of displaying control flow graphs, register life-ranges, debug information etc.
As per the ELF
specification, RELA
sections provide the necessary information in order to relocate symbol references at runtime. A section is described in the following structure:
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
The function at 0x45ada0
is responsible for handling the special case for RELA
sections.
0045ca86 mov rsi, qword [rsp+0x8 {sh_size}] (1)
...
0045ca8e mov rax, 0xaaaaaaaaaaaaaaab (2.a)
...
0045ca9c mul rsi (2.b)
0045ca9f shr rdx, 0x3 (2.c)
0045caa3 lea rsi, [rdx+rdx*2] (2.d)
0045caa7 shl rsi, 0x3 (2.e)
0045caab call sub_40a610 (3)
0045cab0 test rax, rax
0045cab3 mov r14, rax
At (1), the code retrieves the sh_size
for the current RELA
section. The set of instructions at (2) that follow appear daunting at first but it is common for compilers to optimize division operations using multiplication with large constants and shifts. The equivalent code of the operations in Python is:
def align(rsi):
return ((((rsi * 0xaaaaaaaaaaaaaaab) >> 64) >> 3) * 3) << 3
Essentially, the code attempts to align the input size in rsi
to a multiple of 24
. Note however that if the input is less than 12
, the expression returns 0
. We can see that with Python easily:
for i in range(32):
print(f"input: {i}, aligned: {align(i)}")
input: 0, aligned: 0
input: 1, aligned: 0
input: 2, aligned: 0
...
input: 11, aligned: 0
input: 12, aligned: 24
input: 13, aligned: 24
...
input: 23, aligned: 24
input: 24, aligned: 48
input: 25, aligned: 48
...
At (3) the code calls the allocation function at 0x40a610
which takes the size parameter at rsi
. In turn, we follow the execution to the internal malloc()
wrapper at 0x465700
which takes the allocation size parameter at rdi
. We see the following:
00465707 lea rbp, [rdi+0x8] (4)
...
00465712 mov rdi, rbp
00465715 call malloc (5)
...
0046572a add rax, 0x8 (6)
At (4) the code takes the input size and adds 8
to it, presumably to keep some internal heap metadata at the beginning of the buffer. As a result, if we pass 0
as the allocation size argument, the code will allocate 8
bytes on the heap using malloc()
at (5). When the function returns, it adds 8
to the pointer returned by malloc()
in order to point to the usable memory beyond the 8
bytes of heap metadata.
As a result, for the specific case of an allocation size argument of 0
, the returned pointer will already point beyond the end of the buffer.
Execution then continues to write data to the allocated buffer with the pointer at r14
:
0045cb10 mov eax, dword [rbx] (7.a)
0045cb12 mov edx, dword [rbx+0x4] (7.b)
0045cb15 add r14, 0x18 (7.c)
0045cb19 add rbx, 0xc (7.d)
0045cb1d movsxd rsi, dword [rbx-0x4] (7.e)
0045cb21 mov qword [r14-0x18], rax (8)
0045cb25 mov eax, edx
0045cb27 movzx edx, dl
0045cb2a shr eax, 0x8
0045cb2d mov qword [r14-0x8], rsi (9)
0045cb31 shl rax, 0x20
0045cb35 add rax, rdx
0045cb38 cmp rbx, rcx
0045cb3b mov qword [r14-0x10], rax (10)
Here rbx
points to the input ELF
file read in memory by the binary. At the instructions denoted at (7), the input bytes are read to specific registers and the appropriate offsets are calculated. Accounting for the addition of 0x18
at (7.c), at (8), (9), and (10) the code performs memory writes to offsets 0x0
, 0x10
, 0x8
beyond the end of the allocated buffer.
As a result, for a RELA
section with a size less than 12
, multiple out-of-bounds writes on the heap with attacker controlled data can occur that can lead to code execution.
Valgrind output:
==67784== Memcheck, a memory error detector
==67784== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==67784== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==67784== Command: ./nvdisasm-12.8.90 poc
==67784==
==67784== Invalid write of size 8
==67784== at 0x45CB21: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x48A21C9: (below main) (libc_start_call_main.h:58)
==67784== Address 0x4a99358 is 0 bytes after a block of size 8 alloc'd
==67784== at 0x484680F: malloc (vg_replace_malloc.c:446)
==67784== by 0x465719: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x40A999: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x45CAAF: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x48A21C9: (below main) (libc_start_call_main.h:58)
==67784==
==67784== Invalid write of size 8
==67784== at 0x45CB2D: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x48A21C9: (below main) (libc_start_call_main.h:58)
==67784== Address 0x4a99368 is 16 bytes after a block of size 8 alloc'd
==67784== at 0x484680F: malloc (vg_replace_malloc.c:446)
==67784== by 0x465719: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x40A999: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x45CAAF: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x48A21C9: (below main) (libc_start_call_main.h:58)
==67784==
==67784== Invalid write of size 8
==67784== at 0x45CB3B: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x48A21C9: (below main) (libc_start_call_main.h:58)
==67784== Address 0x4a99360 is 8 bytes after a block of size 8 alloc'd
==67784== at 0x484680F: malloc (vg_replace_malloc.c:446)
==67784== by 0x465719: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x40A999: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x45CAAF: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x444CD1-write8-da40c008/nvdisasm-12.8.90)
==67784== by 0x48A21C9: (below main) (libc_start_call_main.h:58)
==67784==
nvdisasm-12.8 fatal : poc is not a supported Elf file
==67784==
==67784== HEAP SUMMARY:
==67784== in use at exit: 24,389 bytes in 154 blocks
==67784== total heap usage: 228 allocs, 74 frees, 39,394 bytes allocated
==67784==
==67784== LEAK SUMMARY:
==67784== definitely lost: 256 bytes in 9 blocks
==67784== indirectly lost: 8,062 bytes in 123 blocks
==67784== possibly lost: 16,071 bytes in 22 blocks
==67784== still reachable: 0 bytes in 0 blocks
==67784== suppressed: 0 bytes in 0 blocks
==67784== Rerun with --leak-check=full to see details of leaked memory
==67784==
==67784== For lists of detected and suppressed errors, rerun with: -s
==67784== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
2025-04-14 - Vendor Disclosure
2025-09-23 - Vendor Patch Release
2025-09-24 - Public Release
Discovered by Dimitrios Tatsis of Cisco Talos.