Fix the problem with a smart card reader being lost when a smart card is removed during a reconnected Windows 2008r2 rdp session. This was due to a improper handling of a message with a duplicate completionID. There is a lengthy comment in the code with explicit details. Also, many minor updates for the code to more closely reflect the protocol documentation - such as correcting packet lengths, undocumented padding, etc.
This commit is contained in:
Brent Collins 2012-08-01 17:05:45 -05:00
parent 9b8044aa6a
commit 9b9398ec12
3 changed files with 239 additions and 24 deletions

View File

@ -28,6 +28,8 @@
#include <freerdp/utils/list.h>
#include <freerdp/utils/thread.h>
#include <freerdp/utils/svc_plugin.h>
#include <freerdp/utils/mutex.h>
#include <freerdp/utils/debug.h>
#include "rdpdr_types.h"
#include "rdpdr_constants.h"
@ -40,6 +42,7 @@ scard_free(DEVICE* dev)
{
SCARD_DEVICE* scard = (SCARD_DEVICE*)dev;
IRP* irp;
COMPLETIONIDINFO* CompletionIdInfo;
freerdp_thread_stop(scard->thread);
freerdp_thread_free(scard->thread);
@ -48,6 +51,12 @@ scard_free(DEVICE* dev)
irp->Discard(irp);
list_free(scard->irp_list);
/* Begin TS Client defect workaround. */
while ((CompletionIdInfo = (COMPLETIONIDINFO*)list_dequeue(scard->CompletionIds)) != NULL)
xfree(CompletionIdInfo);
list_free(scard->CompletionIds);
/* End TS Client defect workaround. */
xfree(dev);
return;
}
@ -130,11 +139,138 @@ scard_thread_func(void* arg)
}
/* Begin TS Client defect workaround. */
static COMPLETIONIDINFO*
scard_mark_duplicate_id(SCARD_DEVICE* scard, uint32 CompletionId)
{
/*
* Search from the beginning of the LIST for one outstanding "CompletionID"
* that matches the one passed in. If there is one, mark it as a duplicate
* if it is not already marked.
*/
LIST_ITEM* item;
COMPLETIONIDINFO* CompletionIdInfo;
for (item = scard->CompletionIds->head; item; item = item->next)
{
CompletionIdInfo = (COMPLETIONIDINFO*)item->data;
if (CompletionIdInfo->ID == CompletionId)
{
if (false == CompletionIdInfo->duplicate)
{
CompletionIdInfo->duplicate = true;
DEBUG_WARN("CompletionID number %u is now marked as a duplicate.", CompletionId);
}
return CompletionIdInfo;
}
}
return NULL; /* Either no items in the list or no match. */
}
static boolean
scard_check_for_duplicate_id(SCARD_DEVICE* scard, uint32 CompletionId)
{
/*
* Search from the end of the LIST for one outstanding "CompletionID"
* that matches the one passed in. Remove it from the list and free the
* memory associated with it. Return whether or not it was marked
* as a duplicate.
*/
LIST_ITEM* item;
COMPLETIONIDINFO* CompletionIdInfo;
boolean duplicate;
for (item = scard->CompletionIds->tail; item; item = item->prev)
{
CompletionIdInfo = (COMPLETIONIDINFO*)item->data;
if (CompletionIdInfo->ID == CompletionId)
{
duplicate = CompletionIdInfo->duplicate;
if (true == duplicate)
{
DEBUG_WARN("CompletionID number %u was previously marked as a duplicate. The response to the command is removed.", CompletionId);
}
list_remove(scard->CompletionIds, CompletionIdInfo);
xfree(CompletionIdInfo);
return duplicate;
}
}
/* This function should only be called when there is
* at least one outstanding CompletionID item in the list.
*/
DEBUG_WARN("Error!!! No CompletionIDs (or no matching IDs) in the list!");
return false;
}
static void
scard_irp_complete(IRP* irp)
{
/* This function is (mostly) a copy of the statically-declared "irp_complete()"
* function except that this function adds extra operations for the
* smart card's handling of duplicate "CompletionID"s. This function needs
* to be in this file so that "scard_irp_request()" can reference it.
*/
int pos;
boolean duplicate;
SCARD_DEVICE* scard = (SCARD_DEVICE*)irp->device;
DEBUG_SVC("DeviceId %d FileId %d CompletionId %d", irp->device->id, irp->FileId, irp->CompletionId);
pos = stream_get_pos(irp->output);
stream_set_pos(irp->output, 12);
stream_write_uint32(irp->output, irp->IoStatus);
stream_set_pos(irp->output, pos);
/* Begin TS Client defect workaround. */
freerdp_mutex_lock(scard->CompletionIdsMutex);
/* Remove from the list the item identified by the CompletionID.
* The function returns whether or not it was a duplicate CompletionID.
*/
duplicate = scard_check_for_duplicate_id(scard, irp->CompletionId);
freerdp_mutex_unlock(scard->CompletionIdsMutex);
if (false == duplicate)
{
svc_plugin_send(irp->devman->plugin, irp->output);
irp->output = NULL;
}
/* End TS Client defect workaround. */
/* irp_free(irp); The "irp_free()" function is statically-declared
* and so is not available to be called
* here. Instead, call it indirectly by calling
* the IRP's "Discard()" function,
* which has already been assigned
* to point to "irp_free()" in "irp_new()".
*/
irp->Discard(irp);
}
/* End TS Client defect workaround. */
static void
scard_irp_request(DEVICE* device, IRP* irp)
{
COMPLETIONIDINFO* CompletionIdInfo;
SCARD_DEVICE* scard = (SCARD_DEVICE*)device;
/* Begin TS Client defect workaround. */
CompletionIdInfo= xnew(COMPLETIONIDINFO);
CompletionIdInfo->ID = irp->CompletionId;/* "duplicate" member is set
* to false by "xnew()"
*/
freerdp_mutex_lock(scard->CompletionIdsMutex);
scard_mark_duplicate_id(scard, irp->CompletionId);
list_enqueue(scard->CompletionIds, CompletionIdInfo);
freerdp_mutex_unlock(scard->CompletionIdsMutex);
irp->Complete = scard_irp_complete; /* Overwrite the previous
* assignment made in
* "irp_new()".
*/
/* End TS Client defect workaround. */
if (irp->MajorFunction == IRP_MJ_DEVICE_CONTROL &&
scard_async_op(irp))
{
@ -161,7 +297,6 @@ scard_irp_request(DEVICE* device, IRP* irp)
freerdp_thread_signal(scard->thread);
}
int
DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)
{
@ -195,6 +330,9 @@ DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)
scard->irp_list = list_new();
scard->thread = freerdp_thread_new();
scard->CompletionIds = list_new();
scard->CompletionIdsMutex = freerdp_mutex_new();
pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE *)scard);
freerdp_thread_start(scard->thread, scard_thread_func, scard);

View File

@ -25,8 +25,74 @@
#include "devman.h"
#include "rdpdr_types.h"
#include <freerdp/utils/mutex.h> /* For CompletionIdsMutex */
#include <freerdp/utils/list.h>
#include <freerdp/utils/debug.h>
/*
* When using Windows Server 2008 R2 as the Terminal Services (TS)
* server, and with a smart card reader connected to the TS client machine
* and used to authenticate to an existing login session, the TS server
* will initiate the protocol initialization of MS-RDPEFS, Section 1.3.1,
* twice as it re-establishes a connection. The TS server starts both
* initializations with a "Server Announce Request" message.
* When the TS client receives this message, as per Section 3.2.5.1.2,
*
* The client SHOULD treat this packet as the beginning
* of a new sequence. The client SHOULD also cancel all
* outstanding requests and release previous references to
* all devices.
*
* As of this writing, the code does not cancel all outstanding requests.
* This leads to a problem where, after the first MS-RDPEFS initialization,
* the TS server sends an SCARD_IOCTL_GETSTATUSCHANGEx control in a message
* that uses an available "CompletionID". The
* TS client doesn't respond immediately because it is blocking while
* waiting for a change in the smart card's status in the reader.
* Then the TS server initiates a second MS-RDPEFS initialization sequence.
* As noted above, this should cancel the outstanding
* SCARD_IOCTL_GETSTATUSCHANGEx request, but it does not.
* At this point, the TS server is free to reuse the previously used
* "CompletionID", and it does reuse it for other SCARD_IOCTLs.
* Therefore, when the user removes (for example) the card from the reader,
* the TS client sends an "IOCompetion" message in response to the
* GETSTATUSCHANGEx using the original "CompletionID". The TS server does not
* expect this "CompletionID" and so, as per Section 3.1.5.2 of MS-RDPEFS,
* it treats that "IOCompletion" message as an error and terminates the
* virtual channel.
*
* The following structure is part of a work-around for this missing
* capability of canceling outstanding requests. This work-around
* allows the TS client to send an "IOCompletion" back to the
* TS server for the second (and subsequent) SCARD_IOCTLs that use
* the same "CompletionID" as the still outstanding
* SCARD_IOCTL_GETSTATUSCHANGEx. The work-around in the TS client
* prevents the client from sending the "IOCompletion" back (when
* the user removes the card) for the SCARD_IOCTL_GETSTATUSCHANGEx.
*
* This TS client expects the responses from the PCSC daemon for the second
* and subsequent SCARD_IOCTLs that use the same "CompletionID"
* to arrive at the TS client before the daemon's response to the
* SCARD_IOCTL_GETSTATUSCHANGEx. This is a race condition.
*
* The "CompletionIDs" are a global pool of IDs across all "DeviceIDs".
* However, this problem of duplicate "CompletionIDs" only affects smart cards.
*
* This structure tracks outstanding Terminal Services server "CompletionIDs"
* used by the redirected smart card device.
*/
struct _COMPLETIONIDINFO
{
uint32 ID; /* CompletionID */
boolean duplicate; /* Indicates whether or not this
* CompletionID is a duplicate of an
* earlier, outstanding, CompletionID.
*/
};
typedef struct _COMPLETIONIDINFO COMPLETIONIDINFO;
struct _SCARD_DEVICE
{
DEVICE device;
@ -37,6 +103,11 @@ struct _SCARD_DEVICE
LIST* irp_list;
freerdp_thread* thread;
LIST* CompletionIds;
freerdp_mutex CompletionIdsMutex; /* Protect the LIST from
* multiple thread writers.
*/
};
typedef struct _SCARD_DEVICE SCARD_DEVICE;

View File

@ -114,7 +114,12 @@ static uint32 sc_output_string(IRP* irp, char *src, boolean wide)
static void sc_output_alignment(IRP *irp, uint32 seed)
{
uint32 size = stream_get_length(irp->output) - 20;
const uint32 field_lengths = 20;/* Remove the lengths of the fields
* RDPDR_HEADER, DeviceID,
* CompletionID, and IoStatus
* of Section 2.2.1.5.5 of MS-RDPEFS.
*/
uint32 size = stream_get_length(irp->output) - field_lengths;
uint32 add = (seed - (size % seed)) % seed;
if (add > 0)
@ -270,14 +275,15 @@ static uint32 handle_EstablishContext(IRP* irp)
rv = SCardEstablishContext(scope, NULL, NULL, &hContext);
stream_write_uint32(irp->output, 4); // ?
stream_write_uint32(irp->output, -1); // ?
stream_write_uint32(irp->output, 4); // cbContext
stream_write_uint32(irp->output, -1); // ReferentID
stream_write_uint32(irp->output, 4);
stream_write_uint32(irp->output, hContext);
/* TODO: store hContext in allowed context list */
sc_output_alignment(irp, 8);
return SCARD_S_SUCCESS;
}
@ -298,6 +304,7 @@ static uint32 handle_ReleaseContext(IRP* irp)
else
DEBUG_SCARD("success 0x%08lx", hContext);
sc_output_alignment(irp, 8);
return rv;
}
@ -316,7 +323,7 @@ static uint32 handle_IsValidContext(IRP* irp)
else
DEBUG_SCARD("Success context: 0x%08x", (unsigned) hContext);
stream_write_uint32(irp->output, rv);
sc_output_alignment(irp, 8);
return rv;
}
@ -454,7 +461,6 @@ static uint32 handle_GetStatusChange(IRP* irp, boolean wide)
/* reset high bytes? */
cur->dwCurrentState &= 0x0000FFFF;
cur->dwEventState &= 0x0000FFFF;
cur->dwEventState = 0;
}
@ -576,7 +582,6 @@ static uint32 handle_Connect(IRP* irp, boolean wide)
stream_write_uint32(irp->output, dwActiveProtocol);
stream_write_uint32(irp->output, 0x00000004);
stream_write_uint32(irp->output, hCard);
stream_seek(irp->output, 28);
sc_output_alignment(irp, 8);
@ -616,8 +621,8 @@ static uint32 handle_Reconnect(IRP* irp)
else
DEBUG_SCARD("Success (proto: 0x%08x)", (unsigned) dwActiveProtocol);
stream_write_uint32(irp->output, dwActiveProtocol);
sc_output_alignment(irp, 8);
stream_write_uint32(irp->output, dwActiveProtocol); /* reversed? */
return rv;
}
@ -1165,7 +1170,7 @@ static uint32 handle_GetAttrib(IRP* irp)
static uint32 handle_AccessStartedEvent(IRP* irp)
{
stream_write_zero(irp->output, 8);
sc_output_alignment(irp, 8);
return SCARD_S_SUCCESS;
}
@ -1365,8 +1370,10 @@ void scard_device_control(SCARD_DEVICE* scard, IRP* irp)
uint32 output_len, input_len, ioctl_code;
uint32 stream_len, result;
uint32 pos, pad_len;
uint32 irp_len;
uint32 irp_result_pos, output_len_pos, result_pos;
const uint32 header_lengths = 16; /* MS-RPCE, Sections 2.2.6.1
* and 2.2.6.2.
*/
stream_read_uint32(irp->input, output_len);
stream_read_uint32(irp->input, input_len);
@ -1383,8 +1390,12 @@ void scard_device_control(SCARD_DEVICE* scard, IRP* irp)
irp_result_pos = stream_get_pos(irp->output);
stream_write_uint32(irp->output, 0x00081001); /* len 8, LE, v1 */
stream_write_uint32(irp->output, 0x00000000); /* MS-RDPEFS
* OutputBufferLength
* will be updated
* later in this
* function.
*/
/* [MS-RPCE] 2.2.6.1 */
stream_write_uint32(irp->output, 0x00081001); /* len 8, LE, v1 */
stream_write_uint32(irp->output, 0xcccccccc); /* filler */
@ -1510,26 +1521,21 @@ void scard_device_control(SCARD_DEVICE* scard, IRP* irp)
/* handle response packet */
pos = stream_get_pos(irp->output);
stream_len = pos - irp_result_pos - 4;
stream_len = pos - irp_result_pos - 4; /* Value of OutputBufferLength */
stream_set_pos(irp->output, irp_result_pos);
stream_write_uint32(irp->output, stream_len);
stream_set_pos(irp->output, output_len_pos);
stream_write_uint32(irp->output, stream_len - 24);
/* Remove the effect of the MS-RPCE Common Type Header and Private
* Header (Sections 2.2.6.1 and 2.2.6.2).
*/
stream_write_uint32(irp->output, stream_len - header_lengths);
stream_set_pos(irp->output, result_pos);
stream_write_uint32(irp->output, result);
stream_set_pos(irp->output, pos);
/* pad stream to 16 byte align */
pad_len = stream_len % 16;
stream_write_zero(irp->output, pad_len);
pos = stream_get_pos(irp->output);
irp_len = stream_len + pad_len;
stream_set_pos(irp->output, irp_result_pos);
stream_write_uint32(irp->output, irp_len);
stream_set_pos(irp->output, pos);
#ifdef WITH_DEBUG_SCARD
freerdp_hexdump(stream_get_data(irp->output), stream_get_length(irp->output));
#endif