TALOS-2026-2421
CVE-2026-8822
A memory corruption vulnerability exists in the vtkDEMReader ReadProfiles functionality of vtk (version(s): 9.6.1). A specially crafted USGS DEM can lead to a buffer overflow. 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.
vtk (version(s): 9.6.1)
vtk - https://vtk.org/
7.5 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE-787 - Out-of-bounds Write
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.
vtkDEMReader parses the USGS Digital Elevation Model format — a fixed-width ASCII format. A Type A record (1024 bytes) describes grid geometry (ground coordinates, spatial resolution, row and column counts). B-records follow, each containing a profile: a 24-byte prefix of four 6-digit integers (profileId[0], profileId[1], profileSize[0], profileSize[1]) and then elevation samples.
The reader parses a malformed DEM sample without error. ASan reports:
[PoC] Update(./poc/seed_mut_oob_row_overflow.dem)
==364==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f23592b6a14
WRITE of size 4 at 0x7f23592b6a14 thread T0
#0 in vtkDEMReader::ReadProfiles(vtkImageData*) at vtkDEMReader.cxx:440
#1 in vtkDEMReader::RequestData(...) at vtkDEMReader.cxx:146
[...]
#10 in main at poc_vtkdemreader_update.cpp:20
0x7f23592b6a14 is located 0 bytes after 610836-byte region
The crash is a 4-byte heap write at LINE_440 inside ReadProfiles(), called from RequestData() at LINE_146. The write lands at address 0x7f23592b6a14, exactly 0 bytes after the end of the allocated region — a right-edge overrun.
The crash-site code in ReadProfiles():
LINE_325 int vtkDEMReader::ReadProfiles(vtkImageData* data)
LINE_326 {
[...]
LINE_395 std::tie(profileId[0], profileId[1], profileSize[0], profileSize[1]) = resultInt4->values();
[...]
LINE_416 rowId = profileId[0] - 1;
LINE_417 columnId = profileId[1] - 1;
LINE_418 lastRow = rowId + profileSize[0] - 1;
[...]
LINE_429 for (row = rowId; row <= lastRow; row++)
LINE_430 {
LINE_431 auto resultInt = vtk::scan_value<int>(fp);
[...]
LINE_439 elevation = resultInt->value();
LINE_440 *(outPtr + columnId + row * numberOfColumns) = elevation * units;
LINE_441 }
[...]
}
At LINE_395, four integers are read from the B-record prefix. profileId[0] and profileSize[0] are attacker-controlled values parsed from the file. At LINE_416, rowId is derived from profileId[0]. At LINE_418, lastRow is computed from rowId + profileSize[0] - 1. The loop at LINE_429 iterates row from rowId to lastRow inclusive, and each iteration writes a float elevation value into the output buffer at LINE_440 using row * numberOfColumns as part of the offset.
outPtr points to the scalar buffer of the output vtkImageData. The buffer is allocated by RequestData() (frame #1 in the crash stack). At LINE_124, RequestData() allocates scalars based on the grid extent published during the information phase:
LINE_116 int vtkDEMReader::RequestData(vtkInformation* vtkNotUsed(request),
LINE_117 vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
LINE_118 {
[...]
LINE_123 output->SetExtent(outInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()));
LINE_124 output->AllocateScalars(outInfo);
[...]
LINE_141 if (this->ReadTypeARecord() == 0)
LINE_142 {
LINE_146 this->ReadProfiles(output);
LINE_147 }
[...]
LINE_152 return 1;
[...]
}
AllocateScalars() at LINE_124 sizes the buffer from the extent — which encodes NumberOfRows × NumberOfColumns. At LINE_146, RequestData() calls ReadProfiles(output), passing the allocated vtkImageData. The buffer size is fixed at this point.
The extent itself is computed during the information phase. RequestInformation() calls ReadTypeARecord() at LINE_96 to parse the Type A header, then derives extent, origin, and spacing at LINE_99:
LINE_80 int vtkDEMReader::RequestInformation(vtkInformation* vtkNotUsed(request),
LINE_81 vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
LINE_82 {
[...]
LINE_96 this->ReadTypeARecord();
LINE_99 this->ComputeExtentOriginAndSpacing(extent, origin, spacing);
[...]
LINE_111 return 1;
[...]
}
RequestInformation() returns 1 unconditionally at LINE_111. The return value of ReadTypeARecord() at LINE_96 is not checked. The pipeline therefore always proceeds to the data phase, regardless of whether the header was parsed successfully or produced consistent values.
The buffer is sized from the Type A record (grid dimensions), but the write loop in ReadProfiles() is driven by B-record fields (profileId[0], profileSize[0]). These two sets of values are parsed independently from different parts of the file, and no validation enforces consistency between them. Specifically, lastRow computed at LINE_418 must satisfy lastRow < NumberOfRows for the write at LINE_440 to remain within bounds. No such check exists between LINE_395 (where the B-record fields are parsed) and LINE_429 (where the loop begins).
Because profileId[0] and profileSize[0] are read directly from the file, an attacker controls:
- the write offset — rowId and lastRow determine where in the buffer the writes land
- the write count — profileSize[0] determines how many iterations the loop executes
- the written content — elevation at LINE_439 is an attacker-controlled integer
The buffer size is also indirectly attacker-controlled through the Type A record’s ground coordinates and spatial resolution. A crafted DEM file that declares a small grid in the Type A record but provides a B-record profile spanning more rows than the grid contains causes ReadProfiles() to write past the end of the allocated buffer.
On non-instrumented builds, the heap corruption is silent — no error is raised and the process continues with a compromised heap state.
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f23592b6a14 at pc 0x7f236b315aa0 bp 0x7ffedb81c2b0 sp 0x7ffedb81c2a0
WRITE of size 4 at 0x7f23592b6a14 thread T0
#0 0x7f236b315a9f in vtkDEMReader::ReadProfiles(vtkImageData*) /opt/vtk-src/IO/Image/vtkDEMReader.cxx:440
#1 0x7f236b311cc9 in vtkDEMReader::RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/IO/Image/vtkDEMReader.cxx:146
#2 0x7f236a190ed7 in vtkExecutive::CallAlgorithm(vtkInformation*, int, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkExecutive.cxx:723
#3 0x7f236a16300b in vtkDemandDrivenPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:447
#4 0x7f236a13efd8 in vtkCompositeDataPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkCompositeDataPipeline.cxx:151
#5 0x7f236a15ffa9 in vtkDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:246
#6 0x7f236a2d159e in vtkStreamingDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:331
#7 0x7f236a1620b9 in vtkDemandDrivenPipeline::UpdateData(int) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:404
#8 0x7f236a2d40ce in vtkStreamingDemandDrivenPipeline::Update(int, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:421
#9 0x7f236a11d387 in vtkAlgorithm::Update(int) /opt/vtk-src/Common/ExecutionModel/vtkAlgorithm.cxx:1660
#10 0x55be193039f7 in main /manuwork/advisories/dem-image-1/poc_vtkdemreader_update.cpp:20
#11 0x7f235b4ca1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#12 0x7f235b4ca28a in __libc_start_main_impl ../csu/libc-start.c:360
#13 0x55be19303404 in _start (/manuwork/advisories/dem-image-1/build-asan/poc_vtkdemreader_update+0x6404) (BuildId: a0662c635ccf9dae226cb5a66299c40ac4c1caaf)
0x7f23592b6a14 is located 0 bytes after 610836-byte region [0x7f2359221800,0x7f23592b6a14)
allocated by thread T0 here:
#0 0x7f236bb5b9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x7f235e597ce3 in vtkBuffer<float>::Allocate(long long) /opt/vtk-src/Common/Core/vtkBuffer.h:202
#2 0x7f235e6c3688 in vtkAOSDataArrayTemplate<float>::ReallocateTuples(long long) /opt/vtk-src/Common/Core/vtkAOSDataArrayTemplate.txx:488
#3 0x7f235e6c3688 in vtkGenericDataArray<vtkAOSDataArrayTemplate<float>, float, 5>::ReallocateTuples(long long) /opt/vtk-src/Common/Core/vtkGenericDataArray.h:358
#4 0x7f235e6c3688 in vtkGenericDataArray<vtkAOSDataArrayTemplate<float>, float, 5>::ReserveTuples(long long) /opt/vtk-src/Common/Core/vtkGenericDataArray.txx:455
#5 0x7f23656243aa in vtkAbstractArray::ReserveValues(long long) /opt/vtk-src/Common/Core/vtkAbstractArray.cxx:216
#6 0x7f23656243aa in vtkAbstractArray::SetNumberOfValues(long long) /opt/vtk-src/Common/Core/vtkAbstractArray.cxx:222
#7 0x7f2368c465aa in vtkImageData::AllocateScalars(int, int) /opt/vtk-src/Common/DataModel/vtkImageData.cxx:1076
#8 0x7f2368c45c5a in vtkImageData::AllocateScalars(vtkInformation*) /opt/vtk-src/Common/DataModel/vtkImageData.cxx:1034
#9 0x7f236b311a31 in vtkDEMReader::RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/IO/Image/vtkDEMReader.cxx:124
#10 0x7f236a190ed7 in vtkExecutive::CallAlgorithm(vtkInformation*, int, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkExecutive.cxx:723
#11 0x7f236a16300b in vtkDemandDrivenPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:447
#12 0x7f236a13efd8 in vtkCompositeDataPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkCompositeDataPipeline.cxx:151
#13 0x7f236a15ffa9 in vtkDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:246
#14 0x7f236a2d159e in vtkStreamingDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:331
#15 0x7f236a1620b9 in vtkDemandDrivenPipeline::UpdateData(int) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:404
#16 0x7f236a2d40ce in vtkStreamingDemandDrivenPipeline::Update(int, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:421
#17 0x7f236a11d387 in vtkAlgorithm::Update(int) /opt/vtk-src/Common/ExecutionModel/vtkAlgorithm.cxx:1660
#18 0x55be193039f7 in main /manuwork/advisories/dem-image-1/poc_vtkdemreader_update.cpp:20
#19 0x7f235b4ca1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#20 0x7f235b4ca28a in __libc_start_main_impl ../csu/libc-start.c:360
#21 0x55be19303404 in _start (/manuwork/advisories/dem-image-1/build-asan/poc_vtkdemreader_update+0x6404) (BuildId: a0662c635ccf9dae226cb5a66299c40ac4c1caaf)
SUMMARY: AddressSanitizer: heap-buffer-overflow /opt/vtk-src/IO/Image/vtkDEMReader.cxx:440 in vtkDEMReader::ReadProfiles(vtkImageData*)
Shadow bytes around the buggy address:
0x7f23592b6780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f23592b6800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f23592b6880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f23592b6900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f23592b6980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7f23592b6a00: 00 00[04]fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7f23592b6a80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7f23592b6b00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7f23592b6b80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7f23592b6c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7f23592b6c80: 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
==364==ABORTING
Vendor Link: https://vtk.org/download/
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
Emmanuel Tacheau of Cisco Talos