OS X Yosemite bug: SCardConnect blocks in SCARD_SHARE_SHARED mode

This is part of the series: "OS X Yosemite and smart cards: known bugs".

SCardConnect(..., SCARD_SHARE_SHARED, ...)

SCardConnect() do not work correctly on Yosemite in a multi application context.

SCardConnect(..., SCARD_SHARE_SHARED, ...) will block its execution until SCardDisconnect() is called in the other application using the card or the card is removed.

The SCARD_SHARE_SHARED flag indicates that the connection shall be shared by different applications. Different application should be able to use the card at the same time. An application can use SCardBeginTransaction()/SCardEndTransaction() to get a temporary exclusive access to the card.

This can be really problematic if an application is not correctly written and SCardDisconnect() is not called. The concurrent application would be blocked forever on SCardConnect().

When SCardConnect() is unblocked by a card removal it will return the error code SCARD_E_NO_SMARTCARD.

This bug is present in Yosemite version 10.10.4. I have not verified if the bug is also present in previous Yosemite versions. Maybe it is a bug introduced in 10.10.4?

See also

Apple bug report #21703315 "PC/SC SCardConnect() blocks in SCARD_SHARE_SHARED mode"

Sample code

Thanks to Mounir for the initial sample code.

The sample application does:
  1. wait for a card insertion
  2. SCardConnect() to the card
  3. sleep for 3 seconds
  4. SCardDisconnect() from the card
  5. go to step 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#ifdef __APPLE__
#include <PCSC/winscard.h>
#include <PCSC/wintypes.h>
#else
#include <winscard.h>
#endif

SCARDCONTEXT hContext;
SCARD_READERSTATE state;

#define CHECK_ERROR(text) \
    if (err != SCARD_S_SUCCESS) \
        printf("\033[0;31m" text ": %s (0x%08x)\033[00m\n",pcsc_stringify_error(err),err); \
    else \
        timed_log(text ": OK\n");

#define CHECK_EXIT(text) \
    CHECK_ERROR(text) \
    if (err != SCARD_S_SUCCESS) return -1;

static void timed_log(const char *msg)
{
    static struct timeval old_tp;
    struct timeval tp, r;

    gettimeofday(&tp, NULL);

    r.tv_sec = tp.tv_sec - old_tp.tv_sec;
    r.tv_usec = tp.tv_usec - old_tp.tv_usec;
    if (r.tv_usec < 0)
    {
        r.tv_sec--;
        r.tv_usec += 1000000;
    }

    printf("%ld.%.6d %s", r.tv_sec, r.tv_usec, msg);
    old_tp = tp;
}

static int WaitForCardEvent(void)
{
    int insert = 0;

    DWORD err;
    while (1)
    {
        timed_log("Waiting for event...\n");
        err = SCardGetStatusChange(hContext, INFINITE, &state, 1);
        CHECK_EXIT("SCardGetStatusChange")

        timed_log("event detected\n");
        state.dwCurrentState = state.dwEventState;

        if (state.dwEventState & SCARD_STATE_PRESENT)
        {
            if (! (state.dwEventState & SCARD_STATE_MUTE))
            {
                timed_log("card inserted\n");
                if (insert)
                    return 1;
            }
            else
                timed_log("card is mute\n");
        }
        else
        {
            timed_log("card removed\n");
            insert = 1;
        }
    }

    return 0;
}

static int UseCard(const char *mszReaders)
{
    DWORD dwActiveProtocol;
    SCARDHANDLE hCard = 0;

    timed_log("calling SCardConnect\n");
    DWORD err = SCardConnect(hContext, mszReaders, SCARD_SHARE_SHARED,
        SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol);
    CHECK_EXIT("SCardConnect")
    timed_log("connected\n");

    sleep(3);

#if 1
    timed_log("calling SCardDisconnect\n");
    err = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
    CHECK_ERROR("SCardDisconnect")
#endif

    return 1;
}

int main(void)
{
    LPSTR mszReaders;
    DWORD err, cchReaders;

    err = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
    CHECK_EXIT("SCardEstablishContext")
    cchReaders = 0;

    err = SCardListReaders(hContext, NULL, NULL, &cchReaders);
    CHECK_EXIT("SCardListReaders")
    mszReaders = calloc(cchReaders, sizeof(char));
    if (!mszReaders)
    {
        printf("calloc\n");
        return -1;
    }
    err = SCardListReaders(hContext, NULL, mszReaders, &cchReaders);
    CHECK_EXIT("SCardListReaders")

    printf("Using Reader: %s\n", mszReaders);

    memset(&state, 0, sizeof state);
    state.szReader = mszReaders;
    err = SCardGetStatusChange(hContext, 0, &state, 1);
    CHECK_EXIT("SCardGetStatusChange")

    while (1)
    {
        WaitForCardEvent();

        UseCard(mszReaders);
    }

    SCardReleaseContext(hContext);


    return 0;
}

