Fix HID transfers, harden the tunnel, add E2E crypto and direct peers

The HID failure came down to the endpoint type map being indexed by
endpoint number without the direction bit. A composite device can have
endpoint 1 as both interrupt IN (0x81) and bulk OUT (0x01); the last one
read won, so interrupt URBs were submitted as bulk and the kernel rejected
them. The device attached and stayed silent.

Endpoint data now comes from the raw descriptors read from /dev/bus/usb
rather than sysfs, which only ever exposes the active alternate setting —
a webcam's isochronous endpoints are invisible there because they only
exist after SET_INTERFACE. Two sysfs parsing bugs fell out of that too:
the numeric endpoint attributes are hex without a prefix (wMaxPacketSize
"0040" was read as 40, not 64), and bInterval was never read at all.

Reliability: three places could freeze the whole process. The share path
fed io.Pipe from the WebSocket read loop, so one slow USB transfer stalled
every tunnel and the keepalives with them. The relay wrote to client
sockets while holding the hub lock, so one peer that stopped reading
blocked routing and registration for everyone. Control transfers ran
inline in the protocol loop behind a 5s timeout. Also fixed: a use-after-
free where a discarded URB's memory could be collected while the kernel
still owned it, a reap loop that spun at 100% CPU on ioctl errors, a
missing attach timeout, a double close(done) panic, and Hash[:8] in the
relay's log line, which let a client with a short hash take the server
down.

Adds mode "both", so one client can offer and consume devices at once.
The tunnel and client-left callbacks became multicast for it: as plain
fields the second manager to register silently unhooked the first.

Tunnel traffic is now AES-256-GCM end to end, on the relay path as well
as directly. The key is derived from the three tokens, not from the group
hash — the relay is told the hash, so a key derived from it would protect
nothing from the one party in the middle. Group IDs are unchanged, so
existing setups keep working; only clients configured without the tokens
drop to unencrypted, relay-only operation.

Peers now try to connect directly, with the relay supplying the public
address neither side can determine for itself. Candidates are raced
because an unreachable address hangs until timeout rather than refusing.
Falling back to the relay is not an error.

Platform reach: cross-compiled targets for ARM, MIPS and RISC-V (the
Linux client needed no code changes — usbdevfs is not architecture
specific), multi-arch Docker images, an Android bridge that accepts
devices over SCM_RIGHTS because apps cannot open /dev/bus/usb, and macOS
builds via system_profiler enumeration.

Adds a Windows KMDF filter driver under driver/windows with its Go side.
UNTESTED: it has never been compiled or run, needs the WDK to build and
an EV certificate to distribute. Treat it as a starting point.

Adds "usb-client diag": says per machine whether sharing and using are
possible, what stands in the way, and what fixes it. Reports can be
uploaded to a relay to get them off machines that are awkward to copy
from.

