resolv 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d76dc1156c60da694ce1f0a96165e6d5a7e29d662ad7140b443e9aa735082f31
4
- data.tar.gz: 7b532fc449aff2e5a63cb6a31892a45da5b41b6090d454f94d3c3e5165c94271
3
+ metadata.gz: 82cf1416dbd548bac4a243078cb8b93a0990f906169eff49752840d3145d0a0a
4
+ data.tar.gz: baf98405285dd3a553873a2f10e20d188e1e73cf066bf5f430b8d206c553978f
5
5
  SHA512:
6
- metadata.gz: 685ce620cc9c83f1d5827de0a374d71f98fbff7629256d2ab7ed65d66eee062a5bc081a869caba31aeb67b4c292ca6dfc53fa4d71180ce15dd82f39d6fb5992b
7
- data.tar.gz: 9897929fbfc67bc3a8133d3d9b2f99e7c9350802cf86d4cc0d8f061f397393f91308d6d1d6d35e46d497a3beb7fcf6424dc47ae92d5f7f39e572ac04b2677409
6
+ metadata.gz: 646bccbb40a992224d079ddd8a586a231a69f67f628d424f79e1079dce4fcc346254ec0288d874ec356e199cfffb5c694b0cea8b51ed2435299a72ec29d88759
7
+ data.tar.gz: 28f34daddd2b5e3262e1b6b4381933c40683c8938129efad0914d495ae522f0c0b53bcbd6108073c1b3b1b4beb7f67d5e8af4b2af17387812c7275bed11c696d
data/lib/resolv.rb CHANGED
@@ -37,7 +37,7 @@ end
37
37
 
38
38
  class Resolv
39
39
 
40
- VERSION = "0.3.0"
40
+ VERSION = "0.3.2"
41
41
 
42
42
  ##
43
43
  # Looks up the first IP address for +name+.
@@ -1219,6 +1219,13 @@ class Resolv
1219
1219
 
1220
1220
  class Str # :nodoc:
1221
1221
  def initialize(string)
1222
+ # A label is limited to 63 octets. [RFC 1035 2.3.4] Checking it here
1223
+ # makes it an invariant of the object: every label, however it was
1224
+ # built, fits in its length octet and cannot wrap it. Callers turn
1225
+ # this into the error their own contract promises.
1226
+ if string.bytesize > 63
1227
+ raise ArgumentError, "DNS label is too long (#{string.bytesize} bytes, max 63): #{string.inspect}"
1228
+ end
1222
1229
  @string = string
1223
1230
  # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
1224
1231
  # This assumes @string is given in ASCII compatible encoding.
@@ -1264,7 +1271,26 @@ class Resolv
1264
1271
  when Name
1265
1272
  return arg
1266
1273
  when String
1267
- return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
1274
+ # A hostname is runtime data rather than a programming mistake, so
1275
+ # both size limits surface as ResolvError to stay rescuable alongside
1276
+ # the rest of name resolution. The type check below is a caller
1277
+ # mistake and keeps raising ArgumentError.
1278
+ begin
1279
+ labels = Label.split(arg)
1280
+ rescue ArgumentError => e
1281
+ raise ResolvError.new(e.message)
1282
+ end
1283
+ # Label::Str enforces the per-label limit. Only the total is knowable
1284
+ # here, and it counts the encoded form, so size starts at 1 for the
1285
+ # root label's terminating zero octet. [RFC 1035 2.3.4, 3.1]
1286
+ size = 1
1287
+ labels.each do |label|
1288
+ size += 1 + label.string.bytesize
1289
+ if size > 255
1290
+ raise ResolvError.new("DNS name is too long (#{size} octets, max 255): #{arg.inspect}")
1291
+ end
1292
+ end
1293
+ return Name.new(labels, /\.\z/ =~ arg ? true : false)
1268
1294
  else
1269
1295
  raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
1270
1296
  end
@@ -1386,12 +1412,24 @@ class Resolv
1386
1412
  @rd == other.rd &&
1387
1413
  @ra == other.ra &&
1388
1414
  @rcode == other.rcode &&
1389
- @question == other.question &&
1415
+ question_equal?(other.question) &&
1390
1416
  @answer == other.answer &&
1391
1417
  @authority == other.authority &&
1392
1418
  @additional == other.additional
1393
1419
  end
1394
1420
 
1421
+ # A question holds the resource class itself, and decoding creates a fresh
1422
+ # class for each unknown type, so the classes cannot be compared by
1423
+ # identity alone.
1424
+ private def question_equal?(other_question) # :nodoc:
1425
+ return false unless @question.length == other_question.length
1426
+ @question.zip(other_question) {|(name, typeclass), (o_name, o_typeclass)|
1427
+ return false unless name == o_name &&
1428
+ Resource::Generic.type_class_equal?(typeclass, o_typeclass)
1429
+ }
1430
+ return true
1431
+ end
1432
+
1395
1433
  def add_question(name, typeclass)
