Skip to content

Commit

Permalink
[ccid] Cherry-pick upstream fix for polling_transfer UaF (#1099)
Browse files Browse the repository at this point in the history
Cherry-pick the fix we landed into the upstream CCID repository,
fixing a possible use-after-free and memory corruption due to race
conditions.

One particular scenario seems to be card insertion/removal happening
at roughly the same times as SCardConnect/SCardDisconnect calls from
clients.

This fixes #1098.
  • Loading branch information
emaxx-google committed Oct 18, 2023
1 parent 8460ea3 commit 79ab957
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
From 8f197d0dcc11387f7864ba26e9379b014b03e137 Mon Sep 17 00:00:00 2001
From: Maksim Ivanov <emaxx@google.com>
Date: Tue, 17 Oct 2023 11:20:48 +0200
Subject: [PATCH] Fix possible use-after-free of polling_transfer

Guard the multi-thread usage of polling_transfer with a mutex, to
prevent the race condition between InterruptStop() and
InterruptRead(): while the former is calling
libusb_cancel_transfer(), the latter might've already called
libusb_free_transfer() on the same pointer.

This replaces the atomic variables that were previously used
for the polling_transfer pointer, because atomic variables don't
prevent this kind of use-after-free race condition.

In practice, the probability of hitting the race condition was
presumably low, but the basic scenario is the
SCardConnect/SCardDisconnect calls overlapping with the time
moments when the card is inserted/removed.
---
src/ccid_usb.c | 46 +++++++++++++++++++++++++++-------------------
1 file changed, 27 insertions(+), 19 deletions(-)

diff --git a/src/ccid_usb.c b/src/ccid_usb.c
index c5d47c9a..391af65d 100644
--- a/src/ccid_usb.c
+++ b/src/ccid_usb.c
@@ -117,7 +117,8 @@ typedef struct
_Atomic bool terminated;

/* libusb transfer for the polling (or NULL) */
- _Atomic (struct libusb_transfer *) polling_transfer;
+ pthread_mutex_t polling_transfer_mutex;
+ struct libusb_transfer *polling_transfer;

/* pointer to the multislot extension (if any) */
struct usbDevice_MultiSlot_Extension *multislot_extension;
@@ -745,7 +746,8 @@ status_t OpenUSBByName(unsigned int reader_index, /*@null@*/ char *device)
usbDevice[reader_index].real_nb_opened_slots = 1;
usbDevice[reader_index].nb_opened_slots = &usbDevice[reader_index].real_nb_opened_slots;
atomic_init(&usbDevice[reader_index].terminated, false);
- atomic_init(&usbDevice[reader_index].polling_transfer, NULL);
+ pthread_mutex_init(&usbDevice[reader_index].polling_transfer_mutex, NULL);
+ usbDevice[reader_index].polling_transfer = NULL;
usbDevice[reader_index].disconnected = false;

/* CCID common information */
@@ -1115,6 +1117,8 @@ status_t CloseUSB(unsigned int reader_index)
usbDevice[reader_index].multislot_extension = NULL;
}

+ pthread_mutex_destroy(&usbDevice[reader_index].polling_transfer_mutex);
+
if (usbDevice[reader_index].ccid.gemalto_firmware_features)
free(usbDevice[reader_index].ccid.gemalto_firmware_features);

@@ -1528,7 +1532,9 @@ int InterruptRead(int reader_index, int timeout /* in ms */)
return IFD_COMMUNICATION_ERROR;
}

- atomic_store(&usbDevice[reader_index].polling_transfer, transfer);
+ pthread_mutex_lock(&usbDevice[reader_index].polling_transfer_mutex);
+ usbDevice[reader_index].polling_transfer = transfer;
+ pthread_mutex_unlock(&usbDevice[reader_index].polling_transfer_mutex);

// The termination might've been requested by the other thread before the
// polling_transfer field was written. In that case, we have to cancel the
@@ -1559,7 +1565,9 @@ int InterruptRead(int reader_index, int timeout /* in ms */)
actual_length = transfer->actual_length;
ret = transfer->status;

- atomic_store(&usbDevice[reader_index].polling_transfer, NULL);
+ pthread_mutex_lock(&usbDevice[reader_index].polling_transfer_mutex);
+ usbDevice[reader_index].polling_transfer = NULL;
+ pthread_mutex_unlock(&usbDevice[reader_index].polling_transfer_mutex);
libusb_free_transfer(transfer);

DEBUG_PERIODIC3("after (%d) (%d)", reader_index, ret);
@@ -1593,8 +1601,6 @@ int InterruptRead(int reader_index, int timeout /* in ms */)
****************************************************************************/
void InterruptStop(int reader_index)
{
- struct libusb_transfer *transfer;
-
/* Multislot reader: redirect to Multi_InterrupStop */
if (usbDevice[reader_index].multislot_extension != NULL)
{
@@ -1607,17 +1613,17 @@ void InterruptStop(int reader_index)
// to be opposite to the one in InterruptRead() to avoid race conditions.
atomic_store(&usbDevice[reader_index].terminated, true);

- transfer = atomic_exchange(&usbDevice[reader_index].polling_transfer,
- NULL);
- if (transfer)
+ pthread_mutex_lock(&usbDevice[reader_index].polling_transfer_mutex);
+ if (usbDevice[reader_index].polling_transfer)
{
int ret;

- ret = libusb_cancel_transfer(transfer);
+ ret = libusb_cancel_transfer(usbDevice[reader_index].polling_transfer);
if (ret < 0)
DEBUG_CRITICAL2("libusb_cancel_transfer failed: %s",
libusb_error_name(ret));
}
+ pthread_mutex_unlock(&usbDevice[reader_index].polling_transfer_mutex);
} /* InterruptStop */


@@ -1668,8 +1674,9 @@ static void *Multi_PollingProc(void *p_ext)
break;
}

- atomic_store(&usbDevice[msExt->reader_index].polling_transfer,
- transfer);
+ pthread_mutex_lock(&usbDevice[msExt->reader_index].polling_transfer_mutex);
+ usbDevice[msExt->reader_index].polling_transfer = transfer;
+ pthread_mutex_unlock(&usbDevice[msExt->reader_index].polling_transfer_mutex);

completed = 0;
while (!completed && !msExt->terminated)
@@ -1695,8 +1702,9 @@ static void *Multi_PollingProc(void *p_ext)
}
}

- atomic_store(&usbDevice[msExt->reader_index].polling_transfer,
- NULL);
+ pthread_mutex_lock(&usbDevice[msExt->reader_index].polling_transfer_mutex);
+ usbDevice[msExt->reader_index].polling_transfer = NULL;
+ pthread_mutex_unlock(&usbDevice[msExt->reader_index].polling_transfer_mutex);

if (0 == rv)
{
@@ -1820,22 +1828,22 @@ static void *Multi_PollingProc(void *p_ext)
****************************************************************************/
static void Multi_PollingTerminate(struct usbDevice_MultiSlot_Extension *msExt)
{
- struct libusb_transfer *transfer;
-
if (msExt && !msExt->terminated)
{
msExt->terminated = true;

- transfer = atomic_exchange(&usbDevice[msExt->reader_index].polling_transfer, NULL);
+ pthread_mutex_lock(&usbDevice[msExt->reader_index].polling_transfer_mutex);

- if (transfer)
+ if (usbDevice[msExt->reader_index].polling_transfer)
{
int ret;

- ret = libusb_cancel_transfer(transfer);
+ ret = libusb_cancel_transfer(usbDevice[msExt->reader_index].polling_transfer);
if (ret < 0)
DEBUG_CRITICAL2("libusb_cancel_transfer failed: %d", ret);
}
+
+ pthread_mutex_unlock(&usbDevice[msExt->reader_index].polling_transfer_mutex);
}
} /* Multi_PollingTerminate */

45 changes: 27 additions & 18 deletions third_party/ccid/src/src/ccid_usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ typedef struct
_Atomic bool terminated;

/* libusb transfer for the polling (or NULL) */
_Atomic (struct libusb_transfer *) polling_transfer;
pthread_mutex_t polling_transfer_mutex;
struct libusb_transfer *polling_transfer;

/* pointer to the multislot extension (if any) */
struct usbDevice_MultiSlot_Extension *multislot_extension;
Expand Down Expand Up @@ -733,7 +734,8 @@ status_t OpenUSBByName(unsigned int reader_index, /*@null@*/ char *device)
usbDevice[reader_index].real_nb_opened_slots = 1;
usbDevice[reader_index].nb_opened_slots = &usbDevice[reader_index].real_nb_opened_slots;
atomic_init(&usbDevice[reader_index].terminated, false);
atomic_init(&usbDevice[reader_index].polling_transfer, NULL);
pthread_mutex_init(&usbDevice[reader_index].polling_transfer_mutex, NULL);
usbDevice[reader_index].polling_transfer = NULL;
usbDevice[reader_index].disconnected = false;

/* CCID common informations */
Expand Down Expand Up @@ -1103,6 +1105,8 @@ status_t CloseUSB(unsigned int reader_index)
usbDevice[reader_index].multislot_extension = NULL;
}

pthread_mutex_destroy(&usbDevice[reader_index].polling_transfer_mutex);

if (usbDevice[reader_index].ccid.gemalto_firmware_features)
free(usbDevice[reader_index].ccid.gemalto_firmware_features);

Expand Down Expand Up @@ -1516,7 +1520,9 @@ int InterruptRead(int reader_index, int timeout /* in ms */)
return IFD_COMMUNICATION_ERROR;
}

atomic_store(&usbDevice[reader_index].polling_transfer, transfer);
pthread_mutex_lock(&usbDevice[reader_index].polling_transfer_mutex);
usbDevice[reader_index].polling_transfer = transfer;
pthread_mutex_unlock(&usbDevice[reader_index].polling_transfer_mutex);

// The termination might've been requested by the other thread before the
// polling_transfer field was written. In that case, we have to cancel the
Expand Down Expand Up @@ -1547,7 +1553,9 @@ int InterruptRead(int reader_index, int timeout /* in ms */)
actual_length = transfer->actual_length;
ret = transfer->status;

atomic_store(&usbDevice[reader_index].polling_transfer, NULL);
pthread_mutex_lock(&usbDevice[reader_index].polling_transfer_mutex);
usbDevice[reader_index].polling_transfer = NULL;
pthread_mutex_unlock(&usbDevice[reader_index].polling_transfer_mutex);
libusb_free_transfer(transfer);

DEBUG_PERIODIC3("after (%d) (%d)", reader_index, ret);
Expand Down Expand Up @@ -1581,8 +1589,6 @@ int InterruptRead(int reader_index, int timeout /* in ms */)
****************************************************************************/
void InterruptStop(int reader_index)
{
struct libusb_transfer *transfer;

/* Multislot reader: redirect to Multi_InterrupStop */
if (usbDevice[reader_index].multislot_extension != NULL)
{
Expand All @@ -1595,16 +1601,17 @@ void InterruptStop(int reader_index)
// to be opposite to the one in InterruptRead() to avoid race conditions.
atomic_store(&usbDevice[reader_index].terminated, true);

transfer = atomic_load(&usbDevice[reader_index].polling_transfer);
if (transfer)
pthread_mutex_lock(&usbDevice[reader_index].polling_transfer_mutex);
if (usbDevice[reader_index].polling_transfer)
{
int ret;

ret = libusb_cancel_transfer(transfer);
ret = libusb_cancel_transfer(usbDevice[reader_index].polling_transfer);
if (ret < 0)
DEBUG_CRITICAL2("libusb_cancel_transfer failed: %s",
libusb_error_name(ret));
}
pthread_mutex_unlock(&usbDevice[reader_index].polling_transfer_mutex);
} /* InterruptStop */


Expand Down Expand Up @@ -1655,8 +1662,9 @@ static void *Multi_PollingProc(void *p_ext)
break;
}

atomic_store(&usbDevice[msExt->reader_index].polling_transfer,
transfer);
pthread_mutex_lock(&usbDevice[msExt->reader_index].polling_transfer_mutex);
usbDevice[msExt->reader_index].polling_transfer = transfer;
pthread_mutex_unlock(&usbDevice[msExt->reader_index].polling_transfer_mutex);

completed = 0;
while (!completed && !msExt->terminated)
Expand All @@ -1682,8 +1690,9 @@ static void *Multi_PollingProc(void *p_ext)
}
}

atomic_store(&usbDevice[msExt->reader_index].polling_transfer,
NULL);
pthread_mutex_lock(&usbDevice[msExt->reader_index].polling_transfer_mutex);
usbDevice[msExt->reader_index].polling_transfer = NULL;
pthread_mutex_unlock(&usbDevice[msExt->reader_index].polling_transfer_mutex);

if (0 == rv)
{
Expand Down Expand Up @@ -1807,22 +1816,22 @@ static void *Multi_PollingProc(void *p_ext)
****************************************************************************/
static void Multi_PollingTerminate(struct usbDevice_MultiSlot_Extension *msExt)
{
struct libusb_transfer *transfer;

if (msExt && !msExt->terminated)
{
msExt->terminated = true;

transfer = atomic_load(&usbDevice[msExt->reader_index].polling_transfer);
pthread_mutex_lock(&usbDevice[msExt->reader_index].polling_transfer_mutex);

if (transfer)
if (usbDevice[msExt->reader_index].polling_transfer)
{
int ret;

ret = libusb_cancel_transfer(transfer);
ret = libusb_cancel_transfer(usbDevice[msExt->reader_index].polling_transfer);
if (ret < 0)
DEBUG_CRITICAL2("libusb_cancel_transfer failed: %d", ret);
}

pthread_mutex_unlock(&usbDevice[msExt->reader_index].polling_transfer_mutex);
}
} /* Multi_PollingTerminate */

Expand Down

0 comments on commit 79ab957

Please sign in to comment.