96 tests, all green under -race. Builds for linux, windows and darwin on
amd64 and arm64.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-11 22:02:04 +02:00
co-authored by Claude Opus 5
parent 54178dce75
commit 9ed473a965
95 changed files with 12181 additions and 892 deletions
+118
View File
@@ -0,0 +1,118 @@
# usbshare — USB-Filtertreiber für Windows
Ein KMDF-Upper-Filtertreiber, der Userspace-Zugriff auf ein USB-Gerät
ermöglicht, **ohne** den vorhandenen Gerätetreiber zu ersetzen. Das ist der
Ansatz, den VirtualHere verwendet, und der Grund, warum es dort kein Zadig
braucht und das Gerät lokal funktionsfähig bleibt.
> ## Lies das zuerst
>
> **Dieser Code ist nie gelaufen.** Ich habe ihn geschrieben, aber weder
> kompiliert noch getestet — dafür braucht es Windows mit dem WDK, und das
> stand mir nicht zur Verfügung. Er ist als Ausgangspunkt gedacht, nicht als
> fertiges Produkt.
>
> Kernel-Code verzeiht nichts: ein Fehler ist ein Bluescreen, kein
> Stacktrace. Ein Treiber, der beim Booten geladen wird und dabei abstürzt,
> kann ein System unbootbar machen. **Teste ausschließlich in einer VM mit
> Snapshot**, bis er stabil läuft.
>
> Rechne mit mehreren Runden Debugging. Die Struktur sollte stimmen, die
> Details fast sicher nicht.
## Warum ein Filtertreiber
Windows hat kein Äquivalent zu Linux' usbdevfs. Um URBs an ein Gerät zu
senden, braucht es Kernel-Code. Die Alternativen:
| Ansatz | Gerät lokal nutzbar | Installation | HID/Massenspeicher |
|--------|---------------------|--------------|--------------------|
| WinUSB | Nein — ersetzt den Treiber | Zadig, pro Gerät | Meist blockiert |
| **Filtertreiber** | **Ja** | INF, pro Gerät oder klassenweit | Ja |
Der Filter setzt sich *über* den vorhandenen Treiber in den Stack. Im
Normalbetrieb reicht er alles unverändert durch. Erst wenn Userspace ein Gerät
beansprucht, fängt er die IRPs des Klassentreibers ab und leitet stattdessen
die URBs aus dem Userspace an den USB-Hub weiter.
```
Ohne Beanspruchung Während des Teilens
┌────────────────────┐ ┌────────────────────┐
│ Klassentreiber │ │ Klassentreiber │
│ (usbhid, usbstor) │ │ (bekommt nichts) │
└─────────┬──────────┘ └─────────┬──────────┘
abgefangen
┌─────────▼──────────┐ ┌─────────▼──────────┐
│ usbshare (Filter) │ │ usbshare (Filter) │◄── usb-client
└─────────┬──────────┘ └─────────┬──────────┘ via IOCTL
│ durchgereicht │ URBs
┌─────────▼──────────┐ ┌─────────▼──────────┐
│ USB-Hub-Treiber │ │ USB-Hub-Treiber │
└────────────────────┘ └────────────────────┘
```
## Dateien
| Datei | Inhalt |
|-------|--------|
| `driver.c` | Treiber-Einstieg, Geräte-Hinzufügen, PnP |
| `queue.c` | IOCTL-Verarbeitung, URB-Weiterleitung |
| `filter.c` | Abfangen der Klassentreiber-Anfragen im beanspruchten Zustand |
| `usbshare.h` | Interne Strukturen |
| `public.h` | IOCTL-Schnittstelle — auch von der Go-Seite verwendet |
| `usbshare.inf` | Installationsdatei |
| `usbshare.vcxproj` | Visual-Studio-Projekt |
## Bauen
Voraussetzungen: Visual Studio 2022 mit „Desktop development with C++",
Windows SDK und [WDK](https://learn.microsoft.com/windows-hardware/drivers/download-the-wdk).
```cmd
msbuild usbshare.vcxproj /p:Configuration=Release /p:Platform=x64
```
## Testen (nur in einer VM)
```cmd
:: Testsignierung erlauben — danach neu starten
bcdedit /set testsigning on
bcdedit /set nointegritychecks on
:: Selbst signieren
makecert -r -pe -ss PrivateCertStore -n "CN=usbshare-test" test.cer
signtool sign /v /s PrivateCertStore /n usbshare-test /t http://timestamp.digicert.com usbshare.sys
:: Installieren: Rechtsklick auf usbshare.inf → Installieren, dann Gerät neu einstecken
```
Für Kernel-Debugging: zweite Maschine oder Host mit WinDbg, verbunden über
`bcdedit /debug on` und `/dbgsettings net`.
## Verteilen
Für den Einsatz außerhalb einer Testmaschine muss der Treiber von Microsoft
gegengezeichnet sein. Dafür brauchst du:
1. **EV-Code-Signing-Zertifikat** — auf eine geprüfte reale Identität
(Firma oder Einzelperson), etwa 300500 €/Jahr, Ausstellung dauert Tage bis
Wochen wegen der Identitätsprüfung.
2. **Microsoft-Partner-Center-Konto**, verifiziert mit demselben Zertifikat.
3. **Attestation Signing**: Treiber hochladen, Microsoft zeichnet gegen.
Ausreichend für die meisten Fälle; volle WHQL-Zertifizierung braucht
zusätzlich HLK-Testläufe.
Diesen Teil kann nur jemand mit einer realen Identität erledigen — er läuft
auf deinen Namen, nicht auf meinen. Das ist die eigentliche Hürde, nicht der
Code.
## Was fehlt
Der Treiber deckt Control-, Bulk- und Interrupt-Transfers ab. Nicht
implementiert:
- **Isochrone Transfers** (Webcams, Audio). Sie brauchen eine andere
URB-Struktur mit Paketdeskriptoren und Bandbreitenreservierung.
- **Auswahl der Konfiguration/Alt-Settings** über den Filter — derzeit wird
die vom Klassentreiber gesetzte übernommen.
- **Reset und Halt-Clear** sind angelegt, aber ungetestet.
+284
View File
@@ -0,0 +1,284 @@
/*
* usbshare - driver entry, device setup and claim lifecycle
*/
#include "usbshare.h"
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
WDF_DRIVER_CONFIG config;
NTSTATUS status;
WDF_DRIVER_CONFIG_INIT(&config, UsbShareEvtDeviceAdd);
status = WdfDriverCreate(DriverObject, RegistryPath,
WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE);
if (!NT_SUCCESS(status)) {
KdPrint(("usbshare: WdfDriverCreate failed 0x%x\n", status));
}
return status;
}
NTSTATUS
UsbShareEvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
NTSTATUS status;
WDFDEVICE device;
WDF_OBJECT_ATTRIBUTES attributes;
WDF_PNPPOWER_EVENT_CALLBACKS pnpCallbacks;
WDF_FILEOBJECT_CONFIG fileConfig;
WDF_IO_QUEUE_CONFIG queueConfig;
PDEVICE_CONTEXT context;
WDFQUEUE queue;
UNREFERENCED_PARAMETER(Driver);
/*
* Declaring ourselves a filter is what makes this driver safe to attach
* to arbitrary devices: the framework then forwards every request we do
* not explicitly handle to the driver below, so a device we know nothing
* about keeps working exactly as before.
*/
WdfFdoInitSetFilter(DeviceInit);
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpCallbacks);
pnpCallbacks.EvtDevicePrepareHardware = UsbShareEvtDevicePrepareHardware;
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpCallbacks);
/*
* File create and close callbacks give us the claim lifecycle: a claim is
* tied to a handle, so when the client exits — cleanly or not — the
* kernel closes the handle and the device goes back to its class driver.
* Without this a crashed client would leave hardware unusable until
* reboot.
*/
WDF_FILEOBJECT_CONFIG_INIT(&fileConfig,
UsbShareEvtDeviceFileCreate,
UsbShareEvtFileClose,
WDF_NO_EVENT_CALLBACK); /* no cleanup callback */
WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig,
WDF_NO_OBJECT_ATTRIBUTES);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DEVICE_CONTEXT);
status = WdfDeviceCreate(&DeviceInit, &attributes, &device);
if (!NT_SUCCESS(status)) {
KdPrint(("usbshare: WdfDeviceCreate failed 0x%x\n", status));
return status;
}
context = GetDeviceContext(device);
RtlZeroMemory(context, sizeof(DEVICE_CONTEXT));
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = device;
status = WdfSpinLockCreate(&attributes, &context->ClaimLock);
if (!NT_SUCCESS(status)) {
return status;
}
status = WdfSpinLockCreate(&attributes, &context->PendingLock);
if (!NT_SUCCESS(status)) {
return status;
}
status = WdfCollectionCreate(&attributes, &context->PendingTransfers);
if (!NT_SUCCESS(status)) {
return status;
}
/*
* Default queue. Requests we do not recognise are forwarded down by the
* framework because this is a filter device.
*/
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
queueConfig.EvtIoDeviceControl = UsbShareEvtIoDeviceControl;
queueConfig.EvtIoInternalDeviceControl = UsbShareEvtIoInternalDeviceControl;
queueConfig.EvtIoDefault = UsbShareEvtIoDefault;
status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue);
if (!NT_SUCCESS(status)) {
KdPrint(("usbshare: WdfIoQueueCreate failed 0x%x\n", status));
return status;
}
/* Publish the interface so user mode can find this device. */
status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_USBSHARE, NULL);
if (!NT_SUCCESS(status)) {
KdPrint(("usbshare: WdfDeviceCreateDeviceInterface failed 0x%x\n", status));
return status;
}
return STATUS_SUCCESS;
}
NTSTATUS
UsbShareEvtDevicePrepareHardware(
_In_ WDFDEVICE Device,
_In_ WDFCMRESLIST ResourcesRaw,
_In_ WDFCMRESLIST ResourcesTranslated
)
{
NTSTATUS status;
PDEVICE_CONTEXT context = GetDeviceContext(Device);
WDF_USB_DEVICE_CREATE_CONFIG createConfig;
USB_DEVICE_DESCRIPTOR deviceDescriptor;
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;
UCHAR i;
UNREFERENCED_PARAMETER(ResourcesRaw);
UNREFERENCED_PARAMETER(ResourcesTranslated);
/* PrepareHardware can run more than once across power transitions. */
if (context->UsbDevice != NULL) {
return STATUS_SUCCESS;
}
WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createConfig, USBD_CLIENT_CONTRACT_VERSION_602);
status = WdfUsbTargetDeviceCreateWithParameters(Device, &createConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&context->UsbDevice);
if (!NT_SUCCESS(status)) {
KdPrint(("usbshare: WdfUsbTargetDeviceCreateWithParameters failed 0x%x\n", status));
return status;
}
WdfUsbTargetDeviceGetDeviceDescriptor(context->UsbDevice, &deviceDescriptor);
context->Info.VendorId = deviceDescriptor.idVendor;
context->Info.ProductId = deviceDescriptor.idProduct;
context->Info.BcdDevice = deviceDescriptor.bcdDevice;
context->Info.DeviceClass = deviceDescriptor.bDeviceClass;
context->Info.DeviceSubClass = deviceDescriptor.bDeviceSubClass;
context->Info.DeviceProtocol = deviceDescriptor.bDeviceProtocol;
context->Info.NumConfigurations = deviceDescriptor.bNumConfigurations;
context->Info.ConfigurationValue = 1;
/*
* Select a configuration so pipe handles become available.
*
* This is the part most likely to need adjusting: on a device the class
* driver has already configured, selecting again may be redundant or
* disruptive. A more careful implementation would query the current
* configuration first and only select if none is active.
*/
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE(&configParams);
status = WdfUsbTargetDeviceSelectConfig(context->UsbDevice,
WDF_NO_OBJECT_ATTRIBUTES,
&configParams);
if (!NT_SUCCESS(status)) {
KdPrint(("usbshare: WdfUsbTargetDeviceSelectConfig failed 0x%x\n", status));
/*
* Not fatal: without pipes only control transfers work, but the
* filter must not break the device for the class driver either way.
*/
return STATUS_SUCCESS;
}
context->UsbInterface = configParams.Types.SingleInterface.ConfiguredUsbInterface;
/* Map pipes by full endpoint address. */
{
BYTE pipeCount = configParams.Types.SingleInterface.NumberConfiguredPipes;
for (i = 0; i < pipeCount; i++) {
WDF_USB_PIPE_INFORMATION pipeInfo;
WDFUSBPIPE pipe;
WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
pipe = WdfUsbInterfaceGetConfiguredPipe(context->UsbInterface, i, &pipeInfo);
if (pipe != NULL) {
context->Pipes[pipeInfo.EndpointAddress] = pipe;
/*
* Let short reads through. Without this a transfer that
* returns fewer bytes than requested fails, which is normal
* and expected for interrupt endpoints.
*/
WdfUsbTargetPipeSetNoMaximumPacketSizeCheck(pipe);
}
}
}
return STATUS_SUCCESS;
}
VOID
UsbShareEvtDeviceFileCreate(
_In_ WDFDEVICE Device,
_In_ WDFREQUEST Request,
_In_ WDFFILEOBJECT FileObject
)
{
UNREFERENCED_PARAMETER(Device);
UNREFERENCED_PARAMETER(FileObject);
/* Opening the handle is always allowed; claiming is a separate step. */
WdfRequestComplete(Request, STATUS_SUCCESS);
}
VOID
UsbShareEvtFileClose(
_In_ WDFFILEOBJECT FileObject
)
{
WDFDEVICE device = WdfFileObjectGetDevice(FileObject);
PDEVICE_CONTEXT context = GetDeviceContext(device);
/*
* The safety net: if this handle held the claim, give the device back.
* This runs whether the client exited cleanly or was killed.
*/
UsbShareReleaseClaim(context, FileObject);
}
BOOLEAN
UsbShareIsClaimed(
_In_ PDEVICE_CONTEXT Context
)
{
BOOLEAN claimed;
WdfSpinLockAcquire(Context->ClaimLock);
claimed = Context->Claimed;
WdfSpinLockRelease(Context->ClaimLock);
return claimed;
}
VOID
UsbShareReleaseClaim(
_In_ PDEVICE_CONTEXT Context,
_In_opt_ WDFFILEOBJECT Owner
)
{
BOOLEAN released = FALSE;
WdfSpinLockAcquire(Context->ClaimLock);
/*
* With an owner given, only that owner may release — otherwise closing an
* unrelated handle would hand the device back while a client is using it.
*/
if (Context->Claimed && (Owner == NULL || Context->ClaimOwner == Owner)) {
Context->Claimed = FALSE;
Context->ClaimOwner = NULL;
released = TRUE;
}
WdfSpinLockRelease(Context->ClaimLock);
if (released) {
KdPrint(("usbshare: device released\n"));
}
}
+92
View File
@@ -0,0 +1,92 @@
/*
* usbshare - intercepting the class driver while the device is claimed
*
* This is what makes the filter approach worth the trouble. While no client
* holds the device, every request is forwarded untouched and the device
* behaves exactly as if this driver were not installed. Only once a client
* claims it do the class driver's requests get swallowed, so the two do not
* fight over the same endpoints.
*/
#include "usbshare.h"
/*
* Forwards a request to the driver below unchanged.
*
* Send-and-forget is right here: we have no interest in the answer, and not
* setting a completion routine avoids holding a reference on a request that
* may outlive our interest in it.
*/
static VOID
UsbShareForward(
_In_ WDFDEVICE Device,
_In_ WDFREQUEST Request
)
{
WDF_REQUEST_SEND_OPTIONS options;
WDF_REQUEST_SEND_OPTIONS_INIT(&options, WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET);
WdfRequestFormatRequestUsingCurrentType(Request);
if (!WdfRequestSend(Request, WdfDeviceGetIoTarget(Device), &options)) {
WdfRequestComplete(Request, WdfRequestGetStatus(Request));
}
}
VOID
UsbShareEvtIoDefault(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request
)
{
WDFDEVICE device = WdfIoQueueGetDevice(Queue);
/*
* Reads and writes are not intercepted even while claimed. They come from
* user mode against the class driver's own interface, and failing them
* would surface as application errors rather than a device that is simply
* busy elsewhere.
*/
UsbShareForward(device, Request);
}
VOID
UsbShareEvtIoInternalDeviceControl(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
WDFDEVICE device = WdfIoQueueGetDevice(Queue);
PDEVICE_CONTEXT context = GetDeviceContext(device);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
/*
* IOCTL_INTERNAL_USB_SUBMIT_URB is how the class driver above us talks to
* the USB stack. Letting those through while a client holds the device
* would mean two parties submitting to the same endpoints: transfers
* would be answered to whoever asked last, and a keyboard would appear to
* type on both machines at once.
*/
if (IoControlCode == IOCTL_INTERNAL_USB_SUBMIT_URB && UsbShareIsClaimed(context)) {
/*
* STATUS_DEVICE_NOT_CONNECTED rather than STATUS_DEVICE_BUSY: class
* drivers treat "busy" as a reason to retry in a tight loop, whereas
* "not connected" makes them stand down until PnP says otherwise —
* which is exactly the state the device is in from their point of view.
*/
WdfRequestComplete(Request, STATUS_DEVICE_NOT_CONNECTED);
return;
}
/*
* Everything else — PnP queries, port status, idle notifications — is
* forwarded even while claimed. Blocking those would confuse the stack
* about the device's existence, and it does still exist.
*/
UsbShareForward(device, Request);
}
+162
View File
@@ -0,0 +1,162 @@
/*
* usbshare - public interface
*
* Shared between the kernel driver and the user mode client. Keep this file
* in sync with internal/usb/driver_windows.go: both sides marshal the same
* structures, and a mismatch corrupts memory rather than failing cleanly.
*/
#pragma once
#include <initguid.h>
/*
* Device interface GUID. User mode enumerates this to find devices that have
* the filter attached.
*
* Generate a fresh GUID if you fork this driver: two drivers exposing the
* same interface would be indistinguishable to clients.
*/
// {8F3D2A14-6C7B-4E59-9A1D-3F5B7C8E2D40}
DEFINE_GUID(GUID_DEVINTERFACE_USBSHARE,
0x8f3d2a14, 0x6c7b, 0x4e59, 0x9a, 0x1d, 0x3f, 0x5b, 0x7c, 0x8e, 0x2d, 0x40);
#define USBSHARE_DEVICE_TYPE 0x8000
#define USBSHARE_IOCTL(index) \
CTL_CODE(USBSHARE_DEVICE_TYPE, 0x800 + (index), METHOD_BUFFERED, FILE_ANY_ACCESS)
/*
* Take exclusive control of the device.
*
* While claimed the filter stops passing the class driver's requests down, so
* the device stops responding to the local system and answers only to URBs
* submitted here. The claim is bound to the file handle: closing it — or the
* process dying — releases the device, which is what stops a crashed client
* from leaving hardware permanently stuck.
*
* Input: none
* Output: USBSHARE_DEVICE_INFO
*/
#define IOCTL_USBSHARE_CLAIM USBSHARE_IOCTL(0)
/* Release the device back to its class driver. Input/output: none. */
#define IOCTL_USBSHARE_RELEASE USBSHARE_IOCTL(1)
/*
* Read the raw descriptor blob: device descriptor followed by every
* configuration descriptor, the same layout a Linux usbdevfs read returns.
*
* Input: none
* Output: raw bytes; STATUS_BUFFER_TOO_SMALL reports the needed size
*/
#define IOCTL_USBSHARE_GET_DESCRIPTORS USBSHARE_IOCTL(2)
/*
* Submit a transfer. Completion is asynchronous: the request stays pending
* until the device answers.
*
* Input: USBSHARE_TRANSFER followed by the payload for OUT transfers
* Output: USBSHARE_TRANSFER_RESULT followed by the payload for IN transfers
*/
#define IOCTL_USBSHARE_SUBMIT USBSHARE_IOCTL(3)
/*
* Cancel a previously submitted transfer.
*
* Input: USBSHARE_CANCEL
* Output: none
*/
#define IOCTL_USBSHARE_CANCEL USBSHARE_IOCTL(4)
/* Select an alternate setting. Input: USBSHARE_SET_INTERFACE. */
#define IOCTL_USBSHARE_SET_INTERFACE USBSHARE_IOCTL(5)
/* Clear a stall on an endpoint. Input: USBSHARE_CLEAR_HALT. */
#define IOCTL_USBSHARE_CLEAR_HALT USBSHARE_IOCTL(6)
/* Reset the port. Input/output: none. */
#define IOCTL_USBSHARE_RESET USBSHARE_IOCTL(7)
#pragma pack(push, 1)
/* Transfer types, matching the USB endpoint attribute values. */
#define USBSHARE_TRANSFER_CONTROL 0
#define USBSHARE_TRANSFER_ISOCHRONOUS 1
#define USBSHARE_TRANSFER_BULK 2
#define USBSHARE_TRANSFER_INTERRUPT 3
/* Direction, taken from the endpoint address bit 7. */
#define USBSHARE_DIR_OUT 0
#define USBSHARE_DIR_IN 1
typedef struct _USBSHARE_DEVICE_INFO {
USHORT VendorId;
USHORT ProductId;
USHORT BcdDevice;
UCHAR DeviceClass;
UCHAR DeviceSubClass;
UCHAR DeviceProtocol;
UCHAR ConfigurationValue;
UCHAR NumConfigurations;
/* USB_DEVICE_SPEED_* from usbdi.h, translated by the client. */
ULONG Speed;
/* Hub port number, used to build a stable bus ID. */
ULONG PortNumber;
} USBSHARE_DEVICE_INFO, *PUSBSHARE_DEVICE_INFO;
typedef struct _USBSHARE_TRANSFER {
/* Caller-assigned, unique among outstanding transfers. Used to cancel. */
ULONG64 Id;
/* Full bEndpointAddress including the direction bit. */
UCHAR EndpointAddress;
/* USBSHARE_TRANSFER_* */
UCHAR Type;
/* USBSHARE_DIR_*, redundant with the address bit but explicit. */
UCHAR Direction;
UCHAR Reserved;
/* Bytes of payload following this header (OUT), or expected (IN). */
ULONG BufferLength;
/* Milliseconds; 0 means no timeout. */
ULONG Timeout;
/*
* Setup packet for control transfers, in USB wire order (little endian).
* Ignored for other types.
*/
UCHAR Setup[8];
} USBSHARE_TRANSFER, *PUSBSHARE_TRANSFER;
typedef struct _USBSHARE_TRANSFER_RESULT {
ULONG64 Id;
/* NTSTATUS from the USB stack; 0 means success. */
LONG Status;
/* USBD_STATUS, kept separate because it distinguishes stall from timeout. */
ULONG UsbdStatus;
/* Bytes actually transferred. Meaningful for OUT transfers too. */
ULONG ActualLength;
} USBSHARE_TRANSFER_RESULT, *PUSBSHARE_TRANSFER_RESULT;
typedef struct _USBSHARE_CANCEL {
ULONG64 Id;
} USBSHARE_CANCEL, *PUSBSHARE_CANCEL;
typedef struct _USBSHARE_SET_INTERFACE {
UCHAR InterfaceNumber;
UCHAR AlternateSetting;
} USBSHARE_SET_INTERFACE, *PUSBSHARE_SET_INTERFACE;
typedef struct _USBSHARE_CLEAR_HALT {
UCHAR EndpointAddress;
} USBSHARE_CLEAR_HALT, *PUSBSHARE_CLEAR_HALT;
#pragma pack(pop)
+555
View File
@@ -0,0 +1,555 @@
/*
* usbshare - IOCTL handling and URB forwarding
*/
#include "usbshare.h"
static VOID UsbShareCompleteTransfer(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target,
_In_ PWDF_REQUEST_COMPLETION_PARAMS Params,
_In_ WDFCONTEXT Context);
static NTSTATUS UsbShareHandleClaim(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request);
static NTSTATUS UsbShareHandleGetDescriptors(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request);
static NTSTATUS UsbShareHandleSubmit(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request);
static NTSTATUS UsbShareHandleCancel(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request);
static NTSTATUS UsbShareHandleSetInterface(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request);
static NTSTATUS UsbShareHandleClearHalt(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request);
VOID
UsbShareEvtIoDeviceControl(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
WDFDEVICE device = WdfIoQueueGetDevice(Queue);
PDEVICE_CONTEXT context = GetDeviceContext(device);
NTSTATUS status;
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
switch (IoControlCode) {
case IOCTL_USBSHARE_CLAIM:
status = UsbShareHandleClaim(context, Request);
break;
case IOCTL_USBSHARE_RELEASE:
UsbShareReleaseClaim(context, WdfRequestGetFileObject(Request));
status = STATUS_SUCCESS;
break;
case IOCTL_USBSHARE_GET_DESCRIPTORS:
status = UsbShareHandleGetDescriptors(context, Request);
break;
case IOCTL_USBSHARE_SUBMIT:
status = UsbShareHandleSubmit(context, Request);
/*
* A submitted transfer completes asynchronously; the completion
* routine owns the request from here.
*/
if (status == STATUS_PENDING) {
return;
}
break;
case IOCTL_USBSHARE_CANCEL:
status = UsbShareHandleCancel(context, Request);
break;
case IOCTL_USBSHARE_SET_INTERFACE:
status = UsbShareHandleSetInterface(context, Request);
break;
case IOCTL_USBSHARE_CLEAR_HALT:
status = UsbShareHandleClearHalt(context, Request);
break;
case IOCTL_USBSHARE_RESET:
status = WdfUsbTargetDeviceResetPortSynchronously(context->UsbDevice);
break;
default:
/*
* Not ours. As a filter we must pass it on rather than fail it —
* some other component in the stack may be waiting for the answer.
*/
{
WDF_REQUEST_SEND_OPTIONS options;
WDF_REQUEST_SEND_OPTIONS_INIT(&options, WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET);
WdfRequestFormatRequestUsingCurrentType(Request);
if (!WdfRequestSend(Request, WdfDeviceGetIoTarget(device), &options)) {
WdfRequestComplete(Request, WdfRequestGetStatus(Request));
}
return;
}
}
WdfRequestComplete(Request, status);
}
static NTSTATUS
UsbShareHandleClaim(
_In_ PDEVICE_CONTEXT Context,
_In_ WDFREQUEST Request
)
{
NTSTATUS status;
PUSBSHARE_DEVICE_INFO info;
WDFFILEOBJECT fileObject = WdfRequestGetFileObject(Request);
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(USBSHARE_DEVICE_INFO),
(PVOID *)&info, NULL);
if (!NT_SUCCESS(status)) {
return status;
}
WdfSpinLockAcquire(Context->ClaimLock);
if (Context->Claimed && Context->ClaimOwner != fileObject) {
WdfSpinLockRelease(Context->ClaimLock);
return STATUS_DEVICE_BUSY;
}
Context->Claimed = TRUE;
Context->ClaimOwner = fileObject;
WdfSpinLockRelease(Context->ClaimLock);
*info = Context->Info;
WdfRequestSetInformation(Request, sizeof(USBSHARE_DEVICE_INFO));
KdPrint(("usbshare: device claimed (%04x:%04x)\n", info->VendorId, info->ProductId));
return STATUS_SUCCESS;
}
static NTSTATUS
UsbShareHandleGetDescriptors(
_In_ PDEVICE_CONTEXT Context,
_In_ WDFREQUEST Request
)
{
NTSTATUS status;
PVOID buffer;
size_t bufferLength;
if (Context->Descriptors == NULL) {
status = UsbShareBuildDescriptorBlob(Context);
if (!NT_SUCCESS(status)) {
return status;
}
}
status = WdfRequestRetrieveOutputBuffer(Request, 1, &buffer, &bufferLength);
if (!NT_SUCCESS(status)) {
return status;
}
if (bufferLength < Context->DescriptorsLength) {
/* Report the needed size so the caller can retry. */
WdfRequestSetInformation(Request, Context->DescriptorsLength);
return STATUS_BUFFER_TOO_SMALL;
}
RtlCopyMemory(buffer, Context->Descriptors, Context->DescriptorsLength);
WdfRequestSetInformation(Request, Context->DescriptorsLength);
return STATUS_SUCCESS;
}
/*
* Builds the descriptor blob: device descriptor followed by every
* configuration descriptor, matching what Linux returns when reading a
* usbdevfs file. The client parses both with the same code.
*/
NTSTATUS
UsbShareBuildDescriptorBlob(
_In_ PDEVICE_CONTEXT Context
)
{
NTSTATUS status;
USB_DEVICE_DESCRIPTOR deviceDescriptor;
PUCHAR blob = NULL;
ULONG blobSize = 0;
ULONG offset;
UCHAR configIndex;
WdfUsbTargetDeviceGetDeviceDescriptor(Context->UsbDevice, &deviceDescriptor);
/* First pass: total up the sizes. */
blobSize = sizeof(USB_DEVICE_DESCRIPTOR);
for (configIndex = 0; configIndex < deviceDescriptor.bNumConfigurations; configIndex++) {
USHORT configSize = 0;
status = WdfUsbTargetDeviceRetrieveConfigDescriptor(Context->UsbDevice, NULL, &configSize);
if (status != STATUS_BUFFER_TOO_SMALL && !NT_SUCCESS(status)) {
return status;
}
blobSize += configSize;
/*
* Only configuration 0 can be retrieved through this API; devices
* with several configurations would need a raw control transfer per
* configuration. They are rare enough to leave for later.
*/
break;
}
blob = (PUCHAR)ExAllocatePool2(POOL_FLAG_NON_PAGED, blobSize, USBSHARE_POOL_TAG);
if (blob == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory(blob, &deviceDescriptor, sizeof(USB_DEVICE_DESCRIPTOR));
offset = sizeof(USB_DEVICE_DESCRIPTOR);
{
USHORT configSize = (USHORT)(blobSize - offset);
status = WdfUsbTargetDeviceRetrieveConfigDescriptor(Context->UsbDevice,
blob + offset,
&configSize);
if (!NT_SUCCESS(status)) {
ExFreePoolWithTag(blob, USBSHARE_POOL_TAG);
return status;
}
}
Context->Descriptors = blob;
Context->DescriptorsLength = blobSize;
return STATUS_SUCCESS;
}
static NTSTATUS
UsbShareHandleSubmit(
_In_ PDEVICE_CONTEXT Context,
_In_ WDFREQUEST Request
)
{
NTSTATUS status;
PUSBSHARE_TRANSFER transfer;
size_t inputLength;
PUCHAR payload;
WDFMEMORY urbMemory;
PURB urb;
WDFUSBPIPE pipe;
PREQUEST_CONTEXT reqContext;
WDF_OBJECT_ATTRIBUTES attributes;
WDFIOTARGET target;
if (!UsbShareIsClaimed(Context)) {
return STATUS_INVALID_DEVICE_STATE;
}
status = WdfRequestRetrieveInputBuffer(Request, sizeof(USBSHARE_TRANSFER),
(PVOID *)&transfer, &inputLength);
if (!NT_SUCCESS(status)) {
return status;
}
if (inputLength < sizeof(USBSHARE_TRANSFER) + transfer->BufferLength) {
return STATUS_BUFFER_TOO_SMALL;
}
payload = (PUCHAR)transfer + sizeof(USBSHARE_TRANSFER);
/* Attach a context so a later cancel can find this request. */
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, REQUEST_CONTEXT);
status = WdfObjectAllocateContext(Request, &attributes, (PVOID *)&reqContext);
if (!NT_SUCCESS(status)) {
return status;
}
reqContext->TransferId = transfer->Id;
reqContext->ExpectedLength = transfer->BufferLength;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = Request;
if (transfer->Type == USBSHARE_TRANSFER_CONTROL) {
status = WdfUsbTargetDeviceCreateUrb(Context->UsbDevice, &attributes,
&urbMemory, &urb);
if (!NT_SUCCESS(status)) {
return status;
}
/*
* The setup packet arrives in USB wire order and is copied verbatim.
* Reinterpreting the fields here would only introduce a chance to get
* the endianness wrong.
*/
UsbBuildVendorRequest(urb,
URB_FUNCTION_VENDOR_DEVICE,
sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST),
(transfer->Direction == USBSHARE_DIR_IN)
? USBD_TRANSFER_DIRECTION_IN : 0,
0,
transfer->Setup[0], /* bmRequestType */
transfer->Setup[1], /* bRequest */
*(USHORT *)&transfer->Setup[2], /* wValue */
*(USHORT *)&transfer->Setup[4], /* wIndex */
payload,
NULL,
transfer->BufferLength,
NULL);
} else {
pipe = Context->Pipes[transfer->EndpointAddress];
if (pipe == NULL) {
return STATUS_INVALID_PARAMETER;
}
status = WdfUsbTargetDeviceCreateUrb(Context->UsbDevice, &attributes,
&urbMemory, &urb);
if (!NT_SUCCESS(status)) {
return status;
}
/*
* Bulk and interrupt share one URB function; the pipe handle decides
* which it actually is.
*/
urb->UrbBulkOrInterruptTransfer.Hdr.Length =
sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER);
urb->UrbBulkOrInterruptTransfer.Hdr.Function =
URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER;
urb->UrbBulkOrInterruptTransfer.PipeHandle = WdfUsbTargetPipeWdmGetPipeHandle(pipe);
urb->UrbBulkOrInterruptTransfer.TransferBuffer = payload;
urb->UrbBulkOrInterruptTransfer.TransferBufferLength = transfer->BufferLength;
urb->UrbBulkOrInterruptTransfer.TransferBufferMDL = NULL;
urb->UrbBulkOrInterruptTransfer.UrbLink = NULL;
urb->UrbBulkOrInterruptTransfer.TransferFlags =
(transfer->Direction == USBSHARE_DIR_IN)
? (USBD_TRANSFER_DIRECTION_IN | USBD_SHORT_TRANSFER_OK)
: 0;
}
reqContext->UrbMemory = urbMemory;
reqContext->Urb = urb;
target = WdfUsbTargetDeviceGetIoTarget(Context->UsbDevice);
status = WdfUsbTargetDeviceFormatRequestForUrb(Context->UsbDevice, Request,
urbMemory, NULL);
if (!NT_SUCCESS(status)) {
return status;
}
WdfRequestSetCompletionRoutine(Request, UsbShareCompleteTransfer, Context);
/* Track it so a cancel can find it. */
WdfSpinLockAcquire(Context->PendingLock);
WdfCollectionAdd(Context->PendingTransfers, Request);
WdfSpinLockRelease(Context->PendingLock);
if (!WdfRequestSend(Request, target, WDF_NO_SEND_OPTIONS)) {
status = WdfRequestGetStatus(Request);
WdfSpinLockAcquire(Context->PendingLock);
WdfCollectionRemove(Context->PendingTransfers, Request);
WdfSpinLockRelease(Context->PendingLock);
return status;
}
return STATUS_PENDING;
}
static VOID
UsbShareCompleteTransfer(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target,
_In_ PWDF_REQUEST_COMPLETION_PARAMS Params,
_In_ WDFCONTEXT CompletionContext
)
{
PDEVICE_CONTEXT context = (PDEVICE_CONTEXT)CompletionContext;
PREQUEST_CONTEXT reqContext = GetRequestContext(Request);
PUSBSHARE_TRANSFER_RESULT result;
NTSTATUS status;
size_t outputLength;
ULONG transferred = 0;
PUCHAR outPayload;
UNREFERENCED_PARAMETER(Target);
WdfSpinLockAcquire(context->PendingLock);
WdfCollectionRemove(context->PendingTransfers, Request);
WdfSpinLockRelease(context->PendingLock);
if (reqContext->Urb != NULL) {
transferred = reqContext->Urb->UrbBulkOrInterruptTransfer.TransferBufferLength;
}
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(USBSHARE_TRANSFER_RESULT),
(PVOID *)&result, &outputLength);
if (!NT_SUCCESS(status)) {
WdfRequestComplete(Request, status);
return;
}
result->Id = reqContext->TransferId;
result->Status = Params->IoStatus.Status;
result->UsbdStatus = (reqContext->Urb != NULL)
? reqContext->Urb->UrbHeader.Status : 0;
result->ActualLength = transferred;
/*
* Copy the received payload after the result header, but only as much as
* the output buffer holds — a device may return more than expected.
*/
if (transferred > 0 && outputLength > sizeof(USBSHARE_TRANSFER_RESULT)) {
ULONG room = (ULONG)(outputLength - sizeof(USBSHARE_TRANSFER_RESULT));
ULONG copy = (transferred < room) ? transferred : room;
outPayload = (PUCHAR)result + sizeof(USBSHARE_TRANSFER_RESULT);
RtlCopyMemory(outPayload,
reqContext->Urb->UrbBulkOrInterruptTransfer.TransferBuffer,
copy);
WdfRequestSetInformation(Request, sizeof(USBSHARE_TRANSFER_RESULT) + copy);
} else {
WdfRequestSetInformation(Request, sizeof(USBSHARE_TRANSFER_RESULT));
}
/*
* Always complete successfully: the transfer's own outcome travels in the
* result structure. Failing the IOCTL would lose the distinction between
* "the ioctl did not work" and "the device stalled".
*/
WdfRequestComplete(Request, STATUS_SUCCESS);
}
static NTSTATUS
UsbShareHandleCancel(
_In_ PDEVICE_CONTEXT Context,
_In_ WDFREQUEST Request
)
{
NTSTATUS status;
PUSBSHARE_CANCEL cancel;
ULONG i, count;
WDFREQUEST target = NULL;
status = WdfRequestRetrieveInputBuffer(Request, sizeof(USBSHARE_CANCEL),
(PVOID *)&cancel, NULL);
if (!NT_SUCCESS(status)) {
return status;
}
WdfSpinLockAcquire(Context->PendingLock);
count = WdfCollectionGetCount(Context->PendingTransfers);
for (i = 0; i < count; i++) {
WDFREQUEST candidate = (WDFREQUEST)WdfCollectionGetItem(Context->PendingTransfers, i);
PREQUEST_CONTEXT candidateContext = GetRequestContext(candidate);
if (candidateContext != NULL && candidateContext->TransferId == cancel->Id) {
target = candidate;
break;
}
}
WdfSpinLockRelease(Context->PendingLock);
if (target == NULL) {
/* Already finished. Not an error: the caller gets its result anyway. */
return STATUS_SUCCESS;
}
WdfRequestCancelSentRequest(target);
return STATUS_SUCCESS;
}
static NTSTATUS
UsbShareHandleSetInterface(
_In_ PDEVICE_CONTEXT Context,
_In_ WDFREQUEST Request
)
{
NTSTATUS status;
PUSBSHARE_SET_INTERFACE params;
WDF_USB_INTERFACE_SELECT_SETTING_PARAMS settingParams;
if (!UsbShareIsClaimed(Context)) {
return STATUS_INVALID_DEVICE_STATE;
}
status = WdfRequestRetrieveInputBuffer(Request, sizeof(USBSHARE_SET_INTERFACE),
(PVOID *)&params, NULL);
if (!NT_SUCCESS(status)) {
return status;
}
if (Context->UsbInterface == NULL) {
return STATUS_INVALID_DEVICE_STATE;
}
/*
* Going through the framework rather than sending a raw SET_INTERFACE is
* essential: the USB stack has to re-open the pipes and, for isochronous
* endpoints, reserve bandwidth. A raw control transfer changes the device
* without telling the stack, after which every later transfer fails.
*/
WDF_USB_INTERFACE_SELECT_SETTING_PARAMS_INIT_SETTING(&settingParams,
params->AlternateSetting);
status = WdfUsbInterfaceSelectSetting(Context->UsbInterface,
WDF_NO_OBJECT_ATTRIBUTES,
&settingParams);
if (!NT_SUCCESS(status)) {
return status;
}
/* Pipe handles change with the setting, so rebuild the map. */
RtlZeroMemory(Context->Pipes, sizeof(Context->Pipes));
{
BYTE pipeCount = WdfUsbInterfaceGetNumConfiguredPipes(Context->UsbInterface);
BYTE i;
for (i = 0; i < pipeCount; i++) {
WDF_USB_PIPE_INFORMATION pipeInfo;
WDFUSBPIPE pipe;
WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
pipe = WdfUsbInterfaceGetConfiguredPipe(Context->UsbInterface, i, &pipeInfo);
if (pipe != NULL) {
Context->Pipes[pipeInfo.EndpointAddress] = pipe;
WdfUsbTargetPipeSetNoMaximumPacketSizeCheck(pipe);
}
}
}
return STATUS_SUCCESS;
}
static NTSTATUS
UsbShareHandleClearHalt(
_In_ PDEVICE_CONTEXT Context,
_In_ WDFREQUEST Request
)
{
NTSTATUS status;
PUSBSHARE_CLEAR_HALT params;
WDFUSBPIPE pipe;
if (!UsbShareIsClaimed(Context)) {
return STATUS_INVALID_DEVICE_STATE;
}
status = WdfRequestRetrieveInputBuffer(Request, sizeof(USBSHARE_CLEAR_HALT),
(PVOID *)&params, NULL);
if (!NT_SUCCESS(status)) {
return status;
}
pipe = Context->Pipes[params->EndpointAddress];
if (pipe == NULL) {
return STATUS_INVALID_PARAMETER;
}
return WdfUsbTargetPipeResetSynchronously(pipe, WDF_NO_HANDLE, NULL);
}
+98
View File
@@ -0,0 +1,98 @@
/*
* usbshare - internal declarations
*/
#pragma once
#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>
#include "public.h"
#define USBSHARE_POOL_TAG 'hsBU'
/*
* Per-device context.
*
* One instance per filtered device. Claimed and ClaimOwner together decide
* whether the class driver's requests are passed down or swallowed.
*/
typedef struct _DEVICE_CONTEXT {
/* The device we are filtering, as a USB target. */
WDFUSBDEVICE UsbDevice;
/* The interface whose pipes we use. Only the first is handled today. */
WDFUSBINTERFACE UsbInterface;
/*
* Pipe handles indexed by endpoint address (0x00-0xFF).
*
* Indexing by full address rather than endpoint number matters: a device
* can have endpoint 1 as both interrupt IN (0x81) and bulk OUT (0x01),
* and conflating them submits transfers of the wrong type.
*/
WDFUSBPIPE Pipes[256];
/*
* Non-zero while user mode holds the device. Guarded by ClaimLock; read
* on the request path, so it must stay cheap.
*/
BOOLEAN Claimed;
/*
* The file object that claimed it. Used to release automatically when
* that handle closes, including when its process dies.
*/
WDFFILEOBJECT ClaimOwner;
WDFSPINLOCK ClaimLock;
/* Outstanding user mode transfers, so cancellation can find them. */
WDFCOLLECTION PendingTransfers;
WDFSPINLOCK PendingLock;
/* Cached descriptor blob, built once on first request. */
PUCHAR Descriptors;
ULONG DescriptorsLength;
/* Device info reported on claim. */
USBSHARE_DEVICE_INFO Info;
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, GetDeviceContext)
/*
* Per-request context, kept so a cancel can locate the WDFREQUEST that
* belongs to a transfer ID.
*/
typedef struct _REQUEST_CONTEXT {
ULONG64 TransferId;
WDFMEMORY UrbMemory;
PURB Urb;
/* Bytes of payload the caller expects back, for IN transfers. */
ULONG ExpectedLength;
} REQUEST_CONTEXT, *PREQUEST_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(REQUEST_CONTEXT, GetRequestContext)
/* driver.c */
DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD UsbShareEvtDeviceAdd;
EVT_WDF_DEVICE_PREPARE_HARDWARE UsbShareEvtDevicePrepareHardware;
EVT_WDF_DEVICE_FILE_CREATE UsbShareEvtDeviceFileCreate;
EVT_WDF_FILE_CLOSE UsbShareEvtFileClose;
/* queue.c */
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL UsbShareEvtIoDeviceControl;
/* filter.c */
EVT_WDF_IO_QUEUE_IO_DEFAULT UsbShareEvtIoDefault;
EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL UsbShareEvtIoInternalDeviceControl;
/* Helpers shared between translation units. */
NTSTATUS UsbShareBuildDescriptorBlob(_In_ PDEVICE_CONTEXT Context);
BOOLEAN UsbShareIsClaimed(_In_ PDEVICE_CONTEXT Context);
VOID UsbShareReleaseClaim(_In_ PDEVICE_CONTEXT Context, _In_opt_ WDFFILEOBJECT Owner);
+69
View File
@@ -0,0 +1,69 @@
;
; usbshare.inf - upper filter for USB devices
;
; Installs usbshare as an upper filter. Two ways to use it:
;
; 1. Per device: right-click the INF and Install, then use Device Manager
; to update the driver for the specific device.
; 2. Class-wide: add usbshare to the UpperFilters of the USB device class,
; which the ClassInstall32 section below does. That covers every USB
; device, which is convenient but means a bug affects everything -
; start per-device.
;
[Version]
Signature = "$WINDOWS NT$"
Class = USBDevice
ClassGuid = {88BAE032-5A81-49f0-BC3D-A4FF138216D6}
Provider = %ManufacturerName%
CatalogFile = usbshare.cat
DriverVer = 01/01/2026,1.0.0.0
PnpLockdown = 1
[DestinationDirs]
DefaultDestDir = 13 ; Driver Store
[SourceDisksNames]
1 = %DiskName%
[SourceDisksFiles]
usbshare.sys = 1
[Manufacturer]
%ManufacturerName% = Standard,NT$ARCH$.10.0...16299
; Matching on the generic USB device ID keeps this installable on anything.
; Narrow it to USB\VID_xxxx&PID_yyyy for a single device.
[Standard.NT$ARCH$.10.0...16299]
%DeviceName% = UsbShare_Install, USB\UNKNOWN
[UsbShare_Install.NT]
CopyFiles = UsbShare_CopyFiles
[UsbShare_Install.NT.HW]
AddReg = UsbShare_AddReg
[UsbShare_CopyFiles]
usbshare.sys
; Registering as an UpperFilter is what places this driver above the class
; driver in the stack, which is the whole point: the class driver keeps
; working and we only step in when a client claims the device.
[UsbShare_AddReg]
HKR,,"UpperFilters",0x00010000,"usbshare"
[UsbShare_Install.NT.Services]
AddService = usbshare,,UsbShare_Service
[UsbShare_Service]
DisplayName = %ServiceName%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %13%\usbshare.sys
[Strings]
ManufacturerName = "usb-server"
DiskName = "usbshare Installation Disk"
DeviceName = "USB Share Filter"
ServiceName = "usbshare USB filter driver"
+53
View File
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}</ProjectGuid>
<TargetVersion>Windows10</TargetVersion>
<DriverType>KMDF</DriverType>
<!-- KMDF 1.15 keeps this loadable back to Windows 10 1507. -->
<KMDF_VERSION_MAJOR>1</KMDF_VERSION_MAJOR>
<KMDF_VERSION_MINOR>15</KMDF_VERSION_MINOR>
<RootNamespace>usbshare</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Driver</ConfigurationType>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<!-- Warnings as errors: in kernel code a warning is usually a crash
waiting for the right input. -->
<TreatWarningAsError>true</TreatWarningAsError>
<WarningLevel>Level4</WarningLevel>
<PreprocessorDefinitions>_WIN64;AMD64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>$(DDK_LIB_PATH)\usbdex.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="driver.c" />
<ClCompile Include="queue.c" />
<ClCompile Include="filter.c" />
<ClInclude Include="usbshare.h" />
<ClInclude Include="public.h" />
<Inf Include="usbshare.inf" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>