Talos Vulnerability Report

TALOS-2024-1931

Imaging Data Commons libdicom DICOM File Meta Information Parsing Use-After-Free vulnerabilities

February 20, 2024
CVE Number

CVE-2024-24793,CVE-2024-24794

SUMMARY

A use-after-free vulnerability exists in the DICOM Element Parsing as implemented in Imaging Data Commons libdicom 1.0.5. A specially crafted DICOM file can cause premature freeing of memory that is used later. To trigger this vulnerability, an attacker would need to induce the vulnerable application to process a malicious DICOM image.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Imaging Data Commons libdicom 1.0.5

PRODUCT URLS

libdicom - https://github.com/ImagingDataCommons/libdicom

CVSSv3 SCORE

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

CWE

CWE-416 - Use After Free

DETAILS

Each DICOM file includes a File Meta Information header and a Data Set. The File Meta information header holds information about the Data Set and the Data Set itself holds the actual image data, patient information, etc. The File Meta Information header consists of a short preamble of 128 bytes of zeroes, followed by the DCOM magic header and finally a series of File Meta Elements. Each elements has an identifying tag (for example version tag, media storage tag etc.) along with the corresponding data in a typical Type-Length-Value representation.

In libdicom the following code pattern can be seen:

    static bool parse_meta_element_create(DcmError **error, void *client, uint32_t tag,
                                          DcmVR vr, char *value, uint32_t length)
    {
        ...
        DcmElement *element = dcm_element_create(error, tag, vr);                    (1)

        if (!dcm_element_set_value(error, element, value, length, false) ||
            !dcm_dataset_insert(error, dataset, element)) {                          (2)
            dcm_element_destroy(element);                                            (3)
            return false;
        }

        return true;
    }

At (1) the code allocates a new element for the sequence, taking a tag as input. At (2) it calls dcm_dataset_insert() in order to insert the element in the current dataset. If the operation fails, the element is freed at (3) calling dcm_element_destroy().

In dcm_dataset_insert() the code checks if the dataset already contains the tag of the element and if it does then dcm_element_destroy() is called deallocating the object.

bool dcm_dataset_insert(DcmError **error,
                        DcmDataSet *dataset, DcmElement *element)
{
	...
    DcmElement *matched_element = dcm_dataset_contains(dataset, element->tag);     (4)
	
    if (matched_element) {
        dcm_error_set(error, DCM_ERROR_CODE_INVALID,
                      "Element already exists",
                      "Inserting Data Element '%08x' into Data Set failed",
                      element->tag);
        dcm_element_destroy(element);                                              (5)
		return false;                                                              (6)
    }
	...
}

Note however at (6) the code proceeds to return false and as a result, in parse_meta_element_create() the code will call dcm_element_destroy() again at (3) deallocating the element.

In dcm_element_destroy() we see a standard set of operations deallocating memory for the object’s pointers and finally deallocating the object itself.

void dcm_element_destroy(DcmElement *element)
{
    if (element) {
        if(element->sequence_pointer) {
            dcm_sequence_destroy(element->sequence_pointer);
        }
        if(element->value_pointer) {
            free(element->value_pointer);
        }
        if(element->value_pointer_array) {
            dcm_free_string_array(element->value_pointer_array, element->vm);
        }
        free(element);
    }
}

As a result, when the applications parses a DICOM file that holds two elements with the same tag, a Use-After-Free scenario can be induced allowing compromise of the system.

CVE-2024-24793 - Element Create

libdicom parses the elements in the File Meta Information header using the parse_meta_element_create() function.

    static bool parse_meta_element_create(DcmError **error, void *client, uint32_t tag,
                                          DcmVR vr, char *value, uint32_t length)
    {
        ...
        DcmElement *element = dcm_element_create(error, tag, vr);                    (1)

        if (!dcm_element_set_value(error, element, value, length, false) ||
            !dcm_dataset_insert(error, dataset, element)) {                          (2)
            dcm_element_destroy(element);                                            (3)
            return false;
        }

        return true;
    }

