USB Passthrough — Phase 2e 安全審查清單

本頁是給外部審查者在 USB passthrough 預設啟用之前要走過一遍的清單。 它本身不是 sign-off — 簽核紀錄留在專案使用的 ticket / 紀錄系統。

在以下每一項都被一個 非程式作者 的審查者 check 並簽核之前, passthrough 必須留在 enable_usb_passthrough(True)(預設 off) 之後。

威脅模型

信任邊界:viewer 是 host 本機信任域之外的 peer。他們可以在 usb DataChannel 上送任意 frame。host 絕對不可:

  • claim 操作者沒有授權的裝置(ACL)。

  • claim 超過政策上限的裝置數量(max_claims)。

  • 在 viewer 驅動的 payload 上花無上界的 buffer 空間(payload cap + credit window)。

  • 對明確行為不端的 viewer 繼續服務(rate / lockout,channel 與同 session 共用 REST auth gate 時繼承)。

viewer 也可能是惡意 host 的受害者 — 但本清單只涵蓋 host 端。 viewer client 的審查獨立排在 Phase 2f。

ACL

  • [ ] 沒有檔案時,UsbAcl 預設為 "deny"。用全新使用者帳號驗證。

  • [ ] 檔案損毀/版本不對時,ACL 同樣預設 deny(測試

    test_unknown_version_is_ignored)。

  • [ ] prompt_on_open rule 沒接 callback 時退到 deny(測試

    test_session_prompt_no_callback_means_deny)。

  • [ ] prompt callback 拋例外時,open 視為被拒(測試

    test_session_prompt_callback_raising_means_deny)。

  • [ ] ACL 檔案在 POSIX 上以 mode 0o600 寫入(測試

    test_save_persists_to_disk_with_safe_mode)。

  • [ ] 建議把 ACL 放在支援 POSIX 權限的檔案系統上;佈署文件需把

    Windows ACL 故事寫清楚。

  • [ ] OQ8 — ACL 完整性(HMAC)已實作。ACL 旁附 HMAC-SHA256

    sidecar 簽章,竄改則 fail-closed(測試 test_tampered_acl_file_fails_closedtest_explicit_key_roundtrip_and_wrong_key_fails)。殘留風險: 預設金鑰存於同使用者可讀的 usb_acl.json.key;同身分的 process 仍可偽造簽章。高保證部署應透過 UsbAcl(hmac_key=...) 改用平台 keychain 衍生金鑰——請審查者確認部署是否採用。

稽核

  • [ ] 每個 ACL 決策都透過 audit_log 以下列其中一個 event_type 記錄:

    usb_open_allowedusb_open_deniedusb_open_rejected_max_claimsusb_open_backend_errorusb_close。手動跑一次後檢視最近的稽核行確認。

  • [ ] 稽核行帶 viewer_id,可追溯到特定 peer(測試

    test_session_audit_captures_open_decisions)。

  • [ ] 稽核紀錄本身有雜湊鏈(round 25)。Passthrough session 結束後

    確認 verify_chain()ok=True

  • [ ] frame 層傳輸紀錄刻意 開,避免擷取 YubiKey 之類裝置的

    key material。只有 ERROR 透過專案 logger 顯示。

協定強化

  • [ ] Frame header 4 bytes;decode_frame 拒絕短於這個的 buffer

    (測試 test_decode_rejects_short_buffer)。

  • [ ] 未知 opcode 拋 ProtocolError(測試

    test_decode_rejects_unknown_opcode)— session 不會看到壞 frame。

  • [ ] Payload 上限 MAX_PAYLOAD_BYTES(16 KiB),decode(測試

    test_decode_rejects_oversize_payload)與 construct(測試 test_frame_constructor_validates)兩端都檢查。

  • [ ] CTRL/BULK/INT request body 解析失敗回 ERROR,不 crash(測試

    test_bad_transfer_payload_returns_error)。

  • [ ] backend 例外 catch 後翻成 {ok: false, error: ...} — session

    絕不把 host 端 RuntimeError 傳到 wire(測試 test_backend_error_translates_to_ok_false)。

資源上界

  • [ ] max_claims 上限有效(測試

    test_max_concurrent_claims_enforced)。

  • [ ] CREDIT-based 入站流量控制阻止 peer 灌滿 host process queue

    (測試 test_credit_exhaustion_returns_error)。

  • [ ] CREDIT 補充每個 reply 1 個 — well-behaved peer 不會 stall

    (測試 test_each_transfer_consumes_then_replenishes_one_credit)。

  • [ ] 壞 payload 的 CREDIT 訊息靜默丟掉(測試

    test_credit_message_with_bad_payload_is_ignored)。

  • [ ] 未知 claim_id 的 CREDIT 靜默(測試

    test_credit_message_for_unknown_claim_is_silent)。

生命週期

  • [ ] close_all() 釋放每個未結 handle,且容忍 per-handle close

    錯誤(測試 test_close_all_releases_every_outstanding_claim)。

  • [ ] FakeHandle close 是 idempotent(測試

    test_backend_handle_close_is_idempotent);libusb backend 在硬體測試時驗證同樣性質。

  • [ ] 關閉 handle 之後再發 transfer 會 raise(測試

    test_fake_handle_transfer_after_close_raises)。

  • [ ] viewer client shutdown() 釋放任何等待中的 request waiter

    (測試 test_shutdown_unblocks_pending_transfers)。

各 OS 需求

  • [ ] Linux libusb:目標裝置的 udev rule 文件化;非 root 測試。

  • [ ] Linux libusb:HID 裝置 claim 之前呼叫

    libusb_detach_kernel_driver;close 時重新 attach。 確認 host OS 的鍵盤/滑鼠在 session 結束後仍可運作。

  • [ ] Windows WinUSB(Phase 2b — 已實作,硬體未驗證):裝置

    必須已與 WinUSB 關聯(Zadig / libwdi),只有綁定者會出現在 list()。在真實硬體上跑 bulk / HID / composite 測試矩陣後 才能簽核。

  • [ ] macOS IOKit(Phase 2c — 已實作,硬體未驗證):原生 IOKit

    列舉 + libusb 傳輸。非 App Store 發行需 notarisation;文件化 SIP 排除清單。在真實硬體上跑測試矩陣後才能簽核。

  • [ ] 三個 backend 都要:開啟已被別 driver 持有的裝置時,要清楚地

    回 “busy” RuntimeError,不 hang 不 crash。

滲透測試情境

以下是建議外部 pen-tester 在 sign-off 之前嘗試的情境。沒有一項 應該成功

  1. ACL 大小寫繞過。試試混合大小寫與前置零的 VID/PID;確認 只有正規形式會 match。

  2. Unicode 正規化繞過。試試視覺相同但 Unicode 不同的序號 字串。

  3. Credit DoS。在小 max_claims 之下盡可能快速送 100 萬筆 transfer frame;確認 host RSS 維持上界。

  4. Frame 切片攻擊。送 header 宣稱 payload 比實際抵達大的 frame; 確認 decode_frame 拒絕被截斷的 stream。

  5. 並行 OPEN race。兩個 peer(或一個 peer 多 thread)同時 OPEN — 確認每個 OPEN request 剛好得到一個 claim_id、bookkeeping 不漂移。

  6. 稽核竄改。直接用 raw SQLite 編輯 audit.db 中的某個 usb_* row;確認 verify_chain() 會 flag 出來。

  7. Prompt callback 計時。慢的 prompt callback(sleep 30 秒) 不應允許另一個 peer 趁機塞 CTRL — 確認 prompt callback 完成前 同一 vid/pid 的後續決策都會等待。

  8. 權限 downgrade。在 Linux 以非特權使用者跑 host 而沒有 udev rule;確認 OPEN 乾淨地失敗,回清楚的 “permission denied” 訊息 而非 crash。

Sign-off

審查者姓名:__________________________________________________

審查者單位:__________________________________________________

日期:________________________________________________________

以上項目全部 check:[ ] 是 [ ] 否 — 在下方列未通過項目。

建議:

[ ] 可以發布 Phase 2 預設啟用。 [ ] 可以發布但保持目前的 opt-in flag。 [ ] block 釋出;需要 remediation。

備註/remediation 清單: