Uncaught RangeError (boundsError) in BuffaloZdo.readMatchDescriptorsResponse crashes Z2M — reproducible from a single command to one device (ember)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 15.7k
- Forks
- 2k
- Avg merge
- 18h 55m
- Merged PRs (30d)
- 35
Description
What happened?
Zigbee2MQTT crashes with an uncaught RangeError thrown from BuffaloZdo.readMatchDescriptorsResponse while parsing a ZDO Match Descriptors Response. On HAOS the Supervisor watchdog restarts the add-on, taking the whole ~61-device network down for ~30–60 s per crash.
I have logged ≥11 identical crashes over 3 days, and I can now reproduce it on demand from a single command to one device.
Every captured crash is preceded seconds earlier by zh:ember:uart:ash: Received frame with CRC error (×2), which suggests a corrupted/truncated frame reaches the ZDO parser, which then reads a match-list length beyond the end of the buffer and throws.
Secondary impact — the blast radius is bigger than the crash itself. Rapid crash→restart cycles can lose the MQTT LWT race: the dying instance's retained bridge/state = offline lands after the new instance's online, so the retained topic stays offline. With availability_mode: all, every Z2M entity in Home Assistant then shows unavailable indefinitely, even though Z2M is healthy and devices respond normally. Recovery required manually re-publishing {"state":"online"} retained to zigbee2mqtt/bridge/state. This turns a 1-minute blip into a fleet-dead-until-a-human-notices outage.
What did you expect to happen?
A malformed or truncated ZDO frame should be length-validated (or caught) at the parse boundary and dropped with a warning — not kill the bridge process.
How to reproduce it (minimal and precise)
Reliable in my environment, crash within ~75 s:
- Have a "Smart Motor" cover on the network, enabled. (Bringnox zebra shade, SmartWings-OEM motor. Reports modelID
Smart Motor, speaks standard ZCL — not Tuya — and runs on an auto-generated definition,supported: false.) - Send it a single position command, e.g.
{"position": 30}. - The motor moves. Within ~60 s:
ash CRC error ×2→RangeError→ crash → watchdog restart.
Controlled repro (2026-07-10) with confounders eliminated: device battery 100% (charge-verified), LQI 144, dedicated router in the same room, freshly restarted and otherwise-idle Z2M. Crashes identically to the degraded-conditions case, so this is not low-battery brownout and not mesh instability. The same NWK address appears in a ROUTE_ERROR_MANY_TO_ONE_ROUTE_FAILURE at each crash.
The device is currently kept disabled as a workaround. I can re-enable it and reproduce on demand with debug logging if that would help — just say what you want captured.
Stack trace (byte-identical in every crash)
RangeError: The value of "offset" is out of range. It must be >= 0 and <= 4. Received 5
at boundsError (node:internal/buffer:88:9)
at Buffer.readUInt8 (node:internal/buffer:254:5)
at BuffaloZdo.readUInt8 (zigbee-herdsman/src/buffalo/buffalo.ts:42:35)
at BuffaloZdo.readListUInt8 (zigbee-herdsman/src/buffalo/buffalo.ts:266:29)
at BuffaloZdo.readMatchDescriptorsResponse (zigbee-herdsman/src/zspec/zdo/buffaloZdo.ts:1780:39)
at Function.readResponse (zigbee-herdsman/src/zspec/zdo/buffaloZdo.ts:1497:32)
at EmberAdapter.onZDOResponse (zigbee-herdsman/src/adapter/ember/adapter/emberAdapter.ts:516:36)
at Ezsp.emit (node:events:519:28)
at Ezsp.ezspIncomingMessageHandler (zigbee-herdsman/src/adapter/ember/ezsp/ezsp.ts:5385:18)
at Ezsp.callbackDispatch (zigbee-herdsman/src/adapter/ember/ezsp/ezsp.ts:778:26)
Log excerpt showing the CRC → crash pairing
[17:53:39] error: zh:ember:uart:ash: Received frame with CRC error (×2)
[17:53:41] info: zh:ember:ezsp: Received network/route error ROUTE_ERROR_MANY_TO_ONE_ROUTE_FAILURE for "50229". (×2)
RangeError: The value of "offset" is out of range. It must be >= 0 and <= 4. Received 5
at boundsError ...
[17:53:42] info: z2m: Connecting to MQTT server ... <-- watchdog restart
Analysis / suggested fix
readMatchDescriptorsResponse reads a match-list length and then reads that many uint8s via readListUInt8, with no check that the buffer actually holds them. When a truncated frame arrives (plausibly ash-layer corruption that slipped past CRC, or a non-compliant device emitting a malformed response), the read runs off the end and the RangeError propagates out of the EZSP callback dispatch, unhandled, killing the process.
Length-validating against the remaining buffer at the ZDO parse boundary — and dropping the frame with a warning rather than throwing — would contain this. A single bad frame from one device should not be able to take down the bridge for an entire network.
I'm filing here rather than in zigbee-herdsman per the note in that repo's issue template, though the fix presumably lands there.
Zigbee2MQTT version
2.12.1 (Home Assistant add-on 2.12.1-1), zigbee-herdsman 10.6.1
Adapter firmware version
EmberZNet 7.4.3 [GA]
Adapter
Sonoff ZBDongle-E V2 (ember), baudrate 115200, no socat
usb-ITEAD_SONOFF_Zigbee_3.0_USB_Dongle_Plus_V2_20240103194853
Setup
Home Assistant OS (generic-x86-64, Dell Wyse 5070), Zigbee2MQTT add-on. ~61-device mesh, channel 20.
Debug log
Available on request — I can reproduce on demand (see above). Happy to capture with debug/debug:zh:* logging enabled if you tell me which namespaces you want.
Note on the CRC errors
I'm aware ash: Received frame with CRC error points at my serial link and I'm separately checking the dongle's USB path (port, extension cable, USB3/EMI proximity). But I'd argue the parser crash is a bug regardless: serial corruption happens in the field, and the ZDO parse boundary is exactly where it should be absorbed rather than turned into a whole-network outage.
Contributor guide
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 zigbee-herdsman/src/zspec/zdo/buffaloZdo.ts at readMatchDescriptorsResponse, then trace the readListUInt8 call through buffalo.ts and the Ember adapter entry point named in the stack trace. Use the controlled single-device reproduction with debug logging to confirm the malformed response path. Done means a truncated ZDO response is contained with a warning instead of propagating a RangeError and crashing Zigbee2MQTT.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- backend, networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100