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