1396
1434
  @question << [Name.create(name), typeclass]
1397
1435
  end
@@ -1498,8 +1536,15 @@ class Resolv
1498
1536
  end
1499
1537
 
1500
1538
  def put_string(d)
1501
- self.put_pack("C", d.length)
1502
- @data << d
1539
+ s = d.to_s
1540
+ # A character-string is prefixed by a single length octet, so it can
1541
+ # hold at most 255 octets. [RFC 1035 3.3] Reject anything longer to
1542
+ # avoid silently truncating the length to its low 8 bits (mod 256).
1543
+ if s.bytesize > 255
1544
+ raise ArgumentError, "character-string is too long (#{s.bytesize} bytes, max 255): #{s.inspect}"
1545
+ end
1546
+ self.put_pack("C", s.bytesize)
1547
+ @data << s
1503
1548
  end
1504
1549
 
1505
1550
  def put_string_list(ds)
@@ -1529,7 +1574,17 @@ class Resolv
1529
1574
  end
1530
1575
 
1531
1576
  def put_label(d)
1532
- self.put_string(d.to_s)
1577
+ s = d.to_s
1578
+ # Label::Str applies this limit when a label is built, so what is left
1579
+ # for here is a raw string handed straight to put_labels. The two ways
1580
+ # an over-long label goes wrong differ: 64 to 255 octets write a length
1581
+ # octet in the reserved or compression pointer range, and 256 or more
1582
+ # wrap it mod 256. Either way the encoded name stops being the name the
1583
+ # caller asked for. [RFC 1035 2.3.4, 4.1.4]
1584
+ if s.bytesize > 63
1585
+ raise ArgumentError, "DNS label is too long (#{s.bytesize} bytes, max 63): #{s.inspect}"
1586
+ end
1587
+ self.put_string(s)
1533
1588
  end
1534
1589
  end
1535
1590
 
@@ -1655,6 +1710,9 @@ class Resolv
1655
1710
  prev_index = @index
1656
1711
  save_index = nil
1657
1712
  d = []
1713
+ # size counts the encoded form, so it starts at 1 for the root
1714
+ # label's terminating zero octet. [RFC 1035 3.1]
1715
+ size = 1
1658
1716
  while true
1659
1717
  raise DecodeError.new("limit exceeded") if @limit <= @index
1660
1718
  case @data.getbyte(@index)
@@ -1675,13 +1733,21 @@ class Resolv
1675
1733
  end
1676
1734
  @index = idx
1677
1735
  else
1678
- d << self.get_label
1736
+ l = self.get_label
1737
+ d << l
1738
+ size += 1 + l.string.bytesize
1739
+ raise DecodeError.new("name label data exceed 255 octets") if size > 255
1679
1740
  end
1680
1741
  end
1681
1742
  end
1682
1743
 
1683
1744
  def get_label
1684
1745
  return Label::Str.new(self.get_string)
1746
+ rescue ArgumentError => e
1747
+ # A length octet of 64..191 is reserved rather than a label length,
1748
+ # but this decoder used to read it as one. [RFC 1035 4.1.4] Report it
1749
+ # the way the rest of a malformed message is reported.
1750
+ raise DecodeError.new(e.message)
1685
1751
  end
1686
1752
 
1687
1753
  def get_question
@@ -1870,8 +1936,9 @@ class Resolv
1870
1936
  key_name = :"key#{key_number}"
1871
1937
  c.const_set(:KeyName, key_name)
1872
1938
  c.const_set(:KeyNumber, key_number)
1873
- self.const_set(:"Key#{key_number}", c)
1874
- ClassHash[key_name] = ClassHash[key_number] = c
1939
+ # Not registered in a constant or in ClassHash. ClassHash creates a
1940
+ # class for every unknown SvcParamKey, so registering them
1941
+ # permanently would let a malicious response exhaust memory.
1875
1942
  return c
1876
1943
  end
1877
1944
  end
@@ -2169,12 +2236,28 @@ class Resolv
2169
2236
  return self.new(msg.get_bytes)
2170
2237
  end
2171
2238
 
2239
+ # create makes a fresh class for each decoded resource, so the type and
2240
+ # class values have to be compared instead of the class itself.
2241
+ def self.type_class_equal?(klass, other) # :nodoc:
2242
+ return true if klass.equal?(other)
2243
+ Generic > klass && Generic > other &&
2244
+ klass::TypeValue == other::TypeValue &&
2245
+ klass::ClassValue == other::ClassValue
2246
+ end
2247
+
2248
+ def ==(other) # :nodoc:
2249
+ return other.is_a?(Generic) &&
2250
+ Generic.type_class_equal?(self.class, other.class) &&
2251
+ @data == other.data
2252
+ end
2253
+
2172
2254
  def self.create(type_value, class_value) # :nodoc:
