Talos Vulnerability Report

TALOS-2026-2424

VTK vtkWindBladeReader FindVariableOffsets heap-based buffer overflow vulnerability

October 29, 2026

CVE Number

CVE-2026-9536

Summary

A heap-based buffer overflow vulnerability exists in the vtkWindBladeReader FindVariableOffsets functionality of vtk (version(s): 9.6.1). A specially crafted .wind can lead to a buffer overflow. 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.

vtk (version(s): 9.6.1)

Product URLs

vtk - https://vtk.org/

CVSSv3 Score

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

CWE

CWE-122 - Heap-based Buffer Overflow

Details

VTK, or the Visualization Toolkit, is an open-source software system for 3D computer graphics, image processing, and scientific visualization. It provides a large collection of algorithms and data structures for rendering, volume visualization, geometric processing, and interaction.

vtkWindBladeReader parses the WindBlade simulation format — an ASCII .wind metadata file describing grid dimensions and variable layout, paired with binary data files containing Fortran-style record-marked float arrays.

The reader parses a malformed WindBlade data set without error. ASan reports:

==4281==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000003330
READ of size 4 at 0x502000003330 thread T0
    #0 vtkWindBladeReader::LoadVariableData(int)     vtkWindBladeReader.cxx:564
    #1 vtkWindBladeReader::SetUpFieldVars(...)        vtkWindBladeReader.cxx:1262
    #2 vtkWindBladeReader::RequestData(...)           vtkWindBladeReader.cxx:386
[...]
0x502000003331 is located 0 bytes after 1-byte region [0x502000003330,0x502000003331)
allocated at vtkWindBladeReader::LoadVariableData(int) line 540

The crash-site code in LoadVariableData():

LINE_528  void vtkWindBladeReader::LoadVariableData(int var)
LINE_529  {
[...]
LINE_540    float* block = new float[this->BlockSize];
LINE_541    this->InitVariableData(var, numberOfComponents, varData, planeSize, rowSize);
LINE_542    for (int comp = 0; comp < numberOfComponents; comp++)
LINE_543    {
LINE_544      size_t cnt;
LINE_545      if ((cnt = fread(block, sizeof(float), this->BlockSize,
LINE_546                       this->Internal->FilePtr)) != this->BlockSize)
LINE_547      {
[...]
LINE_553      }
LINE_555      int pos = comp;
LINE_556      for (int k = this->SubExtent[4]; k <= this->SubExtent[5]; k++)
LINE_558        for (int j = this->SubExtent[2]; j <= this->SubExtent[3]; j++)
LINE_560          for (int i = this->SubExtent[0]; i <= this->SubExtent[1]; i++)
LINE_561          {
LINE_562            int index = (k * planeSize) + (j * rowSize) + i;
LINE_563            varData[pos] = block[index];
LINE_564            pos += numberOfComponents;
LINE_565          }
[...]
}

At LINE_540, block is allocated with this->BlockSize elements. BlockSize is a size_t member variable — its value determines the buffer capacity. At LINE_563, the triple-nested loop reads block[index] where index is computed from grid-derived extents (SubExtent). SubExtent encodes the spatial dimensions of the data slice being loaded.

The ASan trace shows that the allocated region is 1 byte — a new float[N] call with N == 0 produces a minimal allocation. this->BlockSize was therefore 0 at allocation time, yet the loop at LINE_556–LINE_563 still iterates (because SubExtent ranges are non-empty), reading past the end of the buffer.

BlockSize is computed during the information phase. RequestInformation() calls ReadGlobalData() at LINE_255, which parses the .wind metadata. When the DATA_VARIABLES keyword is encountered, ReadGlobalData() calls FindVariableOffsets() at LINE_1615:

LINE_706  bool vtkWindBladeReader::FindVariableOffsets()
LINE_707  {
[...]
LINE_726    int byteCount;
LINE_727    if (fread(&byteCount, sizeof(int), 1, this->Internal->FilePtr) != 1)
LINE_728    {
LINE_729      vtkWarningMacro("... Premature EOF while reading byteCount.");
LINE_730    }
[...]
LINE_733    this->BlockSize = static_cast<size_t>(byteCount / BYTES_PER_DATA);
[...]
}

At LINE_726, byteCount is a signed int read directly from the binary data file — its value is attacker-controlled. BYTES_PER_DATA equals 4. At LINE_733, signed integer division truncates toward zero: any byteCount in the range [-3, 3] produces BlockSize == 0. Negative values produce either 0 or a negative quotient that, cast to size_t, wraps to a very large value (a separate but related issue). No validation between LINE_727 and LINE_733 checks that byteCount is positive, a multiple of 4, or consistent with the grid dimensions declared in the .wind file.

During RequestData(), the call chain consumes BlockSize without re-checking:

LINE_369  int vtkWindBladeReader::RequestData(...)
LINE_370  {
[...]
LINE_386    this->SetUpFieldVars(field);
[...]
}

SetUpFieldVars() iterates over selected variables and calls LoadVariableData() at LINE_1262, which uses this->BlockSize at LINE_540. No validation of BlockSize occurs between the information phase (where it is computed) and the data phase (where it is consumed).

The grid dimensions (GRID_SIZE_X/Y/Z from the .wind file) determine the SubExtent loop bounds at LINE_556–LINE_560. Larger declared grids cause more iterations and deeper out-of-bounds reads from the zero-sized buffer. The attacker therefore controls both the buffer size (via byteCount) and the read depth (via grid dimensions).

The vulnerability allows an attacker to cause a deterministic denial of service by providing a crafted WindBlade data set. On non-instrumented builds, the out-of-bounds reads silently succeed and may expose heap-resident data through the output arrays, creating a potential information disclosure vector.

Crash Information

[PoC] Update(./poc/zero_bytecount/zero_bytecount.wind)
=================================================================
==4285==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000003330 at pc 0x7f164e6b979e bp 0x7ffd1ae8ee30 sp 0x7ffd1ae8ee28
READ of size 4 at 0x502000003330 thread T0
    #0 0x7f164e6b979d in vtkWindBladeReader::LoadVariableData(int) /opt/vtk-src/IO/Geometry/vtkWindBladeReader.cxx:564:26
    #1 0x7f164e6afc2a in vtkWindBladeReader::SetUpFieldVars(vtkStructuredGrid*) /opt/vtk-src/IO/Geometry/vtkWindBladeReader.cxx:1262:13
    #2 0x7f164e6ada6a in vtkWindBladeReader::RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/IO/Geometry/vtkWindBladeReader.cxx:386:11
    #3 0x7f164cfd1ed7 in vtkExecutive::CallAlgorithm(vtkInformation*, int, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkExecutive.cxx:723:33
    #4 0x7f164cfa400b in vtkDemandDrivenPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:447:22
    #5 0x7f164cf7ffd8 in vtkCompositeDataPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkCompositeDataPipeline.cxx:151:32
    #6 0x7f164cfa0fa9 in vtkDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:246:22
    #7 0x7f164d11259e in vtkStreamingDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:331:27
    #8 0x7f164cfa30b9 in vtkDemandDrivenPipeline::UpdateData(int) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:404:16
    #9 0x7f164d1150ce in vtkStreamingDemandDrivenPipeline::Update(int, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:421:34
    #10 0x7f164cf5e387 in vtkAlgorithm::Update(int) /opt/vtk-src/Common/ExecutionModel/vtkAlgorithm.cxx:1660:32
    #11 0x564b747d438e in main /manuwork/advisories/windblade-geometry-1/poc_vtkwindbladereader_update.cpp:16:11
    #12 0x7f163e9091c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #13 0x7f163e90928a in __libc_start_main csu/../csu/libc-start.c:360:3
    #14 0x564b746f05a4 in _start (/manuwork/advisories/windblade-geometry-1/build-afl/poc_vtkwindbladereader_update+0x2d5a4) (BuildId: 4b8d1a5755c7044c4bda645de445fe1123c97576)

0x502000003331 is located 0 bytes after 1-byte region [0x502000003330,0x502000003331)
allocated by thread T0 here:
    #0 0x564b747d1b71 in operator new[](unsigned long) (/manuwork/advisories/windblade-geometry-1/build-afl/poc_vtkwindbladereader_update+0x10eb71) (BuildId: 4b8d1a5755c7044c4bda645de445fe1123c97576)
    #1 0x7f164e6b8931 in vtkWindBladeReader::LoadVariableData(int) /opt/vtk-src/IO/Geometry/vtkWindBladeReader.cxx:540:18
    #2 0x7f164e6afc2a in vtkWindBladeReader::SetUpFieldVars(vtkStructuredGrid*) /opt/vtk-src/IO/Geometry/vtkWindBladeReader.cxx:1262:13
    #3 0x7f164e6ada6a in vtkWindBladeReader::RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/IO/Geometry/vtkWindBladeReader.cxx:386:11
    #4 0x7f164cfd1ed7 in vtkExecutive::CallAlgorithm(vtkInformation*, int, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkExecutive.cxx:723:33
    #5 0x7f164cfa400b in vtkDemandDrivenPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:447:22
    #6 0x7f164cf7ffd8 in vtkCompositeDataPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkCompositeDataPipeline.cxx:151:32
    #7 0x7f164cfa0fa9 in vtkDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:246:22
    #8 0x7f164d11259e in vtkStreamingDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:331:27
    #9 0x7f164cfa30b9 in vtkDemandDrivenPipeline::UpdateData(int) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:404:16
    #10 0x7f164d1150ce in vtkStreamingDemandDrivenPipeline::Update(int, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:421:34
    #11 0x7f164cf5e387 in vtkAlgorithm::Update(int) /opt/vtk-src/Common/ExecutionModel/vtkAlgorithm.cxx:1660:32
    #12 0x564b747d438e in main /manuwork/advisories/windblade-geometry-1/poc_vtkwindbladereader_update.cpp:16:11
    #13 0x7f163e9091c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #14 0x7f163e90928a in __libc_start_main csu/../csu/libc-start.c:360:3
    #15 0x564b746f05a4 in _start (/manuwork/advisories/windblade-geometry-1/build-afl/poc_vtkwindbladereader_update+0x2d5a4) (BuildId: 4b8d1a5755c7044c4bda645de445fe1123c97576)

SUMMARY: AddressSanitizer: heap-buffer-overflow /opt/vtk-src/IO/Geometry/vtkWindBladeReader.cxx:564:26 in vtkWindBladeReader::LoadVariableData(int)
Shadow bytes around the buggy address:
  0x502000003080: fa fa fd fa fa fa fd fa fa fa fd fa fa fa 00 fa
  0x502000003100: fa fa 00 fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x502000003180: fa fa fd fa fa fa 00 fa fa fa fd fa fa fa 00 fa
  0x502000003200: fa fa 00 fa fa fa fd fa fa fa 00 fa fa fa fd fa
  0x502000003280: fa fa 07 fa fa fa fd fd fa fa 00 fa fa fa 00 fa
=>0x502000003300: fa fa 00 fa fa fa[01]fa fa fa fa fa fa fa fa fa
  0x502000003380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000003400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000003480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000003500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000003580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==4285==ABORTING
Aborted

Vendor Response (CVE-2026-9536)

Vendor Link: https://vtk.org/download/

Timeline

2026-06-23 - Vendor Notification
2026-06-25 - Vendor Acknowledged
2026-06-30 - 7-Day
2026-07-02 - Follow-Up Sent
2026-07-07 - Vendor Replied, Dev Team Working
2026-08-07 - 45-Day
2026-08-22 - 60-Day
2026-09-15 - Follow-Up Sent
2026-09-21 - 90-Day
2026-09-23 - Vendor Replied, Dev Team Working
2026-10-12 - Advised Vendor Cisco Will Publish And Close Out

Credit

Emmanuel Tacheau of Cisco Talos