summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp')
-rw-r--r--src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp571
1 files changed, 571 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp
new file mode 100644
index 00000000..7533e653
--- /dev/null
+++ b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp
@@ -0,0 +1,571 @@
+/* $Id: VBoxUSBFilterMgr.cpp $ */
+/** @file
+ * VirtualBox Ring-0 USB Filter Manager.
+ */
+
+/*
+ * Copyright (C) 2007-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 <VBox/usbfilter.h>
+#include "VBoxUSBFilterMgr.h"
+
+#include <iprt/err.h>
+#include <iprt/handletable.h>
+#include <iprt/mem.h>
+#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK
+# include <iprt/spinlock.h>
+#else
+# include <iprt/semaphore.h>
+#endif
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** @def VBOXUSBFILTERMGR_LOCK
+ * Locks the filter list. Careful with scoping since this may
+ * create a temporary variable. Don't call twice in the same function.
+ */
+
+/** @def VBOXUSBFILTERMGR_UNLOCK
+ * Unlocks the filter list.
+ */
+#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK
+
+# define VBOXUSBFILTERMGR_LOCK() \
+ RTSpinlockAcquire(g_Spinlock)
+
+# define VBOXUSBFILTERMGR_UNLOCK() \
+ RTSpinlockRelease(g_Spinlock)
+
+#else
+
+# define VBOXUSBFILTERMGR_LOCK() \
+ do { int rc2 = RTSemFastMutexRequest(g_Mtx); AssertRC(rc2); } while (0)
+
+# define VBOXUSBFILTERMGR_UNLOCK() \
+ do { int rc2 = RTSemFastMutexRelease(g_Mtx); AssertRC(rc2); } while (0)
+
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to an VBoxUSB filter. */
+typedef struct VBOXUSBFILTER *PVBOXUSBFILTER;
+/** Pointer to PVBOXUSBFILTER. */
+typedef PVBOXUSBFILTER *PPVBOXUSBFILTER;
+
+/**
+ * VBoxUSB internal filter representation.
+ */
+typedef struct VBOXUSBFILTER
+{
+ /** The core filter. */
+ USBFILTER Core;
+ /** The filter owner. */
+ VBOXUSBFILTER_CONTEXT Owner;
+ /** The filter Id. */
+ uint32_t uHnd;
+ /** Pointer to the next filter in the list. */
+ PVBOXUSBFILTER pNext;
+} VBOXUSBFILTER;
+
+/**
+ * VBoxUSB filter list.
+ */
+typedef struct VBOXUSBFILTERLIST
+{
+ /** The head pointer. */
+ PVBOXUSBFILTER pHead;
+ /** The tail pointer. */
+ PVBOXUSBFILTER pTail;
+} VBOXUSBFILTERLIST;
+/** Pointer to a VBOXUSBFILTERLIST. */
+typedef VBOXUSBFILTERLIST *PVBOXUSBFILTERLIST;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK
+/** Spinlock protecting the filter lists. */
+static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK;
+#else
+/** Mutex protecting the filter lists. */
+static RTSEMFASTMUTEX g_Mtx = NIL_RTSEMFASTMUTEX;
+#endif
+/** The per-type filter lists.
+ * @remark The first entry is empty (USBFILTERTYPE_INVALID). */
+static VBOXUSBFILTERLIST g_aLists[USBFILTERTYPE_END];
+/** The handle table to match handles to the right filter. */
+static RTHANDLETABLE g_hHndTableFilters = NIL_RTHANDLETABLE;
+
+
+
+/**
+ * Initializes the VBoxUSB filter manager.
+ *
+ * @returns IPRT status code.
+ */
+int VBoxUSBFilterInit(void)
+{
+#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK
+ int rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxUSBFilter");
+#else
+ int rc = RTSemFastMutexCreate(&g_Mtx);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fFlags;
+#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK
+ fFlags = RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE;
+#else
+ fFlags = RTHANDLETABLE_FLAGS_LOCKED;
+#endif
+ rc = RTHandleTableCreateEx(&g_hHndTableFilters, fFlags, 1 /* uBase */, 8192 /* cMax */,
+ NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* not really required, but anyway... */
+ for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++)
+ g_aLists[i].pHead = g_aLists[i].pTail = NULL;
+ }
+ else
+ {
+#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK
+ RTSpinlockDestroy(g_Spinlock);
+ g_Spinlock = NIL_RTSPINLOCK;
+#else
+ RTSemFastMutexDestroy(g_Mtx);
+ g_Mtx = NIL_RTSEMFASTMUTEX;
+#endif
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Internal worker that frees a filter.
+ *
+ * @param pFilter The filter to free.
+ */
+static void vboxUSBFilterFree(PVBOXUSBFILTER pFilter)
+{
+ USBFilterDelete(&pFilter->Core);
+ pFilter->Owner = VBOXUSBFILTER_CONTEXT_NIL;
+ pFilter->pNext = NULL;
+ RTMemFree(pFilter);
+}
+
+
+/**
+ * Terminates the VBoxUSB filter manager.
+ */
+void VBoxUSBFilterTerm(void)
+{
+#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK
+ RTSpinlockDestroy(g_Spinlock);
+ g_Spinlock = NIL_RTSPINLOCK;
+#else
+ RTSemFastMutexDestroy(g_Mtx);
+ g_Mtx = NIL_RTSEMFASTMUTEX;
+#endif
+
+ for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++)
+ {
+ PVBOXUSBFILTER pCur = g_aLists[i].pHead;
+ g_aLists[i].pHead = g_aLists[i].pTail = NULL;
+ while (pCur)
+ {
+ PVBOXUSBFILTER pNext = pCur->pNext;
+ RTHandleTableFree(g_hHndTableFilters, pCur->uHnd);
+ vboxUSBFilterFree(pCur);
+ pCur = pNext;
+ }
+ }
+
+ RTHandleTableDestroy(g_hHndTableFilters, NULL, NULL);
+}
+
+
+/**
+ * Adds a new filter.
+ *
+ * The filter will be validate, duplicated and added.
+ *
+ * @returns IPRT status code.
+ * @param pFilter The filter.
+ * @param Owner The filter owner. Must be non-zero.
+ * @param puId Where to store the filter ID.
+ */
+int VBoxUSBFilterAdd(PCUSBFILTER pFilter, VBOXUSBFILTER_CONTEXT Owner, uintptr_t *puId)
+{
+ /*
+ * Validate input.
+ */
+ int rc = USBFilterValidate(pFilter);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!Owner || Owner == VBOXUSBFILTER_CONTEXT_NIL)
+ return VERR_INVALID_PARAMETER;
+ if (!RT_VALID_PTR(puId))
+ return VERR_INVALID_POINTER;
+
+ /*
+ * Allocate a new filter.
+ */
+ PVBOXUSBFILTER pNew = (PVBOXUSBFILTER)RTMemAlloc(sizeof(*pNew));
+ if (!pNew)
+ return VERR_NO_MEMORY;
+ memcpy(&pNew->Core, pFilter, sizeof(pNew->Core));
+ pNew->Owner = Owner;
+ pNew->pNext = NULL;
+
+ rc = RTHandleTableAlloc(g_hHndTableFilters, pNew, &pNew->uHnd);
+ if (RT_SUCCESS(rc))
+ {
+ *puId = pNew->uHnd;
+
+ /*
+ * Insert it.
+ */
+ PVBOXUSBFILTERLIST pList = &g_aLists[pFilter->enmType];
+
+ VBOXUSBFILTERMGR_LOCK();
+
+ if (pList->pTail)
+ pList->pTail->pNext = pNew;
+ else
+ pList->pHead = pNew;
+ pList->pTail = pNew;
+
+ VBOXUSBFILTERMGR_UNLOCK();
+ }
+ else
+ RTMemFree(pNew);
+
+ return rc;
+}
+
+
+/**
+ * Removes an existing filter.
+ *
+ * The filter will be validate, duplicated and added.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS if successfully removed.
+ * @retval VERR_FILE_NOT_FOUND if the specified filter/owner cannot be found.
+ *
+ * @param Owner The filter owner.
+ * @param uId The ID of the filter that's to be removed.
+ * Returned by VBoxUSBFilterAdd().
+ */
+int VBoxUSBFilterRemove(VBOXUSBFILTER_CONTEXT Owner, uintptr_t uId)
+{
+ /*
+ * Validate input.
+ */
+ if (!uId || uId != (uint32_t)uId)
+ return VERR_INVALID_PARAMETER;
+ if (!Owner || Owner == VBOXUSBFILTER_CONTEXT_NIL)
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * Locate and unlink it.
+ */
+ uint32_t uHnd = (uint32_t)uId;
+ PVBOXUSBFILTER pCur = NULL;
+
+ VBOXUSBFILTERMGR_LOCK();
+
+ for (unsigned i = USBFILTERTYPE_FIRST; !pCur && i < RT_ELEMENTS(g_aLists); i++)
+ {
+ PVBOXUSBFILTER pPrev = NULL;
+ pCur = g_aLists[i].pHead;
+ while (pCur)
+ {
+ if ( pCur->uHnd == uHnd
+ && pCur->Owner == Owner)
+ {
+ PVBOXUSBFILTER pNext = pCur->pNext;
+ if (pPrev)
+ pPrev->pNext = pNext;
+ else
+ g_aLists[i].pHead = pNext;
+ if (!pNext)
+ g_aLists[i].pTail = pPrev;
+ break;
+ }
+
+ pPrev = pCur;
+ pCur = pCur->pNext;
+ }
+ }
+
+ VBOXUSBFILTERMGR_UNLOCK();
+
+ /*
+ * Free it (if found).
+ */
+ if (pCur)
+ {
+ void *pv = RTHandleTableFree(g_hHndTableFilters, pCur->uHnd);
+ Assert(pv == pCur); NOREF(pv);
+ vboxUSBFilterFree(pCur);
+ return VINF_SUCCESS;
+ }
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+VBOXUSBFILTER_CONTEXT VBoxUSBFilterGetOwner(uintptr_t uId)
+{
+ Assert(uId);
+ /*
+ * Validate input.
+ */
+ if (!uId || uId != (uint32_t)uId)
+ return VBOXUSBFILTER_CONTEXT_NIL;
+
+ /*
+ * Result.
+ */
+ VBOXUSBFILTER_CONTEXT Owner = VBOXUSBFILTER_CONTEXT_NIL;
+
+ VBOXUSBFILTERMGR_LOCK();
+
+ PVBOXUSBFILTER pCur = (PVBOXUSBFILTER)RTHandleTableLookup(g_hHndTableFilters, (uint32_t)uId);
+ if (pCur)
+ Owner = pCur->Owner;
+
+ Assert(Owner != VBOXUSBFILTER_CONTEXT_NIL);
+
+ VBOXUSBFILTERMGR_UNLOCK();
+
+ return Owner;
+}
+
+/**
+ * Removes all filters belonging to the specified owner.
+ *
+ * This is typically called when an owner disconnects or
+ * terminates unexpectedly.
+ *
+ * @param Owner The owner
+ */
+void VBoxUSBFilterRemoveOwner(VBOXUSBFILTER_CONTEXT Owner)
+{
+ /*
+ * Collect the filters that should be freed.
+ */
+ PVBOXUSBFILTER pToFree = NULL;
+
+ VBOXUSBFILTERMGR_LOCK();
+
+ for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++)
+ {
+ PVBOXUSBFILTER pPrev = NULL;
+ PVBOXUSBFILTER pCur = g_aLists[i].pHead;
+ while (pCur)
+ {
+ if (pCur->Owner == Owner)
+ {
+ PVBOXUSBFILTER pNext = pCur->pNext;
+ if (pPrev)
+ pPrev->pNext = pNext;
+ else
+ g_aLists[i].pHead = pNext;
+ if (!pNext)
+ g_aLists[i].pTail = pPrev;
+
+ pCur->pNext = pToFree;
+ pToFree = pCur;
+
+ pCur = pNext;
+ }
+ else
+ {
+ pPrev = pCur;
+ pCur = pCur->pNext;
+ }
+ }
+ }
+
+ VBOXUSBFILTERMGR_UNLOCK();
+
+ /*
+ * Free any filters we've found.
+ */
+ while (pToFree)
+ {
+ PVBOXUSBFILTER pNext = pToFree->pNext;
+ void *pv = RTHandleTableFree(g_hHndTableFilters, pToFree->uHnd);
+ Assert(pv == pToFree); NOREF(pv);
+ vboxUSBFilterFree(pToFree);
+ pToFree = pNext;
+ }
+}
+
+/**
+ * Match the specified device against the filters.
+ * Unlike the VBoxUSBFilterMatch, returns Owner also if exclude filter is matched
+ *
+ * @returns Owner on if matched, VBOXUSBFILTER_CONTEXT_NIL it not matched.
+ * @param pDevice The device data as a filter structure.
+ * See USBFilterMatch for how to construct this.
+ * @param puId Where to store the filter id (optional).
+ * @param fRemoveFltIfOneShot Whether or not to remove one-shot filters on
+ * match.
+ * @param pfFilter Where to store whether the device must be filtered or not
+ * @param pfIsOneShot Where to return whetehr the match was a one-shot
+ * filter or not. Optional.
+ *
+ */
+VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatchEx(PCUSBFILTER pDevice, uintptr_t *puId,
+ bool fRemoveFltIfOneShot, bool *pfFilter, bool *pfIsOneShot)
+{
+ /*
+ * Validate input.
+ */
+ int rc = USBFilterValidate(pDevice);
+ AssertRCReturn(rc, VBOXUSBFILTER_CONTEXT_NIL);
+
+ *pfFilter = false;
+ if (puId)
+ *puId = 0;
+
+ /*
+ * Search the lists for a match.
+ * (The lists are ordered by priority.)
+ */
+ VBOXUSBFILTERMGR_LOCK();
+
+ for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++)
+ {
+ PVBOXUSBFILTER pPrev = NULL;
+ PVBOXUSBFILTER pCur = g_aLists[i].pHead;
+ while (pCur)
+ {
+ if (USBFilterMatch(&pCur->Core, pDevice))
+ {
+ /*
+ * Take list specific actions and return.
+ *
+ * The code does NOT implement the case where there are two or more
+ * filter clients, and one of them is releasing a device that's
+ * requested by some of the others. It's just too much work for a
+ * situation that noone will encounter.
+ */
+ if (puId)
+ *puId = pCur->uHnd;
+ VBOXUSBFILTER_CONTEXT Owner = pCur->Owner;
+ *pfFilter = !!(i != USBFILTERTYPE_IGNORE
+ && i != USBFILTERTYPE_ONESHOT_IGNORE);
+
+ if ( i == USBFILTERTYPE_ONESHOT_IGNORE
+ || i == USBFILTERTYPE_ONESHOT_CAPTURE)
+ {
+ if (fRemoveFltIfOneShot)
+ {
+ /* unlink */
+ PVBOXUSBFILTER pNext = pCur->pNext;
+ if (pPrev)
+ pPrev->pNext = pNext;
+ else
+ g_aLists[i].pHead = pNext;
+ if (!pNext)
+ g_aLists[i].pTail = pPrev;
+ }
+ }
+
+ VBOXUSBFILTERMGR_UNLOCK();
+
+ if ( i == USBFILTERTYPE_ONESHOT_IGNORE
+ || i == USBFILTERTYPE_ONESHOT_CAPTURE)
+ {
+ if (fRemoveFltIfOneShot)
+ {
+ void *pv = RTHandleTableFree(g_hHndTableFilters, pCur->uHnd);
+ Assert(pv == pCur); NOREF(pv);
+ vboxUSBFilterFree(pCur);
+ }
+ if (pfIsOneShot)
+ *pfIsOneShot = true;
+ }
+ else
+ {
+ if (pfIsOneShot)
+ *pfIsOneShot = false;
+ }
+ return Owner;
+ }
+
+ pPrev = pCur;
+ pCur = pCur->pNext;
+ }
+ }
+
+ VBOXUSBFILTERMGR_UNLOCK();
+ return VBOXUSBFILTER_CONTEXT_NIL;
+}
+
+/**
+ * Match the specified device against the filters.
+ *
+ * @returns Owner on if matched, VBOXUSBFILTER_CONTEXT_NIL it not matched.
+ * @param pDevice The device data as a filter structure.
+ * See USBFilterMatch for how to construct this.
+ * @param puId Where to store the filter id (optional).
+ */
+VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatch(PCUSBFILTER pDevice, uintptr_t *puId)
+{
+ bool fFilter = false;
+ VBOXUSBFILTER_CONTEXT Owner = VBoxUSBFilterMatchEx(pDevice, puId,
+ true, /* remove filter is it's a one-shot*/
+ &fFilter, NULL /* bool * fIsOneShot */);
+ if (fFilter)
+ {
+ Assert(Owner != VBOXUSBFILTER_CONTEXT_NIL);
+ return Owner;
+ }
+ return VBOXUSBFILTER_CONTEXT_NIL;
+}
+