You need to open 2 terminal windows and run the application concurrently in the 2 terminals.

Result (on Yosemite)

$ CFLAGS="-framework PCSC" make main
cc -framework PCSC main.c -o main

Terminal 1

$ ./main
1436274783.304010 SCardEstablishContext: OK
0.000163 SCardListReaders: OK
0.000108 SCardListReaders: OK
Using Reader: Dell Dell Smart Card Reader Keyboard
0.000971 SCardGetStatusChange: OK
0.000004 Waiting for event...
0.000540 SCardGetStatusChange: OK
0.000005 event detected
0.000001 card removed
0.000001 Waiting for event...

42.709172 SCardGetStatusChange: OK
0.000010 event detected
0.000002 card inserted
0.000001 calling SCardConnect
0.042665 SCardConnect: OK
0.000010 connected
3.000132 calling SCardDisconnect
0.000339 SCardDisconnect: OK
0.000006 Waiting for event...
0.000825 SCardGetStatusChange: OK
0.000006 event detected
0.000001 card inserted
0.000001 Waiting for event...
3.001277 SCardGetStatusChange: OK
0.000008 event detected
0.000001 card inserted
0.000001 Waiting for event...

Terminal 2

$ ./main
1436274812.713724 SCardEstablishContext: OK
0.000145 SCardListReaders: OK
0.000091 SCardListReaders: OK
Using Reader: Dell Dell Smart Card Reader Keyboard
0.001038 SCardGetStatusChange: OK
0.000005 Waiting for event...
0.000717 SCardGetStatusChange: OK
0.000006 event detected
0.000001 card removed
0.000001 Waiting for event...

13.299278 SCardGetStatusChange: OK
0.000007 event detected
0.000001 card inserted
0.000001 calling SCardConnect
3.043707 SCardConnect: OK
0.000014 connected
3.000740 calling SCardDisconnect
0.000396 SCardDisconnect: OK
0.000007 Waiting for event...

Description

I added a newline in the traces just before the card insertion. The number in front of each line is the time that passed since the previous log line. So if you see "1.23 foo" then "foo" happened 1.23 second after the previous log.

In this execution the application in terminal 1 got the card connection in the first place. You can see that SCardConnect() returns after 0.042665 seconds. The state was "connected" for 3.000132 seconds. Then SCardDisconnect() is called.

In terminal 2 we note that SCardConnect() returns after 3.043707 seconds. The application is blocked during the 3 seconds used by the the first application.

Also note that in terminal 1 SCardGetStatusChange() returns after 3.001277 seconds. This is because the card was used by the application in terminal 2 and the card state changed. The bit SCARD_STATE_INUSE changed from 1 (card in use) to 0 (card not used).

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

Terminal 1

$ ./main1436274912.852172 SCardEstablishContext: OK
0.000144 SCardListReaders: OK
0.000134 SCardListReaders: OK
Using Reader: Gemalto PC Twin Reader (70D7E2EE) 00 00
0.000138 SCardGetStatusChange: OK
0.000012 Waiting for event...
0.000089 SCardGetStatusChange: OK
0.000020 event detected
0.000003 card removed
0.000002 Waiting for event...

11.780610 SCardGetStatusChange: OK
0.000014 event detected
0.000002 card inserted
0.000002 calling SCardConnect
0.033486 SCardConnect: OK
0.000013 connected
3.000073 calling SCardDisconnect
0.000125 SCardDisconnect: OK
0.000016 Waiting for event...

Terminal 2

$ ./main 
1436274916.659394 SCardEstablishContext: OK
0.000163 SCardListReaders: OK
0.000149 SCardListReaders: OK
Using Reader: Gemalto PC Twin Reader (70D7E2EE) 00 00
0.000144 SCardGetStatusChange: OK
0.000033 Waiting for event...
0.000108 SCardGetStatusChange: OK
0.000023 event detected
0.000019 card removed
0.000007 Waiting for event...

7.973306 SCardGetStatusChange: OK
0.000025 event detected
0.000009 card inserted
0.000007 calling SCardConnect
0.033451 SCardConnect: OK
0.000028 connected
3.000083 calling SCardDisconnect
0.000215 SCardDisconnect: OK
0.000025 Waiting for event...

Description

On Linux you can see that the 2 applications run at the same time concurrently. No application is blocked by the other.

Known workaround

None known.

Some ideas that should help:
  • Do not forget to call SCardDisconnect() when you no longer use the card, or another application may be blocked for a long time.
  • Connect to a card for short periods of time if possible. Another application would then get a chance to access the card.

Update

This bug is now fixed in Mac OS X El Capitan 10.11.0.