fix(hid): wall-clock timeout and continuation-packet validation#205
Open
AlfioEmanueleFresta wants to merge 2 commits into
Open
fix(hid): wall-clock timeout and continuation-packet validation#205AlfioEmanueleFresta wants to merge 2 commits into
AlfioEmanueleFresta wants to merge 2 commits into
Conversation
2be1951 to
fb6269e
Compare
6 tasks
Per CTAP 2.2 §11.2.4, HidMessageParser must reject any continuation packet whose CID differs from the initial packet, whose SEQ does not start at 0 and increment monotonically, or whose 5th byte has the high bit set (i.e., an init packet for a different transaction). Also reject all-zero packets at the parser level. The previous code silently dropped them as a workaround for hidapi `read_timeout` returning a zeroed buffer on timeout; that workaround now belongs at the read layer, gated on bytes_read == 0. Spec refs: - CTAP 2.2 §11.2.4 USB HID message and packet structure - FIDO U2F HID Protocol v1.2 (the older base spec)
hidapi `read_timeout` returns `Ok(0)` on timeout (not an error) and the leftover zero-initialised buffer used to be silently dropped by the parser, so a stalled or unplugged device could hang `hid_recv_hidapi` indefinitely. Additionally, the CTAP2 protocol layer did not wrap the `cbor_send`/`cbor_recv` pair in any wall-clock timeout (unlike CTAP1). Changes: - `hid_recv_hidapi` now caps each blocking read to a short poll interval, tracks the overall deadline across iterations, treats `Ok(0)` byte counts as per-iteration timeouts to retry against the remaining budget, and returns `TransportError::Timeout` once the budget is exhausted. - `hid_recv` sends `CTAPHID_CANCEL` on `TransportError::Timeout` in addition to `PlatformError::Cancelled`, per CTAP 2.2 §11.2.9.1.5. - A new `cbor_send_recv` helper wraps the CTAP2 send/recv pair in `tokio::time::timeout` and is used by every `ctap2_*` method on the `Ctap2` trait, mirroring `send_apdu_request_wait_uv` in the CTAP1 protocol module. Spec refs: - CTAP 2.2 §11.2 USB HID transport - CTAP 2.2 §11.2.9.1.5 CTAPHID_CANCEL
fb6269e to
130d947
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two HID protocol-level fixes:
Continuation-packet validation. The HID framing parser was accepting continuation packets without checking the CID, the sequence number, or whether the high-bit-set init flag had been re-asserted. A device could splice in foreign-CID continuation frames or repeat init packets and the parser would reassemble a corrupted payload. The parser now enforces the rules from CTAP 2.2 §11.2.4: the first packet must have the init bit set, continuation packets must carry the same CID, SEQ must start at 0 and increment monotonically, and any init-bit packet during continuation is rejected. All-zero packets are also rejected outright (CID
0x00000000is reserved).Wall-clock timeout. The receive loop used
hidapi::read_timeoutwhoseOk(0)return-on-timeout was silently treated as a successful read of an all-zero report (because the report buffer was zero-initialised and the framing parser discarded all-zero packets). The CTAP2 send/recv paths additionally had no outer wall-clock timeout, so a stalled or unplugged device hung the channel until the caller dropped the future. Eachread_timeoutis now capped at a 100ms poll interval, the loop tracks the overall deadline and returnsTransportError::Timeoutonce it is exhausted, andCTAPHID_CANCELis sent on timeout in addition to caller cancellation. A newcbor_send_recvhelper wraps every CTAP2 exchange intokio::time::timeout, symmetric withsend_apdu_request_wait_uvin the CTAP1 protocol module.Notes
Why 100ms for
HID_READ_POLL_INTERVAL? This bounds the latency of two things: cancel propagation when the caller's future is dropped, and wake-up after the per-op wall-clock budget expires. It does not affect end-to-end response latency:hidapi::read_timeoutreturns as soon as the device delivers a report. Per-syscall overhead is negligible and ~10 wakeups/sec per active channel is fine on every platform we care about. 1s would feel sluggish for cancel; 10ms would be wastefully busy. 100ms is a reasonable middle.About the third commit / cancel-on-drop change. The previous version of this PR also contained a commit that replaced the embedded mpsc cancel channel with an
AtomicBool + Notify(CancelState) shared betweenHidChanneland anyHidChannelHandle, fixed aDropdeadlock, and propagated future-drop to in-flight reads via aCancelOnDropRAII guard. That work is independent of the timeout fixes here (the wall-clock fix works fine on top of the existing mpsc cancel) and has been split into a separate draft PR (#208, branchfix/hid-cancel-on-drop) for cleaner review. It stacks on top of this branch.Spec refs
Test plan
cargo buildcleancargo test --lib(140 passed, 2 ignored, including 7 new parser tests)cargo clippy --lib --all-features -- -D warningscleancargo fmt --checkcleanTransportError::Timeoutafter the configured budget and thatCTAPHID_CANCELis logged