Monday, March 6, 2017

macOS Sierra bug: SCardTransmit() silently truncates the card response

This is the first new PC/SC bug I find in macOS Sierra.
I imagine this bug is also present in El Capitan and Yosemite but I have not checked.

SCardTransmit() returns SCARD_S_SUCCESS when it should return SCARD_E_INSUFFICIENT_BUFFER

SCardTransmit() is used to transfer a command to the smart card and get the smart card answer.

If the reception buffer is not large enough to contain the card answer the PC/SC error SCARD_E_INSUFFICIENT_BUFFER should be returned and the expected size indicated in the pcbRecvLength parameter.

Instead, on macOS Sierra, SCARD_S_SUCCESS is returned and the card response is truncated with no indication that something went wrong.

See also

Apple bug report #30868184 "PC/SC SCardTransmit() silently truncates the smart card response".

Sample code

#include <stdio.h>
#include <string.h>

#ifdef __APPLE__
#include <PCSC/winscard.h>
#include <PCSC/wintypes.h>
#else
#include <winscard.h>
#endif

#define CHECK_RV(fct) if (SCARD_S_SUCCESS != rv) { printf(fct"() failed: %s\n", pcsc_stringify_error(rv)); ret = 0; goto error; } else { printf(fct"(): OK\n"); }

int main(void)
{
    int ret = 1;
    SCARDCONTEXT hContext;
    SCARDHANDLE hCard;
    DWORD dwActiveProtocol;
    LONG rv;
    char mszReaders[1024];
    DWORD dwReaders = sizeof(mszReaders);
    SCARD_IO_REQUEST ioRecvPci = *SCARD_PCI_T0; /* use a default value */
    unsigned char bSendBuffer[MAX_BUFFER_SIZE];
    unsigned char bRecvBuffer[MAX_BUFFER_SIZE];
    DWORD send_length, length;

    rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
    CHECK_RV("SCardEstablishContext");

    rv = SCardListReaders(hContext, NULL, mszReaders, &dwReaders);
    CHECK_RV("SCardListReaders");

    rv = SCardConnect(hContext, mszReaders, SCARD_SHARE_SHARED,
        SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard,
        &dwActiveProtocol);
    CHECK_RV("SCardConnect");

    send_length = 5;
    /* GET RANDOM for a GPK card */
    memcpy(bSendBuffer, "\x80\x84\x00\x00\x20", send_length);

    /* expected size is 0x20 + 2 = 34 bytes */
    length = 2;
    printf("Given length: %d\n", length);
    rv = SCardTransmit(hCard, SCARD_PCI_T0, bSendBuffer, send_length,
        &ioRecvPci, bRecvBuffer, &length);
    printf("Expected length: %d\n", length);
    if (SCARD_E_INSUFFICIENT_BUFFER == rv)
        printf("test PASS. Insufficient buffer is expected\n");
    else
        printf("test FAIL\n");
    CHECK_RV("SCardTransmit");
    if (SCARD_S_SUCCESS == rv)
    {
        size_t i;

        for (i=0; i<length; i++)
            printf("%02X ", bRecvBuffer[i]);
        printf("\n");
    }

    rv = SCardDisconnect(hCard, SCARD_UNPOWER_CARD);
    CHECK_RV("SCardDisconnect")

    rv = SCardReleaseContext(hContext);
    CHECK_RV("SCardReleaseContext")

error:
    return ret;
}

The program sends a GET RANDOM command to a GPK card (very old card from Gemplus). The card will answer with 32 random bytes + 2 bytes for SW.

You can use any command that sends at least one bye of data instead.

Result (on Sierra)

$ cc -framework PCSC -g -Wall main.c -o main

SCardEstablishContext(): OK
SCardListReaders(): OK
SCardConnect(): OK
Given length: 2
Expected length: 2
test FAIL
SCardTransmit(): OK
5B 8F 
SCardDisconnect(): OK
SCardReleaseContext(): OK

The value "5B 8F" is just the 2 first bytes returned by the card. The other 30 bytes and the status word (SW) are lost.

I note that if I use the values 0 or 1 for length then SCardTransmit() correctly returns SCARD_E_INSUFFICIENT_BUFFER. So the Sierra code has a check to reject a buffer of smaller than 2 bytes. The code should check the given size compared to the real card answer.

Expected result (on Debian)

$ CFLAGS=`pkg-config --cflags libpcsclite` LDFLAGS=`pkg-config --libs libpcsclite` make main
cc -pthread -I/usr/include/PCSC   -lpcsclite   main.c   -o main

SCardEstablishContext(): OK
SCardListReaders(): OK
SCardConnect(): OK
Given length: 2
Expected length: 34
test PASS. Insufficient buffer is expected
SCardTransmit() failed: Insufficient buffer.

On Debian we get the expected SCARD_E_INSUFFICIENT_BUFFER error. And we also get the correct length value to store the complete card answer. Here 34 bytes.

Known workaround

Be sure your reception buffer is large enough to contain the card answer + 2 bytes for SW.

This should be the case for all correctly written application. That explains why nobody found the bug earlier.

I found the problem while playing with a particular PC/SC Unitary Test (for pcsc-lite) on macOS: BufferOverflow_SCardTransmit.c