mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-07-12 10:14:56 +03:00
harden the format-data path against late/duplicate responses
The format-data rendezvous had the same single-slot race the file-contents path just fixed: the channel thread rewrote clipboard->hmem with no lock while explorer-thread consumers read/freed it, nothing serialized concurrent requests, and no flag told an expected response from a stray one. - Add format_request_mutex (serializes the whole request/response cycle) and hmem_mutex (guards the hmem hand-off and formatDataRespExpected). - cliprdr_send_data_request now takes ownership of the response buffer under hmem_mutex and returns it to the caller, so a later response cannot touch a buffer a consumer is using. All three consumers (GetData, WM_RENDERFORMAT, DELAYED_RENDERING) and the WM_CLIPBOARDUPDATE cleanup use the returned/taken handle instead of the shared slot. - The response handler drops any response arriving while formatDataRespExpected is clear (late/duplicate/unsolicited), consumes the flag on the first response, and no longer dereferences a NULL clipboard in the SetEvent path. Pre-existing issue, not introduced by this branch; generalizes the file-contents hardening to the format-data path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -283,6 +283,14 @@ struct wf_clipboard
|
|||||||
HANDLE thread;
|
HANDLE thread;
|
||||||
HANDLE formatDataRespEvent;
|
HANDLE formatDataRespEvent;
|
||||||
BOOL formatDataRespReceived;
|
BOOL formatDataRespReceived;
|
||||||
|
// Format-data rendezvous, mirroring the req_f* fields. format_request_mutex
|
||||||
|
// serializes whole request/response cycles; hmem_mutex guards the hmem hand-off
|
||||||
|
// and formatDataRespExpected, which lets the response handler drop a late,
|
||||||
|
// duplicate or unsolicited response instead of overwriting the slot a consumer
|
||||||
|
// is about to take.
|
||||||
|
BOOL formatDataRespExpected;
|
||||||
|
HANDLE hmem_mutex;
|
||||||
|
HANDLE format_request_mutex;
|
||||||
|
|
||||||
LPDATAOBJECT data_obj;
|
LPDATAOBJECT data_obj;
|
||||||
HANDLE data_obj_mutex;
|
HANDLE data_obj_mutex;
|
||||||
@@ -334,7 +342,8 @@ BOOL wf_do_empty_cliprdr(wfClipboard *clipboard);
|
|||||||
static BOOL wf_create_file_obj(UINT32 *connID, wfClipboard *clipboard, IDataObject **ppDataObject);
|
static BOOL wf_create_file_obj(UINT32 *connID, wfClipboard *clipboard, IDataObject **ppDataObject);
|
||||||
static void wf_destroy_file_obj(IDataObject *instance);
|
static void wf_destroy_file_obj(IDataObject *instance);
|
||||||
static UINT32 get_remote_format_id(wfClipboard *clipboard, UINT32 local_format);
|
static UINT32 get_remote_format_id(wfClipboard *clipboard, UINT32 local_format);
|
||||||
static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UINT32 format);
|
static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UINT32 formatId,
|
||||||
|
HANDLE *outHmem, SIZE_T *outLen);
|
||||||
static UINT cliprdr_send_lock(wfClipboard *clipboard);
|
static UINT cliprdr_send_lock(wfClipboard *clipboard);
|
||||||
static UINT cliprdr_send_unlock(wfClipboard *clipboard);
|
static UINT cliprdr_send_unlock(wfClipboard *clipboard);
|
||||||
static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, UINT32 streamId,
|
static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, UINT32 streamId,
|
||||||
@@ -443,6 +452,23 @@ static void take_req_fdata(wfClipboard *clipboard, char **data, ULONG *size)
|
|||||||
ReleaseMutex(clipboard->req_f_mutex);
|
ReleaseMutex(clipboard->req_f_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Take ownership of the pending format-data buffer under hmem_mutex. The caller owns
|
||||||
|
// the returned HGLOBAL and must GlobalFree it (or hand it to the clipboard / OLE).
|
||||||
|
static void take_hmem(wfClipboard *clipboard, HANDLE *hmem, SIZE_T *len)
|
||||||
|
{
|
||||||
|
if (!lock_mutex(clipboard->hmem_mutex, INFINITE))
|
||||||
|
{
|
||||||
|
*hmem = NULL;
|
||||||
|
*len = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*hmem = clipboard->hmem;
|
||||||
|
*len = clipboard->hmem_data_len;
|
||||||
|
clipboard->hmem = NULL;
|
||||||
|
clipboard->hmem_data_len = 0;
|
||||||
|
ReleaseMutex(clipboard->hmem_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
// Serialize whole file-contents request/response cycles (request, wait, take) across
|
// Serialize whole file-contents request/response cycles (request, wait, take) across
|
||||||
// streams, enforcing the one-outstanding-request invariant of the req_f* slot. The
|
// streams, enforcing the one-outstanding-request invariant of the req_f* slot. The
|
||||||
// wait is bounded so a wedged request degrades into a failed read instead of blocking
|
// wait is bounded so a wedged request degrades into a failed read instead of blocking
|
||||||
@@ -830,18 +856,16 @@ static void wf_cliprdr_reset_streams(CliprdrDataObject *instance)
|
|||||||
instance->m_nStreams = 0;
|
instance->m_nStreams = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only call after clipboard->hmem has been locked by GlobalLock. */
|
/* Only call after hMem has been locked by GlobalLock; takes ownership and frees it. */
|
||||||
static HRESULT wf_cliprdr_fail_locked_file_descriptor_data(wfClipboard *clipboard,
|
static HRESULT wf_cliprdr_fail_locked_file_descriptor_data(HANDLE hMem,
|
||||||
STGMEDIUM *medium,
|
STGMEDIUM *medium,
|
||||||
CliprdrDataObject *instance,
|
CliprdrDataObject *instance,
|
||||||
IStream **streams,
|
IStream **streams,
|
||||||
ULONG stream_count,
|
ULONG stream_count,
|
||||||
HRESULT error)
|
HRESULT error)
|
||||||
{
|
{
|
||||||
GlobalUnlock(clipboard->hmem);
|
GlobalUnlock(hMem);
|
||||||
GlobalFree(clipboard->hmem);
|
GlobalFree(hMem);
|
||||||
clipboard->hmem = NULL;
|
|
||||||
clipboard->hmem_data_len = 0;
|
|
||||||
medium->hGlobal = NULL;
|
medium->hGlobal = NULL;
|
||||||
wf_cliprdr_release_streams(streams, stream_count);
|
wf_cliprdr_release_streams(streams, stream_count);
|
||||||
wf_cliprdr_reset_streams(instance);
|
wf_cliprdr_reset_streams(instance);
|
||||||
@@ -968,49 +992,45 @@ static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetData(IDataObject *This, FO
|
|||||||
FILEGROUPDESCRIPTORW *dsc;
|
FILEGROUPDESCRIPTORW *dsc;
|
||||||
IStream **streams = NULL;
|
IStream **streams = NULL;
|
||||||
UINT stream_count = 0;
|
UINT stream_count = 0;
|
||||||
SIZE_T hmem_size;
|
SIZE_T hmem_size = 0;
|
||||||
// DWORD remote_format_id = get_remote_format_id(clipboard, instance->m_pFormatEtc[idx].cfFormat);
|
HANDLE hMem = NULL;
|
||||||
// FIXME: origin code may be failed here???
|
if (cliprdr_send_data_request(instance->m_connID, clipboard,
|
||||||
if (cliprdr_send_data_request(instance->m_connID, clipboard, instance->m_pFormatEtc[idx].cfFormat) != 0)
|
instance->m_pFormatEtc[idx].cfFormat, &hMem, &hmem_size) != 0)
|
||||||
{
|
{
|
||||||
return E_UNEXPECTED;
|
return E_UNEXPECTED;
|
||||||
}
|
}
|
||||||
if (!clipboard->hmem)
|
if (!hMem)
|
||||||
{
|
{
|
||||||
return E_UNEXPECTED;
|
return E_UNEXPECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
pMedium->hGlobal = clipboard->hmem; /* points to a FILEGROUPDESCRIPTOR structure */
|
pMedium->hGlobal = hMem; /* points to a FILEGROUPDESCRIPTOR structure */
|
||||||
/* GlobalLock returns a pointer to the first byte of the memory block,
|
/* GlobalLock returns a pointer to the first byte of the memory block,
|
||||||
* in which is a FILEGROUPDESCRIPTOR structure, whose first UINT member
|
* in which is a FILEGROUPDESCRIPTOR structure, whose first UINT member
|
||||||
* is the number of FILEDESCRIPTOR's */
|
* is the number of FILEDESCRIPTOR's */
|
||||||
// dsc = (FILEGROUPDESCRIPTOR *)GlobalLock(clipboard->hmem);
|
dsc = (FILEGROUPDESCRIPTORW *)GlobalLock(hMem);
|
||||||
dsc = (FILEGROUPDESCRIPTORW *)GlobalLock(clipboard->hmem);
|
|
||||||
if (!dsc)
|
if (!dsc)
|
||||||
{
|
{
|
||||||
pMedium->hGlobal = NULL;
|
pMedium->hGlobal = NULL;
|
||||||
GlobalFree(clipboard->hmem);
|
GlobalFree(hMem);
|
||||||
clipboard->hmem = NULL;
|
|
||||||
clipboard->hmem_data_len = 0;
|
|
||||||
wf_cliprdr_reset_streams(instance);
|
wf_cliprdr_reset_streams(instance);
|
||||||
return E_UNEXPECTED;
|
return E_UNEXPECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
hmem_size = clipboard->hmem_data_len;
|
|
||||||
/* cItems is remote-controlled; verify the fixed header exists before reading it. */
|
/* cItems is remote-controlled; verify the fixed header exists before reading it. */
|
||||||
if (hmem_size < offsetof(FILEGROUPDESCRIPTORW, fgd))
|
if (hmem_size < offsetof(FILEGROUPDESCRIPTORW, fgd))
|
||||||
return wf_cliprdr_fail_locked_file_descriptor_data(
|
return wf_cliprdr_fail_locked_file_descriptor_data(
|
||||||
clipboard, pMedium, instance, NULL, 0, E_UNEXPECTED);
|
hMem, pMedium, instance, NULL, 0, E_UNEXPECTED);
|
||||||
|
|
||||||
stream_count = dsc->cItems;
|
stream_count = dsc->cItems;
|
||||||
if (!wf_cliprdr_file_group_descriptor_size_valid(hmem_size, stream_count))
|
if (!wf_cliprdr_file_group_descriptor_size_valid(hmem_size, stream_count))
|
||||||
return wf_cliprdr_fail_locked_file_descriptor_data(
|
return wf_cliprdr_fail_locked_file_descriptor_data(
|
||||||
clipboard, pMedium, instance, NULL, 0, E_UNEXPECTED);
|
hMem, pMedium, instance, NULL, 0, E_UNEXPECTED);
|
||||||
|
|
||||||
streams = (IStream **)calloc(stream_count, sizeof(IStream *));
|
streams = (IStream **)calloc(stream_count, sizeof(IStream *));
|
||||||
if (!streams)
|
if (!streams)
|
||||||
return wf_cliprdr_fail_locked_file_descriptor_data(
|
return wf_cliprdr_fail_locked_file_descriptor_data(
|
||||||
clipboard, pMedium, instance, NULL, 0, E_OUTOFMEMORY);
|
hMem, pMedium, instance, NULL, 0, E_OUTOFMEMORY);
|
||||||
|
|
||||||
for (i = 0; i < stream_count; i++)
|
for (i = 0; i < stream_count; i++)
|
||||||
{
|
{
|
||||||
@@ -1019,11 +1039,11 @@ static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetData(IDataObject *This, FO
|
|||||||
if (!streams[i])
|
if (!streams[i])
|
||||||
{
|
{
|
||||||
return wf_cliprdr_fail_locked_file_descriptor_data(
|
return wf_cliprdr_fail_locked_file_descriptor_data(
|
||||||
clipboard, pMedium, instance, streams, i, E_OUTOFMEMORY);
|
hMem, pMedium, instance, streams, i, E_OUTOFMEMORY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalUnlock(clipboard->hmem);
|
GlobalUnlock(hMem);
|
||||||
wf_cliprdr_reset_streams(instance);
|
wf_cliprdr_reset_streams(instance);
|
||||||
instance->m_pStream = streams;
|
instance->m_pStream = streams;
|
||||||
instance->m_nStreams = stream_count;
|
instance->m_nStreams = stream_count;
|
||||||
@@ -1870,35 +1890,80 @@ UINT wait_response_event(UINT32 connID, wfClipboard *clipboard, HANDLE event, BO
|
|||||||
return ERROR_INTERNAL_ERROR;
|
return ERROR_INTERNAL_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UINT32 formatId)
|
static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UINT32 formatId,
|
||||||
|
HANDLE *outHmem, SIZE_T *outLen)
|
||||||
{
|
{
|
||||||
UINT rc;
|
UINT rc;
|
||||||
UINT32 remoteFormatId;
|
UINT32 remoteFormatId;
|
||||||
CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest;
|
CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest;
|
||||||
|
DWORD timeoutMillis;
|
||||||
|
HANDLE hMem = NULL;
|
||||||
|
SIZE_T len = 0;
|
||||||
|
|
||||||
if (!clipboard || !clipboard->context || !clipboard->context->ClientFormatDataRequest)
|
*outHmem = NULL;
|
||||||
|
*outLen = 0;
|
||||||
|
|
||||||
|
if (!clipboard || !clipboard->context || !clipboard->context->ClientFormatDataRequest ||
|
||||||
|
!clipboard->format_request_mutex)
|
||||||
|
return ERROR_INTERNAL_ERROR;
|
||||||
|
|
||||||
|
// Serialize the whole request/response cycle so a second request cannot overwrite
|
||||||
|
// the shared hmem slot while this one is in flight.
|
||||||
|
timeoutMillis = (clipboard->context->ResponseWaitTimeoutSecs + 1) * 2000;
|
||||||
|
if (!lock_mutex(clipboard->format_request_mutex, timeoutMillis))
|
||||||
return ERROR_INTERNAL_ERROR;
|
return ERROR_INTERNAL_ERROR;
|
||||||
|
|
||||||
rc = try_reset_event(clipboard->formatDataRespEvent);
|
rc = try_reset_event(clipboard->formatDataRespEvent);
|
||||||
if (rc != ERROR_SUCCESS)
|
if (rc != ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
|
ReleaseMutex(clipboard->format_request_mutex);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
clipboard->formatDataRespReceived = FALSE;
|
|
||||||
|
|
||||||
remoteFormatId = get_remote_format_id(clipboard, formatId);
|
remoteFormatId = get_remote_format_id(clipboard, formatId);
|
||||||
|
|
||||||
formatDataRequest.connID = connID;
|
formatDataRequest.connID = connID;
|
||||||
formatDataRequest.requestedFormatId = remoteFormatId;
|
formatDataRequest.requestedFormatId = remoteFormatId;
|
||||||
clipboard->requestedFormatId = formatId;
|
clipboard->requestedFormatId = formatId;
|
||||||
|
|
||||||
|
// Mark a response as expected under hmem_mutex; the handler drops any response
|
||||||
|
// that arrives while this flag is clear.
|
||||||
|
if (!lock_mutex(clipboard->hmem_mutex, INFINITE))
|
||||||
|
{
|
||||||
|
ReleaseMutex(clipboard->format_request_mutex);
|
||||||
|
return ERROR_INTERNAL_ERROR;
|
||||||
|
}
|
||||||
|
clipboard->formatDataRespReceived = FALSE;
|
||||||
|
clipboard->formatDataRespExpected = TRUE;
|
||||||
|
ReleaseMutex(clipboard->hmem_mutex);
|
||||||
|
|
||||||
rc = clipboard->context->ClientFormatDataRequest(clipboard->context, &formatDataRequest);
|
rc = clipboard->context->ClientFormatDataRequest(clipboard->context, &formatDataRequest);
|
||||||
|
if (rc == ERROR_SUCCESS)
|
||||||
|
rc = wait_response_event(connID, clipboard, clipboard->formatDataRespEvent,
|
||||||
|
&clipboard->formatDataRespReceived, &clipboard->hmem, NULL);
|
||||||
|
|
||||||
|
// Take ownership of the response buffer and stop expecting a response, both under
|
||||||
|
// hmem_mutex, so a late/duplicate response can no longer touch the slot.
|
||||||
|
if (lock_mutex(clipboard->hmem_mutex, INFINITE))
|
||||||
|
{
|
||||||
|
hMem = clipboard->hmem;
|
||||||
|
len = clipboard->hmem_data_len;
|
||||||
|
clipboard->hmem = NULL;
|
||||||
|
clipboard->hmem_data_len = 0;
|
||||||
|
clipboard->formatDataRespExpected = FALSE;
|
||||||
|
ReleaseMutex(clipboard->hmem_mutex);
|
||||||
|
}
|
||||||
|
ReleaseMutex(clipboard->format_request_mutex);
|
||||||
|
|
||||||
if (rc != ERROR_SUCCESS)
|
if (rc != ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
|
if (hMem)
|
||||||
|
GlobalFree(hMem);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
return wait_response_event(connID, clipboard, clipboard->formatDataRespEvent,
|
*outHmem = hMem;
|
||||||
&clipboard->formatDataRespReceived, &clipboard->hmem, NULL);
|
*outLen = len;
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, UINT32 streamId, ULONG index,
|
UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, UINT32 streamId, ULONG index,
|
||||||
@@ -2012,11 +2077,13 @@ static LRESULT CALLBACK cliprdr_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM
|
|||||||
{
|
{
|
||||||
if (!is_set_by_instance(clipboard))
|
if (!is_set_by_instance(clipboard))
|
||||||
{
|
{
|
||||||
if (clipboard->hmem)
|
// Drop any stale format-data buffer under hmem_mutex so this does not
|
||||||
{
|
// race the channel thread's response handler.
|
||||||
GlobalFree(clipboard->hmem);
|
HANDLE stale = NULL;
|
||||||
clipboard->hmem = NULL;
|
SIZE_T stale_len = 0;
|
||||||
}
|
take_hmem(clipboard, &stale, &stale_len);
|
||||||
|
if (stale)
|
||||||
|
GlobalFree(stale);
|
||||||
|
|
||||||
cliprdr_send_format_list(clipboard, 0);
|
cliprdr_send_format_list(clipboard, 0);
|
||||||
}
|
}
|
||||||
@@ -2043,20 +2110,21 @@ static LRESULT CALLBACK cliprdr_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM
|
|||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/dataxchg/wm-renderformat?redirectedfrom=MSDN
|
// https://docs.microsoft.com/en-us/windows/win32/dataxchg/wm-renderformat?redirectedfrom=MSDN
|
||||||
// to-do: ensure usage of 0
|
// to-do: ensure usage of 0
|
||||||
if (cliprdr_send_data_request(0, clipboard, (UINT32)wParam) != 0)
|
|
||||||
{
|
{
|
||||||
DEBUG_CLIPRDR("error: cliprdr_send_data_request failed.");
|
HANDLE hMem = NULL;
|
||||||
break;
|
SIZE_T hmemLen = 0;
|
||||||
}
|
if (cliprdr_send_data_request(0, clipboard, (UINT32)wParam, &hMem, &hmemLen) != 0)
|
||||||
|
|
||||||
if (!SetClipboardData((UINT)wParam, clipboard->hmem))
|
|
||||||
{
|
|
||||||
DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError());
|
|
||||||
|
|
||||||
if (clipboard->hmem)
|
|
||||||
{
|
{
|
||||||
GlobalFree(clipboard->hmem);
|
DEBUG_CLIPRDR("error: cliprdr_send_data_request failed.");
|
||||||
clipboard->hmem = NULL;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetClipboardData((UINT)wParam, hMem))
|
||||||
|
{
|
||||||
|
DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError());
|
||||||
|
|
||||||
|
if (hMem)
|
||||||
|
GlobalFree(hMem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2139,22 +2207,22 @@ static LRESULT CALLBACK cliprdr_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM
|
|||||||
|
|
||||||
for (UINT32 i = 0; i < format_ids->size; ++i)
|
for (UINT32 i = 0; i < format_ids->size; ++i)
|
||||||
{
|
{
|
||||||
if (cliprdr_send_data_request(format_ids->connID, clipboard, format_ids->formats[i]) != 0)
|
HANDLE hMem = NULL;
|
||||||
|
SIZE_T hmemLen = 0;
|
||||||
|
if (cliprdr_send_data_request(format_ids->connID, clipboard,
|
||||||
|
format_ids->formats[i], &hMem, &hmemLen) != 0)
|
||||||
{
|
{
|
||||||
DEBUG_CLIPRDR("error: cliprdr_send_data_request failed.");
|
DEBUG_CLIPRDR("error: cliprdr_send_data_request failed.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SetClipboardData(format_ids->formats[i], clipboard->hmem))
|
if (!SetClipboardData(format_ids->formats[i], hMem))
|
||||||
{
|
{
|
||||||
printf("SetClipboardData failed with 0x%x\n", GetLastError());
|
printf("SetClipboardData failed with 0x%x\n", GetLastError());
|
||||||
DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError());
|
DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError());
|
||||||
|
|
||||||
if (clipboard->hmem)
|
if (hMem)
|
||||||
{
|
GlobalFree(hMem);
|
||||||
GlobalFree(clipboard->hmem);
|
|
||||||
clipboard->hmem = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3068,7 +3136,8 @@ wf_cliprdr_server_format_data_response(CliprdrClientContext *context,
|
|||||||
UINT rc = ERROR_INTERNAL_ERROR;
|
UINT rc = ERROR_INTERNAL_ERROR;
|
||||||
BYTE *data;
|
BYTE *data;
|
||||||
HANDLE hMem;
|
HANDLE hMem;
|
||||||
wfClipboard *clipboard;
|
wfClipboard *clipboard = NULL;
|
||||||
|
BOOL locked = FALSE;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -3084,13 +3153,33 @@ wf_cliprdr_server_format_data_response(CliprdrClientContext *context,
|
|||||||
rc = ERROR_INTERNAL_ERROR;
|
rc = ERROR_INTERNAL_ERROR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!lock_mutex(clipboard->hmem_mutex, INFINITE))
|
||||||
|
{
|
||||||
|
rc = ERROR_INTERNAL_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
locked = TRUE;
|
||||||
|
|
||||||
|
// Drop a response nobody is waiting for (late/duplicate/unsolicited) so it
|
||||||
|
// cannot overwrite the slot a consumer is about to take.
|
||||||
|
if (!clipboard->formatDataRespExpected)
|
||||||
|
{
|
||||||
|
ReleaseMutex(clipboard->hmem_mutex);
|
||||||
|
return CHANNEL_RC_OK;
|
||||||
|
}
|
||||||
|
// Consume the expectation so a duplicate response for the same request hits
|
||||||
|
// the check above instead of overwriting the slot before the consumer takes it.
|
||||||
|
clipboard->formatDataRespExpected = FALSE;
|
||||||
|
|
||||||
|
// Free any leftover buffer from a prior request that was never consumed.
|
||||||
|
if (clipboard->hmem)
|
||||||
|
GlobalFree(clipboard->hmem);
|
||||||
clipboard->hmem = NULL;
|
clipboard->hmem = NULL;
|
||||||
clipboard->hmem_data_len = 0;
|
clipboard->hmem_data_len = 0;
|
||||||
|
|
||||||
if (formatDataResponse->msgFlags != CB_RESPONSE_OK)
|
if (formatDataResponse->msgFlags != CB_RESPONSE_OK)
|
||||||
{
|
{
|
||||||
// BOOL emptyRes = wf_do_empty_cliprdr((wfClipboard *)context->custom);
|
|
||||||
// (void)emptyRes;
|
|
||||||
rc = E_FAIL;
|
rc = E_FAIL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -3124,7 +3213,10 @@ wf_cliprdr_server_format_data_response(CliprdrClientContext *context,
|
|||||||
rc = CHANNEL_RC_OK;
|
rc = CHANNEL_RC_OK;
|
||||||
} while (0);
|
} while (0);
|
||||||
|
|
||||||
if (!SetEvent(clipboard->formatDataRespEvent))
|
if (locked)
|
||||||
|
ReleaseMutex(clipboard->hmem_mutex);
|
||||||
|
|
||||||
|
if (clipboard && !SetEvent(clipboard->formatDataRespEvent))
|
||||||
{
|
{
|
||||||
// If failed to set event, set flag to indicate the event is received.
|
// If failed to set event, set flag to indicate the event is received.
|
||||||
DEBUG_CLIPRDR("wf_cliprdr_server_format_data_response(), SetEvent failed with 0x%x", GetLastError());
|
DEBUG_CLIPRDR("wf_cliprdr_server_format_data_response(), SetEvent failed with 0x%x", GetLastError());
|
||||||
@@ -3572,6 +3664,12 @@ BOOL wf_cliprdr_init(wfClipboard *clipboard, CliprdrClientContext *cliprdr)
|
|||||||
if (!(clipboard->req_f_request_mutex = CreateMutex(NULL, FALSE, NULL)))
|
if (!(clipboard->req_f_request_mutex = CreateMutex(NULL, FALSE, NULL)))
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
if (!(clipboard->hmem_mutex = CreateMutex(NULL, FALSE, NULL)))
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
if (!(clipboard->format_request_mutex = CreateMutex(NULL, FALSE, NULL)))
|
||||||
|
goto error;
|
||||||
|
|
||||||
if (!(clipboard->thread = CreateThread(NULL, 0, cliprdr_thread_func, clipboard, 0, NULL)))
|
if (!(clipboard->thread = CreateThread(NULL, 0, cliprdr_thread_func, clipboard, 0, NULL)))
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
@@ -3658,6 +3756,21 @@ BOOL wf_cliprdr_uninit(wfClipboard *clipboard, CliprdrClientContext *cliprdr)
|
|||||||
if (clipboard->req_f_request_mutex)
|
if (clipboard->req_f_request_mutex)
|
||||||
CloseHandle(clipboard->req_f_request_mutex);
|
CloseHandle(clipboard->req_f_request_mutex);
|
||||||
|
|
||||||
|
if (clipboard->hmem_mutex)
|
||||||
|
{
|
||||||
|
// Reclaim any format-data buffer no consumer took, so it is not leaked.
|
||||||
|
HANDLE leftover = NULL;
|
||||||
|
SIZE_T leftover_len = 0;
|
||||||
|
take_hmem(clipboard, &leftover, &leftover_len);
|
||||||
|
if (leftover)
|
||||||
|
GlobalFree(leftover);
|
||||||
|
|
||||||
|
CloseHandle(clipboard->hmem_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clipboard->format_request_mutex)
|
||||||
|
CloseHandle(clipboard->format_request_mutex);
|
||||||
|
|
||||||
clear_file_array(clipboard);
|
clear_file_array(clipboard);
|
||||||
clear_format_map(clipboard);
|
clear_format_map(clipboard);
|
||||||
free(clipboard->format_mappings);
|
free(clipboard->format_mappings);
|
||||||
|
|||||||
@@ -150,6 +150,34 @@ START_TEST(test_file_contents_response_validates_data)
|
|||||||
}
|
}
|
||||||
END_TEST
|
END_TEST
|
||||||
|
|
||||||
|
START_TEST(test_format_data_response_drops_unexpected)
|
||||||
|
{
|
||||||
|
wfClipboard clipboard = { 0 };
|
||||||
|
CliprdrClientContext context = { 0 };
|
||||||
|
CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 };
|
||||||
|
|
||||||
|
clipboard.context = &context;
|
||||||
|
clipboard.hmem_mutex = CreateMutex(NULL, FALSE, NULL);
|
||||||
|
clipboard.formatDataRespEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||||
|
context.Custom = &clipboard;
|
||||||
|
|
||||||
|
ck_assert_ptr_nonnull(clipboard.hmem_mutex);
|
||||||
|
ck_assert_ptr_nonnull(clipboard.formatDataRespEvent);
|
||||||
|
|
||||||
|
// With no outstanding request, an unsolicited response must be dropped: it must
|
||||||
|
// not signal the event or write the hmem slot.
|
||||||
|
clipboard.formatDataRespExpected = FALSE;
|
||||||
|
response.msgFlags = CB_RESPONSE_OK;
|
||||||
|
response.dataLen = 0;
|
||||||
|
ck_assert_int_eq(wf_cliprdr_server_format_data_response(&context, &response), CHANNEL_RC_OK);
|
||||||
|
ck_assert_int_eq(WaitForSingleObject(clipboard.formatDataRespEvent, 0), WAIT_TIMEOUT);
|
||||||
|
ck_assert_ptr_null(clipboard.hmem);
|
||||||
|
|
||||||
|
CloseHandle(clipboard.formatDataRespEvent);
|
||||||
|
CloseHandle(clipboard.hmem_mutex);
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
Suite *wf_cliprdr_invariant_suite(void)
|
Suite *wf_cliprdr_invariant_suite(void)
|
||||||
{
|
{
|
||||||
Suite *s;
|
Suite *s;
|
||||||
@@ -166,6 +194,7 @@ Suite *wf_cliprdr_invariant_suite(void)
|
|||||||
tcase_add_test(tc_core, test_descriptor_size_rejects_extreme_count);
|
tcase_add_test(tc_core, test_descriptor_size_rejects_extreme_count);
|
||||||
tcase_add_test(tc_core, test_file_contents_request_initializes_optional_fields);
|
tcase_add_test(tc_core, test_file_contents_request_initializes_optional_fields);
|
||||||
tcase_add_test(tc_core, test_file_contents_response_validates_data);
|
tcase_add_test(tc_core, test_file_contents_response_validates_data);
|
||||||
|
tcase_add_test(tc_core, test_format_data_response_drops_unexpected);
|
||||||
|
|
||||||
suite_add_tcase(s, tc_core);
|
suite_add_tcase(s, tc_core);
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
Reference in New Issue
Block a user