Talos Vulnerability Report

TALOS-2025-2172

NVIDIA nvdisasm RELA section parsing out-of-bounds write vulnerability

September 24, 2025
CVE Number

CVE-2025-23340

SUMMARY

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.

CONFIRMED VULNERABLE VERSIONS

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

PRODUCT URLS

nvdisasm - https://docs.nvidia.com/cuda/cuda-binary-utilities/index.html

CVSSv3 SCORE

7.8 - CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-131 - Incorrect Calculation of Buffer Size

DETAILS

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.

Crash Information

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)
TIMELINE

2025-04-14 - Vendor Disclosure
2025-09-23 - Vendor Patch Release
2025-09-24 - Public Release

Credit

Discovered by Dimitrios Tatsis of Cisco Talos.