Talos Vulnerability Report

TALOS-2025-2169

NVIDIA nvdisasm symbol table parsing improper array index validation vulnerability

September 24, 2025
CVE Number

CVE-2025-23338

SUMMARY

An improper array index validation vulnerability exists in the symbol table parsing functionality of NVIDIA nvdisasm 12.8.90. A specially crafted ELF file can lead to an out-of-bounds write. 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-129 - Improper Validation of Array Index

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 standard ELF specification, an ELF file can include symbol information in the .symtab section. Each entry in the symbol table has the following layout:

typedef struct {
    Elf64_Word  st_name;
    unsigned char   st_info;
    unsigned char   st_other;
    Elf64_Half  st_shndx;
    Elf64_Addr  st_value;
    Elf64_Xword st_size;
} Elf64_Sym;

It is possible to see the symbol table entries of an ELF file with the standard readelf utility:

$ readelf -s ./elf
Symbol table '.symtab' contains 7 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000efffffff  1000 SECTION LOCAL  DEFAULT    9 .text._Z7add
     2: ffffffed00000000  1000 SECTION LOCAL  DEFAULT   10 .nv.shared._
     3: 0000004e00000005    20 SECTION LOCAL  DEFAULT    8 .nv.constant0

In the function at 0x418af0, nvdisasm parses .symtab entries. As parameters it takes the st_value, the st_index of the entry and another internal structure of the application.

Depending on the st_value and the st_size fields, the code selects a a constant for a shift operation. For example, if at (1) below the rcx and r9 are equal after some math operations, it proceeds to put the 0x2c constant at rsp+0x8 at (2).

00418be7  mov     r8, 0xfffffffffff
00418bf1  mov     r9, rdx
00418bf4  mov     r13, rdi
00418bf7  and     r9, r8
00418bfa  and     r13, r8
00418bfd  sub     r9, r13
00418c00  cmp     rcx, r9                                 (1)
00418c03  jne     0x4199b7
...
004199b7  mov     dword [rsp+0x8 {var_80_1}], 0x2c        (2)
004199bf  jmp     0x4196e0

The shift constant can range from 0x0 to 0x3c in increments of 4. Then, continuing at location 0x04196e0 we see:

004196e0  mov     edi, dword [rsp+0x8 {var_80_1}]    (3)
...
004196f9  shr     rdx, cl                            (4)
00419700  mov     qword [rsp+0x10 {var_78_1}], rdx   (5)

Here rdx has the st_value of the entry. This value is being shifted to the right at (4) by the shift constant chosen earlier, retrieved at (3). Then this new value is saved at rsp+0x10 at (5).

Continuing, execution reaches 0x0418d46 where the shifted value is retrieved and then an AND operation with 0xf takes place. Note here, that at (6), an 8-byte value is retrieved but after the AND, the lower 4 bytes are all set to 0, except from the last one where it will have a maximum value of 0xf.

00418d46  mov     rbp, qword [rsp+0x10 {var_78_1}]   (6)
00418d4b  and     ebp, 0xf                           (7)

At 0x041954d, rbp is incremented by 1, setting the maximum value of rbp here to 0x10.

0041954d  add     rbp, 0x1

Finally, we see the following code:

00418d87  mov     rdi, qword [rbx+rbp*8+0x20]        (8)
00418d8c  test    rdi, rdi
00418d8f  je      0x418d96

00418d91  call    sub_4178e0                         (9)

00418d96  sub     r12, r13
00418d99  mov     qword [rbx+rbp*8+0x20], r15        (10)

At (8), we see the shifted value being used as an index to the buffer at rbx, adding an offset of 0x20. The maximum value of the expression with rbp = 0x10 will be

rbx + 0x10*8 + 0x20 = rbx + 0xa0

The buffer has a size of 0xa0, which is passed at the high level allocation function at 0x40a610:

004191ea  mov     esi, 0xa0
004191ef  call    sub_40a610

As a result, the instruction at (8) will retrieve an 8-byte pointer from the offset rbx+0xa0, which will read 8 bytes beyond the end of the buffer. If the pointer retrieved is NULL, execution will continue at the function call at (9). However, even if the pointer is not NULL, execution will reach the instruction at (10), where the pointer at the register r15 (that points to an offset in the file input) will be written to those 8 bytes beyond the allocated buffer.

As a result, an attacker can write a pointer to attacker controlled data, 8 bytes beyond an allocated buffer, which could lead to code execution.

Crash Information

Output from Valgrind shows:

    ==10666== Memcheck, a memory error detector 
    ==10666== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.                                                                                                   
    ==10666== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info                                                                                                
    ==10666== Command: ./nvdisasm-12.8.90 poc                                                                                                                                   
    ==10666==                                                                                                                                                                   
    ==10666== Invalid read of size 8                                                      
    ==10666==    at 0x418D87: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45BEC3: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x48A21C9: (below main) (libc_start_call_main.h:58)                                                                                                         
    ==10666==  Address 0x4a9cfe8 is 0 bytes after a block of size 168 alloc'd                                                                                                   
    ==10666==    at 0x484680F: malloc (vg_replace_malloc.c:446)                                                                                                                 
    ==10666==    by 0x465719: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x40A999: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x4191F3: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45BEC3: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x48A21C9: (below main) (libc_start_call_main.h:58)                                                                                                         
    ==10666==                                                                                                                                                                   
    ==10666== Invalid write of size 8                                                                                                                                           
    ==10666==    at 0x418D99: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45BEC3: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x48A21C9: (below main) (libc_start_call_main.h:58)     
    ==10666==  Address 0x4a9cfe8 is 0 bytes after a block of size 168 alloc'd
    ==10666==    at 0x484680F: malloc (vg_replace_malloc.c:446)                                                                                                                 
    ==10666==    by 0x465719: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x40A999: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x4191F3: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45BEC3: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x48A21C9: (below main) (libc_start_call_main.h:58)
    ==10666==                                                                             
    ==10666== Invalid read of size 1                                                      
    ==10666==    at 0x417913: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x418D95: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45BEC3: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x48A21C9: (below main) (libc_start_call_main.h:58)
    ==10666==  Address 0xb4 is not stack'd, malloc'd or (recently) free'd
    ==10666==                                                                             
    ==10666==                                                                             
    ==10666== Process terminating with default action of signal 11 (SIGSEGV)
    ==10666==  Access not within mapped region at address 0xB4
    ==10666==    at 0x417913: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x418D95: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45BEC3: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x45CEA5: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x40307A: ??? (in /home/dtatsis/nvdisasm/trizzle-new/analysis/0x412B79-write8-d2d20018-off-by-one-and-more/nvdisasm-12.8.90)
    ==10666==    by 0x48A21C9: (below main) (libc_start_call_main.h:58)
TIMELINE

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

Credit

Discovered by Dimitrios Tatsis of Cisco Talos.