Talos Vulnerability Report

TALOS-2026-2422

VTK vtkBMPReader ExecuteDataWithInformation memory corruption vulnerability

October 29, 2026

CVE Number

CVE-2026-8824

Summary

A memory corruption vulnerability exists in the vtkBMPReader ExecuteDataWithInformation functionality of vtk (version(s): 9.6.1). A specially crafted .bmp file 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-787 - Out-of-bounds Write

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.

vtkBMPReader parses the Windows Bitmap (BMP) image format. The reader parses a malformed BMP sample. ASan reports:

vtkBMPReader.cxx:147 ERR| vtkBMPReader: Only BMP depths of (8,24) are supported. Not 16
==16==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000007ff2
WRITE of size 2 at 0x502000007ff2 thread T0
    #0  in void vtkBMPReaderUpdate2<short>(vtkBMPReader*, vtkImageData*, short*)
        /opt/vtk-src/IO/Image/vtkBMPReader.cxx:428:22
    #1  in vtkBMPReader::ExecuteDataWithInformation(...)
        /opt/vtk-src/IO/Image/vtkBMPReader.cxx:476:5
0x502000007ff2 is located 0 bytes after 2-byte region [0x502000007ff0,0x502000007ff2)

A heap-buffer-overflow WRITE of 2 bytes at LINE_428 inside vtkBMPReaderUpdate2<short>(), called from ExecuteDataWithInformation() at LINE_476. The write lands at address 0x502000007ff2, exactly 0 bytes after the end of a 2-byte allocated region. The error message on the first line — emitted before the crash — indicates that the reader recognized the bit depth as unsupported.

The crash-site code in vtkBMPReaderUpdate2():

LINE_284  template <class OT>
LINE_285  void vtkBMPReaderUpdate2(vtkBMPReader* self, vtkImageData* data, OT* outPtr)
LINE_286  {
[...]
LINE_415    if (self->GetDepth() == 8 && !Keep8bit)
LINE_416    {
LINE_417      outPtr0[0] = (OT)(Colors[inPtr[0] * 3]);
LINE_418      outPtr0[1] = (OT)(Colors[inPtr[0] * 3 + 1]);
LINE_419      outPtr0[2] = (OT)(Colors[inPtr[0] * 3 + 2]);
LINE_420    }
LINE_421    else if (self->GetDepth() == 8 && Keep8bit)
LINE_422    {
LINE_423      outPtr0[0] = (OT)(inPtr[0]);
LINE_424    }
LINE_425    else
LINE_426    {
LINE_427      outPtr0[0] = (OT)(inPtr[2]);
LINE_428      outPtr0[1] = (OT)(inPtr[1]);
LINE_429      outPtr0[2] = (OT)(inPtr[0]);
LINE_430    }
[...]
}

self->GetDepth() returns the bit depth read from the BMP header — a member variable this->Depth. The first two branches handle depth 8. The else branch at LINE_425 is a catch-all: any depth that is not 8 enters this branch, which writes 3 components per pixel into the output buffer (outPtr0[0], outPtr0[1], outPtr0[2] at LINE_427–LINE_429). outPtr0 is a pointer into outPtr, which was passed from the caller.

One level up in the call stack (frame #1), ExecuteDataWithInformation() allocates the output buffer and dispatches to vtkBMPReaderUpdate2():

LINE_451  void vtkBMPReader::ExecuteDataWithInformation(vtkDataObject* output, vtkInformation* outInfo)
LINE_452  {
LINE_453    vtkImageData* data = this->AllocateOutputData(output, outInfo);
[...]
LINE_467    this->ComputeDataIncrements();
[...]
LINE_473    outPtr = data->GetScalarPointer();
LINE_474    switch (data->GetScalarType())
LINE_475    {
LINE_476      vtkTemplateMacro(vtkBMPReaderUpdate2(this, data, static_cast<VTK_TT*>(outPtr)));
[...]
}

AllocateOutputData() at LINE_453 sizes the output buffer based on the scalar component count — a value configured during the information phase. GetScalarPointer() at LINE_473 returns a pointer to that buffer. At LINE_476, the function dispatches into vtkBMPReaderUpdate2() with this (which carries this->Depth) and outPtr. No validation of this->Depth occurs between LINE_451 and LINE_476.

The buffer is sized by the scalar component count. That count is set in ExecuteInformation(), which also reads the bit depth from the file header:

LINE_42   void vtkBMPReader::ExecuteInformation()
LINE_43   {
[...]
LINE_138    if (stream->Read(&this->Depth, 2) != 2)
LINE_139    {
LINE_140      vtkErrorMacro("Error reading depth");
LINE_141      return;
LINE_142    }
LINE_143    vtkByteSwap::Swap2LE(&this->Depth);
LINE_145    if ((this->Depth != 8) && (this->Depth != 24))
LINE_146    {
LINE_147      vtkErrorMacro("Only BMP depths of (8,24) are supported. Not " << this->Depth);
LINE_148      return;
LINE_149    }
[...]
LINE_218    this->SetDataScalarTypeToUnsignedChar();
LINE_219    if ((this->Depth == 8) && this->Allow8BitBMP)
LINE_220    {
LINE_221      this->SetNumberOfScalarComponents(1);
LINE_222    }
LINE_223    else
LINE_224    {
LINE_225      this->SetNumberOfScalarComponents(3);
LINE_226    }
[...]
}

this->Depth is a 2-byte integer read from the BMP DIB header at LINE_138 — its value is attacker-controlled. At LINE_145, the function checks whether the depth is 8 or 24. For any other value, it emits an error message at LINE_147 and returns at LINE_148.

The code at LINE_218–LINE_226 configures the scalar component count: 1 for 8-bit grayscale, 3 for 24-bit RGB. The early return at LINE_148 exits the function before this code is reached. The scalar component count is therefore left at whatever default the reader object holds — typically 1.

The pipeline allocates the output buffer based on that component count. With 1 component, the buffer has room for one value per pixel. But the else branch in vtkBMPReaderUpdate2() writes 3 values per pixel — the buffer is too small by a factor of 3.

VTK’s pipeline dispatches REQUEST_INFORMATION and REQUEST_DATA as separate requests. ExecuteInformation() is called during the information phase; ExecuteDataWithInformation() during the data phase. The early return at LINE_148 exits the information phase — but does not prevent the data phase from running. The parent class vtkImageReader2::RequestInformation() calls this->ExecuteInformation() and then checks this->GetErrorCode() to decide whether to report failure. vtkBMPReader::ExecuteInformation() never calls this->SetErrorCode() before returning at LINE_148 — GetErrorCode() returns NoError, RequestInformation() returns success, and the pipeline proceeds to the data phase.

The data phase runs with this->Depth still holding the attacker-controlled value (unchanged since LINE_138), while the buffer was sized for a component count that was never configured for that depth. The else branch at LINE_425 writes 3 components into a 1-component buffer, producing the heap overflow.

The attacker controls the content written: the values at LINE_427–LINE_429 come from inPtr, which points to raw pixel data from the BMP file. The attacker also controls which branch is taken: any bit depth value other than 8 enters the else branch at LINE_425, which writes 3 components per pixel into a buffer sized for 1.

The observed crash is an out-of-bounds write into heap memory. Because the write lands immediately past the allocated buffer boundary, it corrupts adjacent heap metadata or application objects. On non-instrumented builds this corruption is silent — no error is raised and the process continues with a compromised heap state.

Crash Information

2026-05-04 13:57:49.309 (   0.127s) [    7F455270CDC0]       vtkBMPReader.cxx:147    ERR| vtkBMPReader (0x516000001e80): Only BMP depths of (8,24) are supported. Not 16
=================================================================
==16==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000007ff2 at pc 0x7f456fc27fe8 bp 0x7ffd4c90f8d0 sp 0x7ffd4c90f8c8
WRITE of size 2 at 0x502000007ff2 thread T0
    #0 0x7f456fc27fe7 in void vtkBMPReaderUpdate2<short>(vtkBMPReader*, vtkImageData*, short*) /opt/vtk-src/IO/Image/vtkBMPReader.cxx:428:22
    #1 0x7f456fc00e46 in vtkBMPReader::ExecuteDataWithInformation(vtkDataObject*, vtkInformation*) /opt/vtk-src/IO/Image/vtkBMPReader.cxx:476:5
    #2 0x7f456eaa765f in vtkImageAlgorithm::RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkImageAlgorithm.cxx:61:11
    #3 0x7f456ea5fed7 in vtkExecutive::CallAlgorithm(vtkInformation*, int, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkExecutive.cxx:723:33
    #4 0x7f456ea3200b in vtkDemandDrivenPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:447:22
    #5 0x7f456ea0dfd8 in vtkCompositeDataPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkCompositeDataPipeline.cxx:151:32
    #6 0x7f456ea2efa9 in vtkDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:246:22
    #7 0x7f456eba059e in vtkStreamingDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:331:27
    #8 0x7f456ea310b9 in vtkDemandDrivenPipeline::UpdateData(int) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:404:16
    #9 0x7f456eba30ce in vtkStreamingDemandDrivenPipeline::Update(int, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:421:34
    #10 0x7f456e9ec387 in vtkAlgorithm::Update(int) /opt/vtk-src/Common/ExecutionModel/vtkAlgorithm.cxx:1660:32
    #11 0x55c0556074aa in main /manuwork/medical_harness_vtk/medical_harness_vtk.cpp:775:13
    #12 0x7f456039c1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #13 0x7f456039c28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #14 0x55c05551b964 in _start (/manuwork/medical_harness_vtk/build-afl/medical_harness_vtk+0x2f964) (BuildId: 88f0ec43a4e0ed3bc1bf4f4c79d2279164013566)

0x502000007ff2 is located 0 bytes after 2-byte region [0x502000007ff0,0x502000007ff2)
allocated by thread T0 here:
    #0 0x55c0555bb793 in malloc (/manuwork/medical_harness_vtk/build-afl/medical_harness_vtk+0xcf793) (BuildId: 88f0ec43a4e0ed3bc1bf4f4c79d2279164013566)
    #1 0x7f456577e15f in vtkBuffer<short>::Allocate(long long) /opt/vtk-src/Common/Core/vtkBuffer.h:202:43
    #2 0x7f45658a8958 in vtkAOSDataArrayTemplate<short>::ReallocateTuples(long long) /opt/vtk-src/Common/Core/vtkAOSDataArrayTemplate.txx:488:21
    #3 0x7f45658a8958 in vtkGenericDataArray<vtkAOSDataArrayTemplate<short>, short, 5>::ReallocateTuples(long long) /opt/vtk-src/Common/Core/vtkGenericDataArray.h:358:42
    #4 0x7f45658a8958 in vtkGenericDataArray<vtkAOSDataArrayTemplate<short>, short, 5>::ReserveTuples(long long) /opt/vtk-src/Common/Core/vtkGenericDataArray.txx:455:14
    #5 0x7f4569ef33aa in vtkAbstractArray::ReserveValues(long long) /opt/vtk-src/Common/Core/vtkAbstractArray.cxx:216:16
    #6 0x7f4569ef33aa in vtkAbstractArray::SetNumberOfValues(long long) /opt/vtk-src/Common/Core/vtkAbstractArray.cxx:222:14
    #7 0x7f456d5155aa in vtkImageData::AllocateScalars(int, int) /opt/vtk-src/Common/DataModel/vtkImageData.cxx:1076:12
    #8 0x7f456eaa8c2a in vtkImageAlgorithm::AllocateOutputData(vtkDataObject*, vtkInformation*) /opt/vtk-src/Common/ExecutionModel/vtkImageAlgorithm.cxx:201:11
    #9 0x7f456fc00750 in vtkBMPReader::ExecuteDataWithInformation(vtkDataObject*, vtkInformation*) /opt/vtk-src/IO/Image/vtkBMPReader.cxx:453:30
    #10 0x7f456eaa765f in vtkImageAlgorithm::RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkImageAlgorithm.cxx:61:11
    #11 0x7f456ea5fed7 in vtkExecutive::CallAlgorithm(vtkInformation*, int, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkExecutive.cxx:723:33
    #12 0x7f456ea3200b in vtkDemandDrivenPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:447:22
    #13 0x7f456ea0dfd8 in vtkCompositeDataPipeline::ExecuteData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkCompositeDataPipeline.cxx:151:32
    #14 0x7f456ea2efa9 in vtkDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:246:22
    #15 0x7f456eba059e in vtkStreamingDemandDrivenPipeline::ProcessRequest(vtkInformation*, vtkInformationVector**, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:331:27
    #16 0x7f456ea310b9 in vtkDemandDrivenPipeline::UpdateData(int) /opt/vtk-src/Common/ExecutionModel/vtkDemandDrivenPipeline.cxx:404:16
    #17 0x7f456eba30ce in vtkStreamingDemandDrivenPipeline::Update(int, vtkInformationVector*) /opt/vtk-src/Common/ExecutionModel/vtkStreamingDemandDrivenPipeline.cxx:421:34
    #18 0x7f456e9ec387 in vtkAlgorithm::Update(int) /opt/vtk-src/Common/ExecutionModel/vtkAlgorithm.cxx:1660:32
    #19 0x55c0556074aa in main /manuwork/medical_harness_vtk/medical_harness_vtk.cpp:775:13
    #20 0x7f456039c1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #21 0x7f456039c28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #22 0x55c05551b964 in _start (/manuwork/medical_harness_vtk/build-afl/medical_harness_vtk+0x2f964) (BuildId: 88f0ec43a4e0ed3bc1bf4f4c79d2279164013566)

SUMMARY: AddressSanitizer: heap-buffer-overflow /opt/vtk-src/IO/Image/vtkBMPReader.cxx:428:22 in void vtkBMPReaderUpdate2<short>(vtkBMPReader*, vtkImageData*, short*)
Shadow bytes around the buggy address:
  0x502000007d00: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x502000007d80: fa fa 00 fa fa fa fd fa fa fa 07 fa fa fa fd fa
  0x502000007e00: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x502000007e80: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
  0x502000007f00: fa fa 00 fa fa fa 00 fa fa fa 00 fa fa fa 00 fa
=>0x502000007f80: fa fa 00 fa fa fa 00 fa fa fa fd fd fa fa[02]fa
  0x502000008000: fa fa 00 fa fa fa 00 00 fa fa 00 00 fa fa 00 00
  0x502000008080: fa fa 00 00 fa fa 00 01 fa fa 01 fa fa fa 04 fa
  0x502000008100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000008180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000008200: 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
==16==ABORTING

Vendor Response (CVE-2026-8824)

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