OS X Yosemite bug: PC/SC functions crash after a fork(2)

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

All (?) PC/SC functions

SCardxx() do not work on Yosemite if executed in a son process of the one that has executed SCardEstablishContext().

This happen if SCardEstablishContext() is exected and then the process is forked. The PC/SC functions used in the son using the hContext received by the SCardEstablishContext() in the parent process will crash.

I verified this behaviour only with SCardListReaders() and SCardConnect(). I would not be surprised if it is the case for all PC/SC functions (except SCardEstablishContext() of course).

Using a PC/SC context in a son process after a fork(2) may not be supported by the PC/SC API. The support of such a use case has changed in the official pcsc-lite. The current version (pcsc-lite 1.8.13) will support it but be sure to not use the PC/SC handle in the parent process if you want to use it in the son process (or you will have problems).
In the case of Yosemite the PC/SC function will even crash instead of returning an error code.

See also

Apple bug report #19374107 "PC/SC functions crash after a fork(2)".

"com.apple.pcsc Crashes in SCardDisconnect / transact / libdispatch / fork resource handling code" https://smartcardservices.macosforge.org/trac/ticket/141.

"OpenVPN Crashes in OpenSC pcsc_disconnect on OSX 10.10 Yosemite" https://github.com/OpenSC/OpenSC/issues/333.

Sample code

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

int main(int argc, const char * argv[]) {
 SCARDCONTEXT hContext;
 DWORD err = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
 if (err != SCARD_S_SUCCESS) {
  printf("ScardEstablishedContext: 0x%08x\n",err);
  return -1;
 }

 pid_t pid = fork();
 printf("%s:%d %d\n", __FILE__, __LINE__, pid);
 if (0 == pid)
 {
  /* son process */
  printf("Before\n");
  DWORD cchReaders = 0;
  err = SCardListReaders(hContext, "SCard$AllReaders", NULL, &cchReaders);
  if (err != 0) {
   printf("ScardListReaders: 0x%08x\n",err);
   return -1;
  }
  printf("Has not crashed: %X\n", err);
 }
 else
 {
  /* give some time to the son */
  sleep(1);
 }

 SCardReleaseContext(hContext);

 return 0;
}


Result (on Yosemite)

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

$ ./main
main.c:19 8236
main.c:19 0
Before

I could not generate a crash dump or a backtrace using lldb.
You can see that the line "Has not crashed: xx" is NOT displayed. The function SCardListReaders() has crashed and has not returned.

I can reproduce the problem and generate a crash dump using the pcsc-lite unitary test SCard_fork.py. This is less easy to use since you have to install the PC/SC Python wrapper pyscard first.

The crash dump is then:
Process:               Python [8270]
Path:                  /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
Identifier:            Python
Version:               2.7.6 (2.7.6)
Code Type:             X86-64 (Native)
Parent Process:        Python [8268]
Responsible:           Terminal [450]
User ID:               501

PlugIn Path:             /Library/Python/2.7/site-packages/smartcard/scard/_scard.so
PlugIn Identifier:       _scard.so
PlugIn Version:          ??? (0)

Date/Time:             2015-01-05 15:54:21.335 +0100
OS Version:            Mac OS X 10.10.1 (14B25)
Report Version:        11
Anonymous UUID:        7FE6A9DE-5002-1B38-88FE-227046540C73

Sleep/Wake UUID:       99799303-CF96-4849-9024-4B487D8B2393

Time Awake Since Boot: 7600 seconds
Time Since Wake:       3600 seconds

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x0000000000000110

VM Regions Near 0x110:
--> 
    __TEXT                 000000010837b000-000000010837c000 [    4K] r-x/rwx SM=COW  /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python

