diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 865d6c7..cdd3c49 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,7 @@ ## Unreleased +* Enforce the 255-octet name limit (RFC 1035 Section 2.3.4, RFC 2181 Section 11) against the wire length rather than the dotted presentation length: names of 256 and 257 wire octets were accepted and encoded into messages, and now raise ResolvError * Add support for SVCB (type 64) and HTTPS (type 65) resource records - RFC 9460, including zone file parsing with TargetName origin resolution * Recognises the registered SvcParamKey mnemonics `mandatory`, `alpn`, `no-default-alpn`, `port`, `ipv4hint`, `ipv6hint` (RFC 9460), `ech` (RFC 9848), `dohpath` (RFC 9461), `ohttp` (RFC 9540) and `docpath` (RFC 9953). Any other key reads and writes as `keyNNNNN` * **Breaking (text output only)** - `Dnsruby::IPv6#to_s` and `#inspect` now emit the RFC 5952 canonical form: lowercase hex, no leading zeros, and `::` for the longest run of all-zero fields (never a single field) diff --git a/lib/dnsruby/name.rb b/lib/dnsruby/name.rb index e059467..7101140 100644 --- a/lib/dnsruby/name.rb +++ b/lib/dnsruby/name.rb @@ -93,7 +93,10 @@ def self.split(name) # This method should only be called internally. # Use Name::create to create a new Name def initialize(labels, absolute=true) #:nodoc: all - total_length=labels.length-1 + # The 255-octet limit (RFC 1035 section 2.3.4, RFC 2181 section 11) is + # on the wire form, so count each label's length octet, plus the root's + # zero octet. + total_length=labels.length+1 labels.each do |l| if (!l.kind_of?Label) raise ArgumentError.new("Name::new called with non-labels. Use Name::create instead?") diff --git a/test/tc_name.rb b/test/tc_name.rb index 9470020..2dbb31a 100644 --- a/test/tc_name.rb +++ b/test/tc_name.rb @@ -20,6 +20,11 @@ class TestName < Minitest::Test include Dnsruby + # Names on either side of the 255-octet wire limit: 249 octets of label text + # in 5 labels, plus a length octet each and the root's zero octet, is 255. + AT_LIMIT_NAME = "#{'a' * 63}.#{'b' * 63}.#{'c' * 63}.#{'d' * 59}.e." + OVER_LIMIT_NAME = "#{'a' * 63}.#{'b' * 63}.#{'c' * 63}.#{'d' * 60}.e." + def test_label_length Name::Label.set_max_length(Name::Label::MaxLabelLength) # Other tests may have changed this # Test max label length = 63 @@ -30,13 +35,29 @@ def test_label_length end end + def wire_length(name) + MessageEncoder.new { |msg| msg.put_name(name, true) }.to_s.length + end + + # Test max name length=255. The limit is on the wire form, two octets more + # than the dotted presentation form without its trailing dot. def test_name_length - # Test max name length=255 - begin - Name.create("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123.com") - assert(false, "Name of length > 255 allowed") - rescue ResolvError - end + assert_equal(255, wire_length(Name.create(AT_LIMIT_NAME))) + + error = assert_raises(ResolvError) { Name.create(OVER_LIMIT_NAME) } + assert_match(/Name length is 256,/, error.message) + end + + # An oversized name must not reach the wire through Message either, and a + # name right at the limit must still survive the round trip. + def test_name_length_in_message + assert_raises(ResolvError) { Message.new(OVER_LIMIT_NAME, "A") } + + message = Message.new(AT_LIMIT_NAME, "A") + message.add_answer(RR.create("#{AT_LIMIT_NAME} 3600 IN NS #{AT_LIMIT_NAME}")) + decoded = Message.decode(message.encode) + assert_equal(AT_LIMIT_NAME, decoded.question[0].qname.to_s + ".") + assert_equal(AT_LIMIT_NAME, decoded.answer[0].domainname.to_s + ".") end def test_absolute