Skip to content

Fix touch responsiveness and USB HID issues#777

Open
pwnall wants to merge 1 commit into
google:developfrom
pwnall:fix-artemis-touch-usb
Open

Fix touch responsiveness and USB HID issues#777
pwnall wants to merge 1 commit into
google:developfrom
pwnall:fix-artemis-touch-usb

Conversation

@pwnall
Copy link
Copy Markdown
Member

@pwnall pwnall commented May 27, 2026

This PR addresses two responsiveness issues that may cause the device to hang or drop connections:

  1. Touch State Leak: Implemented Drop for Touch to correctly clear the STATE when a user presence request is interrupted (e.g., by a USB packet or timeout). Added safe extraction in State::touch to handle queued hardware button interrupts (bounces) without hitting unreachable!() and panicking.
  2. USB HID Communication Fixes:
    • Updated MessageAssembler::parse_packet to allow any valid initialization packet (not just CTAPHID_INIT) to abort a hung transaction, in accordance with the CTAP 2.1 specification.
    • Fixed a bug where a new initialization packet arriving immediately after a transaction timeout was incorrectly dropped while returning the MsgTimeout error.

These fixes prevent the firmware from entering unrecoverable states where the LED fails to blink or the device stops responding to the host.

@pwnall pwnall force-pushed the fix-artemis-touch-usb branch from acf9e87 to a05c40b Compare May 27, 2026 21:38
@coveralls
Copy link
Copy Markdown

coveralls commented May 27, 2026

Coverage Status

Coverage is 97.262%pwnall:fix-artemis-touch-usb into google:develop. No base build found for google:develop.

@pwnall pwnall force-pushed the fix-artemis-touch-usb branch 2 times, most recently from 3065d91 to f64ddf4 Compare May 27, 2026 22:31
@pwnall pwnall changed the title Fix Artemis touch responsiveness and USB HID issues Fix touch responsiveness and USB HID issues May 27, 2026
Copy link
Copy Markdown
Member

@ia0 ia0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! I'll only comment on the Touch issue and let @kaczmarczyck address the second point.

Comment thread src/touch.rs Outdated
Comment thread src/touch.rs Outdated
if let Some(state) = this.take() {
state.button.stop();
state.touched.store(true, Relaxed);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not reach unreachable!(). I wonder if you had to add this because you unconditionally reset the STATE in Drop for Touch. In theory, state.button.stop() will start dropping future events and clear any existing pending events. If that's not the case it's a bug in Wasefire.

That said, the applet could be resilient against Wasefire bugs. I don't think it's a good idea because (at least for development) we want to fail as soon as something impossible happens. We could have a resilient cargo feature to avoid crashes as much as possible, which would be used for production keys (there could be security concerns though, it's a trade-off with availability). @kaczmarczyck what do you think?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for explaining!

I removed this diff. I don't want to make development more difficult 😄

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should double check how we deal with parallel connections, and that we don't miss anything else. We didn't test enough since the Wasefire refactoring.

Touches should never be used twice. CTAP2 library code should already ensure this, but the combination with CTAP1 can work in one direction: First requesting on CTAP1, then going into the CTAP2 UP loop. We should drop CTAP1 then. But doesn't need to be done here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And to answer the question, once it is impossible, we can fail when the impossible happens.

This commit addresses responsiveness issues and CTAP protocol deviations
in the USB HID transport and user presence handling:

1. User Presence State Leak: When a user presence request is interrupted
   (e.g., by a timeout or an incoming USB packet), the touch state was
   incorrectly left running in the background. This prevented subsequent
   LED blinking and caused a panic if hardware interrupts (bounces) were
   queued. The `Touch` struct now correctly implements `Drop` to clear
   the state, and event handling safely ignores stale interrupts.

2. Transaction Timeout Race Condition: Following a transaction timeout
   (CTAP 2.1 Section 11.2.5.2), the firmware incorrectly dropped the
   next incoming packet while returning a `MsgTimeout` error. If the
   host initiated a new transaction with a `CTAPHID_INIT` packet
   immediately after a timeout, the new initialization packet was
   discarded, leaving the host waiting indefinitely. The timeout logic
   now preserves new initialization packets and only returns a timeout
   error if the host sends a continuation packet for the aborted message.
@pwnall pwnall force-pushed the fix-artemis-touch-usb branch from f64ddf4 to ff90a86 Compare May 28, 2026 08:02
Copy link
Copy Markdown
Member

@ia0 ia0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for src/touch.rs. Is there a reason the PR is still a draft?

@ia0 ia0 requested a review from kaczmarczyck May 28, 2026 08:11
@pwnall pwnall marked this pull request as ready for review May 28, 2026 08:11
@ia0 ia0 mentioned this pull request May 28, 2026
Copy link
Copy Markdown
Collaborator

@kaczmarczyck kaczmarczyck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving for HID library code

// Otherwise, proceed with processing the packet.
if cid == current_cid {
if cid == current_cid
&& matches!(processed_packet, ProcessedPacket::ContinuationPacket { .. })
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reporting this bug in a very old part of the code. I wrote a test to check the new behavior, feel free to add it here, or I will later:

    #[test]
    fn test_timed_out_new_init() {
        let mut env = TestEnv::default();
        let mut assembler = MessageAssembler::default();
        assert_eq!(
            assembler.parse_packet(
                &mut env,
                &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
                None,
            ),
            Ok(None)
        );
        env.clock().advance(TIMEOUT_DURATION_MS);
        assert_eq!(
            assembler.parse_packet(
                &mut env, 
                &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]),
                None
            ),
            Ok(None)
        ); 
        assert_eq!(
            assembler.parse_packet(
                &mut env,
                &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]),
                None
            ),
            Ok(Some(Message {
                cid: [0x12, 0x34, 0x56, 0x78],
                cmd: CtapHidCommand::Ping,
                payload: vec![0x00; 0x40]
            }))
        );
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants