X41 D-Sec GmbH Security Advisory: X41-2018-002

Multiple Vulnerabilities in OpenSC

Overview

Confirmed Affected Versions: 0.17.0

Confirmed Patched Versions: -

Vendor: OpenSC

Vendor URL: https://github.com/OpenSC/OpenSC

Credit: X41 D-Sec GmbH, Eric Sesterhenn

Status: Public

Advisory-URL: https://www.x41-dsec.de/lab/advisories/x41-2018-002-OpenSC/

Summary and Impact

Multiple issues have been identified in OpenSC, ranging from stack based buffer overflows to out of bounds reads and writes on the heap. They can be triggered by malicious smartcards sending malformed responses to APDU commands. Additionally to those fixes reported here, a lot of minor issues (eg. OOB reads and similar) have been reported and fixed. The OpenSC team (especially Frank Morgner) did an excellent job on identifying and fixing further issues.

Due to the large amount of issues, no individual issues have been rated with CVSS / CVE ID yet.

X41 did not perform a full test or audit on the software, but tried to help identifying as many bugs as possible in over the course of a year.

Product Description

OpenSC provides a set of libraries and utilities to work with smart cards. Its main focus is on cards that support cryptographic operations, and facilitate their use in security applications such as authentication, mail encryption and digital signatures.

OOB Write in muscle_list_files()

In funcion muscle_list_files() in file src/libopensc/card-muscle.c an out of bounds write might occur, since bufLen is not checked.

static int muscle_list_files(sc_card_t *card, u8 *buf, size_t bufLen)
{
        muscle_private_t* priv = MUSCLE_DATA(card);
        mscfs_t *fs = priv->fs;
        int x;
        int count = 0;

        mscfs_check_cache(priv->fs);

        for(x = 0; x < fs->cache.size; x++) {
                u8* oid= fs->cache.array[x].objectId.id;
                sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,
                        "FILE: %02X%02X%02X%02X\n",
                        oid[0],oid[1],oid[2],oid[3]);
                if(0 == memcmp(fs->currentPath, oid, 2)) {
                        buf[0] = oid[2];
                        buf[1] = oid[3];
                        if(buf[0] == 0x00 && buf[1] == 0x00) continue; /* No directories/null names outside of root */
                        buf += 2;
                        count+=2;
                }
        }
        return count;
}

OOB Write in tcos_select_file()

In function tcos_select_file) in file src/libopensc/card-tcos.c a filename is extracted from an APDU response and written into the internal file->name variable.

                case 0x84:
                        memcpy(file->name, d, len);
                        file->namelen = len;
                        break;

No check is performed whether the string retrieved from the card fits into the buffer, which could trigger an OOB write.

OOB Write in piv_validate_general_authentication()

In case piv_validate_general_authentication()in src/libopensc/card-piv.c is called with a datalen parameter greater than 4096, an out of bound write occurs. Currently no caller seems to do this.

OOB Write in gemsafe_get_cert_len()

The function gemsafe_get_cert_len() in file src/libopensc/pkcs15-gemsafeV1.c might write beyond the gemsafe_prkeys and gemsafe_cert arrays in case more than 12 containers are stored on the card.

        ind = 2; /* skip length */
        while (ibuf[ind] == 0x01) {
                if (ibuf[ind+1] == 0xFE) {
                        gemsafe_prkeys[i].ref = ibuf[ind+4];
                        sc_log(card->ctx, "Key container %d is allocated and uses key_ref %d",
                                        i+1, gemsafe_prkeys[i].ref);
                        ind += 9;
                }
                else {
                        gemsafe_prkeys[i].label = NULL;
                        gemsafe_cert[i].label = NULL;
                        sc_log(card->ctx, "Key container %d is unallocated", i+1);
                        ind += 8;
                }
                i++;
        }

OOB Write in util_acl_to_str()

In function util_acl_to_str() in file src/tools/util.c no checks are performed whether the string put together fits into line, which could be abused to trigger limited out of bounds writes.

OOB Write in read_public_key() and read_private_key()

In function read_public_key() in file src/tools/cryptoflex-tool.c the bufsize variable is overwritten with file->size retrieved from the smartcard. This could be bigger than 2048, allowing for a stack based buffer overflow in the sc_read_binary() call.

        u8 buf[2048], *p = buf;
        size_t bufsize, keysize;

        r = select_app_df();
        if (r)
                return 1;
        sc_format_path("I1012", &path);
        r = sc_select_file(card, &path, &file);
        if (r) {
                fprintf(stderr, "Unable to select public key file: %s\n", sc_strerror(r));
                return 2;
        }
        bufsize = file->size;
        sc_file_free(file);
        r = sc_read_binary(card, 0, buf, bufsize, 0);

The same issue can be found in read_private_key()

        bufsize = file->size;                                                   
        sc_file_free(file);                                                     
        r = sc_read_binary(card, 0, buf, bufsize, 0); 

OOB Write in decrypt_response()

In function decrypt_response() in file src/libopensc/card-epass2003.c an out of bounds overwrite can occur. No check is performed if the plaintext buffer fits into the out buffer before copying, leading to a memory overwrite.

        memcpy(out, plaintext, in_len - 2);                                     
        *out_len = in_len - 2;                                                  
        return 0;   

OOB Write in cac_get_serial_nr_from_CUID()

In function cac_get_serial_nr_from_CUID() in file src/libopensc/card-cac.c a serial number is copied into serial->value. The length argument of the memcpy() is the length of the source, not the destination, which can lead to an out of bounds memory write.

        if (priv->cac_id_len) {
                serial->len = MIN(priv->cac_id_len, SC_MAX_SERIALNR);
                memcpy(serial->value, priv->cac_id, priv->cac_id_len);
                SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, SC_SUCCESS);    
        }

Off by One Write in sc_pkcs15emu_esteid_init()

In function sc_pkcs15emu_esteid_init() in file src/libopensc/pkcs15-esteid.c an off by one write with a \x00 occurs in case the sc_read_record() functions returns sizeof(buf) read bytes.

        /* read the serial (document number) */
        r = sc_read_record (card, SC_ESTEID_PD_DOCUMENT_NR, buff, sizeof(buff), SC_RECORD_BY_REC_NR);
        SC_TEST_RET(card->ctx, SC_LOG_DEBUG_NORMAL, r, "read document number failed");
        buff[r] = '\0';

Double Free in sc_file_set_sec_attr()

In function sc_file_set_sec_attr() in file src/libopensc/sc.c a double free occurs in case sec_attr_len is equal to 0, since the call to realloc() will free file->sec_attr and return NULL. The variable is then freed again in the error handling path.

        tmp = (u8 *) realloc(file->sec_attr, sec_attr_len);
        if (!tmp) {
                if (file->sec_attr)
                        free(file->sec_attr);
                file->sec_attr     = NULL;
                file->sec_attr_len = 0;
                return SC_ERROR_OUT_OF_MEMORY;
        }

Double Free in read_file()

In function read_file() in file src/tools/egk-tool.c a double free can be triggered in case two calls to sc_select_file() return a file->size of zero. The first call to realloc() frees the memory, the second frees it again.

        len = file ? file->size : 4096;
        p = realloc(*data, len);
        if (!p) {
                goto err;
        }

Double free in sc_pkcs15emu_sc_hsm_init()

In function sc_pkcs15emu_sc_hsm_init() in file src/libopensc/pkcs15-sc-hsm.c a double free can occur, since this function can be called twice. The call to realloc() with a size of 0 would free priv->EF_C_DevAut with a second call freeing the already freed memory.

                /* save EF_C_DevAut for further use */
                ptr = realloc(priv->EF_C_DevAut, len);
                if (ptr) {
                        memcpy(ptr, efbin, len);
                        priv->EF_C_DevAut = ptr;
                        priv->EF_C_DevAut_len = len;
                }

                ptr = efbin;

Unbound Recursion in iasecc_select_mf()/iasecc_select_file()

Function iasecc_select_file() in file src/libopensc/card-iasecc.c calls iasecc_select_mf() in the same file, which calls iasecc_select_file() again. This can lead to an infinite recursion exhausting the stack.

Timeline

2018-02-03 Issues found

2018-04-18 Vendor contacted

2018-04-18 Vendor reply

2018-05-18 Technical details provided

2018-05-24 Private git branch created, fixing started

2018-08-11 Patched version released: https://github.com/x41sec/OpenSC

Author: Eric Sesterhenn
Date: February 03, 2018