summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp')
-rw-r--r--src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp1763
1 files changed, 1763 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp
new file mode 100644
index 00000000..23d4d088
--- /dev/null
+++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp
@@ -0,0 +1,1763 @@
+/* $Id: VBoxUsbFlt.cpp $ */
+/** @file
+ * VBox USB Monitor Device Filtering functionality
+ */
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxUsbMon.h"
+#include "../cmn/VBoxUsbTool.h"
+
+#include <VBox/cdefs.h>
+#include <VBox/types.h>
+#include <iprt/process.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+#include <iprt/assert.h>
+
+#pragma warning(disable : 4200)
+#include "usbdi.h"
+#pragma warning(default : 4200)
+#include "usbdlib.h"
+#include "VBoxUSBFilterMgr.h"
+#include <VBox/usblib.h>
+#include <devguid.h>
+#include <devpkey.h>
+
+
+/* We should be including ntifs.h but that's not as easy as it sounds. */
+extern "C" {
+NTKERNELAPI PDEVICE_OBJECT IoGetDeviceAttachmentBaseRef(__in PDEVICE_OBJECT DeviceObject);
+}
+
+/*
+ * state transitions:
+ *
+ * (we are not filtering this device )
+ * ADDED --> UNCAPTURED ------------------------------->-
+ * | |
+ * | (we are filtering this device, | (the device is being
+ * | waiting for our device driver | re-plugged to perform
+ * | to pick it up) | capture-uncapture transition)
+ * |-> CAPTURING -------------------------------->|---> REPLUGGING -----
+ * ^ | (device driver picked | |
+ * | | up the device) | (remove cased | (device is removed
+ * | ->---> CAPTURED ---------------------->| by "real" removal | the device info is removed form the list)
+ * | | |------------------->->--> REMOVED
+ * | | |
+ * |-----------<->---> USED_BY_GUEST ------->|
+ * | |
+ * |------------------------<-
+ *
+ * NOTE: the order of enums DOES MATTER!!
+ * Do not blindly modify!! as the code assumes the state is ordered this way.
+ */
+typedef enum
+{
+ VBOXUSBFLT_DEVSTATE_UNKNOWN = 0,
+ VBOXUSBFLT_DEVSTATE_REMOVED,
+ VBOXUSBFLT_DEVSTATE_REPLUGGING,
+ VBOXUSBFLT_DEVSTATE_ADDED,
+ VBOXUSBFLT_DEVSTATE_UNCAPTURED,
+ VBOXUSBFLT_DEVSTATE_CAPTURING,
+ VBOXUSBFLT_DEVSTATE_CAPTURED,
+ VBOXUSBFLT_DEVSTATE_USED_BY_GUEST,
+ VBOXUSBFLT_DEVSTATE_32BIT_HACK = 0x7fffffff
+} VBOXUSBFLT_DEVSTATE;
+
+typedef struct VBOXUSBFLT_DEVICE
+{
+ LIST_ENTRY GlobalLe;
+ /* auxiliary list to be used for gathering devices to be re-plugged
+ * only thread that puts the device to the REPLUGGING state can use this list */
+ LIST_ENTRY RepluggingLe;
+ /* Owning session. Each matched device has an owning session. */
+ struct VBOXUSBFLTCTX *pOwner;
+ /* filter id - if NULL AND device has an owner - the filter is destroyed */
+ uintptr_t uFltId;
+ /* true iff device is filtered with a one-shot filter */
+ bool fIsFilterOneShot;
+ /* true if descriptors could not be read and only inferred from PnP Manager data */
+ bool fInferredDesc;
+ /* The device state. If the non-owner session is requesting the state while the device is grabbed,
+ * the USBDEVICESTATE_USED_BY_HOST is returned. */
+ VBOXUSBFLT_DEVSTATE enmState;
+ volatile uint32_t cRefs;
+ PDEVICE_OBJECT Pdo;
+ uint16_t idVendor;
+ uint16_t idProduct;
+ uint16_t bcdDevice;
+ uint16_t bPort;
+ uint8_t bClass;
+ uint8_t bSubClass;
+ uint8_t bProtocol;
+ char szSerial[MAX_USB_SERIAL_STRING];
+ char szMfgName[MAX_USB_SERIAL_STRING];
+ char szProduct[MAX_USB_SERIAL_STRING];
+ WCHAR szLocationPath[768];
+#if 0
+ char szDrvKeyName[512];
+ BOOLEAN fHighSpeed;
+#endif
+} VBOXUSBFLT_DEVICE, *PVBOXUSBFLT_DEVICE;
+
+#define PVBOXUSBFLT_DEVICE_FROM_LE(_pLe) ( (PVBOXUSBFLT_DEVICE)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_DEVICE, GlobalLe) ) )
+#define PVBOXUSBFLT_DEVICE_FROM_REPLUGGINGLE(_pLe) ( (PVBOXUSBFLT_DEVICE)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_DEVICE, RepluggingLe) ) )
+#define PVBOXUSBFLTCTX_FROM_LE(_pLe) ( (PVBOXUSBFLTCTX)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLTCTX, ListEntry) ) )
+
+typedef struct VBOXUSBFLT_LOCK
+{
+ KSPIN_LOCK Lock;
+ KIRQL OldIrql;
+} VBOXUSBFLT_LOCK, *PVBOXUSBFLT_LOCK;
+
+#define VBOXUSBFLT_LOCK_INIT() \
+ KeInitializeSpinLock(&g_VBoxUsbFltGlobals.Lock.Lock)
+#define VBOXUSBFLT_LOCK_TERM() do { } while (0)
+#define VBOXUSBFLT_LOCK_ACQUIRE() \
+ KeAcquireSpinLock(&g_VBoxUsbFltGlobals.Lock.Lock, &g_VBoxUsbFltGlobals.Lock.OldIrql);
+#define VBOXUSBFLT_LOCK_RELEASE() \
+ KeReleaseSpinLock(&g_VBoxUsbFltGlobals.Lock.Lock, g_VBoxUsbFltGlobals.Lock.OldIrql);
+
+
+typedef struct VBOXUSBFLT_BLDEV
+{
+ LIST_ENTRY ListEntry;
+ uint16_t idVendor;
+ uint16_t idProduct;
+ uint16_t bcdDevice;
+} VBOXUSBFLT_BLDEV, *PVBOXUSBFLT_BLDEV;
+
+#define PVBOXUSBFLT_BLDEV_FROM_LE(_pLe) ( (PVBOXUSBFLT_BLDEV)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_BLDEV, ListEntry) ) )
+
+typedef struct VBOXUSBFLTGLOBALS
+{
+ LIST_ENTRY DeviceList;
+ LIST_ENTRY ContextList;
+ /* devices known to misbehave */
+ LIST_ENTRY BlackDeviceList;
+ VBOXUSBFLT_LOCK Lock;
+ /** Flag whether to force replugging a device we can't query descirptors from.
+ * Short term workaround for @bugref{9479}. */
+ ULONG dwForceReplugWhenDevPopulateFails;
+} VBOXUSBFLTGLOBALS, *PVBOXUSBFLTGLOBALS;
+static VBOXUSBFLTGLOBALS g_VBoxUsbFltGlobals;
+
+static bool vboxUsbFltBlDevMatchLocked(uint16_t idVendor, uint16_t idProduct, uint16_t bcdDevice)
+{
+ for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.BlackDeviceList.Flink;
+ pEntry != &g_VBoxUsbFltGlobals.BlackDeviceList;
+ pEntry = pEntry->Flink)
+ {
+ PVBOXUSBFLT_BLDEV pDev = PVBOXUSBFLT_BLDEV_FROM_LE(pEntry);
+ if (pDev->idVendor != idVendor)
+ continue;
+ if (pDev->idProduct != idProduct)
+ continue;
+ if (pDev->bcdDevice != bcdDevice)
+ continue;
+
+ return true;
+ }
+ return false;
+}
+
+static NTSTATUS vboxUsbFltBlDevAddLocked(uint16_t idVendor, uint16_t idProduct, uint16_t bcdDevice)
+{
+ if (vboxUsbFltBlDevMatchLocked(idVendor, idProduct, bcdDevice))
+ return STATUS_SUCCESS;
+ PVBOXUSBFLT_BLDEV pDev = (PVBOXUSBFLT_BLDEV)VBoxUsbMonMemAllocZ(sizeof (*pDev));
+ if (!pDev)
+ {
+ AssertFailed();
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ pDev->idVendor = idVendor;
+ pDev->idProduct = idProduct;
+ pDev->bcdDevice = bcdDevice;
+ InsertHeadList(&g_VBoxUsbFltGlobals.BlackDeviceList, &pDev->ListEntry);
+ return STATUS_SUCCESS;
+}
+
+static void vboxUsbFltBlDevClearLocked()
+{
+ PLIST_ENTRY pNext;
+ for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.BlackDeviceList.Flink;
+ pEntry != &g_VBoxUsbFltGlobals.BlackDeviceList;
+ pEntry = pNext)
+ {
+ pNext = pEntry->Flink;
+ VBoxUsbMonMemFree(pEntry);
+ }
+}
+
+static void vboxUsbFltBlDevPopulateWithKnownLocked()
+{
+ /* this one halts when trying to get string descriptors from it */
+ vboxUsbFltBlDevAddLocked(0x5ac, 0x921c, 0x115);
+}
+
+
+DECLINLINE(void) vboxUsbFltDevRetain(PVBOXUSBFLT_DEVICE pDevice)
+{
+ Assert(pDevice->cRefs);
+ ASMAtomicIncU32(&pDevice->cRefs);
+}
+
+static void vboxUsbFltDevDestroy(PVBOXUSBFLT_DEVICE pDevice)
+{
+ Assert(!pDevice->cRefs);
+ Assert(pDevice->enmState == VBOXUSBFLT_DEVSTATE_REMOVED);
+ VBoxUsbMonMemFree(pDevice);
+}
+
+DECLINLINE(void) vboxUsbFltDevRelease(PVBOXUSBFLT_DEVICE pDevice)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pDevice->cRefs);
+ Assert(cRefs < UINT32_MAX/2);
+ if (!cRefs)
+ {
+ vboxUsbFltDevDestroy(pDevice);
+ }
+}
+
+static void vboxUsbFltDevOwnerSetLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext, uintptr_t uFltId, bool fIsOneShot)
+{
+ ASSERT_WARN(!pDevice->pOwner, ("device 0x%p has an owner(0x%p)", pDevice, pDevice->pOwner));
+ ++pContext->cActiveFilters;
+ pDevice->pOwner = pContext;
+ pDevice->uFltId = uFltId;
+ pDevice->fIsFilterOneShot = fIsOneShot;
+}
+
+static void vboxUsbFltDevOwnerClearLocked(PVBOXUSBFLT_DEVICE pDevice)
+{
+ ASSERT_WARN(pDevice->pOwner, ("no owner for device 0x%p", pDevice));
+ --pDevice->pOwner->cActiveFilters;
+ ASSERT_WARN(pDevice->pOwner->cActiveFilters < UINT32_MAX/2, ("cActiveFilters (%d)", pDevice->pOwner->cActiveFilters));
+ pDevice->pOwner = NULL;
+ pDevice->uFltId = 0;
+}
+
+static void vboxUsbFltDevOwnerUpdateLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext, uintptr_t uFltId, bool fIsOneShot)
+{
+ if (pDevice->pOwner != pContext)
+ {
+ if (pDevice->pOwner)
+ vboxUsbFltDevOwnerClearLocked(pDevice);
+ if (pContext)
+ vboxUsbFltDevOwnerSetLocked(pDevice, pContext, uFltId, fIsOneShot);
+ }
+ else if (pContext)
+ {
+ pDevice->uFltId = uFltId;
+ pDevice->fIsFilterOneShot = fIsOneShot;
+ }
+}
+
+static PVBOXUSBFLT_DEVICE vboxUsbFltDevGetLocked(PDEVICE_OBJECT pPdo)
+{
+#ifdef VBOX_USB_WITH_VERBOSE_LOGGING
+ for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink;
+ pEntry != &g_VBoxUsbFltGlobals.DeviceList;
+ pEntry = pEntry->Flink)
+ {
+ PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry);
+ for (PLIST_ENTRY pEntry2 = pEntry->Flink;
+ pEntry2 != &g_VBoxUsbFltGlobals.DeviceList;
+ pEntry2 = pEntry2->Flink)
+ {
+ PVBOXUSBFLT_DEVICE pDevice2 = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry2);
+ ASSERT_WARN( pDevice->idVendor != pDevice2->idVendor
+ || pDevice->idProduct != pDevice2->idProduct
+ || pDevice->bcdDevice != pDevice2->bcdDevice, ("duplicate devices in a list!!"));
+ }
+ }
+#endif
+ for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink;
+ pEntry != &g_VBoxUsbFltGlobals.DeviceList;
+ pEntry = pEntry->Flink)
+ {
+ PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry);
+ ASSERT_WARN( pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING
+ || pDevice->enmState == VBOXUSBFLT_DEVSTATE_UNCAPTURED
+ || pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURING
+ || pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURED
+ || pDevice->enmState == VBOXUSBFLT_DEVSTATE_USED_BY_GUEST,
+ ("Invalid device state(%d) for device(0x%p) PDO(0x%p)", pDevice->enmState, pDevice, pDevice->Pdo));
+ if (pDevice->Pdo == pPdo)
+ return pDevice;
+ }
+ return NULL;
+}
+
+static NTSTATUS vboxUsbFltPdoReplug(PDEVICE_OBJECT pDo)
+{
+ LOG(("Replugging PDO(0x%p)", pDo));
+ NTSTATUS Status = VBoxUsbToolIoInternalCtlSendSync(pDo, IOCTL_INTERNAL_USB_CYCLE_PORT, NULL, NULL);
+ ASSERT_WARN(Status == STATUS_SUCCESS, ("replugging PDO(0x%p) failed Status(0x%x)", pDo, Status));
+ LOG(("Replugging PDO(0x%p) done with Status(0x%x)", pDo, Status));
+ return Status;
+}
+
+static bool vboxUsbFltDevCanBeCaptured(PVBOXUSBFLT_DEVICE pDevice)
+{
+ if (pDevice->bClass == USB_DEVICE_CLASS_HUB)
+ {
+ LOG(("device (0x%p), pdo (0x%p) is a hub, can not be captured", pDevice, pDevice->Pdo));
+ return false;
+ }
+ return true;
+}
+
+static PVBOXUSBFLTCTX vboxUsbFltDevMatchLocked(PVBOXUSBFLT_DEVICE pDevice, uintptr_t *puId, bool fRemoveFltIfOneShot, bool *pfFilter, bool *pfIsOneShot)
+{
+ *puId = 0;
+ *pfFilter = false;
+ *pfIsOneShot = false;
+ if (!vboxUsbFltDevCanBeCaptured(pDevice))
+ {
+ LOG(("vboxUsbFltDevCanBeCaptured returned false"));
+ return NULL;
+ }
+
+ USBFILTER DevFlt;
+ USBFilterInit(&DevFlt, USBFILTERTYPE_CAPTURE);
+ USBFilterSetNumExact(&DevFlt, USBFILTERIDX_VENDOR_ID, pDevice->idVendor, true);
+ USBFilterSetNumExact(&DevFlt, USBFILTERIDX_PRODUCT_ID, pDevice->idProduct, true);
+ USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_REV, pDevice->bcdDevice, true);
+
+ /* If we could not read a string descriptor, don't set the filter item at all. */
+ if (pDevice->szMfgName[0])
+ USBFilterSetStringExact(&DevFlt, USBFILTERIDX_MANUFACTURER_STR, pDevice->szMfgName, true /*fMustBePresent*/, true /*fPurge*/);
+ if (pDevice->szProduct[0])
+ USBFilterSetStringExact(&DevFlt, USBFILTERIDX_PRODUCT_STR, pDevice->szProduct, true /*fMustBePresent*/, true /*fPurge*/);
+ if (pDevice->szSerial[0])
+ USBFilterSetStringExact(&DevFlt, USBFILTERIDX_SERIAL_NUMBER_STR, pDevice->szSerial, true /*fMustBePresent*/, true /*fPurge*/);
+
+ /* If device descriptor had to be inferred from PnP Manager data, the class/subclass/protocol may be wrong.
+ * When Windows reports CompatibleIDs 'USB\Class_03&SubClass_00&Prot_00', the device descriptor might be
+ * reporting class 3 (HID), *or* the device descriptor might be reporting class 0 (specified by interface)
+ * and the device's interface reporting class 3. Ignore the class/subclass/protocol in such case, since
+ * we are more or less guaranteed to rely on VID/PID anyway.
+ * See @bugref{9479}.
+ */
+ if (pDevice->fInferredDesc)
+ {
+ LOG(("Device descriptor was not read, only inferred; ignoring class/subclass/protocol!"));
+ }
+ else
+ {
+ LOG(("Setting filter class/subclass/protocol %02X/%02X/%02X\n", pDevice->bClass, pDevice->bSubClass, pDevice->bProtocol));
+ USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_CLASS, pDevice->bClass, true);
+ USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_SUB_CLASS, pDevice->bSubClass, true);
+ USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_PROTOCOL, pDevice->bProtocol, true);
+ }
+
+ /* If the port number looks valid, add it to the filter. */
+ if (pDevice->bPort)
+ {
+ LOG(("Setting filter port %04X\n", pDevice->bPort));
+ USBFilterSetNumExact(&DevFlt, USBFILTERIDX_PORT, pDevice->bPort, true);
+ }
+ else
+ LOG(("Port number not known, ignoring!"));
+
+ /* Run filters on the thing. */
+ PVBOXUSBFLTCTX pOwner = VBoxUSBFilterMatchEx(&DevFlt, puId, fRemoveFltIfOneShot, pfFilter, pfIsOneShot);
+ USBFilterDelete(&DevFlt);
+ return pOwner;
+}
+
+static void vboxUsbFltDevStateMarkReplugLocked(PVBOXUSBFLT_DEVICE pDevice)
+{
+ vboxUsbFltDevOwnerUpdateLocked(pDevice, NULL, 0, false);
+ pDevice->enmState = VBOXUSBFLT_DEVSTATE_REPLUGGING;
+}
+
+static bool vboxUsbFltDevStateIsNotFiltered(PVBOXUSBFLT_DEVICE pDevice)
+{
+ return pDevice->enmState == VBOXUSBFLT_DEVSTATE_UNCAPTURED;
+}
+
+static bool vboxUsbFltDevStateIsFiltered(PVBOXUSBFLT_DEVICE pDevice)
+{
+ return pDevice->enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING;
+}
+
+static uint16_t vboxUsbParseHexNumU16(WCHAR **ppStr)
+{
+ WCHAR *pStr = *ppStr;
+ WCHAR wc;
+ uint16_t num = 0;
+ unsigned u;
+
+ for (int i = 0; i < 4; ++i)
+ {
+ if (!*pStr) /* Just in case the string is too short. */
+ break;
+
+ wc = *pStr;
+ u = wc >= 'A' ? wc - 'A' + 10 : wc - '0'; /* Hex digit to number. */
+ num |= u << (12 - 4 * i);
+ pStr++;
+ }
+ *ppStr = pStr;
+
+ return num;
+}
+
+static uint8_t vboxUsbParseHexNumU8(WCHAR **ppStr)
+{
+ WCHAR *pStr = *ppStr;
+ WCHAR wc;
+ uint16_t num = 0;
+ unsigned u;
+
+ for (int i = 0; i < 2; ++i)
+ {
+ if (!*pStr) /* Just in case the string is too short. */
+ break;
+
+ wc = *pStr;
+ u = wc >= 'A' ? wc - 'A' + 10 : wc - '0'; /* Hex digit to number. */
+ num |= u << (4 - 4 * i);
+ pStr++;
+ }
+ *ppStr = pStr;
+
+ return num;
+}
+
+static bool vboxUsbParseHardwareID(WCHAR *pchIdStr, uint16_t *pVid, uint16_t *pPid, uint16_t *pRev)
+{
+#define VID_PREFIX L"USB\\VID_"
+#define PID_PREFIX L"&PID_"
+#define REV_PREFIX L"&REV_"
+
+ *pVid = *pPid = *pRev = 0xFFFF;
+
+ /* The Hardware ID is in the format USB\VID_xxxx&PID_xxxx&REV_xxxx, with 'xxxx'
+ * being 16-bit hexadecimal numbers. The string is coming from the
+ * Windows PnP manager so OEMs should have no opportunity to mess it up.
+ */
+
+ if (wcsncmp(pchIdStr, VID_PREFIX, wcslen(VID_PREFIX)))
+ return false;
+ /* Point to the start of the vendor ID number and parse it. */
+ pchIdStr += wcslen(VID_PREFIX);
+ *pVid = vboxUsbParseHexNumU16(&pchIdStr);
+
+ if (wcsncmp(pchIdStr, PID_PREFIX, wcslen(PID_PREFIX)))
+ return false;
+ /* Point to the start of the product ID number and parse it. */
+ pchIdStr += wcslen(PID_PREFIX);
+ *pPid = vboxUsbParseHexNumU16(&pchIdStr);
+
+ /* The revision might not be there; the Windows documentation is not
+ * entirely clear if it will be always present for USB devices or not.
+ * If it's not there, still consider this a success. */
+ if (wcsncmp(pchIdStr, REV_PREFIX, wcslen(REV_PREFIX)))
+ return true;
+
+ /* Point to the start of the revision number and parse it. */
+ pchIdStr += wcslen(REV_PREFIX);
+ *pRev = vboxUsbParseHexNumU16(&pchIdStr);
+
+ return true;
+#undef VID_PREFIX
+#undef PID_PREFIX
+#undef REV_PREFIX
+}
+
+static bool vboxUsbParseCompatibleIDs(WCHAR *pchIdStr, uint8_t *pClass, uint8_t *pSubClass, uint8_t *pProt)
+{
+#define CLS_PREFIX L"USB\\Class_"
+#define SUB_PREFIX L"&SubClass_"
+#define PRO_PREFIX L"&Prot_"
+
+ *pClass = *pSubClass = *pProt = 0xFF;
+
+ /* The Compatible IDs string is in the format USB\Class_xx&SubClass_xx&Prot_xx,
+ * with 'xx' being 8-bit hexadecimal numbers. Since this string is provided by the
+ * PnP manager and USB devices always report these as part of the basic USB device
+ * descriptor, we assume all three must be present.
+ */
+
+ if (wcsncmp(pchIdStr, CLS_PREFIX, wcslen(CLS_PREFIX)))
+ return false;
+ /* Point to the start of the device class and parse it. */
+ pchIdStr += wcslen(CLS_PREFIX);
+ *pClass = vboxUsbParseHexNumU8(&pchIdStr);
+
+ if (wcsncmp(pchIdStr, SUB_PREFIX, wcslen(SUB_PREFIX)))
+ return false;
+
+ /* Point to the start of the subclass and parse it. */
+ pchIdStr += wcslen(SUB_PREFIX);
+ *pSubClass = vboxUsbParseHexNumU8(&pchIdStr);
+
+ if (wcsncmp(pchIdStr, PRO_PREFIX, wcslen(PRO_PREFIX)))
+ return false;
+
+ /* Point to the start of the protocol and parse it. */
+ pchIdStr += wcslen(PRO_PREFIX);
+ *pProt = vboxUsbParseHexNumU8(&pchIdStr);
+
+ return true;
+#undef CLS_PREFIX
+#undef SUB_PREFIX
+#undef PRO_PREFIX
+}
+
+#define VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS 10000
+
+static NTSTATUS vboxUsbFltDevPopulate(PVBOXUSBFLT_DEVICE pDevice, PDEVICE_OBJECT pDo /*, BOOLEAN bPopulateNonFilterProps*/)
+{
+ NTSTATUS Status;
+ USB_TOPOLOGY_ADDRESS TopoAddr;
+ PUSB_DEVICE_DESCRIPTOR pDevDr = 0;
+ ULONG ulResultLen;
+ DEVPROPTYPE type;
+ WCHAR wchPropBuf[256];
+ uint16_t port;
+ bool rc;
+
+ pDevice->Pdo = pDo;
+
+ LOG(("Populating Device(0x%p) for PDO(0x%p)", pDevice, pDo));
+
+ pDevDr = (PUSB_DEVICE_DESCRIPTOR)VBoxUsbMonMemAllocZ(sizeof(*pDevDr));
+ if (pDevDr == NULL)
+ {
+ WARN(("Failed to alloc mem for urb"));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ do
+ {
+ pDevice->fInferredDesc = false;
+ Status = VBoxUsbToolGetDescriptor(pDo, pDevDr, sizeof(*pDevDr), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS);
+ if (!NT_SUCCESS(Status))
+ {
+ uint16_t vid, pid, rev;
+ uint8_t cls, sub, prt;
+
+ WARN(("getting device descriptor failed, Status (0x%x); falling back to IoGetDeviceProperty", Status));
+
+ /* Try falling back to IoGetDevicePropertyData. */
+ Status = IoGetDevicePropertyData(pDo, &DEVPKEY_Device_HardwareIds, LOCALE_NEUTRAL, 0, sizeof(wchPropBuf), wchPropBuf, &ulResultLen, &type);
+ if (!NT_SUCCESS(Status))
+ {
+ /* This just isn't our day. We have no idea what the device is. */
+ WARN(("IoGetDevicePropertyData failed for DEVPKEY_Device_HardwareIds, Status (0x%x)", Status));
+ break;
+ }
+ rc = vboxUsbParseHardwareID(wchPropBuf, &vid, &pid, &rev);
+ if (!rc)
+ {
+ /* This *really* should not happen. */
+ WARN(("Failed to parse Hardware ID"));
+ break;
+ }
+
+ /* Now grab the Compatible IDs to get the class/subclass/protocol. */
+ Status = IoGetDevicePropertyData(pDo, &DEVPKEY_Device_CompatibleIds, LOCALE_NEUTRAL, 0, sizeof(wchPropBuf), wchPropBuf, &ulResultLen, &type);
+ if (!NT_SUCCESS(Status))
+ {
+ /* We really kind of need these. */
+ WARN(("IoGetDevicePropertyData failed for DEVPKEY_Device_CompatibleIds, Status (0x%x)", Status));
+ break;
+ }
+ rc = vboxUsbParseCompatibleIDs(wchPropBuf, &cls, &sub, &prt);
+ if (!rc)
+ {
+ /* This *really* should not happen. */
+ WARN(("Failed to parse Hardware ID"));
+ break;
+ }
+
+ LOG(("Parsed HardwareID: vid=%04X, pid=%04X, rev=%04X, class=%02X, subcls=%02X, prot=%02X", vid, pid, rev, cls, sub, prt));
+ if (vid == 0xFFFF || pid == 0xFFFF)
+ break;
+
+ LOG(("Successfully fell back to IoGetDeviceProperty result"));
+ pDevDr->idVendor = vid;
+ pDevDr->idProduct = pid;
+ pDevDr->bcdDevice = rev;
+ pDevDr->bDeviceClass = cls;
+ pDevDr->bDeviceSubClass = sub;
+ pDevDr->bDeviceProtocol = prt;
+
+ /* The USB device class/subclass/protocol may not be accurate. We have to be careful when comparing
+ * and not take mismatches too seriously.
+ */
+ pDevice->fInferredDesc = true;
+ }
+
+ /* Query the location path. The path is purely a function of the physical device location
+ * and does not change if the device changes, and also does not change depending on
+ * whether the device is captured or not.
+ * NB: We ignore any additional strings and only look at the first one.
+ */
+ Status = IoGetDevicePropertyData(pDo, &DEVPKEY_Device_LocationPaths, LOCALE_NEUTRAL, 0, sizeof(pDevice->szLocationPath), pDevice->szLocationPath, &ulResultLen, &type);
+ if (!NT_SUCCESS(Status))
+ {
+ /* We do need this, but not critically. On Windows 7, we may get STATUS_OBJECT_NAME_NOT_FOUND. */
+ WARN(("IoGetDevicePropertyData failed for DEVPKEY_Device_LocationPaths, Status (0x%x)", Status));
+ }
+ else
+ {
+ LOG_STRW(pDevice->szLocationPath);
+ }
+
+ // Disabled, but could be used as a fallback instead of IoGetDevicePropertyData; it should work even
+ // when this code is entered from the PnP IRP processing path.
+#if 0
+ {
+ HUB_DEVICE_CONFIG_INFO HubInfo;
+
+ memset(&HubInfo, 0, sizeof(HubInfo));
+ HubInfo.Version = 1;
+ HubInfo.Length = sizeof(HubInfo);
+
+ NTSTATUS Status = VBoxUsbToolIoInternalCtlSendSync(pDo, IOCTL_INTERNAL_USB_GET_DEVICE_CONFIG_INFO, &HubInfo, NULL);
+ ASSERT_WARN(Status == STATUS_SUCCESS, ("GET_DEVICE_CONFIG_INFO for PDO(0x%p) failed Status(0x%x)", pDo, Status));
+ LOG(("Querying hub device config info for PDO(0x%p) done with Status(0x%x)", pDo, Status));
+
+ if (Status == STATUS_SUCCESS)
+ {
+ uint16_t vid, pid, rev;
+ uint8_t cls, sub, prt;
+
+ LOG(("Hub flags: %X\n", HubInfo.HubFlags));
+ LOG_STRW(HubInfo.HardwareIds.Buffer);
+ LOG_STRW(HubInfo.CompatibleIds.Buffer);
+ if (HubInfo.DeviceDescription.Buffer)
+ LOG_STRW(HubInfo.DeviceDescription.Buffer);
+
+ rc = vboxUsbParseHardwareID(HubInfo.HardwareIds.Buffer, &pid, &vid, &rev);
+ if (!rc)
+ {
+ /* This *really* should not happen. */
+ WARN(("Failed to parse Hardware ID"));
+ }
+
+ /* The CompatibleID the IOCTL gives is not always the same as what the PnP Manager uses
+ * (thanks, Microsoft). It might look like "USB\DevClass_00&SubClass_00&Prot_00" or like
+ * "USB\USB30_HUB". In such cases, we must consider the class/subclass/protocol
+ * information simply unavailable.
+ */
+ rc = vboxUsbParseCompatibleIDs(HubInfo.CompatibleIds.Buffer, &cls, &sub, &prt);
+ if (!rc)
+ {
+ /* This is unfortunate but not fatal. */
+ WARN(("Failed to parse Compatible ID"));
+ }
+ LOG(("Parsed HardwareID from IOCTL: vid=%04X, pid=%04X, rev=%04X, class=%02X, subcls=%02X, prot=%02X", vid, pid, rev, cls, sub, prt));
+
+ ExFreePool(HubInfo.HardwareIds.Buffer);
+ ExFreePool(HubInfo.CompatibleIds.Buffer);
+ if (HubInfo.DeviceDescription.Buffer)
+ ExFreePool(HubInfo.DeviceDescription.Buffer);
+ }
+ }
+#endif
+
+ /* Query the topology address from the hub driver. This is not trivial to translate to the location
+ * path, but at least we can get the port number this way.
+ */
+ memset(&TopoAddr, 0, sizeof(TopoAddr));
+ Status = VBoxUsbToolIoInternalCtlSendSync(pDo, IOCTL_INTERNAL_USB_GET_TOPOLOGY_ADDRESS, &TopoAddr, NULL);
+ ASSERT_WARN(Status == STATUS_SUCCESS, ("GET_TOPOLOGY_ADDRESS for PDO(0x%p) failed Status(0x%x)", pDo, Status));
+ LOG(("Querying topology address for PDO(0x%p) done with Status(0x%x)", pDo, Status));
+
+ port = 0;
+ if (Status == STATUS_SUCCESS)
+ {
+ uint16_t *pPort = &TopoAddr.RootHubPortNumber;
+
+ /* The last non-zero port number is the one we're looking for. It might be on the
+ * root hub directly, or on some downstream hub.
+ */
+ for (int i = 0; i < RT_ELEMENTS(TopoAddr.HubPortNumber) + 1; ++i) {
+ if (*pPort)
+ port = *pPort;
+ pPort++;
+ }
+ LOG(("PCI bus/dev/fn: %02X:%02X:%02X, parsed port: %u\n", TopoAddr.PciBusNumber, TopoAddr.PciDeviceNumber, TopoAddr.PciFunctionNumber, port));
+ LOG(("RH port: %u, hub ports: %u/%u/%u/%u/%u/%u\n", TopoAddr.RootHubPortNumber, TopoAddr.HubPortNumber[0],
+ TopoAddr.HubPortNumber[1], TopoAddr.HubPortNumber[2], TopoAddr.HubPortNumber[3], TopoAddr.HubPortNumber[4], TopoAddr.HubPortNumber[5]));
+
+ /* In the extremely unlikely case that the port number does not fit into 8 bits, force
+ * it to zero to indicate that we can't use it.
+ */
+ if (port > 255)
+ port = 0;
+ }
+
+ if (vboxUsbFltBlDevMatchLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice))
+ {
+ WARN(("found a known black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice));
+ Status = STATUS_UNSUCCESSFUL;
+ break;
+ }
+
+ LOG(("Device pid=%x vid=%x rev=%x port=%x", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice, port));
+ pDevice->bPort = port;
+ pDevice->idVendor = pDevDr->idVendor;
+ pDevice->idProduct = pDevDr->idProduct;
+ pDevice->bcdDevice = pDevDr->bcdDevice;
+ pDevice->bClass = pDevDr->bDeviceClass;
+ pDevice->bSubClass = pDevDr->bDeviceSubClass;
+ pDevice->bProtocol = pDevDr->bDeviceProtocol;
+ pDevice->szSerial[0] = 0;
+ pDevice->szMfgName[0] = 0;
+ pDevice->szProduct[0] = 0;
+
+ /* If there are no strings, don't even try to get any string descriptors. */
+ if (pDevDr->iSerialNumber || pDevDr->iManufacturer || pDevDr->iProduct)
+ {
+ int langId;
+
+ Status = VBoxUsbToolGetLangID(pDo, &langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS);
+ if (!NT_SUCCESS(Status))
+ {
+ WARN(("reading language ID failed"));
+ if (Status == STATUS_CANCELLED)
+ {
+ WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice));
+ vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice);
+ Status = STATUS_UNSUCCESSFUL;
+ }
+ break;
+ }
+
+ if (pDevDr->iSerialNumber)
+ {
+ Status = VBoxUsbToolGetStringDescriptor(pDo, pDevice->szSerial, sizeof (pDevice->szSerial), pDevDr->iSerialNumber, langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS);
+ if (!NT_SUCCESS(Status))
+ {
+ WARN(("reading serial number failed"));
+ ASSERT_WARN(pDevice->szSerial[0] == '\0', ("serial is not zero!!"));
+ if (Status == STATUS_CANCELLED)
+ {
+ WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice));
+ vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice);
+ Status = STATUS_UNSUCCESSFUL;
+ break;
+ }
+ LOG(("pretending success.."));
+ Status = STATUS_SUCCESS;
+ }
+ }
+
+ if (pDevDr->iManufacturer)
+ {
+ Status = VBoxUsbToolGetStringDescriptor(pDo, pDevice->szMfgName, sizeof (pDevice->szMfgName), pDevDr->iManufacturer, langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS);
+ if (!NT_SUCCESS(Status))
+ {
+ WARN(("reading manufacturer name failed"));
+ ASSERT_WARN(pDevice->szMfgName[0] == '\0', ("szMfgName is not zero!!"));
+ if (Status == STATUS_CANCELLED)
+ {
+ WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice));
+ vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice);
+ Status = STATUS_UNSUCCESSFUL;
+ break;
+ }
+ LOG(("pretending success.."));
+ Status = STATUS_SUCCESS;
+ }
+ }
+
+ if (pDevDr->iProduct)
+ {
+ Status = VBoxUsbToolGetStringDescriptor(pDo, pDevice->szProduct, sizeof (pDevice->szProduct), pDevDr->iProduct, langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS);
+ if (!NT_SUCCESS(Status))
+ {
+ WARN(("reading product name failed"));
+ ASSERT_WARN(pDevice->szProduct[0] == '\0', ("szProduct is not zero!!"));
+ if (Status == STATUS_CANCELLED)
+ {
+ WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice));
+ vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice);
+ Status = STATUS_UNSUCCESSFUL;
+ break;
+ }
+ LOG(("pretending success.."));
+ Status = STATUS_SUCCESS;
+ }
+ }
+
+ LOG((": strings: '%s':'%s':'%s' (lang ID %x)",
+ pDevice->szMfgName, pDevice->szProduct, pDevice->szSerial, langId));
+ }
+
+ LOG(("Populating Device(0x%p) for PDO(0x%p) Succeeded", pDevice, pDo));
+ Status = STATUS_SUCCESS;
+ } while (0);
+
+ VBoxUsbMonMemFree(pDevDr);
+ LOG(("Populating Device(0x%p) for PDO(0x%p) Done, Status (0x%x)", pDevice, pDo, Status));
+ return Status;
+}
+
+static bool vboxUsbFltDevCheckReplugLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext)
+{
+ ASSERT_WARN(pContext, ("context is NULL!"));
+
+ LOG(("Current context is (0x%p)", pContext));
+ LOG(("Current Device owner is (0x%p)", pDevice->pOwner));
+
+ /* check if device is already replugging */
+ if (pDevice->enmState <= VBOXUSBFLT_DEVSTATE_ADDED)
+ {
+ LOG(("Device (0x%p) is already replugging, return..", pDevice));
+ /* it is, do nothing */
+ ASSERT_WARN(pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING,
+ ("Device (0x%p) state is NOT REPLUGGING (%d)", pDevice, pDevice->enmState));
+ return false;
+ }
+
+ if (pDevice->pOwner && pContext != pDevice->pOwner)
+ {
+ LOG(("Device (0x%p) is owned by another context(0x%p), current is(0x%p)", pDevice, pDevice->pOwner, pContext));
+ /* this device is owned by another context, we're not allowed to do anything */
+ return false;
+ }
+
+ uintptr_t uId = 0;
+ bool bNeedReplug = false;
+ bool fFilter = false;
+ bool fIsOneShot = false;
+ PVBOXUSBFLTCTX pNewOwner = vboxUsbFltDevMatchLocked(pDevice, &uId,
+ false, /* do not remove a one-shot filter */
+ &fFilter, &fIsOneShot);
+ LOG(("Matching Info: Filter (0x%p), NewOwner(0x%p), fFilter(%d), fIsOneShot(%d)", uId, pNewOwner, (int)fFilter, (int)fIsOneShot));
+ if (pDevice->pOwner && pNewOwner && pDevice->pOwner != pNewOwner)
+ {
+ LOG(("Matching: Device (0x%p) is requested another owner(0x%p), current is(0x%p)", pDevice, pNewOwner, pDevice->pOwner));
+ /* the device is owned by another owner, we can not change the owner here */
+ return false;
+ }
+
+ if (!fFilter)
+ {
+ LOG(("Matching: Device (0x%p) should NOT be filtered", pDevice));
+ /* the device should NOT be filtered, check the current state */
+ if (vboxUsbFltDevStateIsNotFiltered(pDevice))
+ {
+ LOG(("Device (0x%p) is NOT filtered", pDevice));
+ /* no changes */
+ if (fIsOneShot)
+ {
+ ASSERT_WARN(pNewOwner, ("no new owner"));
+ LOG(("Matching: This is a one-shot filter (0x%p), removing..", uId));
+ /* remove a one-shot filter and keep the original filter data */
+ int tmpRc = VBoxUSBFilterRemove(pNewOwner, uId);
+ ASSERT_WARN(RT_SUCCESS(tmpRc), ("remove filter failed, rc (%d)", tmpRc));
+ if (!pDevice->pOwner)
+ {
+ LOG(("Matching: updating the one-shot owner to (0x%p), fltId(0x%p)", pNewOwner, uId));
+ /* update owner for one-shot if the owner is changed (i.e. assigned) */
+ vboxUsbFltDevOwnerUpdateLocked(pDevice, pNewOwner, uId, true);
+ }
+ else
+ {
+ LOG(("Matching: device already has owner (0x%p) assigned", pDevice->pOwner));
+ }
+ }
+ else
+ {
+ LOG(("Matching: This is NOT a one-shot filter (0x%p), newOwner(0x%p)", uId, pNewOwner));
+ if (pNewOwner)
+ {
+ vboxUsbFltDevOwnerUpdateLocked(pDevice, pNewOwner, uId, false);
+ }
+ }
+ }
+ else
+ {
+ LOG(("Device (0x%p) IS filtered", pDevice));
+ /* the device is currently filtered, we should release it only if
+ * 1. device does not have an owner
+ * or
+ * 2. it should be released bue to a one-shot filter
+ * or
+ * 3. it is NOT grabbed by a one-shot filter */
+ if (!pDevice->pOwner || fIsOneShot || !pDevice->fIsFilterOneShot)
+ {
+ LOG(("Matching: Need replug"));
+ bNeedReplug = true;
+ }
+ }
+ }
+ else
+ {
+ LOG(("Matching: Device (0x%p) SHOULD be filtered", pDevice));
+ /* the device should be filtered, check the current state */
+ ASSERT_WARN(uId, ("zero uid"));
+ ASSERT_WARN(pNewOwner, ("zero pNewOwner"));
+ if (vboxUsbFltDevStateIsFiltered(pDevice))
+ {
+ LOG(("Device (0x%p) IS filtered", pDevice));
+ /* the device is filtered */
+ if (pNewOwner == pDevice->pOwner)
+ {
+ LOG(("Device owner match"));
+ /* no changes */
+ if (fIsOneShot)
+ {
+ LOG(("Matching: This is a one-shot filter (0x%p), removing..", uId));
+ /* remove a one-shot filter and keep the original filter data */
+ int tmpRc = VBoxUSBFilterRemove(pNewOwner, uId);
+ ASSERT_WARN(RT_SUCCESS(tmpRc), ("remove filter failed, rc (%d)", tmpRc));
+ }
+ else
+ {
+ LOG(("Matching: This is NOT a one-shot filter (0x%p), Owner(0x%p)", uId, pDevice->pOwner));
+ vboxUsbFltDevOwnerUpdateLocked(pDevice, pDevice->pOwner, uId, false);
+ }
+ }
+ else
+ {
+ ASSERT_WARN(!pDevice->pOwner, ("device should NOT have owner"));
+ LOG(("Matching: Need replug"));
+ /* the device needs to be filtered, but the owner changes, replug needed */
+ bNeedReplug = true;
+ }
+ }
+ else
+ {
+ /* the device is currently NOT filtered,
+ * we should replug it only if
+ * 1. device does not have an owner
+ * or
+ * 2. it should be captured due to a one-shot filter
+ * or
+ * 3. it is NOT released by a one-shot filter */
+ if (!pDevice->pOwner || fIsOneShot || !pDevice->fIsFilterOneShot)
+ {
+ bNeedReplug = true;
+ LOG(("Matching: Need replug"));
+ }
+ }
+ }
+
+ if (bNeedReplug)
+ {
+ LOG(("Matching: Device needs replugging, marking as such"));
+ vboxUsbFltDevStateMarkReplugLocked(pDevice);
+ }
+ else
+ {
+ LOG(("Matching: Device does NOT need replugging"));
+ }
+
+ return bNeedReplug;
+}
+
+static void vboxUsbFltReplugList(PLIST_ENTRY pList)
+{
+ PLIST_ENTRY pNext;
+ for (PLIST_ENTRY pEntry = pList->Flink;
+ pEntry != pList;
+ pEntry = pNext)
+ {
+ pNext = pEntry->Flink;
+ PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_REPLUGGINGLE(pEntry);
+ LOG(("replugging matched PDO(0x%p), pDevice(0x%p)", pDevice->Pdo, pDevice));
+ ASSERT_WARN(pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING
+ || pDevice->enmState == VBOXUSBFLT_DEVSTATE_REMOVED,
+ ("invalid state(0x%x) for device(0x%p)", pDevice->enmState, pDevice));
+
+ vboxUsbFltPdoReplug(pDevice->Pdo);
+ ObDereferenceObject(pDevice->Pdo);
+ vboxUsbFltDevRelease(pDevice);
+ }
+}
+
+typedef struct VBOXUSBFLTCHECKWALKER
+{
+ PVBOXUSBFLTCTX pContext;
+} VBOXUSBFLTCHECKWALKER, *PVBOXUSBFLTCHECKWALKER;
+
+static DECLCALLBACK(BOOLEAN) vboxUsbFltFilterCheckWalker(PFILE_OBJECT pHubFile,
+ PDEVICE_OBJECT pHubDo, PVOID pvContext)
+{
+ PVBOXUSBFLTCHECKWALKER pData = (PVBOXUSBFLTCHECKWALKER)pvContext;
+ PVBOXUSBFLTCTX pContext = pData->pContext;
+
+ LOG(("Visiting pHubFile(0x%p), pHubDo(0x%p), oContext(0x%p)", pHubFile, pHubDo, pContext));
+ KIRQL Irql = KeGetCurrentIrql();
+ ASSERT_WARN(Irql == PASSIVE_LEVEL, ("unexpected IRQL (%d)", Irql));
+
+ PDEVICE_RELATIONS pDevRelations = NULL;
+
+ NTSTATUS Status = VBoxUsbMonQueryBusRelations(pHubDo, pHubFile, &pDevRelations);
+ if (Status == STATUS_SUCCESS && pDevRelations)
+ {
+ ULONG cReplugPdos = pDevRelations->Count;
+ LIST_ENTRY ReplugDevList;
+ InitializeListHead(&ReplugDevList);
+ for (ULONG k = 0; k < pDevRelations->Count; ++k)
+ {
+ PDEVICE_OBJECT pDevObj;
+
+ /* Grab the PDO+reference. We won't need the upper layer device object
+ * anymore, so dereference that right here, and drop the PDO ref later.
+ */
+ pDevObj = IoGetDeviceAttachmentBaseRef(pDevRelations->Objects[k]);
+ LOG(("DevObj=%p, PDO=%p\n", pDevRelations->Objects[k], pDevObj));
+ ObDereferenceObject(pDevRelations->Objects[k]);
+ pDevRelations->Objects[k] = pDevObj;
+
+ LOG(("Found existing USB PDO 0x%p", pDevObj));
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ PVBOXUSBFLT_DEVICE pDevice = vboxUsbFltDevGetLocked(pDevObj);
+ if (pDevice)
+ {
+ LOG(("Found existing device info (0x%p) for PDO 0x%p", pDevice, pDevObj));
+ bool bReplug = vboxUsbFltDevCheckReplugLocked(pDevice, pContext);
+ if (bReplug)
+ {
+ LOG(("Replug needed for device (0x%p)", pDevice));
+ InsertHeadList(&ReplugDevList, &pDevice->RepluggingLe);
+ vboxUsbFltDevRetain(pDevice);
+ /* do not dereference object since we will use it later */
+ }
+ else
+ {
+ LOG(("Replug NOT needed for device (0x%p)", pDevice));
+ ObDereferenceObject(pDevObj);
+ }
+
+ VBOXUSBFLT_LOCK_RELEASE();
+
+ pDevRelations->Objects[k] = NULL;
+ --cReplugPdos;
+ ASSERT_WARN((uint32_t)cReplugPdos < UINT32_MAX/2, ("cReplugPdos(%d) state broken", cReplugPdos));
+ continue;
+ }
+ VBOXUSBFLT_LOCK_RELEASE();
+
+ LOG(("NO device info found for PDO 0x%p", pDevObj));
+ VBOXUSBFLT_DEVICE Device;
+ Status = vboxUsbFltDevPopulate(&Device, pDevObj /*, FALSE /* only need filter properties */);
+ if (NT_SUCCESS(Status))
+ {
+ uintptr_t uId = 0;
+ bool fFilter = false;
+ bool fIsOneShot = false;
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ PVBOXUSBFLTCTX pCtx = vboxUsbFltDevMatchLocked(&Device, &uId,
+ false, /* do not remove a one-shot filter */
+ &fFilter, &fIsOneShot);
+ VBOXUSBFLT_LOCK_RELEASE();
+ NOREF(pCtx);
+ LOG(("Matching Info: Filter (0x%p), pCtx(0x%p), fFilter(%d), fIsOneShot(%d)", uId, pCtx, (int)fFilter, (int)fIsOneShot));
+ if (fFilter)
+ {
+ LOG(("Matching: This device SHOULD be filtered"));
+ /* this device needs to be filtered, but it's not,
+ * leave the PDO in array to issue a replug request for it
+ * later on */
+ continue;
+ }
+ }
+ else
+ {
+ WARN(("vboxUsbFltDevPopulate for PDO 0x%p failed with Status 0x%x", pDevObj, Status));
+ if ( Status == STATUS_CANCELLED
+ && g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails)
+ {
+ /*
+ * This can happen if the device got suspended and is in D3 state where we can't query any strings.
+ * There is no known way to set the power state of the device, especially if there is no driver attached yet.
+ * The sledgehammer approach is to just replug the device to force it out of suspend, see bugref @{9479}.
+ */
+ continue;
+ }
+ }
+
+ LOG(("Matching: This device should NOT be filtered"));
+ /* this device should not be filtered, and it's not */
+ ObDereferenceObject(pDevObj);
+ pDevRelations->Objects[k] = NULL;
+ --cReplugPdos;
+ ASSERT_WARN((uint32_t)cReplugPdos < UINT32_MAX/2, ("cReplugPdos is %d", cReplugPdos));
+ }
+
+ LOG(("(%d) non-matched PDOs to be replugged", cReplugPdos));
+
+ if (cReplugPdos)
+ {
+ for (ULONG k = 0; k < pDevRelations->Count; ++k)
+ {
+ if (!pDevRelations->Objects[k])
+ continue;
+
+ Status = vboxUsbFltPdoReplug(pDevRelations->Objects[k]);
+ ASSERT_WARN(Status == STATUS_SUCCESS, ("vboxUsbFltPdoReplug failed! Status(0x%x)", Status));
+ ObDereferenceObject(pDevRelations->Objects[k]);
+ if (!--cReplugPdos)
+ break;
+ }
+
+ ASSERT_WARN(!cReplugPdos, ("cReplugPdos reached zero!"));
+ }
+
+ vboxUsbFltReplugList(&ReplugDevList);
+
+ ExFreePool(pDevRelations);
+ }
+ else
+ {
+ WARN(("VBoxUsbMonQueryBusRelations failed for hub DO(0x%p), Status(0x%x), pDevRelations(0x%p)",
+ pHubDo, Status, pDevRelations));
+ }
+
+ LOG(("Done Visiting pHubFile(0x%p), pHubDo(0x%p), oContext(0x%p)", pHubFile, pHubDo, pContext));
+
+ return TRUE;
+}
+
+NTSTATUS VBoxUsbFltFilterCheck(PVBOXUSBFLTCTX pContext)
+{
+ KIRQL Irql = KeGetCurrentIrql();
+ ASSERT_WARN(Irql == PASSIVE_LEVEL, ("unexpected IRQL (%d)", Irql));
+
+ LOG(("Running filters, Context (0x%p)..", pContext));
+
+ VBOXUSBFLTCHECKWALKER Data;
+ Data.pContext = pContext;
+ vboxUsbMonHubDevWalk(vboxUsbFltFilterCheckWalker, &Data);
+
+ LOG(("DONE Running filters, Context (0x%p)", pContext));
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS VBoxUsbFltClose(PVBOXUSBFLTCTX pContext)
+{
+ LOG(("Closing context(0x%p)", pContext));
+ LIST_ENTRY ReplugDevList;
+ InitializeListHead(&ReplugDevList);
+
+ ASSERT_WARN(pContext, ("null context"));
+
+ KIRQL Irql = KeGetCurrentIrql();
+ ASSERT_WARN(Irql == PASSIVE_LEVEL, ("irql==(%d)", Irql));
+
+ VBOXUSBFLT_LOCK_ACQUIRE();
+
+ pContext->bRemoved = TRUE;
+ RemoveEntryList(&pContext->ListEntry);
+
+ LOG(("removing owner filters"));
+ /* now re-arrange the filters */
+ /* 1. remove filters */
+ VBoxUSBFilterRemoveOwner(pContext);
+
+ LOG(("enumerating devices.."));
+ /* 2. check if there are devices owned */
+ for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink;
+ pEntry != &g_VBoxUsbFltGlobals.DeviceList;
+ pEntry = pEntry->Flink)
+ {
+ PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry);
+ if (pDevice->pOwner != pContext)
+ continue;
+
+ LOG(("found device(0x%p), pdo(0x%p), state(%d), filter id(0x%p), oneshot(%d)",
+ pDevice, pDevice->Pdo, pDevice->enmState, pDevice->uFltId, (int)pDevice->fIsFilterOneShot));
+ ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED, ("VBOXUSBFLT_DEVSTATE_ADDED state for device(0x%p)", pDevice));
+ ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED, ("VBOXUSBFLT_DEVSTATE_REMOVED state for device(0x%p)", pDevice));
+
+ vboxUsbFltDevOwnerClearLocked(pDevice);
+
+ if (vboxUsbFltDevCheckReplugLocked(pDevice, pContext))
+ {
+ LOG(("device needs replug"));
+ InsertHeadList(&ReplugDevList, &pDevice->RepluggingLe);
+ /* retain to ensure the device is not removed before we issue a replug */
+ vboxUsbFltDevRetain(pDevice);
+ /* keep the PDO alive */
+ ObReferenceObject(pDevice->Pdo);
+ }
+ else
+ {
+ LOG(("device does NOT need replug"));
+ }
+ }
+
+ VBOXUSBFLT_LOCK_RELEASE();
+
+ /* this should replug all devices that were either skipped or grabbed due to the context's */
+ vboxUsbFltReplugList(&ReplugDevList);
+
+ LOG(("SUCCESS done context(0x%p)", pContext));
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS VBoxUsbFltCreate(PVBOXUSBFLTCTX pContext)
+{
+ LOG(("Creating context(0x%p)", pContext));
+ memset(pContext, 0, sizeof (*pContext));
+ pContext->Process = RTProcSelf();
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ InsertHeadList(&g_VBoxUsbFltGlobals.ContextList, &pContext->ListEntry);
+ VBOXUSBFLT_LOCK_RELEASE();
+ LOG(("SUCCESS context(0x%p)", pContext));
+ return STATUS_SUCCESS;
+}
+
+int VBoxUsbFltAdd(PVBOXUSBFLTCTX pContext, PUSBFILTER pFilter, uintptr_t *pId)
+{
+ LOG(("adding filter, Context (0x%p)..", pContext));
+ *pId = 0;
+ /* LOG the filter details. */
+ LOG((__FUNCTION__": %s %s %s",
+ USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) : "<null>",
+ USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) : "<null>",
+ USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) : "<null>"));
+#ifdef VBOX_USB_WITH_VERBOSE_LOGGING
+ LOG(("VBoxUSBClient::addFilter: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x Type%#x",
+ USBFilterGetNum(pFilter, USBFILTERIDX_VENDOR_ID),
+ USBFilterGetNum(pFilter, USBFILTERIDX_PRODUCT_ID),
+ USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_REV),
+ USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_CLASS),
+ USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS),
+ USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_PROTOCOL),
+ USBFilterGetNum(pFilter, USBFILTERIDX_BUS),
+ USBFilterGetNum(pFilter, USBFILTERIDX_PORT),
+ USBFilterGetFilterType(pFilter)));
+#endif
+
+ /* We can't get the bus/port numbers. Ignore them while matching. */
+ USBFilterSetMustBePresent(pFilter, USBFILTERIDX_BUS, false);
+ USBFilterSetMustBePresent(pFilter, USBFILTERIDX_PORT, false);
+
+ /* We may not be able to reconstruct the class/subclass/protocol if we aren't able to
+ * read the device descriptor. Don't require these to be present. See also the fInferredDesc flag.
+ */
+ USBFilterSetMustBePresent(pFilter, USBFILTERIDX_DEVICE_CLASS, false);
+ USBFilterSetMustBePresent(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS, false);
+ USBFilterSetMustBePresent(pFilter, USBFILTERIDX_DEVICE_PROTOCOL, false);
+
+ /* We may also be unable to read string descriptors. Often the userland can't read the
+ * string descriptors either because the device is in a low-power state, but it can happen
+ * that the userland gets lucky and reads the strings, but by the time we get to read them
+ * they're inaccessible due to power management. So, don't require the strings to be present.
+ */
+ USBFilterSetMustBePresent(pFilter, USBFILTERIDX_MANUFACTURER_STR, false);
+ USBFilterSetMustBePresent(pFilter, USBFILTERIDX_PRODUCT_STR, false);
+ USBFilterSetMustBePresent(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR, false);
+
+ uintptr_t uId = 0;
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ /* Add the filter. */
+ int rc = VBoxUSBFilterAdd(pFilter, pContext, &uId);
+ VBOXUSBFLT_LOCK_RELEASE();
+ if (RT_SUCCESS(rc))
+ {
+ LOG(("ADDED filter id 0x%p", uId));
+ ASSERT_WARN(uId, ("uid is NULL"));
+#ifdef VBOX_USBMON_WITH_FILTER_AUTOAPPLY
+ VBoxUsbFltFilterCheck();
+#endif
+ }
+ else
+ {
+ WARN(("VBoxUSBFilterAdd failed rc (%d)", rc));
+ ASSERT_WARN(!uId, ("uid is not NULL"));
+ }
+
+ *pId = uId;
+ return rc;
+}
+
+int VBoxUsbFltRemove(PVBOXUSBFLTCTX pContext, uintptr_t uId)
+{
+ LOG(("removing filter id(0x%p), Context (0x%p)..", pContext, uId));
+ Assert(uId);
+
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ int rc = VBoxUSBFilterRemove(pContext, uId);
+ if (!RT_SUCCESS(rc))
+ {
+ WARN(("VBoxUSBFilterRemove failed rc (%d)", rc));
+ VBOXUSBFLT_LOCK_RELEASE();
+ return rc;
+ }
+
+ LOG(("enumerating devices.."));
+ for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink;
+ pEntry != &g_VBoxUsbFltGlobals.DeviceList;
+ pEntry = pEntry->Flink)
+ {
+ PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry);
+ if (pDevice->fIsFilterOneShot)
+ {
+ ASSERT_WARN(!pDevice->uFltId, ("oneshot filter on device(0x%p): unexpected uFltId(%d)", pDevice, pDevice->uFltId));
+ }
+
+ if (pDevice->uFltId != uId)
+ continue;
+
+ ASSERT_WARN(pDevice->pOwner == pContext, ("Device(0x%p) owner(0x%p) not match to (0x%p)", pDevice, pDevice->pOwner, pContext));
+ if (pDevice->pOwner != pContext)
+ continue;
+
+ LOG(("found device(0x%p), pdo(0x%p), state(%d), filter id(0x%p), oneshot(%d)",
+ pDevice, pDevice->Pdo, pDevice->enmState, pDevice->uFltId, (int)pDevice->fIsFilterOneShot));
+ ASSERT_WARN(!pDevice->fIsFilterOneShot, ("device(0x%p) is filtered with a oneshot filter", pDevice));
+ pDevice->uFltId = 0;
+ /* clear the fIsFilterOneShot flag to ensure the device is replugged on the next VBoxUsbFltFilterCheck call */
+ pDevice->fIsFilterOneShot = false;
+ }
+ VBOXUSBFLT_LOCK_RELEASE();
+
+ LOG(("done enumerating devices"));
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef VBOX_USBMON_WITH_FILTER_AUTOAPPLY
+ VBoxUsbFltFilterCheck();
+#endif
+ }
+ return rc;
+}
+
+static USBDEVICESTATE vboxUsbDevGetUserState(PVBOXUSBFLTCTX pContext, PVBOXUSBFLT_DEVICE pDevice)
+{
+ if (vboxUsbFltDevStateIsNotFiltered(pDevice))
+ return USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+
+ /* the device is filtered, or replugging */
+ if (pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING)
+ {
+ ASSERT_WARN(!pDevice->pOwner, ("replugging device(0x%p) still has an owner(0x%p)", pDevice, pDevice->pOwner));
+ ASSERT_WARN(!pDevice->uFltId, ("replugging device(0x%p) still has filter(0x%p)", pDevice, pDevice->uFltId));
+ /* no user state for this, we should not return it tu the user */
+ return USBDEVICESTATE_USED_BY_HOST;
+ }
+
+ /* the device is filtered, if owner differs from the context, return as USED_BY_HOST */
+ ASSERT_WARN(pDevice->pOwner, ("device(0x%p) has noowner", pDevice));
+ /* the id can be null if a filter is removed */
+// Assert(pDevice->uFltId);
+
+ if (pDevice->pOwner != pContext)
+ {
+ LOG(("Device owner differs from the current context, returning used by host"));
+ return USBDEVICESTATE_USED_BY_HOST;
+ }
+
+ switch (pDevice->enmState)
+ {
+ case VBOXUSBFLT_DEVSTATE_UNCAPTURED:
+ case VBOXUSBFLT_DEVSTATE_CAPTURING:
+ return USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+ case VBOXUSBFLT_DEVSTATE_CAPTURED:
+ return USBDEVICESTATE_HELD_BY_PROXY;
+ case VBOXUSBFLT_DEVSTATE_USED_BY_GUEST:
+ return USBDEVICESTATE_USED_BY_GUEST;
+ default:
+ WARN(("unexpected device state(%d) for device(0x%p)", pDevice->enmState, pDevice));
+ return USBDEVICESTATE_UNSUPPORTED;
+ }
+}
+
+NTSTATUS VBoxUsbFltGetDevice(PVBOXUSBFLTCTX pContext, HVBOXUSBDEVUSR hDevice, PUSBSUP_GETDEV_MON pInfo)
+{
+ if (!hDevice)
+ return STATUS_INVALID_PARAMETER;
+
+ memset (pInfo, 0, sizeof (*pInfo));
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink;
+ pEntry != &g_VBoxUsbFltGlobals.DeviceList;
+ pEntry = pEntry->Flink)
+ {
+ PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry);
+ Assert(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED);
+ Assert(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED);
+
+ if (pDevice != hDevice)
+ continue;
+
+ USBDEVICESTATE enmUsrState = vboxUsbDevGetUserState(pContext, pDevice);
+ pInfo->enmState = enmUsrState;
+ VBOXUSBFLT_LOCK_RELEASE();
+ return STATUS_SUCCESS;
+ }
+
+ VBOXUSBFLT_LOCK_RELEASE();
+
+ /* We should not get this far with valid input. */
+ return STATUS_INVALID_PARAMETER;
+}
+
+NTSTATUS VBoxUsbFltPdoAdd(PDEVICE_OBJECT pPdo, BOOLEAN *pbFiltered)
+{
+ *pbFiltered = FALSE;
+ PVBOXUSBFLT_DEVICE pDevice;
+
+ /* Find the real PDO+reference. Dereference when we're done with it. Note that
+ * the input pPdo was not explicitly referenced so we're not dropping its ref.
+ */
+ PDEVICE_OBJECT pDevObj = IoGetDeviceAttachmentBaseRef(pPdo);
+ LOG(("DevObj=%p, real PDO=%p\n", pPdo, pDevObj));
+ pPdo = pDevObj;
+
+ /* first check if device is in the a already */
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ pDevice = vboxUsbFltDevGetLocked(pPdo);
+ if (pDevice)
+ {
+ LOG(("found device (0x%p), state(%d) for PDO(0x%p)", pDevice, pDevice->enmState, pPdo));
+ ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED, ("VBOXUSBFLT_DEVSTATE_ADDED state for device(0x%p)", pDevice));
+ ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED, ("VBOXUSBFLT_DEVSTATE_REMOVED state for device(0x%p)", pDevice));
+ *pbFiltered = pDevice->enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING;
+ VBOXUSBFLT_LOCK_RELEASE();
+ ObDereferenceObject(pPdo);
+ return STATUS_SUCCESS;
+ }
+ VBOXUSBFLT_LOCK_RELEASE();
+ pDevice = (PVBOXUSBFLT_DEVICE)VBoxUsbMonMemAllocZ(sizeof (*pDevice));
+ if (!pDevice)
+ {
+ WARN(("VBoxUsbMonMemAllocZ failed"));
+ ObDereferenceObject(pPdo);
+ return STATUS_NO_MEMORY;
+ }
+
+ pDevice->enmState = VBOXUSBFLT_DEVSTATE_ADDED;
+ pDevice->cRefs = 1;
+ NTSTATUS Status = vboxUsbFltDevPopulate(pDevice, pPdo /* , TRUE /* need all props */);
+ if (!NT_SUCCESS(Status))
+ {
+ WARN(("vboxUsbFltDevPopulate failed, Status 0x%x", Status));
+ ObDereferenceObject(pPdo);
+ VBoxUsbMonMemFree(pDevice);
+ return Status;
+ }
+
+ uintptr_t uId;
+ bool fFilter = false;
+ bool fIsOneShot = false;
+ PVBOXUSBFLTCTX pCtx;
+ PVBOXUSBFLT_DEVICE pTmpDev;
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ /* (paranoia) re-check the device is still not here */
+ pTmpDev = vboxUsbFltDevGetLocked(pPdo);
+
+ /* Drop the PDO ref, now we won't need it anymore. */
+ ObDereferenceObject(pPdo);
+
+ if (pTmpDev)
+ {
+ LOG(("second try: found device (0x%p), state(%d) for PDO(0x%p)", pDevice, pDevice->enmState, pPdo));
+ ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED, ("second try: VBOXUSBFLT_DEVSTATE_ADDED state for device(0x%p)", pDevice));
+ ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED, ("second try: VBOXUSBFLT_DEVSTATE_REMOVED state for device(0x%p)", pDevice));
+ *pbFiltered = pTmpDev->enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING;
+ VBOXUSBFLT_LOCK_RELEASE();
+ VBoxUsbMonMemFree(pDevice);
+ return STATUS_SUCCESS;
+ }
+
+ LOG(("Created Device 0x%p for PDO 0x%p", pDevice, pPdo));
+
+ pCtx = vboxUsbFltDevMatchLocked(pDevice, &uId,
+ true, /* remove a one-shot filter */
+ &fFilter, &fIsOneShot);
+ LOG(("Matching Info: Filter (0x%p), pCtx(0x%p), fFilter(%d), fIsOneShot(%d)", uId, pCtx, (int)fFilter, (int)fIsOneShot));
+ if (fFilter)
+ {
+ LOG(("Created Device 0x%p should be filtered", pDevice));
+ ASSERT_WARN(pCtx, ("zero ctx"));
+ ASSERT_WARN(uId, ("zero uId"));
+ pDevice->enmState = VBOXUSBFLT_DEVSTATE_CAPTURING;
+ }
+ else
+ {
+ LOG(("Created Device 0x%p should NOT be filtered", pDevice));
+ ASSERT_WARN(!uId == !pCtx, ("invalid uid(0x%p) - ctx(0x%p) pair", uId, pCtx)); /* either both zero or both not */
+ pDevice->enmState = VBOXUSBFLT_DEVSTATE_UNCAPTURED;
+ }
+
+ if (pCtx)
+ vboxUsbFltDevOwnerSetLocked(pDevice, pCtx, fIsOneShot ? 0 : uId, fIsOneShot);
+
+ InsertHeadList(&g_VBoxUsbFltGlobals.DeviceList, &pDevice->GlobalLe);
+
+ /* do not need to signal anything here -
+ * going to do that once the proxy device object starts */
+ VBOXUSBFLT_LOCK_RELEASE();
+
+ *pbFiltered = fFilter;
+
+ return STATUS_SUCCESS;
+}
+
+BOOLEAN VBoxUsbFltPdoIsFiltered(PDEVICE_OBJECT pPdo)
+{
+ VBOXUSBFLT_DEVSTATE enmState = VBOXUSBFLT_DEVSTATE_REMOVED;
+
+ /* Find the real PDO+reference. Dereference when we're done with it. Note that
+ * the input pPdo was not explicitly referenced so we're not dropping its ref.
+ */
+ PDEVICE_OBJECT pDevObj = IoGetDeviceAttachmentBaseRef(pPdo);
+ LOG(("DevObj=%p, real PDO=%p\n", pPdo, pDevObj));
+ pPdo = pDevObj;
+
+ VBOXUSBFLT_LOCK_ACQUIRE();
+
+ PVBOXUSBFLT_DEVICE pDevice = vboxUsbFltDevGetLocked(pPdo);
+ if (pDevice)
+ enmState = pDevice->enmState;
+
+ VBOXUSBFLT_LOCK_RELEASE();
+ ObDereferenceObject(pPdo);
+
+ return enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING;
+}
+
+NTSTATUS VBoxUsbFltPdoRemove(PDEVICE_OBJECT pPdo)
+{
+ PVBOXUSBFLT_DEVICE pDevice;
+ VBOXUSBFLT_DEVSTATE enmOldState;
+
+ /* Find the real PDO+reference. Dereference when we're done with it. Note that
+ * the input pPdo was not explicitly referenced so we're not dropping its ref.
+ */
+ PDEVICE_OBJECT pDevObj = IoGetDeviceAttachmentBaseRef(pPdo);
+ LOG(("DevObj=%p, real PDO=%p\n", pPdo, pDevObj));
+ pPdo = pDevObj;
+
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ pDevice = vboxUsbFltDevGetLocked(pPdo);
+ if (pDevice)
+ {
+ RemoveEntryList(&pDevice->GlobalLe);
+ enmOldState = pDevice->enmState;
+ pDevice->enmState = VBOXUSBFLT_DEVSTATE_REMOVED;
+ }
+ VBOXUSBFLT_LOCK_RELEASE();
+ ObDereferenceObject(pPdo);
+ if (pDevice)
+ vboxUsbFltDevRelease(pDevice);
+ return STATUS_SUCCESS;
+}
+
+HVBOXUSBFLTDEV VBoxUsbFltProxyStarted(PDEVICE_OBJECT pPdo)
+{
+ PVBOXUSBFLT_DEVICE pDevice;
+ VBOXUSBFLT_LOCK_ACQUIRE();
+
+ /* NB: The USB proxy (VBoxUSB.sys) passes us the real PDO, not anything above that. */
+ pDevice = vboxUsbFltDevGetLocked(pPdo);
+ /*
+ * Prevent a host crash when vboxUsbFltDevGetLocked fails to locate the matching PDO
+ * in g_VBoxUsbFltGlobals.DeviceList (see @bugref{6509}).
+ */
+ if (pDevice == NULL)
+ {
+ WARN(("failed to get device for PDO(0x%p)", pPdo));
+ }
+ else if (pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURING)
+ {
+ pDevice->enmState = VBOXUSBFLT_DEVSTATE_CAPTURED;
+ LOG(("The proxy notified proxy start for the captured device 0x%p", pDevice));
+ vboxUsbFltDevRetain(pDevice);
+ }
+ else
+ {
+ WARN(("invalid state, %d", pDevice->enmState));
+ pDevice = NULL;
+ }
+ VBOXUSBFLT_LOCK_RELEASE();
+ return pDevice;
+}
+
+void VBoxUsbFltProxyStopped(HVBOXUSBFLTDEV hDev)
+{
+ PVBOXUSBFLT_DEVICE pDevice = (PVBOXUSBFLT_DEVICE)hDev;
+ /*
+ * Prevent a host crash when VBoxUsbFltProxyStarted fails, returning NULL.
+ * See @bugref{6509}.
+ */
+ if (pDevice == NULL)
+ {
+ WARN(("VBoxUsbFltProxyStopped called with NULL device pointer"));
+ return;
+ }
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ if (pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURED
+ || pDevice->enmState == VBOXUSBFLT_DEVSTATE_USED_BY_GUEST)
+ {
+ /* this is due to devie was physically removed */
+ LOG(("The proxy notified proxy stop for the captured device 0x%p, current state %d", pDevice, pDevice->enmState));
+ pDevice->enmState = VBOXUSBFLT_DEVSTATE_CAPTURING;
+ }
+ else
+ {
+ if (pDevice->enmState != VBOXUSBFLT_DEVSTATE_REPLUGGING)
+ {
+ WARN(("invalid state, %d", pDevice->enmState));
+ }
+ }
+ VBOXUSBFLT_LOCK_RELEASE();
+
+ vboxUsbFltDevRelease(pDevice);
+}
+
+
+static NTSTATUS vboxUsbFltRegKeyQuery(PWSTR ValueName, ULONG ValueType, PVOID ValueData, ULONG ValueLength, PVOID Context, PVOID EntryContext)
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ RT_NOREF(ValueName, Context);
+ if ( ValueType == REG_DWORD
+ && ValueLength == sizeof(ULONG))
+ *(ULONG *)EntryContext = *(ULONG *)ValueData;
+ else
+ Status = STATUS_OBJECT_TYPE_MISMATCH;
+
+ return Status;
+}
+
+
+NTSTATUS VBoxUsbFltInit()
+{
+ int rc = VBoxUSBFilterInit();
+ if (RT_FAILURE(rc))
+ {
+ WARN(("VBoxUSBFilterInit failed, rc (%d)", rc));
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ memset(&g_VBoxUsbFltGlobals, 0, sizeof (g_VBoxUsbFltGlobals));
+ InitializeListHead(&g_VBoxUsbFltGlobals.DeviceList);
+ InitializeListHead(&g_VBoxUsbFltGlobals.ContextList);
+ InitializeListHead(&g_VBoxUsbFltGlobals.BlackDeviceList);
+ vboxUsbFltBlDevPopulateWithKnownLocked();
+ VBOXUSBFLT_LOCK_INIT();
+
+ /*
+ * Check whether the setting to force replugging USB devices when
+ * querying string descriptors fail is set in the registry,
+ * see @bugref{9479}.
+ */
+ RTL_QUERY_REGISTRY_TABLE aParams[] =
+ {
+ {vboxUsbFltRegKeyQuery, 0, L"ForceReplugWhenDevPopulateFails", &g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails, REG_DWORD, &g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails, sizeof(ULONG) },
+ { NULL, 0, NULL, NULL, 0, 0, 0 }
+ };
+ UNICODE_STRING UnicodePath = RTL_CONSTANT_STRING(L"\\VBoxUSB");
+
+ NTSTATUS Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL, UnicodePath.Buffer, &aParams[0], NULL, NULL);
+ if (Status == STATUS_SUCCESS)
+ {
+ if (g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails)
+ LOG(("Forcing replug of USB devices where querying the descriptors fail\n"));
+ }
+ else
+ LOG(("RtlQueryRegistryValues() -> %#x, assuming defaults\n", Status));
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS VBoxUsbFltTerm()
+{
+ bool bBusy = false;
+ VBOXUSBFLT_LOCK_ACQUIRE();
+ do
+ {
+ if (!IsListEmpty(&g_VBoxUsbFltGlobals.ContextList))
+ {
+ AssertFailed();
+ bBusy = true;
+ break;
+ }
+
+ PLIST_ENTRY pNext = NULL;
+ for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink;
+ pEntry != &g_VBoxUsbFltGlobals.DeviceList;
+ pEntry = pNext)
+ {
+ pNext = pEntry->Flink;
+ PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry);
+ Assert(!pDevice->uFltId);
+ Assert(!pDevice->pOwner);
+ if (pDevice->cRefs != 1)
+ {
+ AssertFailed();
+ bBusy = true;
+ break;
+ }
+ }
+ } while (0);
+
+ VBOXUSBFLT_LOCK_RELEASE()
+
+ if (bBusy)
+ {
+ return STATUS_DEVICE_BUSY;
+ }
+
+ for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink;
+ pEntry != &g_VBoxUsbFltGlobals.DeviceList;
+ pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink)
+ {
+ RemoveEntryList(pEntry);
+ PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry);
+ pDevice->enmState = VBOXUSBFLT_DEVSTATE_REMOVED;
+ vboxUsbFltDevRelease(pDevice);
+ }
+
+ vboxUsbFltBlDevClearLocked();
+
+ VBOXUSBFLT_LOCK_TERM();
+
+ VBoxUSBFilterTerm();
+
+ return STATUS_SUCCESS;
+}
+