fix: make BLE HELLO write-once, port duplicate-link resolution to Android (#29, #52) - #87
Conversation
b4a07ac to
cb7a6bd
Compare
…ni5arga#29, ni5arga#52) The BLE link layer is unauthenticated, and HELLO was accepted at any time on both platforms, mutating an already-announced link's identity: - iOS (ni5arga#29): handleHello set remoteTag and re-ran resolveDuplicateLinks unconditionally. An attacker holding a link to a victim could send a second HELLO claiming the victim's tag; the resolver's tie-break then tore the victim's real link down — a targeted, persistent link-starve. - Android (ni5arga#29): remoteTag was assigned *above* the announced guard, so a re-HELLO silently re-labelled a live link and the attacker's later payloads surfaced to JS under a different peer's id (attribution spoof). Both now ignore HELLO once the link is announced: identity is written exactly once, and a stranger cannot re-label or kill a live link. Android also gained the duplicate-link resolver iOS already had (ni5arga#52): the same lexicographic tie-break, adapted to Android's remoteTag id model (keeper announced before the loser is dropped, so dropLink's existing same-tag guard suppresses a spurious onDisconnected). The ble-mesh README now documents write-once HELLO and cross-platform duplicate resolution to match the code. iOS compiles clean. Kotlin reviewed by hand; needs a CI/maintainer compile and the two-phone validation tracked in ni5arga#21.
cb7a6bd to
6cf8e5c
Compare
commitchan
left a comment
There was a problem hiding this comment.
Read this closely since it's the unauthenticated wire and the #29 hijack I'd flagged.
The core fix is right on both platforms: moving the announced guard to the very top of handleHello makes HELLO write-once, so a re-HELLO on a live link returns before remoteTag is ever touched or the resolver re-runs — which is exactly what killed the iOS link-starve and the Android attribution re-label. The legitimate first-HELLO-per-link duplicate resolution still runs, since that path has announced == false.
The Android resolveDuplicateLink is a faithful line-for-line port of the iOS resolver, and — the part I checked hardest — the tie-break agrees across platforms: both compare hex(localTag) < remoteTag as lowercase fixed-width hex strings, which preserves byte order, so both phones independently pick the same physical wire to keep (smaller tag keeps its outbound link; the peer keeps that same link as its inbound). The post-announce loser drop is safe too: dropLink's guard only emits onDisconnected when no other announced link shares the tag, and the keeper does share it, so JS sees no spurious disconnect for the peer it's keeping.
No JS/TS touched, so the suite is unaffected — 231 green locally, typecheck clean. The red CI on this PR was unrelated Expo SDK-57 dependency drift breaking expo install --check on every PR; fixed on main in #88, so this rebases onto a green base.
Native background behaviour still wants real two-phone testing (can't be done in CI), but the logic is small and correct on read. Merging.
Fixes #29, and the Android half of #52.
The BLE wire is unauthenticated and HELLO was accepted at any time, so a re-HELLO could rewrite a live link's identity: