Commit graph

211 commits

Author SHA1 Message Date
329a7aa37f debug: log endpoint model at build time and URB dispatch details
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:17:17 +00:00
e5f1d0599e fix: add endpoint/setup context to URB error logs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:56:04 +00:00
015fc393ba fix: forward CLEAR_FEATURE(ENDPOINT_HALT) to real device for host passthrough
When a real USB device stalls an endpoint, the VM sends
CLEAR_FEATURE(ENDPOINT_HALT) to recover. Previously this was a no-op,
leaving the endpoint permanently stalled and causing the guest to hang.

Now the CLEAR_FEATURE handler in device.rs finds the interface that owns
the stalled endpoint and calls clear_halt on its handler. For host
passthrough this forwards to nusb's new Interface::clear_halt; for
simulated devices it remains a no-op.

Fixes YubiKey (and other CCID devices) hanging after endpoint stall
during passthrough.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:31:27 +00:00
b57039d22a fix: compute devid from import response for vhci attach
The client was passing devid=0 to vhci_hcd during attach, but the
server computes expected_devid from (bus_num << 16) | dev_num. This
caused the server's devid validation (added in 76f5134) to reject
every URB from the kernel client.
2026-03-27 19:56:52 +00:00
bb3c603172 feat(fuzz): add AFL++ with cargo-afl fuzzing support
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>
2026-03-27 00:33:34 +00:00
81edffd2f8 Improve duplicate seqnum handling
Properly track duplicates to ensure we never respond more than once.
2026-03-26 20:13:38 +00:00
76f5134e25 fix: close connection on devid mismatch, matching kernel behavior
The kernel's valid_request() in stub_rx.c tears down the TCP connection
when devid doesn't match (SDEV_EVENT_ERROR_TCP). Previously we sent an
error response and continued, which is non-standard. Now we break out
of the loop to close the connection, matching the kernel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:21:49 +00:00
d5ddb48eff fix: use correct RET_UNLINK status codes matching kernel behavior
The CMD_UNLINK handler returned -EINVAL for devid mismatch and -ENOENT
for not-found URBs, but the Linux kernel's stub_recv_cmd_unlink() returns
status 0 in both cases. Non-standard status codes could confuse the host
kernel's vhci_hcd driver. Fixes 64 fuzzer crash artifacts across
fuzz_urb_hid and fuzz_urb_cdc targets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:18:47 +00:00
32daaa5e2f feat(fuzz): add gen-fuzz-corpus nix app
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:14:49 +00:00
58f1e8b471 feat(fuzz): add corpus seed generator with seeds for all targets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:08:24 +00:00
b0adff2a9b docs: add fuzz corpus seeding implementation plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:04:58 +00:00
22be4dd064 docs: add fuzz corpus seeding design spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 23:57:14 +00:00
dc58a36c9b Update CLAUDE.md 2026-03-25 23:09:59 +00:00
e5339e9df6 fix: reject non-zero status in OP_REQ_DEVLIST/IMPORT requests
Replace debug_assert!(status == 0) with proper error returns. Per the
USB/IP protocol spec, the status field in these requests is "unused,
shall be set to 0" — a non-zero value indicates a non-compliant client
and should be rejected at the parsing boundary.

Also document fuzzer crash triage guidelines in CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:39:56 +00:00
d97381396a docs: add CLAUDE.md and fuzzing section to README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:24:04 +00:00
d49d371509 feat: add fuzz-clean-usbip app and extract shared fuzz-env
Adds `nix run .#fuzz-clean-usbip -- <target>` to re-check crash/oom/timeout
artifacts and delete the ones that no longer reproduce (already fixed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:21:34 +00:00
2e2a5961d6 fix: update nusb rev in both lib and cli, update flake cargoHash
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:17:10 +00:00
da9560cd99 fix: update nusb to rev with fixed fuzz_parse_concatenated_config_descriptors return type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:13:33 +00:00
565e0f5834 feat: add nix fuzz devShell and fuzz-usbip app with --fork support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:08:55 +00:00
dfc7e366d9 feat: add fuzz_urb_cdc target
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:06:36 +00:00
963c96cf8b feat: add fuzz_urb_uac target
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:06:32 +00:00
4ccae7bf37 feat: add fuzz_urb_hid target
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:06:24 +00:00
0e6b602b1d feat: add fuzz_handle_client target
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:06:20 +00:00
60bef00b00 feat: add fuzz crate scaffold and fuzz_parse_command target
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:06:17 +00:00
c59bb719d5 feat: add fuzz_helpers module with response validation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:00:17 +00:00
72e9ba292e refactor: extract MockSocket to cfg(any(test, fuzz)) module
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:59:04 +00:00
d7f630500d feat: add fuzz feature flag and arbitrary dependency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:58:24 +00:00
c8c7c3271f docs: add implementation plan for fuzzing setup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:55:21 +00:00
1cb95dd57c docs: add spec for fuzzing setup targeting host codepaths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:49:39 +00:00
e0aa6bfbf6 refactor: remove rusb backend, keep only nusb
- Delete lib/examples/ (unused upstream leftovers)
- Remove rusb crate dependency
- Define local Direction enum in consts.rs (Out=0x00, In=0x80)
- Remove RusbUsbHostInterfaceHandler and RusbUsbHostDeviceHandler
- Replace rusb device enumeration with nusb in new_from_host*
- Update flake.nix cargoHash and Cargo.lock

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:19:51 +00:00
80083fdf93 docs: add spec for removing rusb backend and lib/examples
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:03:20 +00:00
9f270e271d docs: clarify Direction enum discriminant values in spec
Values are intentionally USB-spec-aligned (0x00/0x80), not rusb-aligned.
Safe since the enum is only pattern-matched, never cast to integer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:22:50 +00:00
c2742138f7 docs: fix spec gaps — add consts.rs Direction, device.rs Version, protocol comment
Spec reviewer caught three missing items that would cause build failures:
- pub use rusb::Direction needs local replacement enum
- rusb::Version conversion impls in device.rs need removal
- stale rusb::Direction comment in usbip_protocol.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:19:48 +00:00
8002dfa9e5 docs: add spec for removing rusb backend and lib/examples
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:17:12 +00:00
2a8247c909 Update README.md 2026-03-25 13:14:19 +00:00
ed4fa758f1 fix: guarantee RET_SUBMIT before RET_UNLINK wire ordering
The previous TOCTOU fix prevented sending both responses from two
threads, but a channel-ordering problem remained: the spawn_blocking
task and the UNLINK handler both sent to the same mpsc channel from
different threads, so RET_UNLINK could arrive at the kernel before
RET_SUBMIT.  The kernel (vhci_rx.c) then gives back the URB via the
unlink path, and the subsequent RET_SUBMIT triggers "cannot find a
urb" → VDEV_EVENT_ERROR_TCP → device disconnect.

Fix by making the spawn_blocking task the sole sender of RET_UNLINK
when the URB is still in-flight.  The UNLINK handler now stores its
response header in the InFlightUrb entry instead of sending
immediately.  The spawn_blocking task drains this after sending
RET_SUBMIT (or instead of it, if cancelled), guaranteeing same-thread
FIFO ordering through the channel.  The UNLINK handler only sends
RET_UNLINK directly for the "not found" case, where the entry has
already been removed — meaning RET_SUBMIT is already in the channel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:53:49 +00:00
80a9f35e39 fix: close TOCTOU race in UNLINK handling that crashes vhci_hcd
The spawn_blocking task checked is_cancelled() before removing the URB
from in_flight, allowing the UNLINK handler to find the entry, cancel
the token, and send RET_UNLINK(status=0) in the gap — while the task
proceeded to also send RET_SUBMIT. Both responses for the same URB is
a fatal protocol violation: if the kernel receives RET_UNLINK first it
gives back the URB, then RET_SUBMIT can't find it and disconnects.

Fix by removing from in_flight before checking is_cancelled(), making
the UNLINK handler and completion path mutually exclusive on the map.

Also downgrade "not found in-flight" to trace — it's a normal race for
isochronous transfers, not an error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:35:38 +00:00
c56dced930 fix: revert ISO packet descriptor byte order change
The ISO packet descriptors in this kernel's USB/IP implementation use
big-endian encoding (consistent with the rest of the protocol header),
not little-endian as assumed from older kernel source. The LE change
caused length=192 to be read as 0xC0000000, immediately failing the
bounds check on the first isochronous URB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:09:53 +00:00
075ec146e4 fix: UAC1 audio device recognition, idle timeout, and ISO byte order
Three fixes for the UAC1 loopback test device:

- Add CS_ENDPOINT descriptors (type 0x25) after audio streaming isochronous
  endpoints. The Linux snd-usb-audio driver requires these to recognize the
  device. Added class_specific_descriptor field to UsbEndpoint (mirroring the
  existing field on UsbInterface) and emit it in the config descriptor builder.

- Remove the 30s URB read timeout from handle_urb_loop. The connection lifetime
  is managed by the kernel (vhci_hcd closes the socket on device detach). An
  application-level timeout killed healthy idle devices. Fixed tests to properly
  shutdown() write halves instead of relying on the timeout.

- Fix ISO packet descriptor byte order from big-endian to little-endian. The
  USB/IP protocol uses big-endian for header fields but little-endian for ISO
  descriptors (matching the kernel's usbip_pack_iso using cpu_to_le32). With
  big-endian, field values like length=192 were byte-swapped to ~3GB, corrupting
  isochronous audio streams.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:52:31 +00:00
18a413870a feat: add UAC1 loopback test device and fix endpoint attribute dispatch
Add a simulated USB Audio Class 1 loopback device for testing
isochronous transfers. Audio sent to the playback OUT endpoint
(48kHz/16-bit/stereo) is looped back to the capture IN endpoint.

- Add UsbEndpoint::transfer_type() masking bmAttributes to bits 0-1,
  fixing dispatch for isochronous endpoints with sync-type sub-bits
- Update all endpoint attribute dispatch sites across the library
- Add UacLoopbackBuffer, UacControlHandler, UacStreamOutHandler,
  UacStreamInHandler in lib/src/uac.rs
- Add build_uac_loopback_device() builder function
- Add `test_uac connect` CLI subcommand
- Add 10 unit tests covering buffer, descriptors, and handler behavior
- Add design spec and implementation plan docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:43:31 +00:00
e4cdc4beec Update .gitignore, remove obsolete plan doc 2026-03-25 00:30:38 +00:00
5120b1a3b9 fix: improve reliability with typed error handling and poison recovery
- Replace string-based USB error classification with ErrorKind matching:
  nusb TransferError is now preserved through io::Error instead of being
  destroyed by format!(). Stall→ConnectionReset→EPIPE, Cancelled→
  Interrupted→ENOENT, Disconnected→ConnectionAborted→ESHUTDOWN.
- Replace fragile string matching in interrupt IN retry loop with direct
  TransferError::Cancelled pattern match.
- Replace 21 production Mutex::lock().unwrap() calls with
  .unwrap_or_else(|e| e.into_inner()) to recover from mutex poisoning
  instead of cascading panics across the server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:21:10 +00:00
804a4910a0 fix: USB spec conformance and reliability improvements
Spec conformance:
- Add missing standard control requests for simulated devices:
  GetConfiguration, GetStatus (device/interface/endpoint),
  ClearFeature, SetFeature, SetAddress
- Replace debug_assert with truncate for path/bus_id wire format
  to prevent protocol desync in release builds

Reliability:
- server() now returns Result instead of panicking on bind failure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:12:36 +00:00
3f4e8effce fix: security hardening and data integrity for untrusted USB/IP clients
Critical fixes:
- Validate endpoint number is 0-15 (kernel parity: stub_rx.c)
- Cap in-flight URBs at 256 to prevent DoS resource exhaustion
- Replace expect() with graceful handling on lock contention in find_ep
- Use validated transfer_buffer_length for ISO allocation instead of
  unchecked multiplication of client-supplied values

High-priority fixes:
- Validate devid matches imported device in CMD_SUBMIT and CMD_UNLINK
- Fix string descriptor bLength u8 overflow for long strings (>126 chars)
- Use saturating_add for ISO actual_length sum, capped at transfer_buffer_length
- Truncate IN response data exceeding transfer_buffer_length

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 00:09:20 +00:00
a3dccbca9d Update flake.nix to allow nix flake check torun tests 2026-03-25 00:07:13 +00:00
d81f5826dc feat: add fc-vsock transport for Firecracker/Cloud-Hypervisor VMs
Support connecting to guests via Firecracker/Cloud-Hypervisor's Unix
domain socket vsock proxy. The host opens the VMM's socket file,
performs a CONNECT/OK text handshake, then uses the stream for USB/IP.

New address format: fc-vsock:/path/to/socket:<port>

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 15:44:15 +00:00
4c368c02b5 feat: concurrent ISO pipelining via nusb update and &self handlers
Update nusb to c1380673 which allows multiple IsoEndpoint instances per
address, enabling concurrent URB submission from separate threads.

Change UsbInterfaceHandler trait methods from &mut self to &self and
replace Arc<Mutex<Box<dyn Handler>>> with Arc<dyn Handler>. This
removes the serialization bottleneck where the handler mutex was held
for the entire USB transfer duration, causing ISO audio to play at
~67% speed.

Handlers needing interior mutability (HID, CDC) now use Mutex on
individual fields. Passthrough handlers already used Arc<Mutex<>>
internally and need no changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 15:10:28 +00:00
9250c062aa fix: prevent idle disconnect by retrying interrupt IN and skipping
read timeout while URBs are in-flight

- Interrupt IN transfers now retry with 1s intervals (up to 5 min)
  instead of returning an error on timeout. Previously, nusb's 1s
  timeout caused a cancelled transfer error (-ENOENT) which told the
  kernel the endpoint was intentionally shut down, killing HID.
- Release the nusb Interface Mutex before blocking on interrupt and
  ISO transfers so other URBs on the same interface aren't starved.
- URB read timeout now skips when in-flight URBs exist (e.g. pending
  interrupt transfers), preventing false idle disconnects.
- Use appropriate timeouts per transfer type: interrupt=5min,
  isochronous=5s, control/bulk=1s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 13:04:40 +00:00
b73b4b31f4 fix: ISO alt setting and actual_length for isochronous transfers
- Add set_alt_setting() to UsbInterfaceHandler trait so host passthrough
  handlers can update the physical USB interface's alternate setting via
  nusb::Interface::set_alt_setting() instead of raw control transfers.
  This allows nusb to find ISO endpoints after alt setting changes.
- Fix ISO OUT actual_length to equal the sum of per-packet actual_lengths
  instead of transfer_buffer_length. The kernel validates this invariant
  and disconnects the device on mismatch ("total length of iso packets
  not equal to actual length of buffer").

Enables successful USB Audio Class 2 isochronous playback through
host passthrough.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:45:55 +00:00
85146a12fe fix: passthrough descriptors, SET_INTERFACE forwarding, and error mapping
- Forward Configuration, BOS, and DeviceQualifier descriptors to the
  real device in passthrough mode, preserving IADs, class-specific
  descriptors (audio, HID, DFU), and endpoint companions
- Forward SET_INTERFACE to the physical device so alt settings are
  actually applied (critical for audio streaming)
- Round up interrupt IN buffers to max_packet_size (matching bulk IN)
- Map USB errors to proper Linux URB status codes: STALL→EPIPE(-32),
  cancelled→ENOENT(-2), timeout→ETIMEDOUT(-110)
- Propagate UrbResponse.status instead of hardcoding 0

Fixes USB Audio Class 2 device detection (kernel error "Audio class
v2/v3 interfaces need an interface association") and device reset
loops caused by EPROTO being returned for benign STALL/cancel errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:40:44 +00:00