Skip to content

SIGSEGV (exit 139) when _discover_route() called for unknown NWK — v1.1.0 regression #277

Description

@seanpaget

Summary

zigpy-znp v1.1.0 introduced a SIGSEGV regression via PR #273 ("Retry after route rediscovery on NWK_NO_ROUTE in send_packet"). When a relay message arrives from an NWK address not in zigpy's device registry, zigpy calls _discover_unknown_device(), which issues a ZDO IEEE_addr_req to that NWK. send_packet then checks for an active route, finds none, and calls _discover_route(nwk). The ZDO.ExtRouteDisc.Req call to the ZNP stack crashes the HA process (SIGSEGV, Python exit code 139) when the destination NWK has no routing table entry at all.

This did not occur with v1.0.0 because NWK_NO_ROUTE was returned as an error and propagated without triggering route rediscovery.

Environment

Component Version
HA Core 2026.6.4
ZHA 2.0.0
zigpy 1.6.0
zigpy-znp 1.1.0
Coordinator SMLIGHT SLZB-MR1 (CC2652P7, ZNP, port 7638)
Python 3.14

Reproduction

  1. Have one or more Zigbee devices on the mesh that are not in zigpy's device registry (e.g. devices removed from ZHA without being factory-reset, or devices from a previous coordinator that retained network credentials).
  2. Wait for the device to broadcast a routing relay message (~30 min interval for routers).
  3. handle_relays() receives the relay, fails get_device(nwk=X), and calls _discover_unknown_device(X).
  4. _discover_unknown_device() calls zigpy.zdo.broadcast(IEEE_addr_req, NWKAddrOfInterest=X).
  5. send_packet() (zigpy-znp) detects the ZDO destination has no route and calls _discover_route(X).
  6. _znp.request(ZDO.ExtRouteDisc.Req(Dst=X, Options=UNICAST, Radius=30)) causes SIGSEGV.

Supervisor log shows Watchdog found Home Assistant exited with code 139 every ~30 minutes, correlating exactly with the ghost device's relay heartbeat interval.

Crash signature (HA Core log, faulthandler output)

File ".../zigpy/application.py", line 1703, in _device
File ".../zigpy/application.py", line 1658, in get_device
File ".../zigpy/application.py", line 1090, in request

Supervisor: WARNING [supervisor.homeassistant.core] Watchdog found Home Assistant exited with code 139, restarting...

Affected code path

# zigpy_znp/zigbee/application.py — send_packet()
if (
    packet.dst_ep == ZDO_ENDPOINT
    and dst_addr.mode == t.AddrMode.NWK
    and dst_addr.address != self.state.node_info.nwk
):
    route_status = await self._znp.request(
        c.ZDO.ExtRouteChk.Req(Dst=dst_addr.address, ...)
    )
    if route_status.Status != c.zdo.RoutingStatus.SUCCESS:
        await self._discover_route(dst_addr.address)  # ← crashes when no routing table entry exists
# zigpy_znp/zigbee/application.py — _discover_route()
await self._znp.request(
    c.ZDO.ExtRouteDisc.Req(
        Dst=nwk,                              # unknown NWK — no routing table entry
        Options=c.zdo.RouteDiscoveryOptions.UNICAST,
        Radius=30,
    ),
)

Suggested fix

Guard _discover_route() (or the ExtRouteDisc.Req call) against the case where device is None (i.e. the destination NWK is not in the zigpy device registry). A device unknown to zigpy cannot have a route rediscovered in a meaningful way, and the ZNP stack crashes on the attempt.

Alternatively, _discover_unknown_device() in zigpy could be the layer that guards this — but the SIGSEGV is in the ZNP stack, so the guard should be in zigpy-znp.

Workaround

Patch handle_relays in zigpy/application.py to skip _discover_unknown_device():

except KeyError:
    LOGGER.warning("Received relays from an unknown device: %s", nwk)
    return  # skip discovery — crashes ZNP via _discover_route on unknown NWK

This restores v1.0.0 behaviour (warn and return) and eliminates the crash.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions