device_digest is not unique across boards, weakening the USB provisioning anti-swap guard
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 94.5k
- Forks
- 12.5k
- Avg merge
- 21h 27m
- Merged PRs (30d)
- 43
Description
device_digest is not unique across boards, weakening the USB provisioning anti-swap guard
Repo: ruvnet/RuView (firmware) · Severity: Medium-High · Measured: 2026-09-14, three ESP32-C6 boards on 0.8.12
Symptom
Two physically distinct boards report the same device_digest over RUVIEW_HELLO_V1:
node 11 device_digest=062009cef304369d
node 12 device_digest=062009cef304369d <-- identical
node 13 device_digest=e1303fbd5cd4cebd
Read in one pass across three ports, each nonce-matched, stable across reboots and across a
firmware reflash. Confirmed distinct hardware — esptool reports different efuse base MACs
for all three.
Not duplicate efuses
The firmware computes SHA-256("ruview-device-v1" || esp_efuse_mac_get_default())[0..8].
With distinct base MACs the digests cannot collide, so this was checked directly. Brute
force over the documented formula and every plausible variant — domain with and without the
NUL, raw / reversed / lowercase-hex / uppercase-hex / colon-separated MAC, 6-byte base and
8-byte extended MAC, prefix and suffix ordering — reproduces none of the three observed
digests, including the unique one.
So the shipped digest is not a function of the MAC in the way the source describes, and it
is not the all-zeros failure path either: the firmware believes it succeeded. It is silently
wrong rather than failing closed.
Impact
deviceDigest is the anti-swap guard in Mac USB provisioning
(RuViewNodeOnboardingModule.swift:119):
let attempt = try hello(on: descriptor, path: portPath, ...)
guard let hello = attempt.node, hello.deviceDigest == expectedDeviceDigest else {
throw RuViewNodeOnboardingError.identityMismatch
}
It re-runs HELLO before writing NVS to confirm the board is the one the operator selected.
With a colliding digest that guard cannot distinguish those two boards, so it degrades from
"verified identity" to "verified chip family". Serial paths do re-enumerate in practice —
observed during flashing — so a mix-up is reachable, and would write the wrong node ID to
the wrong board.
Detection is unaffected: the device list keys on serial path, so all boards appear
correctly.
Caveat on attribution
release_bins/c6-onboarding/esp32-csi-node.bin is a tracked binary blob, so it cannot be
proven that the serial_onboarding.c in the tree is the source that built it. The behaviour
is measured and reproducible; the cause needs a from-source rebuild to pin down.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with serial_onboarding.c and the RUVIEW_HELLO_V1 digest path, then compare it with the tracked release_bins/c6-onboarding/esp32-csi-node.bin; the issue notes that the binary may not come from the tree. Use the reported ESP32-C6 boards and efuse MACs to reproduce the mismatch from a source build. Check RuViewNodeOnboardingModule.swift:119 afterward to verify that distinct boards cannot pass the same identity guard.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, swift
- Domain
- embedded-iot, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100