From 0415f01bcf5994d6a4a3887b2d2fc39b2a1d9426 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Fri, 2 Oct 2026 14:40:30 +0900 Subject: [PATCH] Bound the number of compression pointers per name Commit 4c2f71b (CVE-2025-24294) capped a decompressed name at 255 octets, bounding its length but not the work: a chain of compression pointers adds no label octets, so the cap never fires while the decoder follows every pointer, and many records sharing one chain make decoding quadratic. Cap the pointers followed per name at 128. Reported by: whiteshadow_8500 (https://hackerone.com/whiteshadow_8500) Co-Authored-By: Claude Opus 4.8 --- lib/resolv.rb | 5 +++++ test/resolv/test_dns.rb | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/resolv.rb b/lib/resolv.rb index 36e2d28..717ee64 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -1845,6 +1845,9 @@ def get_labels # size counts the encoded form, so it starts at 1 for the root # label's terminating zero octet. [RFC 1035 3.1] size = 1 + # A pointer chain decodes to few or no labels, so the 255-octet cap + # never bounds its work; cap the pointers followed per name too. + pointers = 0 while true raise DecodeError.new("limit exceeded") if @limit <= @index case @data.getbyte(@index) @@ -1855,6 +1858,8 @@ def get_labels end return d when 192..255 + pointers += 1 + raise DecodeError.new("too many compression pointers") if pointers > 128 idx = self.get_unpack('n')[0] & 0x3fff if prev_index <= idx raise DecodeError.new("non-backward name pointer") diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb index 52f54b9..b8ecef2 100644 --- a/test/resolv/test_dns.rb +++ b/test/resolv/test_dns.rb @@ -670,6 +670,22 @@ def test_too_long_address end end + # A pointer chain hidden in a carrier RR's opaque RDATA, with a second RR's + # name pointing at its top, decodes to no labels but follows many pointers. + def test_too_many_compression_pointers + hops = 130 + header = [0, 0, 0, 2, 0, 0].pack("n6") + carrier = "\x00" + [60000, 1, 0].pack("nnN") + chain = +"\x00"; prev = 23 + hops.times { |j| chain << [0xC000 | prev].pack("n"); prev = 24 + 2 * j } + carrier << [chain.bytesize].pack("n") << chain + top = 24 + 2 * (hops - 1) + victim = [0xC000 | top].pack("n") + [60000, 1, 0, 0].pack("nnNn") + assert_raise_with_message(Resolv::DNS::DecodeError, /too many compression pointers/) do + Resolv::DNS::Message.decode(header + carrier + victim) + end + end + # A DNS label is limited to 63 octets. [RFC 1035 2.3.4] Writing a longer label # through the label path must raise instead of overflowing the length octet. def test_put_label_rejects_label_over_63_octets