2173
2255
  c = Class.new(Generic)
2174
2256
  c.const_set(:TypeValue, type_value)
2175
2257
  c.const_set(:ClassValue, class_value)
2176
- Generic.const_set("Type#{type_value}_Class#{class_value}", c)
2177
- ClassHash[[type_value, class_value]] = c
2258
+ # Not registered in a constant or in ClassHash. get_class creates a
2259
+ # class for every unknown (type, class) pair, so registering them
2260
+ # permanently would let a malicious response exhaust memory.
2178
2261
  return c
2179
2262
  end
2180
2263
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resolv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tanaka Akira
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2023-12-13 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: Thread-aware DNS resolver library in Ruby.
14
13
  email:
@@ -17,17 +16,9 @@ executables: []
17
16
  extensions: []
18
17
  extra_rdoc_files: []
19
18
  files:
20
- - ".github/dependabot.yml"
21
- - ".github/workflows/test.yml"
22
- - ".gitignore"
23
- - Gemfile
24
19
  - LICENSE.txt
25
20
  - README.md
26
- - Rakefile
27
- - bin/console
28
- - bin/setup
29
21
  - lib/resolv.rb
30
- - resolv.gemspec
31
22
  homepage: https://github.com/ruby/resolv
32
23
  licenses:
33
24
  - Ruby
@@ -35,7 +26,6 @@ licenses:
35
26
  metadata:
36
27
  homepage_uri: https://github.com/ruby/resolv
37
28
  source_code_uri: https://github.com/ruby/resolv
38
- post_install_message:
39
29
  rdoc_options: []
40
30
  require_paths:
41
31
  - lib
@@ -50,8 +40,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
50
40
  - !ruby/object:Gem::Version
51
41
  version: '0'
52
42
  requirements: []
53
- rubygems_version: 3.5.0.dev
54
- signing_key:
43
+ rubygems_version: 3.6.9
55
44
  specification_version: 4
56
45
  summary: Thread-aware DNS resolver library in Ruby.
57
46
  test_files: []
@@ -1,6 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: 'github-actions'
4
- directory: '/'
5
- schedule:
6
- interval: 'weekly'
@@ -1,28 +0,0 @@
1
- name: test
2
-
3
- on: [push, pull_request]
4
-
5
- jobs:
6
- ruby-versions:
7
- uses: ruby/actions/.github/workflows/ruby_versions.yml@master
8
- with:
9
- engine: cruby
10
- min_version: 2.5
11
-
12
- build:
13
- needs: ruby-versions
14
- name: build (${{ matrix.ruby }} / ${{ matrix.os }})
15
- strategy:
16
- matrix:
17
- ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
18
- os: [ ubuntu-latest, macos-latest ]
19
- runs-on: ${{ matrix.os }}
20
- steps:
21
- - uses: actions/checkout@v4
22
- - name: Set up Ruby
23
- uses: ruby/setup-ruby@v1
24
- with:
25
- ruby-version: ${{ matrix.ruby }}
26
- bundler-cache: true
27
- - name: Run test
28
- run: bundle exec rake test
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- Gemfile.lock
data/Gemfile DELETED
@@ -1,7 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gemspec
4
-
5
- gem "rake"
6
- gem "test-unit"
7
- gem "test-unit-ruby-core"
data/Rakefile DELETED
@@ -1,10 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
3
-
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test/lib"
6
- t.ruby_opts << "-rhelper"
7
- t.test_files = FileList["test/**/test_*.rb"]
8
- end
9
-
10
- task :default => :test
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "resolv"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/resolv.gemspec DELETED
@@ -1,29 +0,0 @@
1
- name = File.basename(__FILE__, ".gemspec")
2
- version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir|
3
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
4
- /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
5
- end rescue nil
6
- end
7
-
8
- Gem::Specification.new do |spec|
9
- spec.name = name
10
- spec.version = version
11
- spec.authors = ["Tanaka Akira"]
12
- spec.email = ["akr@fsij.org"]
13
-
14
- spec.summary = %q{Thread-aware DNS resolver library in Ruby.}
15
- spec.description = %q{Thread-aware DNS resolver library in Ruby.}
16
- spec.homepage = "https://github.com/ruby/resolv"
17
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
18
- spec.licenses = ["Ruby", "BSD-2-Clause"]
19
-
20
- spec.metadata["homepage_uri"] = spec.homepage
21
- spec.metadata["source_code_uri"] = spec.homepage
22
-
23
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
- end
26
- spec.bindir = "exe"
27
- spec.executables = []
28
- spec.require_paths = ["lib"]
29
- end