Skip to content

Instantly share code, notes, and snippets.

@yonran
Created November 11, 2014 17:43
Show Gist options
  • Save yonran/eda700d8802e209473cd to your computer and use it in GitHub Desktop.
Save yonran/eda700d8802e209473cd to your computer and use it in GitHub Desktop.
OSX 10.10 Yosemite SCardStatus bug test
// clang -framework PCSC scardstatus_bug.c -o scardstatus_bug
// Tests SCardStatus bug in OSX 10.10 Yosemite.
// Connects to a smart card and calls SCardStatus.
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <PCSC/wintypes.h>
#include <PCSC/winscard.h>
#define TEXT(x) x
typedef char TCHAR;
static LPCTSTR SCARD_ALL_READERS = TEXT("SCard$AllReaders\000");
void logError(const char *where, DWORD scardError) {
fprintf(stderr, "Error at %s: 0x%x\n", where, scardError);
exit(1);
}
int main(int argc, const char * argv[]) {
BOOL workaroundSCardStatus = 0;
for (int i = 1; i < argc; i++) {
if (0 == strcmp("--workaround-scardstatus", argv[i])) {
workaroundSCardStatus = 1;
}
}
printf("--workaround-scardstatus is%s specified\n", workaroundSCardStatus?"":" NOT");
SCARDCONTEXT hContext;
DWORD err = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
if (err != 0) {
logError("SCardEstablishContext", err);
} else {
DWORD cchReaders = 0;
err = SCardListReaders(hContext, SCARD_ALL_READERS, NULL, &cchReaders);
if (err != 0) {
logError("SCardListReaders", err);
} else {
LPTSTR mszReaders = calloc(cchReaders, sizeof(TCHAR));
if (mszReaders == NULL) {
perror("calloc for SCardListReaders");
} else {
err = SCardListReaders(hContext, SCARD_ALL_READERS, mszReaders, &cchReaders);
if (err != 0) {
logError("SCardListReaders", err);
} else if ('\0' == *mszReaders) {
printf("There are no smart card readers. Please plug one in.\n");
} else {
DWORD cReaders = 0;
for (LPTSTR readerName = mszReaders; *readerName != '\0'; readerName += strlen(readerName) + 1) {
printf("Found reader %s\n", readerName);
cReaders++;
}
LPTSTR szChosenReader = mszReaders;
SCARD_READERSTATE readerState;
memset(&readerState, 0, sizeof(SCARD_READERSTATE));
readerState.szReader = szChosenReader;
readerState.dwCurrentState = SCARD_STATE_UNAWARE;
err = SCardGetStatusChange(hContext, 0, &readerState, 1);
if (err != 0) {
logError("SCardGetStatusChange", err);
} else if (! (SCARD_STATE_PRESENT & readerState.dwEventState)) {
printf("Card is not present in %s. Please insert smart card.\n", szChosenReader);
} else {
SCARDHANDLE hCard;
DWORD dwActiveProtocol;
err = SCardConnect(hContext, szChosenReader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_ANY, &hCard, &dwActiveProtocol);
if (err != 0) {
logError("SCardConnect", err);
} else {
DWORD chReaderLen;
err = SCardStatus(hCard, NULL, &chReaderLen, NULL, NULL, NULL, NULL);
if (err != 0) {
logError("SCardStatus without buf", err);
} else {
if (workaroundSCardStatus)
chReaderLen++;
LPTSTR mszReaderNames = calloc(chReaderLen, sizeof(TCHAR));
if (NULL == mszReaderNames) {
perror("calloc for SCardStatus");
} else {
DWORD dwState;
DWORD dwProtocol;
BYTE atr[32];
DWORD cbAtrLen = 32;
err = SCardStatus(hCard, mszReaderNames, &chReaderLen, &dwState, &dwProtocol, atr, &cbAtrLen);
if (err != 0) {
logError("SCardStatus with buf", err);
} else {
printf("State: %d, protocol: %d, ATR length: %d\n", dwState, dwProtocol, cbAtrLen);
BOOL gotMultiStringEnd = 0;
for (DWORD i = 0; i < chReaderLen; i += strlen(mszReaderNames + i) + 1) {
LPTSTR readerName = mszReaderNames + i;
if ('\0' == *readerName) {
gotMultiStringEnd = 1;
break;
}
printf("Name of reader: %s\n", readerName);
}
if (! gotMultiStringEnd) {
fprintf(stderr, "SCardStatus reader names multistring did not end with empty string!\n");
}
}
free(mszReaderNames);
}
}
err = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
if (err != 0) {
logError("SCardDisconnect", err);
}
}
}
}
free(mszReaders);
}
}
}
err = SCardReleaseContext(hContext);
if (err != 0) logError("SCardReleaseContext", err);
return 0;
}
@yonran
Copy link
Author

yonran commented Nov 11, 2014

I have filed bug 18941693 with bugreport.apple.com. Since there is no external link, here is a copy of my bug report:

Summary:
In the WinSCard API, When supplying pcchReaderLen to SCardStatus, one is supposed to call SCardStatus twice, first to get the length and second to copy the multistring value into szReaderName. In OSX 10.10 Yosemite. there are 2 bugs:

  1. When the buffer is NULL, SCardStatus writes out a length that only includes the strlen of the name. It does give enough space for the NUL terminator and multistring NUL!
  2. When the buffer is non-NULL, the reader name is written to the buffer, but only a single NUL character is written! (Multistrings should be terminated by an empty string).

Sample C code that fails:

DWORD namesLength;
SCardStatus(..., NULL, &namesLength, ...);
LPTSTR names = malloc(namesLength);
SCardStatus(..., names, &namesLength, ...); // returns SCARD_E_INSUFFICIENT_BUFFER

This affected my own project, jnasmartcardio: jnasmartcardio/jnasmartcardio#20

Steps to Reproduce:

  1. Compile attached C program (clang -framework PCSC scardstatus_bug.c -o scardstatus_bug).
  2. Attach a smart card reader and plug in any smart card.
  3. Run scardstatus_bug.
  4. Run scardstatus_bug --workaround-scardstatus, which works around off-by-one error but still gives error when the value is not a multistring.

Expected Results:
Program should connect to card and print information about card. The first cal to SCardStatus should give a buffer size that is appropriate for the second call to SCardStatus. The buffer should be a multistring as described by Raymond Chen here: http://blogs.msdn.com/b/oldnewthing/archive/2009/10/08/9904646.aspx

Actual Results:
3. When allocating a buffer of size specified by first SCardStatus call, the second SCardStatus call fails with SCARD_E_INSUFFICIENT_BUFFER
4. When allocating a buffer that is 1 + size specified by first SCardStatus call, the SCardStatus reader names multistring did not end with empty string

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment