Re: Race condition with SCardGetStatusChange() when USB Reader is removed
Ludovic Rousseau <[email protected]> Thu, 4 May 2017 10:17:23 +0200
| Newsgroups | gmane.comp.lib.muscle |
|---|---|
| Message-ID | <CAGstE8D2ph7XQodbgewZ+BVEgY34p8bVWYM3UZkGoAELhKXHHw@mail.gmail.com> |
2017-05-03 14:42 GMT+02:00 Maximilian Stein <[email protected]>: > Hello, > Hello, > > it is possible that a client application listening for events via > SCardGetStatusChange() is not informed that an USB reader was removed, > thus blocking until given or internal time-out. > Yes. It is possible. pcsc-lite may contain bugs. > > Summary: > Upon USB reader removal the first thing to happen is that the readers > event handling thread is stopped. The last thing it does before exiting > is to signal the event to the listening clients, but at this point the > readerStates structure of the reader is not changed / uninitialized. The > client receives the event notification but can detect no changes. This > behaviour depends on the timing of both the client application and > pcscd, thus it's a bit tricky to figure out. > > > Long version: > The removal of an USB reader is detected by the hotplug monitoring > mechanism which then calls RFRemoveReader() [readerfactory.c]. > RFRemoveReader() boils down to removeReader(), which first stops the > event handling thread of the reader (EHDestroyEventHandler() in > [eventhandler.c]) and then frees / reinitialises the READER_STATE and > READER_CONTEXT structs of the reader. > > EHDestroyEventHandler() sets LockId of the reader context so that the > event handler thread (EHStatusHandlerThread()) will exit at the end of > the loop. If the reader was removed *after a successful* call to > IFDStatusICC(), e.g. while sleeping for PCSCLITE_STATUS_POLL_RATE, the > global readerStates struct contains valid *unchanged* information. > Before the thread exits it calls EHSignalEventToClients() but the client > will not detect any event, because readerStates is unchanged. > > Inside SCardGetStatusChange() [winscard_clnt.c] the client receives the > event notification and wakes up. It immediately calls getReaderStates() > which synchronizes the global readerStates structs. Since there was no > change on the server side yet, the client detects no events. As a > result, the client stays inside the SCardGetStatusChange() loop and > listens again for an event notification. *But* if checking the > readerStates takes a bit longer, the client is not fast enough to listen > for one more very important event: There is another event notification > on USB reader removal at the end of RFRemoveReader(). Then all status > structs for the reader are initialized again and > EHSignalEventToClients() is called. A client that receives this event > notification will notice that the reader disappeared. But if the client > is not fast enough to start listening for events again (as mentioned > above), no change is detected. This results in the client missing the > change and waiting for more event notifications until time-out (which > can be set to INFINITE) is reached. > > Attached you can find a patch that provokes this behaviour > [Provoke-race-condition-in-SCardGetStatusChange.patch]. To reproduce you > need to attach an USB reader, then call a client application that calls > SCardGetStatusChange() for the PnP-Notification and remove the reader. > > > Suggestions for a fix: > 1) Remove EHSignalEventToClient() when EHStatusHandlerThread() exits. > First at this point there is no updated data visible to the client. > Second an event notification will be given at the end of RFRemoveReader(). > 2) Clean up readerState - or set error status as if IFDStatusICC() > failed - when EHStatusHandlerThread() exits, before calling > EHSignalEventToClient(). > > > Best regards > Maximilian > I tried to reproduce the problem with the attached sample code but without success. I tried using the special reader "\\?PnP?\Notification" and also using the current reader name but could not reproduce the problem. Yes, I first applied your patch and I get the extra sleep() in pcscd. You can change line 52 of my sample code to use the PnP reader or the normal one. Can you provide a/your sample code to reproduce the problem? P.S. RFUnInitializeReader() always returns success, so the check for (rv > != SCARD_S_SUCCESS) in removeReader() [readerfactory.c] is a bit > confusing and could be removed? > Correct. Fixed in https://github.com/LudovicRousseau/PCSC/commit/789dae8a8acf8d1693597159d7d590a96bdd6424 > > P.P.S. In ContextThread() [winscard_svc.c], sending readerStates to the > client in CMD_GET_READER_STATES is prone to a race condition (even if > very unlikely). The readerStates array is not protected by a mutex, so > it can be changed while it is copied/sent to the client socket. But I'm > uncertain if this would ever result in any problematic inconsistencies. > Exact. readerStates may be inaccurate or even incoherent (like the reader name) when sent to the client. From "Atomic Types" [1]: " In practice, you can assume that int is atomic. You can also assume that pointer types are atomic; that is very convenient. Both of these assumptions are true on all of the machines that the GNU C Library supports and on all POSIX systems we know of. " The 2 only fields in READER_STATE structure that are not int types are readerName[] and cardAtr[]. These fields should not change often. I do not think this is a real problem. But I still created a bug at [2] so I (or someone else) can work on time when time permit. Regards, [1] https://www.gnu.org/software/libc/manual/html_node/Atomic-Types.html [2] https://alioth.debian.org/tracker/index.php?func=detail&aid=315725&group_id=30105&atid=410085 -- Dr. Ludovic Rousseau _______________________________________________ Pcsclite-muscle mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pcsclite-muscle
SCardGetStatusChange4.py
(text/x-python, 3 KB)
#! /usr/bin/env python
# SCardGetStatusChange.py : Unitary test for SCardGetStatusChange()
# Copyright (C) 2011 Ludovic Rousseau
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from smartcard.scard import *
from smartcard.pcsc.PCSCExceptions import *
def parseEventState(eventstate):
stateList = list()
states = {SCARD_STATE_IGNORE: "Ignore",
SCARD_STATE_CHANGED: "Changed",
SCARD_STATE_UNKNOWN: "Unknown",
SCARD_STATE_UNAVAILABLE: "Unavailable",
SCARD_STATE_EMPTY: "Empty",
SCARD_STATE_PRESENT: "Present",
SCARD_STATE_ATRMATCH: "ATR match",
SCARD_STATE_EXCLUSIVE: "Exclusive",
SCARD_STATE_INUSE: "In use",
SCARD_STATE_MUTE: "Mute",
SCARD_STATE_UNPOWERED: "Unpowered"}
for state in states:
if eventstate & state:
stateList.append(states[state])
return stateList
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
if hresult != SCARD_S_SUCCESS:
raise EstablishContextException(hresult)
hresult, readers = SCardListReaders(hcontext, [])
if hresult != SCARD_S_SUCCESS:
raise ListReadersException(hresult)
print('PC/SC Readers:', readers)
pnp = False
readerstates = {}
if pnp:
readers = ("\\\\?PnP?\\Notification",)
for reader in (readers):
print(reader)
readerstates[reader] = (reader, SCARD_STATE_UNAWARE)
if not pnp:
hresult, newstates = SCardGetStatusChange(hcontext, 0,
list(readerstates.values()))
if hresult != SCARD_S_SUCCESS:
raise BaseSCardException(hresult)
print(newstates)
for readerState in newstates:
readername, eventstate, atr = readerState
readerstates[readername] = (readername, eventstate)
print("Remove the reader")
hresult, newstates = SCardGetStatusChange(hcontext, 1000000,
list(readerstates.values()))
print("SCardGetStatusChange()", SCardGetErrorMessage(hresult))
if hresult != SCARD_S_SUCCESS and hresult != SCARD_E_TIMEOUT:
raise BaseSCardException(hresult)
for readerState in newstates:
readername, eventstate, atr = readerState
print(readername, hex(eventstate))
print(parseEventState(eventstate))
hresult = SCardReleaseContext(hcontext)
print("SCardReleaseContext()", SCardGetErrorMessage(hresult))
if hresult != SCARD_S_SUCCESS:
raise ReleaseContextException(hresult)