An out-of-bounds read vulnerability exists in the JPEGBITSCodec::InternalCode 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.
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
Grassroot DICOM - https://sourceforge.net/projects/gdcm/
7.4 - CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer
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 specially crafted DICOM file can trigger an out-of-bounds read in several compression routines, such as grayscale_convert and null_convert. This vulnerability arises due to the absence of a size check on the source memory buffer during processing.
Based on the input file provided for compression, various functions will be invoked later to handle the color space. The relevant information will be stored within a JPEG compression object called cinfo, which is set up within a function named jinit_color_converter.
For instance, depending on the encoding format, the function pointer for color conversion may be assigned as follows:
cconvert->pub.color_convert = grayscale_convert; (for grayscale conversion)
cconvert->pub.color_convert = null_convert; (for no conversion)
The function jinit_color_converter is responsible for configuring the color_convert object as required. Below is the implementation that handles this setup.
LINE 1. /*
LINE 2. * Module initialization routine for input colorspace conversion.
LINE 3. */
LINE 4.
LINE 5. GLOBAL(void)
LINE 6. jinit_color_converter (j_compress_ptr cinfo)
LINE 7. {
[...]
LINE 47.
LINE 48. /* Check num_components, set conversion method based on requested space */
LINE 49. switch (cinfo->jpeg_color_space) {
LINE 50. case JCS_GRAYSCALE:
LINE 53. if (cinfo->in_color_space == JCS_GRAYSCALE)
LINE 54. cconvert->pub.color_convert = grayscale_convert;
[...]
LINE 62. break;
LINE 63.
LINE 64. case JCS_RGB:
[...]
LINE 67. if (cinfo->in_color_space == JCS_RGB && RGB_PIXELSIZE == 3)
LINE 68. cconvert->pub.color_convert = null_convert;
[...]
LINE 71. break;
[...]
LINE 112. }
LINE 113. }
To gain deeper insights into the underlying process, it is important to examine the construction. Utilizing the rr record tool proves to be highly effective in this scenario, as it helps identify functions responsible for handling JPEG compression.
During the compression process, a function named JPEGBITSCodec::InternalCode is invoked. This function plays a key role in managing JPEG compression. Its input parameter is a vector that is allocated with a fixed length, determined by the pixel data size recorded in the file. This length, referred to as len, is set accordingly and passed as a parameter to the function.
LINE 114. /*
LINE 115. * Sample routine for JPEG compression. We assume that the target file name
LINE 116. * and a compression quality factor are passed in.
LINE 117. */
LINE 118.
LINE 119. bool JPEGBITSCodec::InternalCode(const char* input, unsigned long len, std::ostream &os)
LINE 120. {
LINE 121. int quality = 100; (void)len;
LINE 122. (void)quality;
LINE 123. JSAMPLE * image_buffer = (JSAMPLE*)(void*)const_cast<char*>(input); /* Points to large array of R,G,B-order data */
LINE 124. const unsigned int *dims = this->GetDimensions();
LINE 125. int image_height = dims[1]; /* Number of rows in image */
LINE 126. int image_width = dims[0]; /* Number of columns in image */
LINE 127.
[...]
LINE 277. row_stride = image_width * cinfo.input_components; /* JSAMPLEs per row in image_buffer */
LINE 278.
LINE 279. if( this->GetPlanarConfiguration() == 0 )
LINE 280. {
LINE 281. while (cinfo.next_scanline < cinfo.image_height) {
LINE 282. /* jpeg_write_scanlines expects an array of pointers to scanlines.
LINE 283. * Here the array is only one element long, but you could pass
LINE 284. * more than one scanline at a time if that's more convenient.
LINE 285. */
LINE 286. row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride]; <---- OOBO here
LINE 287. (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
LINE 288. }
LINE 289. }
[...]
LINE 328. }
Earlier in the code, a vulnerability can be observed at LINE 286 due to the absence of bounds checking. Specifically, the assignment to row_pointer may result in a potential out-of-bounds (OOBO) value, as there is no validation to ensure that image_buffer[cinfo.next_scanline * row_stride] stays within the bounds of image_buffer or its length (len).
The computed row_pointer buffer is then passed as a parameter to jpeg_write_scanlines, where it is used as the scanlines parameter. Within jpeg_write_scanlines, we can observe a call to cinfo->main->process_data at LINE 361, with scanlines (corresponding to the previously mentioned row_pointer) being passed as the second parameter.
LINE 329. GLOBAL(JDIMENSION)
LINE 330. jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines,
LINE 331. JDIMENSION num_lines)
LINE 332. {
[...]
LINE 355. /* Ignore any extra scanlines at bottom of image. */
LINE 356. rows_left = cinfo->image_height - cinfo->next_scanline;
LINE 357. if (num_lines > rows_left)
LINE 358. num_lines = rows_left;
LINE 359.
LINE 360. row_ctr = 0;
LINE 361. (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines);
LINE 362. cinfo->next_scanline += row_ctr;
LINE 363. return row_ctr;
LINE 364. }
In this case, the function pointer cinfo->main->process_data at LINE 361 points to the process_data_simple_main function. When this function is called, the scanlines parameter is passed as input_buf.
LINE 365. METHODDEF(void)
LINE 366. process_data_simple_main (j_compress_ptr cinfo,
LINE 367. JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
LINE 368. JDIMENSION in_rows_avail)
LINE 369. {
LINE 370. my_main_ptr mainPtr = (my_main_ptr) cinfo->main;
LINE 371. JDIMENSION data_unit = (JDIMENSION)(cinfo->data_unit);
LINE 372.
LINE 373. while (mainPtr->cur_iMCU_row < cinfo->total_iMCU_rows) {
LINE 374. /* Read input data if we haven't filled the main buffer yet */
LINE 375. if (mainPtr->rowgroup_ctr < data_unit)
LINE 376. (*cinfo->prep->pre_process_data) (cinfo,
LINE 377. input_buf, in_row_ctr, in_rows_avail,
LINE 378. mainPtr->buffer, &mainPtr->rowgroup_ctr,
LINE 379. (JDIMENSION) data_unit);
LINE 380.
[...]
LINE 412. }
At LINE 376, the function pointer (*cinfo->prep->pre_process_data) points to the pre_process_data function. This function is called with input_buf as one of its parameters.
LINE 413. METHODDEF(void)
LINE 414. pre_process_data (j_compress_ptr cinfo,
LINE 415. JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
LINE 416. JDIMENSION in_rows_avail,
LINE 417. JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr,
LINE 418. JDIMENSION out_row_groups_avail)
LINE 419. {
LINE 420. my_prep_ptr prep = (my_prep_ptr) cinfo->prep;
LINE 421. int numrows, ci;
LINE 422. JDIMENSION inrows;
LINE 423. jpeg_component_info * compptr;
LINE 424.
LINE 425. while (*in_row_ctr < in_rows_avail &&
LINE 426. *out_row_group_ctr < out_row_groups_avail) {
LINE 427. /* Do color conversion to fill the conversion buffer. */
LINE 428. inrows = in_rows_avail - *in_row_ctr;
LINE 429. numrows = cinfo->max_v_samp_factor - prep->next_buf_row;
LINE 430. numrows = (int) MIN((JDIMENSION) numrows, inrows);
LINE 431. (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr,
LINE 432. prep->color_buf,
LINE 433. (JDIMENSION) prep->next_buf_row,
LINE 434. numrows);
[...]
LINE 470. }
LINE 471. }
Finally, the color conversion function mentioned earlier, (*cinfo->cconvert->color_convert) at LINE 431, is invoked. This function dynamically calls different routines based on the content of the malicious DICOM file, potentially leading to various crashes.
Below is an excerpt from the grayscale_convert function, where the crash occurs at LINE 495:
LINE 473. /*
LINE 474. * Convert some rows of samples to the JPEG colorspace.
LINE 475. * This version handles grayscale output with no conversion.
LINE 476. * The source can be either plain grayscale or YCbCr (since Y == gray).
LINE 477. */
LINE 478.
LINE 479. METHODDEF(void)
LINE 480. grayscale_convert (j_compress_ptr cinfo,
LINE 481. JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
LINE 482. JDIMENSION output_row, int num_rows)
LINE 483. {
LINE 484. register JSAMPROW inptr;
LINE 485. register JSAMPROW outptr;
LINE 486. register JDIMENSION col;
LINE 487. JDIMENSION num_cols = cinfo->image_width;
LINE 488. int instride = cinfo->input_components;
LINE 489.
LINE 490. while (--num_rows >= 0) {
LINE 491. inptr = *input_buf++;
LINE 492. outptr = output_buf[0][output_row];
LINE 493. output_row++;
LINE 494. for (col = 0; col < num_cols; col++) {
LINE 495. outptr[col] = inptr[0]; // <----- crashing here
LINE 496. inptr += instride; // <----- OOBO here
LINE 497. }
LINE 498. }
LINE 499. }
The variables cinfo->image_width, cinfo->input_components, and num_rows are directly influenced by values extracted from the malicious DICOM file.
Additionally, the pointer inptr, which is obtained at LINE 491, is derived and computed from the input_buf pointer that is passed as an argument to the function. Upon analysis, we can an out-of-bounds read issue occurring at LINE 496
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
NumberOfDimensions: 2
Dimensions: (256,58112,1)
SamplesPerPixel :1
BitsAllocated :16
BitsStored :16
HighBit :15
PixelRepresentation:0
ScalarType found :UINT16
PhotometricInterpretation: MONOCHROME2
PlanarConfiguration: 0
TransferSyntax: 1.2.840.10008.1.2.1
Origin: (0,0,0)
Spacing: (2.21,2.21,1)
DirectionCosines: (1,0,0,0,1,0)
Rescale Intercept/Slope: (0,1)
Program received signal SIGSEGV, Segmentation fault.
grayscale_convert (cinfo=<optimized out>, input_buf=0x7fffffffda30, output_buf=<optimized out>, output_row=1, num_rows=<optimized out>) at /src/gdcm-git/Utilities/gdcmjpeg/jccolor.c:295
295 outptr[col] = inptr[0]; /* don't need GETJSAMPLE() here */
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────────────────────
RAX 0x555555f644a8 ◂— 0
RBX 0x200
RCX 0
RDX 0x7ffff7c6c000 ◂— 0
RDI 0x555555f644b8 ◂— 0x221
RSI 2
R8 1
R9 1
R10 0x7fffffffda30 —▸ 0x555555716590 (my_error_exit) ◂— endbr64
R11 0x555555f60008 —▸ 0x555555f642b8 ◂— 0
R12 0x7fffffffdba0 —▸ 0x7fffffffda30 —▸ 0x555555716590 (my_error_exit) ◂— endbr64
R13 0x7fffffffda28 —▸ 0x7ffff7c6be10 ◂— 0
R14 0x555555f5ff98 —▸ 0x5555557ec640 (start_pass_prep) ◂— endbr64
R15 0x7fffffffdba0 —▸ 0x7fffffffda30 —▸ 0x555555716590 (my_error_exit) ◂— endbr64
RBP 1
RSP 0x7fffffffd8d0 ◂— 1
RIP 0x5555557ed8d0 (grayscale_convert+80) ◂— movzx ecx, word ptr [rdx]
─────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────────────────────────
► 0x5555557ed8d0 <grayscale_convert+80> movzx ecx, word ptr [rdx] ECX, [0x7ffff7c6c000] => 0
0x5555557ed8d3 <grayscale_convert+83> add rax, 2 RAX => 0x555555f644aa (0x555555f644a8 + 0x2)
0x5555557ed8d7 <grayscale_convert+87> add rdx, rsi RDX => 0x7ffff7c6c002 (0x7ffff7c6c000 + 0x2)
0x5555557ed8da <grayscale_convert+90> mov word ptr [rax - 2], cx [0x555555f644a8] <= 0
0x5555557ed8de <grayscale_convert+94> cmp rax, rdi 0x555555f644aa - 0x555555f644b8 EFLAGS => 0x10283 [ CF pf af zf SF IF df of ac ]
0x5555557ed8e1 <grayscale_convert+97> ✔ jne grayscale_convert+80 <grayscale_convert+80>
↓
0x5555557ed8d0 <grayscale_convert+80> movzx ecx, word ptr [rdx] ECX, [0x7ffff7c6c002] => 0
0x5555557ed8d3 <grayscale_convert+83> add rax, 2 RAX => 0x555555f644ac (0x555555f644aa + 0x2)
0x5555557ed8d7 <grayscale_convert+87> add rdx, rsi RDX => 0x7ffff7c6c004 (0x7ffff7c6c002 + 0x2)
0x5555557ed8da <grayscale_convert+90> mov word ptr [rax - 2], cx [0x555555f644aa] <= 0
0x5555557ed8de <grayscale_convert+94> cmp rax, rdi 0x555555f644ac - 0x555555f644b8 EFLAGS => 0x10283 [ CF pf af zf SF IF df of ac ]
───────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────────────────────
In file: /src/gdcm-git/Utilities/gdcmjpeg/jccolor.c:295
290 while (--num_rows >= 0) {
291 inptr = *input_buf++;
292 outptr = output_buf[0][output_row];
293 output_row++;
294 for (col = 0; col < num_cols; col++) {
► 295 outptr[col] = inptr[0]; /* don't need GETJSAMPLE() here */
296 inptr += instride;
297 }
298 }
299 }
300
───────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd8d0 ◂— 1
01:0008│ 0x7fffffffd8d8 —▸ 0x5555557ec76c (pre_process_data+220) ◂— mov rax, qword ptr [rsp + 0x10]
02:0010│ 0x7fffffffd8e0 —▸ 0x555555f5ff98 —▸ 0x5555557ec640 (start_pass_prep) ◂— endbr64
03:0018│ 0x7fffffffd8e8 ◂— 0x555500000000
04:0020│ 0x7fffffffd8f0 —▸ 0x7fffffffd9c4 ◂— 0xc2ebf50000000000
05:0028│ 0x7fffffffd8f8 ◂— 0x101c60000
06:0030│ 0x7fffffffd900 —▸ 0x555555f60024 ◂— 0
07:0038│ 0x7fffffffd908 —▸ 0x555555f5ffa8 —▸ 0x555555f60008 —▸ 0x555555f642b8 ◂— 0
─────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────
► 0 0x5555557ed8d0 grayscale_convert+80
1 0x5555557ec76c pre_process_data+220
2 0x5555557ec488 process_data_simple_main+120
3 0x5555557e7efb gdcmjpeg16_jpeg_write_scanlines+187
4 0x5555557168a6 gdcm::JPEG16Codec::InternalCode(char const*, unsigned long, std::ostream&)+518
5 0x5555556edeec gdcm::JPEGCodec::Code(gdcm::DataElement const&, gdcm::DataElement&)+1164
6 0x5555556b3f56 gdcm::ImageChangeTransferSyntax::TryJPEGCodec(gdcm::DataElement const&, gdcm::Bitmap const&, gdcm::Bitmap&)+358
7 0x5555556b5a33 gdcm::ImageChangeTransferSyntax::Change()+1747
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Below is an excerpt from the null_convert function, where the crash occurs at LINE 526:
LINE 502. /*
LINE 503. * Convert some rows of samples to the JPEG colorspace.
LINE 504. * This version handles multi-component colorspaces without conversion.
LINE 505. * We assume input_components == num_components.
LINE 506. */
LINE 507.
LINE 508. METHODDEF(void)
LINE 509. null_convert (j_compress_ptr cinfo,
LINE 510. JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
LINE 511. JDIMENSION output_row, int num_rows)
LINE 512. {
LINE 513. register JSAMPROW inptr;
LINE 514. register JSAMPROW outptr;
LINE 515. register JDIMENSION col;
LINE 516. register int ci;
LINE 517. int nc = cinfo->num_components;
LINE 518. JDIMENSION num_cols = cinfo->image_width;
LINE 519.
LINE 520. while (--num_rows >= 0) {
LINE 521. /* It seems fastest to make a separate pass for each component. */
LINE 522. for (ci = 0; ci < nc; ci++) {
LINE 523. inptr = *input_buf;
LINE 524. outptr = output_buf[ci][output_row];
LINE 525. for (col = 0; col < num_cols; col++) {
LINE 526. outptr[col] = inptr[ci]; // <----- crashing here
LINE 527. inptr += nc; // <----- OOBO here
LINE 528. }
LINE 529. }
LINE 530. input_buf++;
LINE 531. output_row++;
LINE 532. }
LINE 533. }
The variables cinfo->image_width, cinfo->input_components, and num_rows are directly influenced by values extracted from the malicious DICOM file.
Additionally, the pointer inptr, which is obtained at LINE 523, is derived and computed from the input_buf pointer that is passed as an argument to the function. Upon analysis, we can observe an out-of-bounds read issue occurring at LINE 527
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
NumberOfDimensions: 2
Dimensions: (256,58112,1)
SamplesPerPixel :1
BitsAllocated :16
BitsStored :16
HighBit :0
PixelRepresentation:0
ScalarType found :UINT16
PhotometricInterpretation: RGB
PlanarConfiguration: 0
TransferSyntax: 1.2.840.10008.1.2.1
Origin: (0,0,0)
Spacing: (1,1,1)
DirectionCosines: (1,0,0,0,1,0)
Rescale Intercept/Slope: (0,1)
Program received signal SIGSEGV, Segmentation fault.
null_convert (cinfo=<optimized out>, input_buf=0x7fffffffda28, output_buf=0x555555f58ae8, output_row=0, num_rows=<optimized out>) at /src/gdcm-git/Utilities/gdcmjpeg/jccolor.c:326
326 outptr[col] = inptr[ci]; /* don't need GETJSAMPLE() here */
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────────────────────
RAX 0x555555f5dea8 ◂— 0
RBX 0x7fffffffda28 —▸ 0x7ffff7c6bc10 ◂— 0
RCX 0
RDX 0x7ffff7c6c000 ◂— 0
RDI 0x555555f5df58 ◂— 0x221
RSI 6
R8 0
R9 0
R10 0x7ffff7c6bc10 ◂— 0
R11 0x200
R12 0
R13 1
R14 0x555555f58ad8 —▸ 0x5555557ec640 (start_pass_prep) ◂— endbr64
R15 0x7fffffffdba0 —▸ 0x7fffffffda30 —▸ 0x555555716590 (my_error_exit) ◂— endbr64
RBP 0x555555f58ae8 —▸ 0x555555f58b48 —▸ 0x555555f5dd58 ◂— 0
RSP 0x7fffffffd8b8 ◂— 1
RIP 0x5555557ed998 (null_convert+104) ◂— movzx ecx, word ptr [rdx]
─────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────────────────────────
► 0x5555557ed998 <null_convert+104> movzx ecx, word ptr [rdx] ECX, [0x7ffff7c6c000] => 0
0x5555557ed99b <null_convert+107> add rax, 2 RAX => 0x555555f5deaa (0x555555f5dea8 + 0x2)
0x5555557ed99f <null_convert+111> add rdx, rsi RDX => 0x7ffff7c6c006 (0x7ffff7c6c000 + 0x6)
0x5555557ed9a2 <null_convert+114> mov word ptr [rax - 2], cx [0x555555f5dea8] <= 0
0x5555557ed9a6 <null_convert+118> cmp rdi, rax 0x555555f5df58 - 0x555555f5deaa EFLAGS => 0x10212 [ cf pf AF zf sf IF df of ac ]
0x5555557ed9a9 <null_convert+121> ✔ jne null_convert+104 <null_convert+104>
↓
0x5555557ed998 <null_convert+104> movzx ecx, word ptr [rdx] ECX, [0x7ffff7c6c006] => 0
0x5555557ed99b <null_convert+107> add rax, 2 RAX => 0x555555f5deac (0x555555f5deaa + 0x2)
0x5555557ed99f <null_convert+111> add rdx, rsi RDX => 0x7ffff7c6c00c (0x7ffff7c6c006 + 0x6)
0x5555557ed9a2 <null_convert+114> mov word ptr [rax - 2], cx [0x555555f5deaa] <= 0
0x5555557ed9a6 <null_convert+118> cmp rdi, rax 0x555555f5df58 - 0x555555f5deac EFLAGS => 0x10216 [ cf PF AF zf sf IF df of ac ]
───────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────────────────────
In file: /src/gdcm-git/Utilities/gdcmjpeg/jccolor.c:326
321 /* It seems fastest to make a separate pass for each component. */
322 for (ci = 0; ci < nc; ci++) {
323 inptr = *input_buf;
324 outptr = output_buf[ci][output_row];
325 for (col = 0; col < num_cols; col++) {
► 326 outptr[col] = inptr[ci]; /* don't need GETJSAMPLE() here */
327 inptr += nc;
328 }
329 }
330 input_buf++;
331 output_row++;
───────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd8b8 ◂— 1
01:0008│ 0x7fffffffd8c0 ◂— 1
02:0010│ 0x7fffffffd8c8 —▸ 0x7fffffffdba0 —▸ 0x7fffffffda30 —▸ 0x555555716590 (my_error_exit) ◂— endbr64
03:0018│ 0x7fffffffd8d0 —▸ 0x7fffffffda28 —▸ 0x7ffff7c6bc10 ◂— 0
04:0020│ 0x7fffffffd8d8 —▸ 0x5555557ec76c (pre_process_data+220) ◂— mov rax, qword ptr [rsp + 0x10]
05:0028│ 0x7fffffffd8e0 —▸ 0x555555f58ad8 —▸ 0x5555557ec640 (start_pass_prep) ◂— endbr64
06:0030│ 0x7fffffffd8e8 ◂— 0x555500000000
07:0038│ 0x7fffffffd8f0 —▸ 0x7fffffffd9c4 ◂— 0xb3a3ea0000000000
─────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────
► 0 0x5555557ed998 null_convert+104
1 0x5555557ec76c pre_process_data+220
2 0x5555557ec488 process_data_simple_main+120
3 0x5555557e7efb gdcmjpeg16_jpeg_write_scanlines+187
4 0x5555557168a6 gdcm::JPEG16Codec::InternalCode(char const*, unsigned long, std::ostream&)+518
5 0x5555556edeec gdcm::JPEGCodec::Code(gdcm::DataElement const&, gdcm::DataElement&)+1164
6 0x5555556b3f56 gdcm::ImageChangeTransferSyntax::TryJPEGCodec(gdcm::DataElement const&, gdcm::Bitmap const&, gdcm::Bitmap&)+358
7 0x5555556b5a33 gdcm::ImageChangeTransferSyntax::Change()+1747
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
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.