Skip to content

dns: fix EDNS0ClientSubnet address truncation for non-octet-aligned prefix lengths#4960

Open
T3pp31 wants to merge 2 commits intosecdev:masterfrom
T3pp31:fix/edns0-client-subnet-pack-subnet-plen
Open

dns: fix EDNS0ClientSubnet address truncation for non-octet-aligned prefix lengths#4960
T3pp31 wants to merge 2 commits intosecdev:masterfrom
T3pp31:fix/edns0-client-subnet-pack-subnet-plen

Conversation

@T3pp31
Copy link
Copy Markdown
Contributor

@T3pp31 T3pp31 commented Apr 7, 2026

Summary

Fixes #4942.

EDNS0ClientSubnet._pack_subnet had two related bugs when source_plen is set explicitly:

  1. Wrong byte count — it stripped trailing zero bytes rather than using ceil(source_plen / 8), so source_plen=23 on 101.132.0.0 produced a 2-byte address instead of 3.
  2. Host bits not zeroed — even after the byte count is correct, the final partial byte must have its host bits cleared per RFC 7871. For example source_plen=23 on 101.132.255.0 must produce 0xfe as the third byte, not 0xff.

Per RFC 7871 §6:

ADDRESS … MUST be truncated to the number of bits indicated by the SOURCE PREFIX-LENGTH field, padded with 0 bits to pad to the end of the last octet needed.

Changes

  • _pack_subnet now accepts an optional plen parameter. When provided it truncates to exactly ceil(plen / 8) bytes and masks off host bits in the last partial byte when plen % 8 != 0.
  • i2m and i2len pass pkt.source_plen so explicit prefix lengths are respected during packet building.
  • When plen is None (auto-detect mode) the original trailing-zero-stripping behaviour is preserved.

Reproduction

from scapy.layers.dns import EDNS0ClientSubnet

# Bug 1: wrong byte count
b = EDNS0ClientSubnet(source_plen=23, address='101.132.0.0')
# Before: 00080006000117006584    (2-byte address, optlen=6)
# After:  000800070001170065840 0  (3-byte address, optlen=7)

# Bug 2: host bits not zeroed
b = EDNS0ClientSubnet(source_plen=23, address='101.132.255.0')
# Before: 000800070001170065 84ff  (0xff — host bit set)
# After:  000800070001170065 84fe  (0xfe — host bit cleared)

Tests

Five regression tests added to test/scapy/layers/dns_edns0.uts:

  • source_plen=23 with trailing-zero last byte (original report)
  • source_plen=24 octet-aligned sanity check
  • source_plen=0 produces empty address field
  • source_plen=23 with host bits set in last byte (IPv4)
  • source_plen=33 with host bits set in last byte (IPv6)

…refix lengths

When source_plen is set to a value whose ceil(plen/8) byte count exceeds
the number of non-zero bytes in the address (e.g. source_plen=23 for
101.132.0.0), _pack_subnet incorrectly stripped the trailing zero byte,
producing a 2-byte address instead of the required 3 bytes.

Per RFC 7871, the ADDRESS field MUST be truncated to ceil(source_plen/8)
bytes. Fix _pack_subnet to accept an optional plen parameter and apply
RFC-compliant truncation when it is provided. i2m and i2len now pass
pkt.source_plen so the correct byte count is used during packet building.

Fixes: secdev#4942
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

Codecov Report

❌ Patch coverage is 92.85714% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 80.30%. Comparing base (9ae49f8) to head (a7076c7).

Files with missing lines Patch % Lines
scapy/layers/dns.py 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4960      +/-   ##
==========================================
- Coverage   80.30%   80.30%   -0.01%     
==========================================
  Files         379      379              
  Lines       93164    93173       +9     
==========================================
- Hits        74820    74818       -2     
- Misses      18344    18355      +11     
Files with missing lines Coverage Δ
scapy/layers/dns.py 83.79% <92.85%> (+0.17%) ⬆️

... and 10 files with indirect coverage changes

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

RFC 7871 requires that the ADDRESS field be padded with 0 bits to the
end of the last octet.  The previous fix correctly computed the number
of bytes (ceil(plen/8)) but left host bits set in the final partial byte
when plen is not a multiple of 8.

For example, source_plen=23 on 101.132.255.0 should produce 0xfe as
the third byte, not 0xff.

Apply a bitmask to the last byte when plen % 8 != 0, and add regression
tests for IPv4 and IPv6 cases where the input address has host bits set.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

_pack_subnet method will lose 1 byte when converting 101.132.0.0/23 to bytes

1 participant