The code follows the pattern above and as a result, when the applications parses a DICOM file that holds two elements with the same tag in the File Meta Information header, a use-after-free scenario can be induced which can lead to further memory corruption and possible arbitrary code execution.

Crash Information

Building the library with ASAN yields the following report upon processing the PoC:

    ==6442==ERROR: AddressSanitizer: heap-use-after-free on address 0x50b000000510 at pc 0x7fda51073114 bp 0x7ffc360d4f60 sp 0x7ffc360d4f58
        READ of size 4 at 0x50b000000510 thread T0
            #0 0x7fda51073113 in dcm_element_destroy /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-data.c:195:64
            #1 0x7fda5108ec8a in parse_meta_element_create /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-file.c:478:9
            #2 0x7fda51099a3b in parse_element_body /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-parse.c:632:18
            #3 0x7fda51095e6b in dcm_parse_group /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-parse.c:848:14
            #4 0x7fda5108c66a in dcm_filehandle_read_file_meta /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-file.c:540:10
            #5 0x7fda5108c66a in dcm_filehandle_get_file_meta /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-file.c:581:13
            #6 0x7fda5149d13d in dicom_file_new /home/dtatsis/openslide-fuzz/openslide/build.afl/../src/openslide-vendor-dicom.c:336:18
            #7 0x7fda5149bf9b in dicom_detect /home/dtatsis/openslide-fuzz/openslide/build.afl/../src/openslide-vendor-dicom.c:566:29
            #8 0x7fda51436575 in detect_format /home/dtatsis/openslide-fuzz/openslide/build.afl/../src/openslide.c:139:9
            #9 0x7fda51436ac8 in openslide_open /home/dtatsis/openslide-fuzz/openslide/build.afl/../src/openslide.c:209:44
            #10 0x56102f43d4c0 in main /home/dtatsis/openslide-fuzz/harness.c:59:23
            #11 0x7fda510d2d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
            #12 0x7fda510d2e3f in __libc_start_main csu/../csu/libc-start.c:392:3
            #13 0x56102f360674 in _start (/home/dtatsis/openslide-fuzz/openslide_fuzz+0x68674) (BuildId: 554b359a6a8febbb)

    0x50b000000510 is located 0 bytes inside of 112-byte region [0x50b000000510,0x50b000000580)
    freed by thread T0 here:
        #0 0x56102f3fc776 in free (/home/dtatsis/openslide-fuzz/openslide_fuzz+0x104776) (BuildId: 554b359a6a8febbb)
        #1 0x7fda5107e043 in dcm_dataset_insert /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-data.c:1438:9

    previously allocated by thread T0 here:
        #0 0x56102f3fcc08 in calloc (/home/dtatsis/openslide-fuzz/openslide_fuzz+0x104c08) (BuildId: 554b359a6a8febbb)
        #1 0x7fda5106ee93 in dcm_calloc /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom.c:45:20
        
    SUMMARY: AddressSanitizer: heap-use-after-free /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-data.c:195:64 in dcm_element_destroy

In a regular build of the library, parsing the PoC leads to a crash:

    $ ./dcm-getframe -V ../../../../triage/id:000000,sig:06,src:000007,time:4502,execs:889,op:havoc,rep:1 0
        INFO     [Tue Jan  9 14:49:41 2024] - Read filehandle '../../../../triage/id:000000,sig:06,src:000007,time:4502,execs:889,op:havoc,rep:1'
        INFO     [Tue Jan  9 14:49:41 2024] - Read frame 0
        free(): double free detected in tcache 2
        [1]    4503 IOT instruction (core dumped)  ./dcm-getframe -V  0

CVE-2024-24794 - Sequence End

An element with a tag that supports the Sequence of Items representation will include all its data as a sequence of zero or more items. When libdicom is done parsing the sequence it enters parse_meta_sequence_end().

static bool parse_meta_sequence_end(DcmError **error,
                                    void *client,
                                    uint32_t tag,
                                    DcmVR vr,
                                    uint32_t length)
{
    DcmElement *element = dcm_element_create(error, tag, vr);        (1)
	...
    if (!dcm_dataset_insert(error, dataset, element)) {              (2)
        dcm_element_destroy(element);                                (3)
        return false;
	}
}

The code follows the pattern above and as a result, when the applications parses a DICOM file that holds two Sequence elements with the same tag, a Use-After-Free scenario can be induced which can lead to further memory corruption and possible arbitrary code execution.

Crash Information

Building the library with ASAN yields the following report upon processing the PoC:

==35094==ERROR: AddressSanitizer: heap-use-after-free on address 0x50b0000001a0 at pc 0x7f366505f114 bp 0x7fffa656cdc0 sp 0x7fffa656cdb8
READ of size 4 at 0x50b0000001a0 thread T0
    #0 0x7f366505f113 in dcm_element_destroy /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-data.c:195:64
    #1 0x7f366507a9e9 in parse_meta_sequence_end /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-file.c
    #2 0x7f3665085219 in parse_element_sequence /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-parse.c:418:10
    #3 0x7f3665085219 in parse_element_body /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-parse.c:662:18
    #4 0x7f3665080f88 in parse_toplevel_dataset /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-parse.c:741:14
    #5 0x7f3665080f88 in dcm_parse_dataset /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-parse.c:773:10
    #6 0x7f36650793bd in dcm_filehandle_read_metadata /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-file.c:769:10
    #7 0x7f366507b014 in dcm_filehandle_get_metadata_subset /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-file.c:827:28
    #8 0x7f366507c69b in dcm_filehandle_prepare_read_frame /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-file.c:1151:13
    #9 0x7f366507d864 in dcm_filehandle_read_frame /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-file.c:1269:10
    #10 0x562db8051370 in main /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/tools/dcm-getframe.c:69:23
    #11 0x7f3664cb0d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #12 0x7f3664cb0e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #13 0x562db7f78224 in _start (/home/dtatsis/openslide-fuzz/openslide/build.afl/subprojects/libdicom-1.0.5/dcm-getframe+0x69224) (BuildId: 8070d34a9894aa2a)

0x50b0000001a0 is located 0 bytes inside of 112-byte region [0x50b0000001a0,0x50b000000210)
freed by thread T0 here:
    #0 0x562db8014326 in free (/home/dtatsis/openslide-fuzz/openslide/build.afl/subprojects/libdicom-1.0.5/dcm-getframe+0x105326) (BuildId: 8070d34a9894aa2a)
    #1 0x7f366506a043 in dcm_dataset_insert /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-data.c:1438:9

previously allocated by thread T0 here:
    #0 0x562db80147b8 in calloc (/home/dtatsis/openslide-fuzz/openslide/build.afl/subprojects/libdicom-1.0.5/dcm-getframe+0x1057b8) (BuildId: 8070d34a9894aa2a)
    #1 0x7f366505ae93 in dcm_calloc /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom.c:45:20

SUMMARY: AddressSanitizer: heap-use-after-free /home/dtatsis/openslide-fuzz/openslide/build.afl/../subprojects/libdicom-1.0.5/src/dicom-data.c:195:64 in dcm_element_destroy

In a regular build of the library, parsing the PoC leads to a crash:

$ ./dcm-getframe -V ../../../../triage/seq_end/seq.dcom 0
INFO     [Thu Jan 18 19:47:27 2024] - Read filehandle '../../../../triage/seq_end/seq.dcom'
INFO     [Thu Jan 18 19:47:27 2024] - Read frame 0
[1]    36993 segmentation fault (core dumped)  ./dcm-getframe -V ../../../../triage/seq_end/seq.dcom 0
VENDOR RESPONSE

The vendor has issued a fix on Github.com

TIMELINE

2024-02-01 - Vendor Disclosure
2024-02-01 - Initial Vendor Contact
2024-02-02 - Vendor Patch Release
2024-02-20 - Public Release

Credit

Discovered by Dimitrios Tatsis of Cisco Talos.