SDL: hidapi/windows: fix use-after-free SBH corruption due to overlapped ReadFile in hidapi not being canceled for all threads...

From f5c731212e012260ea78e9a85b18cdcaffb90bca Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 25 May 2023 15:26:50 -0700
Subject: [PATCH] hidapi/windows: fix use-after-free SBH corruption due to
 overlapped ReadFile in hidapi not being canceled for all threads before
 device close

- hidapi already called CancelIo on hid_close but that only cancels pending IO for the current thread. Controller read/writes originate from multiple threads (serialized, but on a different thread nonetheless) but device destruction was always done on the main device thread which left any pending overlapped reads still running after hidapi's internal read buffer is deallocated leading to intermittent free list corruption.

Signed-off-by: Sam Lantinga <slouken@libsdl.org>
---
 src/hidapi/windows/hid.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/hidapi/windows/hid.c b/src/hidapi/windows/hid.c
index 71ac1a809731..09c4c5754bcc 100644
--- a/src/hidapi/windows/hid.c
+++ b/src/hidapi/windows/hid.c
@@ -1303,10 +1303,23 @@ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned c
 
 void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
 {
+	typedef BOOL (WINAPI *CancelIoEx_t)(HANDLE hFile, LPOVERLAPPED lpOverlapped);
+	CancelIoEx_t CancelIoExFunc = (CancelIoEx_t)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CancelIoEx");
+
 	if (!dev)
 		return;
 
-	CancelIo(dev->device_handle);
+	if (CancelIoExFunc) {
+		CancelIoExFunc(dev->device_handle, NULL);
+	} else {
+		/* Windows XP, this will only cancel I/O on the current thread */
+		CancelIo(dev->device_handle);
+	}
+	if (dev->read_pending) {
+		DWORD bytes_read = 0;
+
+		GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/);
+	}
 	free_hid_device(dev);
 }