fix: bound NSEC bitmap length against record end#1731
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1731 +/- ##
==========================================
- Coverage 99.76% 99.70% -0.06%
==========================================
Files 33 33
Lines 3440 3444 +4
Branches 473 475 +2
==========================================
+ Hits 3432 3434 +2
- Misses 5 6 +1
- Partials 3 4 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Fixes a packet-parsing integrity issue in DNSIncoming._read_bitmap for NSEC records by validating bitmap window boundaries against the record’s declared RDATA end, preventing offset corruption that could break parsing of subsequent records and pollute cached NSEC “absent type” bits.
Changes:
- Add strict bounds checks in
_read_bitmap(window header fits, bitmap length is 1..32, bitmap does not overrun the record end) and raiseIncomingDecodeErroron violations so_read_othersskips the malformed record safely. - Update the Cython
.pxdlocals to keepbitmap_endunsigned for correct comparisons in compiled builds. - Add regression tests ensuring malformed NSEC bitmaps are rejected while subsequent records in the same packet still parse.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/zeroconf/_protocol/incoming.py |
Adds record-end bounds validation for NSEC bitmap parsing and raises IncomingDecodeError to prevent offset corruption. |
src/zeroconf/_protocol/incoming.pxd |
Declares bitmap_end as unsigned int for Cython-compiled parity and correct unsigned comparisons. |
tests/test_protocol.py |
Adds regression tests for NSEC bitmap overrun and zero-length window handling, ensuring later records still parse. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
coverage is incomplete |
PR Review — fix: bound NSEC bitmap length against record endThe fix is correct and well-scoped: the three guards (window header fits, bitmap_length in 1..32 per RFC 4034 §4.1.2, bitmap end within record) together prevent the offset corruption described, and routing through 🟡 Important1. Two of the three new guard branches are untested (`src/zeroconf/_protocol/incoming.py`, L396-407)Codecov flags this patch at 50% (1 missing + 1 partial), and the gap maps to two specific branches that the new tests don't reach:
Suggested additions:
Splitting these out also means each guard fails for one specific reason in its error message, which is easier to triage from a Checklist
SummaryThe fix is correct and well-scoped: the three guards (window header fits, bitmap_length in 1..32 per RFC 4034 §4.1.2, bitmap end within record) together prevent the offset corruption described, and routing through Automated review by Kōan505b2c2 |
Confirmed — Codecov's 50% /
Adding those two cases gets the patch back to 100% and also gives each failure mode a distinct error message in the logs. |
A malicious NSEC bitmap_length that overruns the declared record end left self.offset pointing past the next record's header, corrupting every subsequent record in the same packet. The forgiving Python slice also let the parser synthesize attacker-controlled rdtypes from adjacent bytes and cache them on the resulting DNSNsec. Validate the window header is in-record, reject bitmap_length 0 or > 32 (RFC 4034 §4.1.2), and raise IncomingDecodeError so _read_others resets the offset to the NSEC's declared end.
Rebase with requested adjustmentsBranch StatsActions performed
CI statusCI will be checked asynchronously. Automated by Kōan |
505b2c2 to
495acf5
Compare
Summary
_read_bitmapread an attacker-controlledbitmap_lengthbyte and unconditionally advancedself.offsetby2 + bitmap_length, with no check against the NSEC record's declared end. Because Python slicing tolerates overshoot, the bitmap silently consumed adjacent bytes (the next record's header) as bitmap data and leftself.offsetpastend._read_othersonly resets the offset on exception, so a malformed-but-non-throwing NSEC corrupted the parse for every subsequent record in the same packet and let a LAN peer pollute the NSEC cache with attacker-chosen "absent" type bits — enough to talk anAddressResolverout of issuing follow-up queries.Fixes #1725
Changes
bitmap_lengthof 0 or > 32 (RFC 4034 §4.1.2 bounds the per-window bitmap to 1..32 bytes).bitmap_end > endso a window cannot reach past the NSEC record's declared length.IncomingDecodeErrorso_read_othersresetsself.offsettoendand the next record parses normally.bitmap_endasunsigned intin the.pxdso the Cython compare againstendstays unsigned end-to-end.Test plan
test_nsec_bitmap_length_overruns_record_end— crafts an NSEC withbitmap_length=255and a trailing PTR; the PTR must still parse and the NSEC must be skipped. Fails onmaster, passes with the fix.test_nsec_bitmap_zero_length_window_rejected— zero-length window block rejected per RFC 4034 §4.1.2.test_parse_packet_with_nsec_record,test_dns_compression_invalid_skips_record,test_dns_compression_points_forward, andtest_parse_own_packet_nsecstill pass — valid bitmaps unaffected.SKIP_CYTHON=1 poetry run pytest tests/test_protocol.py tests/test_handlers.py tests/test_services.py→ 84 passed.Quality Report
Changes: 3 files changed, 69 insertions(+)
Code scan: clean
Tests: passed (4 PASSED)
Branch hygiene: clean
Generated by Kōan post-mission quality pipeline