Koenkk / Koenkk/zigbee2mqtt

Uncaught RangeError (boundsError) in BuffaloZdo.readMatchDescriptorsResponse crashes Z2M — reproducible from a single command to one device (ember)

Open
#32,567 3 comments 0 reactions 0 assignees View on GitHub

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:

  1. 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.)
  2. Send it a single position command, e.g. {"position": 30}.
  3. The motor moves. Within ~60 s: ash CRC error ×2RangeError → 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.