Add a second fuzzing engine alongside the existing libFuzzer/cargo-fuzz setup. AFL++ runs with persistent mode (afl::fuzz! macro), LLVM plugins (CmpLog, IJON), and a SymCC concolic companion for hybrid fuzzing. - cargo-afl built from afl.rs with a patch for CARGO_AFL_DIR / CARGO_AFL_LLVM_DIR env-var overrides - AFL++ built with LLVM 22 plugins to match rust-nightly - Persistent-mode fuzz targets in lib/fuzz-afl/ - --jobs N parallel fuzzing: main instance in foreground, secondaries and SymCC companion as systemd transient units in a slice - Ctrl+c / exit cleans up all background processes via slice stop - AFL_AUTORESUME=1 for clean restarts after previous runs - fuzz-clean-afl collects crashes from all instance directories - Shared harness logic in lib/src/fuzz_harness.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
137 lines
5.7 KiB
Markdown
137 lines
5.7 KiB
Markdown
# usbip-rs
|
|
|
|
Implements a userspace server-side server for the usbip protocol, and a tool for the client-side to set up the socket and hand it off to the kernel. In usbip terminology, the "server"/"host" is the side that has the physical device being shared, and the "client" is the side the device is being shared to.
|
|
|
|
This project is based on [https://github.com/jiegec/usbip](jiegec/usbip), with significant changes and additions.
|
|
|
|
## Why?
|
|
|
|
Implementing the server side in userspace instead of the kernel's `usbip_host` driver reduces the attack surface considerably. The kernel is not exposed directly to potentially untrusted network input. The userspace server talks to the USB device via /dev/bus/usb/..., and can validate the client's traffic before passing it on to the real USB device. It also makes it possible to implement strong sandboxing via seccomp and namespaces for the server side. The client side is of course still all in the kernel, since making the USB devices visible to the client kernel's drivers is the whole point. But for my use case, the server side is trusted and client side is untrusted, so securing the host side is critical.
|
|
|
|
There are a few other userspace usbip server implementations around, but as far as I could tell, none of them support isochronous mode. Isochronous is needed for e.g. USB audio devices and webcams. This project does, though my testing so far is limited to playback on USB audio devices which work well.
|
|
|
|
This implementation also allows for reversing the connection flow. In standard usbip, the server side listens for connections, and the client side connects to it to access the device. usbip-rs supports having the client listen and the server initiating the connection, which maps much better to my needs.
|
|
|
|
usbip-rs also supports using vsock instead of TCP as the transport, making the "ip" in "usbip" not really true. This is useful when sharing a device to a virtual machine on the same hardware.
|
|
|
|
## Building
|
|
|
|
### With Nix
|
|
|
|
```bash
|
|
nix build
|
|
```
|
|
|
|
### With Cargo
|
|
|
|
Requires `libusb` and `libudev` (Linux) system dependencies.
|
|
|
|
```bash
|
|
cargo build --release
|
|
```
|
|
|
|
The CLI binary is at `target/release/usbip-rs`.
|
|
|
|
## Usage
|
|
|
|
The CLI has four top-level commands: `client`, `host`, `test_hid`, and `test_uac`. Transport addresses use the format `vsock:<port>`, `vsock:<cid>:<port>`, `tcp:<port>`, `tcp:<host>:<port>`, or `fc-vsock:<path>:<port>`.
|
|
|
|
### Pass through a real USB device
|
|
|
|
On the client machine (needs `vhci_hcd` kernel module):
|
|
|
|
```bash
|
|
usbip-rs client listen tcp:3240
|
|
```
|
|
|
|
On the host machine (where the USB device is plugged in):
|
|
|
|
```bash
|
|
usbip-rs host connect tcp:<client-ip>:3240 1-2
|
|
```
|
|
|
|
The device argument is a bus ID (e.g. `1-2`) or device path (e.g. `/dev/bus/usb/001/002`).
|
|
|
|
### Test with a simulated HID keyboard
|
|
|
|
```bash
|
|
# Client side
|
|
usbip-rs client listen vsock:5000
|
|
|
|
# Host side (simulated keyboard, no real device needed)
|
|
usbip-rs test_hid connect vsock:5000
|
|
```
|
|
|
|
### Test with a simulated UAC1 loopback audio device (not working)
|
|
|
|
```bash
|
|
# Client side
|
|
usbip-rs client listen tcp:3240
|
|
|
|
# Host side (simulated USB audio device, no real device needed)
|
|
usbip-rs test_uac connect tcp:3240
|
|
```
|
|
|
|
The UAC1 loopback device advertises 48kHz 16-bit stereo playback and capture. Audio sent to the playback endpoint is looped back to the capture endpoint.
|
|
|
|
## Fuzzing
|
|
|
|
Fuzz targets exercise the host-side codepaths that process untrusted client data. Requires nightly Rust (provided by the Nix apps). Two fuzzing engines are available: libFuzzer (via cargo-fuzz) and AFL++ (via cargo-afl).
|
|
|
|
Targets (shared across both engines):
|
|
- `fuzz_parse_command` — protocol deserialization
|
|
- `fuzz_handle_client` — full connection lifecycle (negotiation + URB loop)
|
|
- `fuzz_urb_hid` — URB loop with HID keyboard device
|
|
- `fuzz_urb_uac` — URB loop with UAC1 audio device (isochronous)
|
|
- `fuzz_urb_cdc` — URB loop with CDC ACM serial device (bulk)
|
|
|
|
### libFuzzer (cargo-fuzz)
|
|
|
|
```bash
|
|
nix run .#fuzz-cargo # List targets
|
|
nix run .#fuzz-cargo -- fuzz_urb_hid # Single process
|
|
nix run .#fuzz-cargo -- fuzz_urb_hid --fork=8 # Parallel (overnight)
|
|
nix run .#fuzz-clean-cargo -- fuzz_urb_hid # Prune fixed artifacts
|
|
```
|
|
|
|
Crash artifacts: `lib/fuzz-cargo/artifacts/<target>/`.
|
|
|
|
### AFL++ (cargo-afl)
|
|
|
|
AFL++ runs with persistent mode, LLVM plugins (CmpLog, IJON), and a SymCC concolic companion. The main fuzzer instance runs in the foreground; secondary instances and the SymCC companion run as systemd transient units for proper process management.
|
|
|
|
```bash
|
|
nix run .#fuzz-afl # List targets
|
|
nix run .#fuzz-afl -- fuzz_urb_hid # Single instance + SymCC
|
|
nix run .#fuzz-afl -- fuzz_urb_hid --jobs 8 # 1 main + 7 secondaries + SymCC
|
|
|
|
# Stop background processes manually (also cleaned up on ctrl+c/exit)
|
|
systemctl --user stop fuzz_afl_fuzz_urb_hid.slice
|
|
|
|
# Prune fixed crashes
|
|
nix run .#fuzz-clean-afl -- fuzz_urb_hid
|
|
|
|
# View aggregate stats across all instances
|
|
nix develop .#fuzz-afl -c bash -c 'afl-whatsup lib/fuzz-afl/output/fuzz_urb_hid'
|
|
```
|
|
|
|
Crashes: `lib/fuzz-afl/output/<target>/*/crashes/`. SymCC companion log: `lib/fuzz-afl/output/<target>/symcc/companion.log`.
|
|
|
|
Corpora are separate between engines; copy manually if desired.
|
|
|
|
## Project structure
|
|
|
|
```
|
|
├── cli/ CLI binary (usbip-rs)
|
|
├── lib/ Library crate (usbip-rs)
|
|
│ ├── src/
|
|
│ ├── fuzz-cargo/ Fuzz targets (libFuzzer / cargo-fuzz)
|
|
│ ├── fuzz-afl/ Fuzz targets (AFL++ / cargo-afl)
|
|
│ └── examples/
|
|
├── nix/ Nix patches (cargo-afl env-var overrides)
|
|
└── flake.nix Nix build, devShells, fuzzing apps
|
|
```
|
|
|
|
## License
|
|
|
|
MIT — see [LICENSE](LICENSE).
|