Grassroot DICOM Overlay::GrabOverlayFromPixelData out-of-bounds read vulnerability
Grassroot DICOM 3.024版本存在越界读取漏洞,位于Overlay::GrabOverlayFromPixelData函数。该漏洞由未验证内存缓冲区大小引起,处理特定构造的DICOM文件时导致信息泄露。CVSSv3评分为7.4。 Cisco Talos团队于2025年12月16日公开披露。 2025-12-15 23:59:0 Author: talosintelligence.com(查看原文) 阅读量:0 收藏

SUMMARY

An out-of-bounds read vulnerability exists in the Overlay::GrabOverlayFromPixelData functionality of Grassroot DICOM 3.024. A specially crafted DICOM file can lead to an information leak. 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.

Grassroot DICOM 3.024

PRODUCT URLS

Grassroot DICOM - https://sourceforge.net/projects/gdcm/

CVSSv3 SCORE

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

CWE

CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer

DETAILS

Grassroots DiCoM is a C++ library for DICOM medical files. It is accessible from Python, C#, Java and PHP. It supports RAW, JPEG, JPEG 2000, JPEG-LS, RLE and deflated transfer syntax. It comes with a super fast scanner implementation to quickly scan hundreds of DICOM files. It supports SCU network operations (C-ECHO, C-FIND, C-STORE, C-MOVE). PS 3.3 & 3.6 are distributed as XML files. It also provides PS 3.15 certificates and password based mecanism to anonymize and de-identify DICOM dataset

A specifically designed DICOM file can cause an out-of-bounds read in the Overlay::GrabOverlayFromPixelData function. This occurs because there is no size verification to ensure the source memory buffer’s bounds are respected during processing. When attempting to load a malformed DICOM file, the following scenario arises:

 Program received signal SIGSEGV, Segmentation fault.
    0x000061bd11dbc3b7 in gdcm::Overlay::GrabOverlayFromPixelData (this=0x61bd26fb94a0, ds=...) at /src/gdcm-src/Source/MediaStorageAndFileFormat/gdcmOverlay.cxx:283
   281     while( p != end )
   282       {
 ► 283       const uint8_t val = *p & pmask;
   284       assert( val == 0x0 || val == pmask );

From the crash, it is immediately apparent that an out-of-bounds issue is occurring. Upon examining the code responsible for the crash:

In file: /src/gdcm-3.0.24/Source/MediaStorageAndFileFormat/gdcmOverlay.cxx:283
   278     int c = 0;
   279     uint8_t pmask = (uint8_t)(1 << Internal->BitPosition);
   280     assert( length / 1 == ovlength * 8 );
   281     while( p != end )
   282       {
 ► 283       const uint8_t val = *p & pmask;
   284       assert( val == 0x0 || val == pmask );
   285       // 128 -> 0x80
   286       if( val )
   287         {
   288         overlay[ c / 8 ] |= (unsigned char)(0x1 << c%8);

Upon inspecting variables such as p:

pwndbg> p/x p
$1 = 0x716187b5b000
pwndbg> vmmap 0x716187b5b000
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size  Offset File (set vmmap-prefer-relpaths on)
    0x716187b03000     0x716187b5b000 r--p    58000  1bd000 /usr/lib/x86_64-linux-gnu/libc.so.6
►   0x716187b5b000     0x716187b5c000 ---p     1000  215000 /usr/lib/x86_64-linux-gnu/libc.so.6 +0x0
    0x716187b5c000     0x716187b60000 r--p     4000  215000 /usr/lib/x86_64-linux-gnu/libc.so.6

Additionally, examining the end variable suggests the presence of an out-of-bounds issue:

pwndbg> p end
$3 = (const uint8_t *) 0x716188cf3320 <error: Cannot access memory at address 0x716188cf3320>

The code crashes due to an attempt to read memory belonging to glibc, which lacks read permissions. Below is an excerpt from the Overlay::GrabOverlayFromPixelData function:

LINE 1. bool Overlay::GrabOverlayFromPixelData(DataSet const &ds)
LINE 2. {
LINE 3.   const unsigned int ovlength = Internal->Rows * Internal->Columns / 8;
LINE 4.   Internal->Data.resize( ovlength ); // set to 0
LINE 5.   if( Internal->BitsAllocated == 8 )
LINE 6.     {
LINE 7.     if( !ds.FindDataElement( Tag(0x7fe0,0x0010) ) )
LINE 8.       {
LINE 9.       gdcmWarningMacro("Could not find Pixel Data. Cannot extract Overlay." );
LINE 10.       return false;
LINE 11.       }
LINE 12.     const DataElement &pixeldata = ds.GetDataElement( Tag(0x7fe0,0x0010) );
LINE 13.     const ByteValue *bv = pixeldata.GetByteValue();
LINE 14.     if( !bv )
LINE 15.       {
LINE 16.       gdcmWarningMacro("Could not extract overlay from encapsulated stream." );
LINE 17.       return false;
LINE 18.       }
LINE 19.     const char *array = bv->GetPointer();
LINE 20.     const unsigned int length = ovlength * 8 * 1; //bv->GetLength();
LINE 21.     const uint8_t *p = (const uint8_t*)(const void*)array;
LINE 22.     const uint8_t *end = (const uint8_t*)(const void*)(array + length);
LINE 23.     assert( 8 * ovlength == (unsigned int)Internal->Rows * Internal->Columns );
LINE 24.     if( Internal->Data.empty() )
LINE 25.       {
LINE 26.       gdcmWarningMacro("Internal Data is empty." );
LINE 27.       return false;
LINE 28.       }
LINE 29.     unsigned char * overlay = (unsigned char*)Internal->Data.data();
LINE 30.     int c = 0;
LINE 31.     uint8_t pmask = (uint8_t)(1 << Internal->BitPosition);
LINE 32.     assert( length / 1 == ovlength * 8 );
LINE 33.     while( p != end )
LINE 34.       {
LINE 35.       const uint8_t val = *p & pmask; 
LINE 36.       assert( val == 0x0 || val == pmask );
LINE 37.       // 128 -> 0x80
LINE 38.       if( val )
LINE 39.         {
LINE 40.         overlay[ c / 8 ] |= (unsigned char)(0x1 << c%8);
LINE 41.         }
LINE 42.       else
LINE 43.         {
LINE 44.         // else overlay[ c / 8 ] is already 0
LINE 45.         }
LINE 46.       ++p;
LINE 47.       ++c;
LINE 48.       }
LINE 49.     assert( (unsigned)c / 8 == ovlength );
LINE 50.     }
   [...]
LINE 109. }

The end pointer is computed LINE 22 to the value of the (array + length) where length come from the result of ovlength * 8 * 1 at LINE 29 and array LINE 19 The array is pointer to bytes read from file in memory, which is given the same value to p LINE 21. array obtained from the call to bv->GetPointer() where bv is a ByteValue * LINE 13 is the corresponding data to the pixeldata DICOM record, a Tag(0x7fe0,0x0010) at LINE12 The length of this array is bv->GetLength() corresponding to the size stored into the DICOM pixeldata DICOM record Tag(0x7fe0,0x0010)

The ovlengthis computed with Internal->Rows * Internal->Columns / 8 LINE 3 The issue is happening when length is bigger than the real size of the memory of end pointer causing an out-of-bounds read of memory pointed by p. The malformed file is enabling to control the length and the real size of p enabling the control of the while-loop LINE 33 leading potentially to infoleaks. By the way GLIBC contains a lot of sensitives info which can be used later for exploitation.

The crash occurs due to an out-of-bounds read triggered when the end pointer is calculated incorrectly. The issue arises as follows: - At LINE 22, the end pointer is computed as (array + length), where length is determined from the expression ovlength * 8 * 1 at LINE 29. - The array pointer, initialized at LINE 19, points to bytes read from the file into memory. It is also assigned to p at LINE 21. - The array is obtained from the call to bv->GetPointer(), where bv is a ByteValue* at LINE 13. This corresponds to the data in the pixeldata DICOM record, identified by the Tag (0x7fe0, 0x0010) at LINE 12. - The real size of this array could be determined by bv->GetLength(), which represents the size of the DICOM PixelData record stored in the Tag (0x7fe0, 0x0010).

The calculation of ovlength is done at LINE 3 as Internal->Rows * Internal->Columns / 8. However, the issue occurs when the length value exceeds the actual size of the memory allocated to the array. This causes the end pointer to point beyond the valid memory range. As a result, the while loop at LINE 33 iterates over invalid memory, leading to an out-of-bounds read from the p pointer. The malformed DICOM file manipulates the length and the actual size of p to control the loop behavior, potentially leaking sensitive information.

Given that GLIBC contains a wealth of sensitive data, such as function pointers and memory layouts, this vulnerability could be exploited further for information leaks or other malicious purposes.

Crash Information

Program received signal SIGSEGV, Segmentation fault.
0x000061bd11dbc3b7 in gdcm::Overlay::GrabOverlayFromPixelData (this=0x61bd26fb94a0, ds=...) at /src/gdcm-src/Source/MediaStorageAndFileFormat/gdcmOverlay.cxx:283
283	      const uint8_t val = *p & pmask;
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────────────────────
*RAX  0x716187b5b000 ◂— 0
*RBX  1
*RCX  0x10
*RDX  0x10
*RDI  0x10
*RSI  0x10
*R8   0x61bd26fc20b0 —▸ 0x61bd122404f0 (vtable for gdcm::ByteValue+16) —▸ 0x61bd11dbe22a (gdcm::ByteValue::~ByteValue()) ◂— endbr64 
*R9   0x61bd12243e00 (typeinfo for gdcm::Value) —▸ 0x716187e93c30 (vtable for __cxxabiv1::__si_class_type_info+16) —▸ 0x716187d24fe0 (__cxxabiv1::__si_class_type_info::~__si_class_type_info()) ◂— endbr64 
*R10  0x22
*R11  0x716187b60ce0 (main_arena+96) —▸ 0x61bd26fd2f30 ◂— 0
*R12  1
*R13  0x61bd11d556b9 (main) ◂— endbr64 
*R14  0x61bd12111798 (__do_global_dtors_aux_fini_array_entry) —▸ 0x61bd11d55430 (__do_global_dtors_aux) ◂— endbr64 
*R15  0x716187f05040 (_rtld_global) —▸ 0x716187f062e0 —▸ 0x61bd11b54000 ◂— 0x10102464c457f
*RBP  0x7ffc4702ea70 —▸ 0x7ffc4702ef30 —▸ 0x7ffc4702f4f0 —▸ 0x7ffc4702f510 —▸ 0x7ffc4702f5d0 ◂— ...
*RSP  0x7ffc4702e7f0 —▸ 0x61bd26fb9238 ◂— 0
*RIP  0x61bd11dbc3b7 (gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1529) ◂— movzx eax, byte ptr [rax]
─────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────────────────────────
 ► 0x61bd11dbc3b7 <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1529>    movzx  eax, byte ptr [rax]            EAX, [0x716187b5b000] => 0
   0x61bd11dbc3ba <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1532>    and    al, byte ptr [rbp - 0x262]     AL => 0 (0 & 1)
   0x61bd11dbc3c0 <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1538>    mov    byte ptr [rbp - 0x261], al     [0x7ffc4702e80f] <= 0
   0x61bd11dbc3c6 <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1544>    cmp    byte ptr [rbp - 0x261], 0      0 - 0     EFLAGS => 0x10246 [ cf PF af ZF sf IF df of ac ]
   0x61bd11dbc3cd <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1551>  ✔ je     gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1608 <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1608>
    ↓
   0x61bd11dbc406 <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1608>    cmp    byte ptr [rbp - 0x261], 0      0 - 0     EFLAGS => 0x10246 [ cf PF af ZF sf IF df of ac ]
   0x61bd11dbc40d <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1615>  ✔ je     gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1691 <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1691>
    ↓
   0x61bd11dbc459 <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1691>    add    qword ptr [rbp - 0x248], 1       [0x7ffc4702e828] <= 0x716187b5b001 (0x716187b5b000 + 0x1)
   0x61bd11dbc461 <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1699>    add    dword ptr [rbp - 0x25c], 1       [0x7ffc4702e814] <= 0x27aff1 (0x27aff0 + 0x1)
   0x61bd11dbc468 <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1706>    mov    rax, qword ptr [rbp - 0x248]     RAX, [0x7ffc4702e828] => 0x716187b5b001 ◂— 0
   0x61bd11dbc46f <gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1713>    cmp    rax, qword ptr [rbp - 0x1d8]     0x716187b5b001 - 0x716188cf3320     EFLAGS => 0x10287 [ CF PF af zf SF IF df of ac ]
───────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────────────────────
In file: /src/gdcm-3.0.24/Source/MediaStorageAndFileFormat/gdcmOverlay.cxx:283
   278     int c = 0;
   279     uint8_t pmask = (uint8_t)(1 << Internal->BitPosition);
   280     assert( length / 1 == ovlength * 8 );
   281     while( p != end )
   282       {
 ► 283       const uint8_t val = *p & pmask;
   284       assert( val == 0x0 || val == pmask );
   285       // 128 -> 0x80
   286       if( val )
   287         {
   288         overlay[ c / 8 ] |= (unsigned char)(0x1 << c%8);
───────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffc4702e7f0 —▸ 0x61bd26fb9238 ◂— 0
01:0008│-278 0x7ffc4702e7f8 —▸ 0x61bd26fb94a0 —▸ 0x61bd12111a30 (vtable for gdcm::Overlay+16) —▸ 0x61bd11dba6ba (gdcm::Overlay::~Overlay()) ◂— endbr64 
02:0010│-270 0x7ffc4702e800 ◂— 0x102600000000002
03:0018│-268 0x7ffc4702e808 ◂— 0x17b221aa94c00
04:0020│-260 0x7ffc4702e810 ◂— 0x27aff04702e8e0
05:0028│-258 0x7ffc4702e818 ◂— 0x28266226fb94a0
06:0030│-250 0x7ffc4702e820 ◂— 0x14133104702ea70
07:0038│-248 0x7ffc4702e828 —▸ 0x716187b5b000 ◂— 0
─────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────
 ► 0   0x61bd11dbc3b7 gdcm::Overlay::GrabOverlayFromPixelData(gdcm::DataSet const&)+1529
   1   0x61bd11de08b7 gdcm::DoOverlays(gdcm::DataSet const&, gdcm::Pixmap&)+1615
   2   0x61bd11de39fd gdcm::PixmapReader::ReadImageInternal(gdcm::MediaStorage const&, bool)+9433
   3   0x61bd11de1522 gdcm::PixmapReader::ReadImage(gdcm::MediaStorage const&)+44
   4   0x61bd11d5e4be gdcm::ImageReader::ReadImage(gdcm::MediaStorage const&)+70
   5   0x61bd11ddde6e gdcm::PixmapReader::Read()+388
   6   0x61bd11d5e476 gdcm::ImageReader::Read()+28
   7   0x61bd11d5578d main+212
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
TIMELINE

2025-07-15 - Vendor Disclosure
2025-08-04 - Talos Follow-up
2025-09-01 - Talos Follow-up
2025-10-06 - Talos Follow-up
2025-12-16 - Public Release

Discovered by Emmanuel Tacheau of Cisco Talos.


文章来源: https://talosintelligence.com/vulnerability_reports/TALOS-2025-2211
如有侵权请联系:admin#unsafe.sh