Skip to content

Raise ParsingError instead of asserting on oversized frame payload - #763

Open
I-mho wants to merge 1 commit into
zigpy:devfrom
I-mho:fix/ash-oversized-payload
Open

I-mho wants to merge 1 commit into
zigpy:devfrom
I-mho:fix/ash-oversized-payload

Conversation

@I-mho

@I-mho I-mho commented Oct 4, 2026

Copy link
Copy Markdown

…payload

DataFrame.from_bytes() derandomizes the payload via _randomize(), which asserted len(data) <= len(PSEUDO_RANDOM_DATA_SEQUENCE) (256 bytes). A CRC-valid frame with a payload longer than 256 bytes therefore escaped with an uncaught AssertionError instead of ParsingError, and under python -O (assertions stripped) the payload was silently truncated to 256 bytes. Raise ParsingError on oversized payloads so malformed input is rejected consistently.

Fixes #762

@codecov

codecov Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.58%. Comparing base (aea9248) to head (3dc3976).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev     #763   +/-   ##
=======================================
  Coverage   99.58%   99.58%           
=======================================
  Files          64       64           
  Lines        4331     4332    +1     
=======================================
+ Hits         4313     4314    +1     
  Misses         18       18           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@zigpy-review-bot zigpy-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good: the right fix, at the right layer. It's a direct replacement for the assert, and the new test pins the 256/257 boundary.

One correction to the impact described in #762, so nobody overrates this: on the real receive path, the uncaught AssertionError could not disrupt anything. AshProtocol.data_received() wraps _unstuff_bytes() + parse_frame() in except Exception, which already caught the AssertionError, logged it at debug level and called _reject_frame(). I checked this against dev by feeding a CRC-valid 257-byte DATA frame through data_received(): it is rejected the same way before and after this PR. The change that matters in practice is the python -O case. On dev under -O, a 300-byte payload parses "successfully" as a truncated 256-byte ezsp_frame and gets passed up the stack. With this PR it is rejected. That alone makes the PR worth merging, along with the clearer exception type.

Optional:

  • _randomize() is also used by DataFrame.to_bytes(), so encoding an oversized outgoing frame now raises ParsingError instead of AssertionError. The name is a bit odd on the send side, but nothing catches either one there, real EZSP frames are far below 256 bytes, and the message ("Frame payload is too long") reads fine in both directions. No change needed, just noting it.
  • The commit subject/PR title was cut off at GitHub's limit ("…oversized frame …"). It's worth fixing when squash-merging; recent bellows titles use plain sentence case, e.g. "Raise ParsingError instead of asserting on oversized ASH frame payloads".
Verified (3 checks)
  • Full test suite at f6dcf8e: 706 passed. CI is green on 3.11–3.14.
  • data_received() with a stuffed, CRC-valid 257-byte DATA frame: _reject_frame() is called and frame_received() is not, on both dev and this PR.
  • On dev with python -O, DataFrame.from_bytes() on a 300-byte payload returns a 256-byte ezsp_frame (silent truncation). On this PR it raises ParsingError.

Comment thread tests/test_ash.py
seq_len = len(ash.PSEUDO_RANDOM_DATA_SEQUENCE)

# A payload of exactly the sequence length still parses.
ash.DataFrame.from_bytes(ash.AshFrame.append_crc(bytes([0x00]) + bytes(seq_len)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: this line only checks that no exception is raised. Asserting on the result would also pin that the derandomized payload isn't truncated at the boundary, e.g. assert len(ash.DataFrame.from_bytes(...).ezsp_frame) == seq_len.

@puddly

puddly commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

I don't think this is actually reachable in practice:

#define EZSP_MAX_FRAME_LENGTH     (218 + 1 + 1)
...
#define ASH_MAX_DATA_FIELD_LEN    EZSP_MAX_FRAME_LENGTH                                              ///< ash max data field len
...
#define ASH_MAX_FRAME_LEN         (ASH_MAX_DATA_FIELD_LEN + 1)                                       ///< ash max frame len

That being said, it doesn't hurt.

@TheJulianJES TheJulianJES changed the title fix(ash): raise ParsingError instead of asserting on oversized frame … Raise ParsingError instead of asserting on oversized frame payload Oct 4, 2026
…payload

DataFrame.from_bytes() derandomizes the payload via _randomize(), which
asserted len(data) <= len(PSEUDO_RANDOM_DATA_SEQUENCE) (256 bytes). A CRC-valid
frame with a payload longer than 256 bytes therefore escaped with an uncaught
AssertionError instead of ParsingError, and under `python -O` (assertions
stripped) the payload was silently truncated to 256 bytes. Raise ParsingError
on oversized payloads so malformed input is rejected consistently.

Fixes zigpy#762

Co-Authored-By: hw1186 <ro49165703@gmail.com>
@I-mho
I-mho force-pushed the fix/ash-oversized-payload branch from f6dcf8e to 3dc3976 Compare October 5, 2026 08:37
@zigpy-review-bot zigpy-review-bot added the bugfix This PR fixes a bug label Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix This PR fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ash.DataFrame.from_bytes raises uncaught AssertionError on oversized payload

3 participants