mirror of
https://github.com/FreeRDP/FreeRDP.git
synced 2025-01-24 16:24:08 +08:00
Issue #684.
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:
parent
9b8044aa6a
commit
9b9398ec12
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user