Application Specific Information:
crashed on child side of fork pre-exec

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libdispatch.dylib      0x00007fff8ddae5d2 _dispatch_queue_wakeup_with_qos_slow + 525
1   libdispatch.dylib      0x00007fff8ddae5f8 _dispatch_queue_wakeup_with_qos_slow + 563
2   libdispatch.dylib      0x00007fff8dda3cfc _dispatch_mach_msg_send + 1690
3   libdispatch.dylib      0x00007fff8dda35e4 dispatch_mach_send + 326
4   libxpc.dylib           0x00007fff887048b1 _xpc_connection_send_message_with_reply_f + 125
5   libxpc.dylib           0x00007fff887047c2 xpc_connection_send_message_with_reply_sync + 185
6   com.apple.pcsc         0x00000001086fec5b transact + 604
7   com.apple.pcsc         0x00000001086ffe73 SCardListReaders + 140
8   _scard.so              0x00000001086ec6a5 _wrap_SCardListReaders + 181 (scard_wrap.c:3401)
9   org.python.python      0x0000000108407180 PyEval_EvalFrameEx + 12778
10  org.python.python      0x0000000108403d62 PyEval_EvalCodeEx + 1413
11  org.python.python      0x000000010840a57d 0x108380000 + 566653
12  org.python.python      0x00000001084073e3 PyEval_EvalFrameEx + 13389
13  org.python.python      0x0000000108403d62 PyEval_EvalCodeEx + 1413
14  org.python.python      0x00000001084037d7 PyEval_EvalCode + 54
15  org.python.python      0x00000001084237bd 0x108380000 + 669629
16  org.python.python      0x0000000108423860 PyRun_FileExFlags + 133
17  org.python.python      0x00000001084233fd PyRun_SimpleFileExFlags + 769
18  org.python.python      0x0000000108434b23 Py_Main + 3051
19  libdyld.dylib          0x00007fff8ca555c9 start + 1

Thread 0 crashed with X86 Thread State (64-bit):
  rax: 0x0000000000000000  rbx: 0x00007f97b2517a50  rcx: 0x0000000000000100  rdx: 0x0000000000000001
  rdi: 0x00007f97b244f620  rsi: 0x0000000000000001  rbp: 0x00007fff57883990  rsp: 0x00007fff57883960
   r8: 0x0000000000000006   r9: 0x00000000fffff000  r10: 0x00007fff7584b244  r11: 0x0000000000000206
  r12: 0x0000000000000001  r13: 0x00007f97b244f620  r14: 0x0000000000001000  r15: 0x00007fff76583bc0
  rip: 0x00007fff8ddae5d2  rfl: 0x0000000000010206  cr2: 0x0000000000000110
  
Logical CPU:     1
Error Code:      0x00000006
Trap Number:     14

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

$ ./main
main.c:19 2274
main.c:19 0
Before
Has not crashed: 0

Here the line "Has not crashed: 0" is displayed and SCardListReaders() returned SCARD_S_SUCCESS.

Known workaround

Do not share a PC/SC context between a father process and its sons. It is a dangerous use the of PC/SC API. In the case of Yosemite it will even make your application crash.

Update, 4 April 2015

A reader signals that the Mac OS X behaviour is normal according to fork(2) manpage.

CAVEATS
There are limits to what you can do in the child process. To be totally safe you should restrict yourself to only executing async-signal safe operations until such time as one of the exec functions is called. All APIs, including global data symbols, in any framework or library should be assumed to be unsafe after a fork() unless explicitly documented to be safe or async-signal safe. If you need to use these frameworks in the child process, you must exec. In this situation it is reasonable to exec yourself.

Mavericks

Maybe it is an expected behaviour. But it is a regression compared to Mavericks (Mac OS X 10.9).

On Mavericks the output is:
$ ./main
a.c:19 2297
a.c:19 0
Before
Has not crashed: 0

Update, 14 November 2015

Answer from Apple to my bug report:
Please know this is intended behavior. Please review the CAVEATS section in the fork() manpage.

To clarify: It is intended that the results are undefined & unsupported for using Darwin APIs beyond the POSIX standard in a forked child of a parent process that had also used Darwin APIs beyond the POSIX standard. In such cases we try to at least make the crash report contain a clear indication that the program attempted such unsupported behavior. In this case the crash doesn’t have any such indication; for that we apologize.