Add RDP Smart Card Redirection Support

Resolves: RHEL-171999
This commit is contained in:
Ondrej Holy 2026-07-02 08:07:33 +00:00
parent 6ecf4a031a
commit b01e0672d5
9 changed files with 15879 additions and 1 deletions

View File

@ -0,0 +1,551 @@
From 2e1113794d125269f30fc7acf4d0f3d898d74d52 Mon Sep 17 00:00:00 2001
From: David Fort <contact@hardening-consulting.com>
Date: Mon, 15 Jun 2026 16:30:40 +0200
Subject: [PATCH] [channels] handle CHANNEL_OPTION_SHOW_PROTOCOL server-side
With some static channels like rdpdr or clipdr there's no marking of packet
length so in theory you can't handle packets that you don't know (you don't know
how many bytes to skip) or that are badly encoded (WTSVirtualChannelRead linearizes
the traffic so you can't know which bytes are part of the current PDU and which
bytes are from the next one). To solve that this patch adds server-side the handling
of a CHANNEL_OPTION_SHOW_PROTOCOL with WTSVirtualChannelOpen, when you set that
option you get CHANNEL_PDU_HEADER before the packet payload, and so you can do the
reassembly of a PDU. This allows to skip a PDU when you don't know the type, and
go to the next one correctly if for instance the padding at the end of the PDU is
incorrect (think of some smartcard commands invalidly padded by FreeRDP).
The patch also provides a ChannelPduTracker component that can do the reassembly
for you.
---
include/freerdp/utils/channel_pdu_tracker.h | 65 +++++++++++
libfreerdp/core/peer.c | 60 +++++-----
libfreerdp/core/server.c | 82 ++++++++------
libfreerdp/utils/CMakeLists.txt | 1 +
libfreerdp/utils/channel_pdu_tracker.c | 119 ++++++++++++++++++++
5 files changed, 259 insertions(+), 68 deletions(-)
create mode 100644 include/freerdp/utils/channel_pdu_tracker.h
create mode 100644 libfreerdp/utils/channel_pdu_tracker.c
diff --git a/include/freerdp/utils/channel_pdu_tracker.h b/include/freerdp/utils/channel_pdu_tracker.h
new file mode 100644
index 0000000..96c52b5
--- /dev/null
+++ b/include/freerdp/utils/channel_pdu_tracker.h
@@ -0,0 +1,65 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * static channel utility functions
+ *
+ * Copyright 2026 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FREERDP_UTILS_CHANNEL_TRACKER_H_
+#define FREERDP_UTILS_CHANNEL_TRACKER_H_
+
+#include <winpr/wtypes.h>
+#include <winpr/stream.h>
+
+#include <freerdp/api.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ typedef struct ChannelPduTracker ChannelPduTracker;
+
+ /**
+ * frees a ChannelPduTracker
+ * @param tracker the tracker to release
+ * @since version 3.28.0
+ */
+ FREERDP_API void ChannelPduTracker_free(ChannelPduTracker* tracker);
+
+ /**
+ * Creates a ChannelPduTracker
+ * @param vc the HANDLE of the channel
+ * @return the created ChannelPduTracker, nullptr if something failed
+ * @since version 3.28.0
+ */
+ WINPR_ATTR_MALLOC(ChannelPduTracker_free, 1)
+ FREERDP_API ChannelPduTracker* ChannelPduTracker_new(HANDLE vc);
+
+ /**
+ * Polls the channel and return a PDU if a whole PDU was reassembled
+ * @param tracker the ChannelPduTracker to poll a packet from
+ * @param ok a pointer to a status BOOL
+ * @return the reassembled PDU if any
+ * @since version 3.28.0
+ */
+ WINPR_ATTR_MALLOC(Stream_Release, 1)
+ FREERDP_API wStream* ChannelPduTracker_poll(ChannelPduTracker* tracker, BOOL* ok);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_UTILS_CHANNEL_TRACKER_H_ */
diff --git a/libfreerdp/core/peer.c b/libfreerdp/core/peer.c
index 3e3f024..355c578 100644
--- a/libfreerdp/core/peer.c
+++ b/libfreerdp/core/peer.c
@@ -48,17 +48,11 @@ static state_run_t peer_recv_pdu(freerdp_peer* client, wStream* s);
static HANDLE freerdp_peer_virtual_channel_open(freerdp_peer* client, const char* name,
UINT32 flags)
{
- UINT32 index = 0;
- BOOL joined = FALSE;
- rdpMcsChannel* mcsChannel = nullptr;
- rdpPeerChannel* peerChannel = nullptr;
- rdpMcs* mcs = nullptr;
-
WINPR_ASSERT(client);
WINPR_ASSERT(client->context);
WINPR_ASSERT(client->context->rdp);
WINPR_ASSERT(name);
- mcs = client->context->rdp->mcs;
+ rdpMcs* mcs = client->context->rdp->mcs;
WINPR_ASSERT(mcs);
if (flags & WTS_CHANNEL_OPTION_DYNAMIC)
@@ -69,6 +63,9 @@ static HANDLE freerdp_peer_virtual_channel_open(freerdp_peer* client, const char
if (length > 8)
return nullptr; /* SVC maximum name length is 8 */
+ BOOL joined = FALSE;
+ UINT32 index = 0;
+ rdpMcsChannel* mcsChannel = nullptr;
for (; index < mcs->channelCount; index++)
{
mcsChannel = &(mcs->channels[index]);
@@ -86,7 +83,7 @@ static HANDLE freerdp_peer_virtual_channel_open(freerdp_peer* client, const char
if (!joined)
return nullptr; /* channel is not joined */
- peerChannel = (rdpPeerChannel*)mcsChannel->handle;
+ rdpPeerChannel* peerChannel = (rdpPeerChannel*)mcsChannel->handle;
if (peerChannel)
{
@@ -1473,33 +1470,30 @@ freerdp_peer* freerdp_peer_new(int sockfd)
(void)freerdp_tcp_set_nodelay(WLog_Get(TAG), WLOG_DEBUG, sockfd);
}
- if (client)
- {
- client->sockfd = sockfd;
- client->ContextSize = sizeof(rdpContext);
- client->Initialize = freerdp_peer_initialize;
+ client->sockfd = sockfd;
+ client->ContextSize = sizeof(rdpContext);
+ client->Initialize = freerdp_peer_initialize;
#if defined(WITH_FREERDP_DEPRECATED)
- client->GetFileDescriptor = freerdp_peer_get_fds;
+ client->GetFileDescriptor = freerdp_peer_get_fds;
#endif
- client->GetEventHandle = freerdp_peer_get_event_handle;
- client->GetEventHandles = freerdp_peer_get_event_handles;
- client->CheckFileDescriptor = freerdp_peer_check_fds;
- client->Close = freerdp_peer_close;
- client->Disconnect = freerdp_peer_disconnect;
- client->SendChannelData = freerdp_peer_send_channel_data;
- client->SendChannelPacket = freerdp_peer_send_channel_packet;
- client->SendServerRedirection = freerdp_peer_send_server_redirection_pdu;
- client->IsWriteBlocked = freerdp_peer_is_write_blocked;
- client->DrainOutputBuffer = freerdp_peer_drain_output_buffer;
- client->HasMoreToRead = freerdp_peer_has_more_to_read;
- client->VirtualChannelOpen = freerdp_peer_virtual_channel_open;
- client->VirtualChannelClose = freerdp_peer_virtual_channel_close;
- client->VirtualChannelWrite = freerdp_peer_virtual_channel_write;
- client->VirtualChannelRead = nullptr; /* must be defined by server application */
- client->VirtualChannelGetData = freerdp_peer_virtual_channel_get_data;
- client->VirtualChannelSetData = freerdp_peer_virtual_channel_set_data;
- client->SetState = freerdp_peer_set_state;
- }
+ client->GetEventHandle = freerdp_peer_get_event_handle;
+ client->GetEventHandles = freerdp_peer_get_event_handles;
+ client->CheckFileDescriptor = freerdp_peer_check_fds;
+ client->Close = freerdp_peer_close;
+ client->Disconnect = freerdp_peer_disconnect;
+ client->SendChannelData = freerdp_peer_send_channel_data;
+ client->SendChannelPacket = freerdp_peer_send_channel_packet;
+ client->SendServerRedirection = freerdp_peer_send_server_redirection_pdu;
+ client->IsWriteBlocked = freerdp_peer_is_write_blocked;
+ client->DrainOutputBuffer = freerdp_peer_drain_output_buffer;
+ client->HasMoreToRead = freerdp_peer_has_more_to_read;
+ client->VirtualChannelOpen = freerdp_peer_virtual_channel_open;
+ client->VirtualChannelClose = freerdp_peer_virtual_channel_close;
+ client->VirtualChannelWrite = freerdp_peer_virtual_channel_write;
+ client->VirtualChannelRead = nullptr; /* must be defined by server application */
+ client->VirtualChannelGetData = freerdp_peer_virtual_channel_get_data;
+ client->VirtualChannelSetData = freerdp_peer_virtual_channel_set_data;
+ client->SetState = freerdp_peer_set_state;
return client;
}
diff --git a/libfreerdp/core/server.c b/libfreerdp/core/server.c
index 3c998f0..7afcb62 100644
--- a/libfreerdp/core/server.c
+++ b/libfreerdp/core/server.c
@@ -76,23 +76,27 @@ static rdpPeerChannel* wts_get_dvc_channel_by_id(WTSVirtualChannelManager* vcm,
return HashTable_GetItemValue(vcm->dynamicVirtualChannels, &ChannelId);
}
-static BOOL wts_queue_receive_data(rdpPeerChannel* channel, const BYTE* Buffer, UINT32 Length)
+static BOOL wts_queue_receive_data(rdpPeerChannel* channel, const BYTE* Buffer1, UINT32 Length1,
+ const BYTE* Buffer2, UINT32 Length2)
{
- BYTE* buffer = nullptr;
- wtsChannelMessage* messageCtx = nullptr;
-
WINPR_ASSERT(channel);
- messageCtx = (wtsChannelMessage*)malloc(sizeof(wtsChannelMessage) + Length);
+ wtsChannelMessage* messageCtx =
+ (wtsChannelMessage*)malloc(sizeof(wtsChannelMessage) + Length1 + Length2);
if (!messageCtx)
return FALSE;
WINPR_ASSERT(channel->channelId <= UINT16_MAX);
messageCtx->channelId = (UINT16)channel->channelId;
- messageCtx->length = Length;
+ messageCtx->length = Length1 + Length2;
messageCtx->offset = 0;
- buffer = (BYTE*)(messageCtx + 1);
- CopyMemory(buffer, Buffer, Length);
+ BYTE* buffer = (BYTE*)(messageCtx + 1);
+ CopyMemory(buffer, Buffer1, Length1);
+ if (Length2 && Buffer2)
+ {
+ buffer += Length1;
+ CopyMemory(buffer, Buffer2, Length2);
+ }
return MessageQueue_Post(channel->queue, messageCtx, 0, nullptr, nullptr);
}
@@ -252,7 +256,7 @@ static BOOL wts_read_drdynvc_data(rdpPeerChannel* channel, wStream* s, UINT32 le
if (Stream_GetPosition(channel->receiveData) >= channel->dvc_total_length)
{
ret = wts_queue_receive_data(channel, Stream_Buffer(channel->receiveData),
- channel->dvc_total_length);
+ channel->dvc_total_length, nullptr, 0);
channel->dvc_total_length = 0;
}
else
@@ -260,7 +264,7 @@ static BOOL wts_read_drdynvc_data(rdpPeerChannel* channel, wStream* s, UINT32 le
}
else
{
- ret = wts_queue_receive_data(channel, Stream_ConstPointer(s), length);
+ ret = wts_queue_receive_data(channel, Stream_ConstPointer(s), length, nullptr, 0);
}
return ret;
@@ -454,6 +458,17 @@ static BOOL WTSProcessChannelData(rdpPeerChannel* channel, UINT16 channelId, con
WINPR_ASSERT(channel->vcm);
WINPR_UNUSED(channelId);
+ if (channel->channelFlags & CHANNEL_OPTION_SHOW_PROTOCOL)
+ {
+ const CHANNEL_PDU_HEADER header = {
+ .length = (UINT32)size,
+ .flags = flags,
+ };
+
+ return wts_queue_receive_data(channel, (const BYTE*)&header, sizeof(header), data,
+ (UINT32)size);
+ }
+
if (flags & CHANNEL_FLAG_FIRST)
{
Stream_ResetPosition(channel->receiveData);
@@ -482,7 +497,7 @@ static BOOL WTSProcessChannelData(rdpPeerChannel* channel, UINT16 channelId, con
ret = FALSE;
else
ret = wts_queue_receive_data(channel, Stream_Buffer(channel->receiveData),
- (UINT32)pos);
+ (UINT32)pos, nullptr, 0);
}
Stream_ResetPosition(channel->receiveData);
@@ -1353,7 +1368,7 @@ static void peer_channel_queue_free_message(void* obj)
static rdpPeerChannel* channel_new(WTSVirtualChannelManager* vcm, freerdp_peer* client,
UINT32 ChannelId, UINT16 index, UINT16 type, size_t chunkSize,
- const char* name)
+ const char* name, UINT32 flags)
{
wObject queueCallbacks = WINPR_C_ARRAY_INIT;
queueCallbacks.fnObjectFree = peer_channel_queue_free_message;
@@ -1371,6 +1386,7 @@ static rdpPeerChannel* channel_new(WTSVirtualChannelManager* vcm, freerdp_peer*
channel->channelType = type;
channel->creationStatus =
(type == RDP_PEER_CHANNEL_TYPE_SVC) ? ERROR_SUCCESS : ERROR_OPERATION_IN_PROGRESS;
+ channel->channelFlags = flags;
return channel;
fail:
@@ -1378,44 +1394,35 @@ fail:
return nullptr;
}
-HANDLE WINAPI FreeRDP_WTSVirtualChannelOpen(HANDLE hServer, WINPR_ATTR_UNUSED DWORD SessionId,
- LPSTR pVirtualName)
+HANDLE WINAPI FreeRDP_WTSVirtualChannelOpenStatic(HANDLE hServer, WINPR_ATTR_UNUSED DWORD SessionId,
+ LPSTR pVirtualName, UINT32 Flags)
{
- size_t length = 0;
- rdpMcs* mcs = nullptr;
- rdpMcsChannel* joined_channel = nullptr;
- freerdp_peer* client = nullptr;
- rdpPeerChannel* channel = nullptr;
- WTSVirtualChannelManager* vcm = nullptr;
- HANDLE hChannelHandle = nullptr;
- rdpContext* context = nullptr;
- vcm = (WTSVirtualChannelManager*)hServer;
-
+ WTSVirtualChannelManager* vcm = (WTSVirtualChannelManager*)hServer;
if (!vcm)
{
SetLastError(ERROR_INVALID_DATA);
return nullptr;
}
- client = vcm->client;
+ freerdp_peer* client = vcm->client;
WINPR_ASSERT(client);
- context = client->context;
+ rdpContext* context = client->context;
WINPR_ASSERT(context);
WINPR_ASSERT(context->rdp);
WINPR_ASSERT(context->settings);
- mcs = context->rdp->mcs;
+ rdpMcs* mcs = context->rdp->mcs;
WINPR_ASSERT(mcs);
- length = strnlen(pVirtualName, CHANNEL_NAME_LEN + 1);
-
+ size_t length = strnlen(pVirtualName, CHANNEL_NAME_LEN + 1);
if (length > CHANNEL_NAME_LEN)
{
SetLastError(ERROR_NOT_FOUND);
return nullptr;
}
+ rdpMcsChannel* joined_channel = nullptr;
UINT32 index = 0;
for (; index < mcs->channelCount; index++)
{
@@ -1433,8 +1440,7 @@ HANDLE WINAPI FreeRDP_WTSVirtualChannelOpen(HANDLE hServer, WINPR_ATTR_UNUSED DW
return nullptr;
}
- channel = (rdpPeerChannel*)joined_channel->handle;
-
+ rdpPeerChannel* channel = (rdpPeerChannel*)joined_channel->handle;
if (!channel)
{
const UINT32 VCChunkSize =
@@ -1442,7 +1448,7 @@ HANDLE WINAPI FreeRDP_WTSVirtualChannelOpen(HANDLE hServer, WINPR_ATTR_UNUSED DW
WINPR_ASSERT(index <= UINT16_MAX);
channel = channel_new(vcm, client, joined_channel->ChannelId, (UINT16)index,
- RDP_PEER_CHANNEL_TYPE_SVC, VCChunkSize, pVirtualName);
+ RDP_PEER_CHANNEL_TYPE_SVC, VCChunkSize, pVirtualName, Flags);
if (!channel)
goto fail;
@@ -1450,7 +1456,7 @@ HANDLE WINAPI FreeRDP_WTSVirtualChannelOpen(HANDLE hServer, WINPR_ATTR_UNUSED DW
joined_channel->handle = channel;
}
- hChannelHandle = (HANDLE)channel;
+ HANDLE hChannelHandle = (HANDLE)channel;
return hChannelHandle;
fail:
channel_free(channel);
@@ -1458,6 +1464,11 @@ fail:
return nullptr;
}
+HANDLE WINAPI FreeRDP_WTSVirtualChannelOpen(HANDLE hServer, DWORD SessionId, LPSTR pVirtualName)
+{
+ return FreeRDP_WTSVirtualChannelOpenStatic(hServer, SessionId, pVirtualName, 0);
+}
+
HANDLE WINAPI FreeRDP_WTSVirtualChannelOpenEx(DWORD SessionId, LPSTR pVirtualName, DWORD flags)
{
wStream* s = nullptr;
@@ -1481,7 +1492,7 @@ HANDLE WINAPI FreeRDP_WTSVirtualChannelOpenEx(DWORD SessionId, LPSTR pVirtualNam
if (!(flags & WTS_CHANNEL_OPTION_DYNAMIC))
{
HashTable_Unlock(g_ServerHandles);
- return FreeRDP_WTSVirtualChannelOpen((HANDLE)vcm, SessionId, pVirtualName);
+ return FreeRDP_WTSVirtualChannelOpenStatic((HANDLE)vcm, SessionId, pVirtualName, flags);
}
freerdp_peer* client = vcm->client;
@@ -1521,7 +1532,8 @@ HANDLE WINAPI FreeRDP_WTSVirtualChannelOpenEx(DWORD SessionId, LPSTR pVirtualNam
const UINT32 VCChunkSize =
freerdp_settings_get_uint32(client->context->settings, FreeRDP_VCChunkSize);
- channel = channel_new(vcm, client, 0, 0, RDP_PEER_CHANNEL_TYPE_DVC, VCChunkSize, pVirtualName);
+ channel =
+ channel_new(vcm, client, 0, 0, RDP_PEER_CHANNEL_TYPE_DVC, VCChunkSize, pVirtualName, flags);
if (!channel)
{
diff --git a/libfreerdp/utils/CMakeLists.txt b/libfreerdp/utils/CMakeLists.txt
index 6f821a6..30b7ca9 100644
--- a/libfreerdp/utils/CMakeLists.txt
+++ b/libfreerdp/utils/CMakeLists.txt
@@ -19,6 +19,7 @@ set(MODULE_NAME "freerdp-utils")
set(MODULE_PREFIX "FREERDP_UTILS")
set(${MODULE_PREFIX}_SRCS
+ channel_pdu_tracker.c
encoded_types.c
helpers.c
passphrase.c
diff --git a/libfreerdp/utils/channel_pdu_tracker.c b/libfreerdp/utils/channel_pdu_tracker.c
new file mode 100644
index 0000000..18e9aab
--- /dev/null
+++ b/libfreerdp/utils/channel_pdu_tracker.c
@@ -0,0 +1,119 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * static channel utility functions
+ *
+ * Copyright 2026 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/utils/channel_pdu_tracker.h>
+
+#include <winpr/wtsapi.h>
+
+struct ChannelPduTracker
+{
+ HANDLE vc;
+ wStream* currentPacket;
+ BYTE buffer[CHANNEL_PDU_LENGTH];
+ BYTE* writePtr;
+};
+
+#define TAG "ChannelPduTracker"
+
+wStream* ChannelPduTracker_poll(ChannelPduTracker* tracker, BOOL* ok)
+{
+ WINPR_ASSERT(tracker);
+ WINPR_ASSERT(ok);
+
+ ULONG sz = 0;
+ *ok = FALSE;
+ const ULONG readSz =
+ WINPR_ASSERTING_INT_CAST(ULONG, CHANNEL_PDU_LENGTH - (tracker->writePtr - tracker->buffer));
+ if (!WTSVirtualChannelRead(tracker->vc, INFINITE, (char*)tracker->writePtr, readSz, &sz))
+ return nullptr;
+
+ tracker->writePtr += sz;
+ const size_t recvSz = (tracker->writePtr - tracker->buffer);
+ if (recvSz < sizeof(CHANNEL_PDU_HEADER))
+ {
+ *ok = TRUE;
+ return nullptr;
+ }
+
+ CHANNEL_PDU_HEADER* header = (CHANNEL_PDU_HEADER*)tracker->buffer;
+ if (header->length > CHANNEL_CHUNK_LENGTH)
+ {
+ WLog_ERR(TAG, "chunk size %" PRIu32 " is too big", header->length);
+ return nullptr;
+ }
+
+ if (recvSz - sizeof(CHANNEL_PDU_HEADER) < header->length)
+ {
+ WLog_ERR(TAG, "chunk size %" PRIu32 " is too big", header->length);
+ return nullptr;
+ }
+
+ tracker->writePtr = tracker->buffer;
+ if (header->flags & CHANNEL_FLAG_FIRST)
+ Stream_ResetPosition(tracker->currentPacket);
+
+ if (!Stream_EnsureRemainingCapacity(tracker->currentPacket, header->length))
+ return nullptr;
+
+ Stream_Write(tracker->currentPacket, header + 1, header->length);
+
+ if (header->flags & CHANNEL_FLAG_LAST)
+ {
+ Stream_SealLength(tracker->currentPacket);
+ Stream_ResetPosition(tracker->currentPacket);
+ wStream* ret = tracker->currentPacket;
+
+ tracker->currentPacket = Stream_New(nullptr, 4096);
+ if (!tracker->currentPacket)
+ {
+ Stream_Release(ret);
+ WLog_ERR(TAG, "error allocating new currentPacket");
+ return nullptr;
+ }
+ }
+
+ *ok = TRUE;
+ return nullptr;
+}
+
+void ChannelPduTracker_free(ChannelPduTracker* tracker)
+{
+ if (!tracker)
+ return;
+
+ Stream_Release(tracker->currentPacket);
+ free(tracker);
+}
+
+ChannelPduTracker* ChannelPduTracker_new(HANDLE vc)
+{
+ ChannelPduTracker* ret = calloc(1, sizeof(*ret));
+ if (!ret)
+ return nullptr;
+
+ ret->currentPacket = Stream_New(nullptr, 4096);
+ if (!ret->currentPacket)
+ {
+ free(ret);
+ return nullptr;
+ }
+ ret->vc = vc;
+ ret->writePtr = ret->buffer;
+ return ret;
+}
--
2.54.0

View File

@ -0,0 +1,26 @@
From 8223c5a0abdf4d5eae40c66e1329d62766578ce1 Mon Sep 17 00:00:00 2001
From: Sayed Kaif <metsw24@gmail.com>
Date: Wed, 24 Jun 2026 17:44:53 +0530
Subject: [PATCH] [channels,rdpdr] fix off-by-one scan length in
rdpdr_read_ustring
---
channels/rdpdr/server/rdpdr_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/channels/rdpdr/server/rdpdr_main.c b/channels/rdpdr/server/rdpdr_main.c
index bbb3b7e..62e5b90 100644
--- a/channels/rdpdr/server/rdpdr_main.c
+++ b/channels/rdpdr/server/rdpdr_main.c
@@ -222,7 +222,7 @@ static UINT32 g_ClientId = 0;
static const WCHAR* rdpdr_read_ustring(wLog* log, wStream* s, size_t bytelen)
{
- const size_t charlen = (bytelen + 1) / sizeof(WCHAR);
+ const size_t charlen = bytelen / sizeof(WCHAR);
const WCHAR* str = Stream_ConstPointer(s);
if (!Stream_CheckAndLogRequiredLengthWLog(log, s, bytelen))
return nullptr;
--
2.54.0

View File

@ -30,7 +30,7 @@
Name: freerdp
Epoch: 2
Version: 3.27.1
Release: 1%{?dist}
Release: 2%{?dist}
Summary: Free implementation of the Remote Desktop Protocol (RDP)
# The effective license is Apache-2.0 but:
@ -47,6 +47,24 @@ URL: http://www.freerdp.com/
Source0: FreeRDP-%{version}-repack.tar.gz
Source1: freerdp_download_and_repack.sh
# Add RDP Smart Card Redirection Support
# https://github.com/FreeRDP/FreeRDP/pull/12928
# https://github.com/FreeRDP/FreeRDP/commit/2a6d532a1d633145ae48e55ae471f2f2d8e56157
# https://github.com/FreeRDP/FreeRDP/commit/5cdd46594ec792715a00ecb935dcac7aadde0fc3
# https://github.com/FreeRDP/FreeRDP/commit/2e1113794d125269f30fc7acf4d0f3d898d74d52
# https://github.com/FreeRDP/FreeRDP/commit/8223c5a0abdf4d5eae40c66e1329d62766578ce1
# https://github.com/FreeRDP/FreeRDP/commit/1a15128e93705d5680c84b9e6368245d247fe893
# https://github.com/FreeRDP/FreeRDP/commit/a41855e88ea37b92a4d506430bce4b09e76f7d76
# https://github.com/FreeRDP/FreeRDP/pull/12756
Patch: channels-handle-CHANNEL_OPTION_SHOW_PROTOCOL-server-.patch
Patch: utils-smartcard-validate-cbAtr-against-rgbAtr-size-o.patch
Patch: utils-scard-fix-endianess-issue.patch
Patch: utils-test-replace-NULL.patch
Patch: channels-rdpdr-fix-off-by-one-scan-length-in-rdpdr_r.patch
Patch: utils-pdu-tracker-clean-up-warnings-fix-poll-return.patch
Patch: utils-pdu-tracker-increase-capacity-for-fragmented-P.patch
Patch: support-rdpdr-device-control-responses.patch
BuildRequires: gcc
BuildRequires: gcc-c++
BuildRequires: alsa-lib-devel
@ -378,6 +396,10 @@ find %{buildroot} -name "*.a" -delete
%{_libdir}/pkgconfig/winpr-tools3.pc
%changelog
* Wed Jul 01 2026 Ondrej Holy <oholy@redhat.com> - 2:3.27.1-2
- Add RDP Smart Card Redirection Support
Resolves: RHEL-171999
* Wed Jul 01 2026 Ondrej Holy <oholy@redhat.com> - 2:3.27.1-1
- Update to 3.27.1
Resolves: RHEL-186097, RHEL-186938, RHEL-186960, RHEL-186965, RHEL-186980

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,156 @@
From 1a15128e93705d5680c84b9e6368245d247fe893 Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Mon, 29 Jun 2026 10:23:37 +0200
Subject: [PATCH] [utils,pdu-tracker] clean up warnings, fix poll return
---
libfreerdp/utils/channel_pdu_tracker.c | 87 +++++++++++++++-----------
1 file changed, 52 insertions(+), 35 deletions(-)
diff --git a/libfreerdp/utils/channel_pdu_tracker.c b/libfreerdp/utils/channel_pdu_tracker.c
index 18e9aab..2b46a98 100644
--- a/libfreerdp/utils/channel_pdu_tracker.c
+++ b/libfreerdp/utils/channel_pdu_tracker.c
@@ -21,16 +21,18 @@
#include <winpr/wtsapi.h>
+#include <freerdp/log.h>
+#define TTAG FREERDP_TAG("utils.ChannelPduTracker")
+
struct ChannelPduTracker
{
HANDLE vc;
wStream* currentPacket;
- BYTE buffer[CHANNEL_PDU_LENGTH];
- BYTE* writePtr;
+ char buffer[CHANNEL_PDU_LENGTH];
+ size_t offset;
+ wLog* log;
};
-#define TAG "ChannelPduTracker"
-
wStream* ChannelPduTracker_poll(ChannelPduTracker* tracker, BOOL* ok)
{
WINPR_ASSERT(tracker);
@@ -38,58 +40,70 @@ wStream* ChannelPduTracker_poll(ChannelPduTracker* tracker, BOOL* ok)
ULONG sz = 0;
*ok = FALSE;
- const ULONG readSz =
- WINPR_ASSERTING_INT_CAST(ULONG, CHANNEL_PDU_LENGTH - (tracker->writePtr - tracker->buffer));
- if (!WTSVirtualChannelRead(tracker->vc, INFINITE, (char*)tracker->writePtr, readSz, &sz))
+
+ WINPR_ASSERT(tracker->offset <= CHANNEL_PDU_LENGTH);
+ const ULONG readSz = WINPR_ASSERTING_INT_CAST(ULONG, CHANNEL_PDU_LENGTH - tracker->offset);
+ if (!WTSVirtualChannelRead(tracker->vc, INFINITE, &tracker->buffer[tracker->offset], readSz,
+ &sz))
return nullptr;
- tracker->writePtr += sz;
- const size_t recvSz = (tracker->writePtr - tracker->buffer);
+ tracker->offset += sz;
+ WINPR_ASSERT(tracker->offset <= CHANNEL_PDU_LENGTH);
+
+ const size_t recvSz = tracker->offset;
if (recvSz < sizeof(CHANNEL_PDU_HEADER))
{
*ok = TRUE;
return nullptr;
}
- CHANNEL_PDU_HEADER* header = (CHANNEL_PDU_HEADER*)tracker->buffer;
+ const CHANNEL_PDU_HEADER* header = (const CHANNEL_PDU_HEADER*)tracker->buffer;
if (header->length > CHANNEL_CHUNK_LENGTH)
{
- WLog_ERR(TAG, "chunk size %" PRIu32 " is too big", header->length);
+ WLog_Print(tracker->log, WLOG_ERROR, "chunk size %" PRIu32 " is too big", header->length);
return nullptr;
}
- if (recvSz - sizeof(CHANNEL_PDU_HEADER) < header->length)
+ const size_t actual = recvSz - sizeof(CHANNEL_PDU_HEADER);
+ if (actual != header->length)
{
- WLog_ERR(TAG, "chunk size %" PRIu32 " is too big", header->length);
+ WLog_Print(tracker->log, WLOG_ERROR,
+ "Expected chunk size %" PRIu32 " does not match received data size %" PRIuz,
+ header->length, actual);
return nullptr;
}
- tracker->writePtr = tracker->buffer;
- if (header->flags & CHANNEL_FLAG_FIRST)
+ tracker->offset = 0;
+ if ((header->flags & CHANNEL_FLAG_FIRST) != 0)
Stream_ResetPosition(tracker->currentPacket);
- if (!Stream_EnsureRemainingCapacity(tracker->currentPacket, header->length))
+ if (!Stream_CheckAndLogRequiredCapacityWLog(tracker->log, tracker->currentPacket,
+ header->length))
return nullptr;
- Stream_Write(tracker->currentPacket, header + 1, header->length);
+ Stream_Write(tracker->currentPacket, &tracker->buffer[sizeof(CHANNEL_PDU_HEADER)],
+ header->length);
- if (header->flags & CHANNEL_FLAG_LAST)
+ if ((header->flags & CHANNEL_FLAG_LAST) == 0)
{
- Stream_SealLength(tracker->currentPacket);
- Stream_ResetPosition(tracker->currentPacket);
- wStream* ret = tracker->currentPacket;
-
- tracker->currentPacket = Stream_New(nullptr, 4096);
- if (!tracker->currentPacket)
- {
- Stream_Release(ret);
- WLog_ERR(TAG, "error allocating new currentPacket");
- return nullptr;
- }
+ *ok = TRUE;
+ return nullptr;
+ }
+
+ Stream_SealLength(tracker->currentPacket);
+ Stream_ResetPosition(tracker->currentPacket);
+ wStream* ret = tracker->currentPacket;
+
+ tracker->currentPacket = Stream_New(nullptr, 4096);
+ if (!tracker->currentPacket)
+ {
+ Stream_Release(ret);
+ WLog_Print(tracker->log, WLOG_ERROR, "error allocating new currentPacket");
+ return nullptr;
}
*ok = TRUE;
- return nullptr;
+ return ret;
}
void ChannelPduTracker_free(ChannelPduTracker* tracker)
@@ -109,11 +123,14 @@ ChannelPduTracker* ChannelPduTracker_new(HANDLE vc)
ret->currentPacket = Stream_New(nullptr, 4096);
if (!ret->currentPacket)
- {
- free(ret);
- return nullptr;
- }
+ goto fail;
+
ret->vc = vc;
- ret->writePtr = ret->buffer;
+ ret->log = WLog_Get(TTAG);
+ if (!ret->log)
+ goto fail;
return ret;
+fail:
+ ChannelPduTracker_free(ret);
+ return nullptr;
}
--
2.54.0

View File

@ -0,0 +1,26 @@
From a41855e88ea37b92a4d506430bce4b09e76f7d76 Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Mon, 29 Jun 2026 15:50:18 +0200
Subject: [PATCH] [utils,pdu-tracker] increase capacity for fragmented PDU
---
libfreerdp/utils/channel_pdu_tracker.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/libfreerdp/utils/channel_pdu_tracker.c b/libfreerdp/utils/channel_pdu_tracker.c
index 2b46a98..63c112d 100644
--- a/libfreerdp/utils/channel_pdu_tracker.c
+++ b/libfreerdp/utils/channel_pdu_tracker.c
@@ -77,8 +77,7 @@ wStream* ChannelPduTracker_poll(ChannelPduTracker* tracker, BOOL* ok)
if ((header->flags & CHANNEL_FLAG_FIRST) != 0)
Stream_ResetPosition(tracker->currentPacket);
- if (!Stream_CheckAndLogRequiredCapacityWLog(tracker->log, tracker->currentPacket,
- header->length))
+ if (!Stream_EnsureRemainingCapacity(tracker->currentPacket, header->length))
return nullptr;
Stream_Write(tracker->currentPacket, &tracker->buffer[sizeof(CHANNEL_PDU_HEADER)],
--
2.54.0

View File

@ -0,0 +1,36 @@
From 2a6d532a1d633145ae48e55ae471f2f2d8e56157 Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Fri, 19 Jun 2026 20:15:15 +0200
Subject: [PATCH] [utils,scard] fix endianess issue
---
libfreerdp/utils/smartcard_pack.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/libfreerdp/utils/smartcard_pack.c b/libfreerdp/utils/smartcard_pack.c
index bb977c4..83d1fbd 100644
--- a/libfreerdp/utils/smartcard_pack.c
+++ b/libfreerdp/utils/smartcard_pack.c
@@ -319,14 +319,15 @@ static LONG smartcard_ndr_read_atrmask(wLog* log, wStream* s, LocateCards_ATRMas
/* [MS-RDPESC] 2.2.1.5: cbAtr is range(0..36), the number of valid bytes in the fixed
* rgbAtr/rgbMask arrays. A larger value walks past them when the mask is compared byte
* by byte, so reject it here rather than trusting the wire value. */
- const LocateCards_ATRMask* masks = *data;
+ LocateCards_ATRMask* masks = *data;
for (size_t x = 0; x < min; x++)
{
- if (masks[x].cbAtr > ARRAYSIZE(masks[x].rgbAtr))
+ const UINT32 val = winpr_Data_Get_UINT32(&masks[x].cbAtr);
+ if (val > ARRAYSIZE(masks[x].rgbAtr))
{
WLog_Print(log, WLOG_ERROR,
- "LocateCards_ATRMask[%" PRIuz "]::cbAtr %" PRIu32 " exceeds %" PRIuz, x,
- masks[x].cbAtr, (size_t)ARRAYSIZE(masks[x].rgbAtr));
+ "LocateCards_ATRMask[%" PRIuz "]::cbAtr %" PRIu32 " exceeds %" PRIuz, x, val,
+ (size_t)ARRAYSIZE(masks[x].rgbAtr));
free(*data);
*data = nullptr;
return STATUS_DATA_ERROR;
--
2.54.0

View File

@ -0,0 +1,189 @@
From ececc5cbd0646ef012ca178322e6ef922a11f0c1 Mon Sep 17 00:00:00 2001
From: metsw24-max <metsw24@gmail.com>
Date: Fri, 19 Jun 2026 12:32:01 +0530
Subject: [PATCH] [utils,smartcard] validate cbAtr against rgbAtr size on
unpack
---
libfreerdp/utils/smartcard_pack.c | 36 ++++++++-
libfreerdp/utils/test/CMakeLists.txt | 2 +-
libfreerdp/utils/test/TestSmartcardPack.c | 93 +++++++++++++++++++++++
3 files changed, 129 insertions(+), 2 deletions(-)
create mode 100644 libfreerdp/utils/test/TestSmartcardPack.c
diff --git a/libfreerdp/utils/smartcard_pack.c b/libfreerdp/utils/smartcard_pack.c
index d8f9cab..bb977c4 100644
--- a/libfreerdp/utils/smartcard_pack.c
+++ b/libfreerdp/utils/smartcard_pack.c
@@ -312,7 +312,27 @@ static LONG smartcard_ndr_read_atrmask(wLog* log, wStream* s, LocateCards_ATRMas
BYTE** ppv;
} u;
u.ppc = data;
- return smartcard_ndr_read(log, s, u.ppv, min, sizeof(LocateCards_ATRMask), type);
+ const LONG status = smartcard_ndr_read(log, s, u.ppv, min, sizeof(LocateCards_ATRMask), type);
+ if (status != SCARD_S_SUCCESS)
+ return status;
+
+ /* [MS-RDPESC] 2.2.1.5: cbAtr is range(0..36), the number of valid bytes in the fixed
+ * rgbAtr/rgbMask arrays. A larger value walks past them when the mask is compared byte
+ * by byte, so reject it here rather than trusting the wire value. */
+ const LocateCards_ATRMask* masks = *data;
+ for (size_t x = 0; x < min; x++)
+ {
+ if (masks[x].cbAtr > ARRAYSIZE(masks[x].rgbAtr))
+ {
+ WLog_Print(log, WLOG_ERROR,
+ "LocateCards_ATRMask[%" PRIuz "]::cbAtr %" PRIu32 " exceeds %" PRIuz, x,
+ masks[x].cbAtr, (size_t)ARRAYSIZE(masks[x].rgbAtr));
+ free(*data);
+ *data = nullptr;
+ return STATUS_DATA_ERROR;
+ }
+ }
+ return SCARD_S_SUCCESS;
}
static LONG smartcard_ndr_read_fixed_string_a(wLog* log, wStream* s, CHAR** data, size_t min,
@@ -2454,6 +2474,13 @@ static LONG smartcard_unpack_reader_state_a(wLog* log, wStream* s, LPSCARD_READE
Stream_Read_UINT32(s, readerState->dwCurrentState); /* dwCurrentState (4 bytes) */
Stream_Read_UINT32(s, readerState->dwEventState); /* dwEventState (4 bytes) */
Stream_Read_UINT32(s, readerState->cbAtr); /* cbAtr (4 bytes) */
+ if (readerState->cbAtr > ARRAYSIZE(readerState->rgbAtr))
+ {
+ WLog_Print(log, WLOG_ERROR,
+ "SCARD_READERSTATEA[%" PRIu32 "]::cbAtr %" PRIu32 " exceeds %" PRIuz, index,
+ readerState->cbAtr, (size_t)ARRAYSIZE(readerState->rgbAtr));
+ goto fail;
+ }
Stream_Read(s, readerState->rgbAtr, 36); /* rgbAtr [0..36] (36 bytes) */
}
@@ -2528,6 +2555,13 @@ static LONG smartcard_unpack_reader_state_w(wLog* log, wStream* s, LPSCARD_READE
Stream_Read_UINT32(s, readerState->dwCurrentState); /* dwCurrentState (4 bytes) */
Stream_Read_UINT32(s, readerState->dwEventState); /* dwEventState (4 bytes) */
Stream_Read_UINT32(s, readerState->cbAtr); /* cbAtr (4 bytes) */
+ if (readerState->cbAtr > ARRAYSIZE(readerState->rgbAtr))
+ {
+ WLog_Print(log, WLOG_ERROR,
+ "SCARD_READERSTATEW[%" PRIu32 "]::cbAtr %" PRIu32 " exceeds %" PRIuz, index,
+ readerState->cbAtr, (size_t)ARRAYSIZE(readerState->rgbAtr));
+ goto fail;
+ }
Stream_Read(s, readerState->rgbAtr, 36); /* rgbAtr [0..36] (36 bytes) */
}
diff --git a/libfreerdp/utils/test/CMakeLists.txt b/libfreerdp/utils/test/CMakeLists.txt
index b90c5f4..0c2bf84 100644
--- a/libfreerdp/utils/test/CMakeLists.txt
+++ b/libfreerdp/utils/test/CMakeLists.txt
@@ -5,7 +5,7 @@ disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR})
set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
-set(${MODULE_PREFIX}_TESTS TestRingBuffer.c TestPodArrays.c TestEncodedTypes.c)
+set(${MODULE_PREFIX}_TESTS TestRingBuffer.c TestPodArrays.c TestEncodedTypes.c TestSmartcardPack.c)
create_test_sourcelist(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_DRIVER} ${${MODULE_PREFIX}_TESTS})
diff --git a/libfreerdp/utils/test/TestSmartcardPack.c b/libfreerdp/utils/test/TestSmartcardPack.c
new file mode 100644
index 0000000..8c04600
--- /dev/null
+++ b/libfreerdp/utils/test/TestSmartcardPack.c
@@ -0,0 +1,93 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <winpr/stream.h>
+#include <freerdp/channels/scard.h>
+#include <freerdp/utils/smartcard_pack.h>
+
+/* Build a LocateCardsByATRA call (cbContext=0, cAtrs=1, cReaders=0) holding a single
+ * LocateCards_ATRMask whose cbAtr is set to the requested value. */
+static wStream* build_locate_cards_by_atr_a(UINT32 cbAtr)
+{
+ wStream* s = Stream_New(NULL, 24 + 8 + sizeof(LocateCards_ATRMask));
+ if (!s)
+ return NULL;
+
+ Stream_Write_UINT32(s, 0); /* cbContext = 0 */
+ Stream_Write_UINT32(s, 0); /* pbContextNdrPtr = 0 */
+ Stream_Write_UINT32(s, 1); /* cAtrs = 1 */
+ Stream_Write_UINT32(s, 0x00020000); /* rgAtrMasksNdrPtr */
+ Stream_Write_UINT32(s, 0); /* cReaders = 0 */
+ Stream_Write_UINT32(s, 0); /* rgReaderStatesNdrPtr = 0 */
+ Stream_Write_UINT32(s, 1); /* conformant count = cAtrs */
+ Stream_Write_UINT32(s, cbAtr); /* LocateCards_ATRMask::cbAtr */
+ Stream_Zero(s, 36); /* rgbAtr[36] */
+ Stream_Zero(s, 36); /* rgbMask[36] */
+
+ Stream_SealLength(s);
+ if (!Stream_SetPosition(s, 0))
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+ return s;
+}
+
+static BOOL run_case(UINT32 cbAtr, BOOL expectAccept)
+{
+ wStream* s = build_locate_cards_by_atr_a(cbAtr);
+ if (!s)
+ return FALSE;
+
+ LocateCardsByATRA_Call call = { 0 };
+ const LONG status = smartcard_unpack_locate_cards_by_atr_a_call(s, &call);
+ Stream_Free(s, TRUE);
+
+ const BOOL accepted = (status == SCARD_S_SUCCESS) ? TRUE : FALSE;
+ if (accepted != expectAccept)
+ {
+ printf("cbAtr=%" PRIu32 ": expected %s, unpack returned 0x%08" PRIX32 "\n", cbAtr,
+ expectAccept ? "accept" : "reject", (UINT32)status);
+ free(call.rgAtrMasks);
+ return FALSE;
+ }
+
+ free(call.rgAtrMasks);
+ return TRUE;
+}
+
+int TestSmartcardPack(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* Valid: cbAtr within the fixed 36-byte rgbAtr/rgbMask arrays. */
+ if (!run_case(0, TRUE))
+ return -1;
+ if (!run_case(36, TRUE))
+ return -1;
+
+ /* Out of range: cbAtr larger than the buffers would later overrun them. */
+ if (!run_case(37, FALSE))
+ return -1;
+ if (!run_case(0xFFFFFFFF, FALSE))
+ return -1;
+
+ return 0;
+}
--
2.54.0

View File

@ -0,0 +1,46 @@
From 5cdd46594ec792715a00ecb935dcac7aadde0fc3 Mon Sep 17 00:00:00 2001
From: Armin Novak <armin.novak@thincast.com>
Date: Fri, 19 Jun 2026 20:58:44 +0200
Subject: [PATCH] [utils,test] replace NULL
---
libfreerdp/utils/test/TestSmartcardPack.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/libfreerdp/utils/test/TestSmartcardPack.c b/libfreerdp/utils/test/TestSmartcardPack.c
index 8c04600..4c465f1 100644
--- a/libfreerdp/utils/test/TestSmartcardPack.c
+++ b/libfreerdp/utils/test/TestSmartcardPack.c
@@ -25,9 +25,9 @@
* LocateCards_ATRMask whose cbAtr is set to the requested value. */
static wStream* build_locate_cards_by_atr_a(UINT32 cbAtr)
{
- wStream* s = Stream_New(NULL, 24 + 8 + sizeof(LocateCards_ATRMask));
+ wStream* s = Stream_New(nullptr, 24 + 8 + sizeof(LocateCards_ATRMask));
if (!s)
- return NULL;
+ return nullptr;
Stream_Write_UINT32(s, 0); /* cbContext = 0 */
Stream_Write_UINT32(s, 0); /* pbContextNdrPtr = 0 */
@@ -44,7 +44,7 @@ static wStream* build_locate_cards_by_atr_a(UINT32 cbAtr)
if (!Stream_SetPosition(s, 0))
{
Stream_Free(s, TRUE);
- return NULL;
+ return nullptr;
}
return s;
}
@@ -59,7 +59,7 @@ static BOOL run_case(UINT32 cbAtr, BOOL expectAccept)
const LONG status = smartcard_unpack_locate_cards_by_atr_a_call(s, &call);
Stream_Free(s, TRUE);
- const BOOL accepted = (status == SCARD_S_SUCCESS) ? TRUE : FALSE;
+ const BOOL accepted = status == SCARD_S_SUCCESS;
if (accepted != expectAccept)
{
printf("cbAtr=%" PRIu32 ": expected %s, unpack returned 0x%08" PRIX32 "\n", cbAtr,
--
2.54.0