Skip to content

Harden peripheral server against malformed writes; route central pinning through fail-closed API#39

Merged
tdaira merged 1 commit into
mainfrom
fix/audit-medium-findings
Jul 4, 2026
Merged

Harden peripheral server against malformed writes; route central pinning through fail-closed API#39
tdaira merged 1 commit into
mainfrom
fix/audit-medium-findings

Conversation

@tdaira

@tdaira tdaira commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Addresses the Medium-severity findings in this repo from the bleRPC cross-repo security audit.

peripheral_py: malformed-write DoS (parsing/DoS)

_on_write parsed every inbound GATT write with Container.deserialize and had no exception guard, so a truncated frame or out-of-range header could throw out of the bless write callback. The body is now split into _dispatch_write and wrapped in try/except — malformed packets are logged and dropped.

peripheral_py: unbounded flash_read allocation (parsing/DoS)

handle_flash_read called os.urandom(req.length) with length an unbounded uint32. A hostile length=0xFFFFFFFF allocates ~4 GiB. It is now rejected above MAX_FLASH_READ_LENGTH = 8192 (the proto contract) before allocating.

central_fw: fail-closed identity pinning (auth/MitM)

Routes the existing CONFIG_BLERPC_PEER_ED25519_PUBLIC_KEY pin check through the library's new verify_fn / pin_identity API, so the identity is verified mid-handshake and a mismatch aborts before a session is established.

Tests

Verified functionally (not review-only):

  • peripheral_py: focused test suite passes (9/9). flash_read accepts a request at the 8192 limit and rejects 8193 and a hostile 0xFFFFFFFF (~4 GiB) before allocating; _on_write swallows truncated, empty, garbage, and unknown-enum frames without crashing the callback (_dispatch_write alone raises on them, confirming the guard is load-bearing). py_compile + ruff check / ruff format --check clean.
  • central_fw: built against the new blerpc-protocol C API (no warnings), flashed to an nRF54L15 DK, and verified end-to-end against a Python peripheral:
    • correct pinned identity → E2E encryption established (peer identity verified); encrypted echo and counter-stream RPCs succeed.
    • wrong pinned identity → key exchange aborts (peer identity rejected), no session established — the fail-closed MitM defense fires.
    • the 8192 flash_read bound was observed rejecting an oversized (12160-byte) read on live traffic.
  • CI green across all platform jobs.

Depends on tdaira/blerpc-protocol#3, which changes the signature of blerpc_central_perform_key_exchange (merged together).

🤖 Generated with Claude Code

https://claude.ai/code/session_01KvpxJjBYhuSJb34DNFtD7V

…d API

Security fixes (Medium) from a cross-repo audit:

- peripheral_py: wrap the GATT write callback so a malformed packet
  (truncated / out-of-range container header) is dropped instead of
  propagating out of the bless write handler and disrupting the connection.
- peripheral_py: bound flash_read length to 8192 (the proto contract) before
  os.urandom(), preventing a ~4 GiB allocation / OOM from a hostile uint32.
- central_fw: route the existing Ed25519 identity pinning through the
  library's new fail-closed verify callback (blerpc_central_perform_key_exchange
  now requires a verifier when pin_identity is true).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KvpxJjBYhuSJb34DNFtD7V
@tdaira
tdaira merged commit 853d96d into main Jul 4, 2026
7 checks passed
@tdaira
tdaira deleted the fix/audit-medium-findings branch July 4, 2026 12:02
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.

1 participant