mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* lib/resolv.rb: randomize source port and transaction id.
CVE-2008-1447. * lib/resolv-replace.rb (UDPSocket#bind): don't resolv host if host is "". git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@18424 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
25efcaf400
commit
2577fa6dcc
3 changed files with 172 additions and 122 deletions
|
@ -1,3 +1,11 @@
|
|||
Fri Aug 8 10:53:52 2008 Tanaka Akira <akr@fsij.org>
|
||||
|
||||
* lib/resolv.rb: randomize source port and transaction id.
|
||||
CVE-2008-1447.
|
||||
|
||||
* lib/resolv-replace.rb (UDPSocket#bind): don't resolv host if host is
|
||||
"".
|
||||
|
||||
Fri Aug 8 04:20:14 2008 NARUSE, Yui <naruse@ruby-lang.org>
|
||||
|
||||
* common.mk: see both $(srcdir)/enc/trans and enc/trans.
|
||||
|
|
|
@ -23,7 +23,8 @@ end
|
|||
class UDPSocket
|
||||
alias original_resolv_bind bind
|
||||
def bind(host, port)
|
||||
original_resolv_bind(IPSocket.getaddress(host), port)
|
||||
host = IPSocket.getaddress(host) if host != ""
|
||||
original_resolv_bind(host, port)
|
||||
end
|
||||
|
||||
alias original_resolv_connect connect
|
||||
|
|
281
lib/resolv.rb
281
lib/resolv.rb
|
@ -3,6 +3,11 @@ require 'fcntl'
|
|||
require 'timeout'
|
||||
require 'thread'
|
||||
|
||||
begin
|
||||
require 'securerandom'
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
# Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can
|
||||
# handle multiple DNS requests concurrently without blocking. The ruby
|
||||
# interpreter.
|
||||
|
@ -284,11 +289,6 @@ class Resolv
|
|||
|
||||
UDPSize = 512
|
||||
|
||||
##
|
||||
# Group of DNS resolver threads
|
||||
|
||||
DNSThreadGroup = ThreadGroup.new
|
||||
|
||||
##
|
||||
# Creates a new DNS resolver. See Resolv::DNS.new for argument details.
|
||||
#
|
||||
|
@ -330,13 +330,6 @@ class Resolv
|
|||
@mutex.synchronize {
|
||||
unless @initialized
|
||||
@config.lazy_initialize
|
||||
|
||||
if nameserver = @config.single?
|
||||
@requester = Requester::ConnectedUDP.new(nameserver)
|
||||
else
|
||||
@requester = Requester::UnconnectedUDP.new
|
||||
end
|
||||
|
||||
@initialized = true
|
||||
end
|
||||
}
|
||||
|
@ -349,8 +342,6 @@ class Resolv
|
|||
def close
|
||||
@mutex.synchronize {
|
||||
if @initialized
|
||||
@requester.close if @requester
|
||||
@requester = nil
|
||||
@initialized = false
|
||||
end
|
||||
}
|
||||
|
@ -479,7 +470,7 @@ class Resolv
|
|||
|
||||
def each_resource(name, typeclass, &proc)
|
||||
lazy_initialize
|
||||
q = Queue.new
|
||||
requester = make_requester
|
||||
senders = {}
|
||||
begin
|
||||
@config.resolv(name) {|candidate, tout, nameserver|
|
||||
|
@ -488,11 +479,9 @@ class Resolv
|
|||
msg.add_question(candidate, typeclass)
|
||||
unless sender = senders[[candidate, nameserver]]
|
||||
sender = senders[[candidate, nameserver]] =
|
||||
@requester.sender(msg, candidate, q, nameserver)
|
||||
requester.sender(msg, candidate, nameserver)
|
||||
end
|
||||
sender.send
|
||||
reply = reply_name = nil
|
||||
timeout(tout, ResolvTimeout) { reply, reply_name = q.pop }
|
||||
reply, reply_name = requester.request(sender, tout)
|
||||
case reply.rcode
|
||||
when RCode::NoError
|
||||
extract_resources(reply, reply_name, typeclass, &proc)
|
||||
|
@ -504,7 +493,15 @@ class Resolv
|
|||
end
|
||||
}
|
||||
ensure
|
||||
@requester.delete(q)
|
||||
requester.close
|
||||
end
|
||||
end
|
||||
|
||||
def make_requester # :nodoc:
|
||||
if nameserver = @config.single?
|
||||
Requester::ConnectedUDP.new(nameserver)
|
||||
else
|
||||
Requester::UnconnectedUDP.new
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -539,45 +536,106 @@ class Resolv
|
|||
}
|
||||
end
|
||||
|
||||
if defined? SecureRandom
|
||||
def self.random(arg) # :nodoc:
|
||||
begin
|
||||
SecureRandom.random_number(arg)
|
||||
rescue NotImplementedError
|
||||
rand(arg)
|
||||
end
|
||||
end
|
||||
else
|
||||
def self.random(arg) # :nodoc:
|
||||
rand(arg)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def self.rangerand(range) # :nodoc:
|
||||
base = range.begin
|
||||
len = range.end - range.begin
|
||||
if !range.exclude_end?
|
||||
len += 1
|
||||
end
|
||||
base + random(len)
|
||||
end
|
||||
|
||||
RequestID = {}
|
||||
RequestIDMutex = Mutex.new
|
||||
|
||||
def self.allocate_request_id(host, port) # :nodoc:
|
||||
id = nil
|
||||
RequestIDMutex.synchronize {
|
||||
h = (RequestID[[host, port]] ||= {})
|
||||
begin
|
||||
id = rangerand(0x0000..0xffff)
|
||||
end while h[id]
|
||||
h[id] = true
|
||||
}
|
||||
id
|
||||
end
|
||||
|
||||
def self.free_request_id(host, port, id) # :nodoc:
|
||||
RequestIDMutex.synchronize {
|
||||
key = [host, port]
|
||||
if h = RequestID[key]
|
||||
h.delete id
|
||||
if h.empty?
|
||||
RequestID.delete key
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def self.bind_random_port(udpsock) # :nodoc:
|
||||
begin
|
||||
port = rangerand(1024..65535)
|
||||
udpsock.bind("", port)
|
||||
rescue Errno::EADDRINUSE
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
class Requester # :nodoc:
|
||||
def initialize
|
||||
@senders = {}
|
||||
@sock = nil
|
||||
end
|
||||
|
||||
def request(sender, tout)
|
||||
timelimit = Time.now + tout
|
||||
sender.send
|
||||
while (now = Time.now) < timelimit
|
||||
timeout = timelimit - now
|
||||
if !IO.select([@sock], nil, nil, timeout)
|
||||
raise ResolvTimeout
|
||||
end
|
||||
reply, from = recv_reply
|
||||
begin
|
||||
msg = Message.decode(reply)
|
||||
rescue DecodeError
|
||||
next # broken DNS message ignored
|
||||
end
|
||||
if s = @senders[[from,msg.id]]
|
||||
break
|
||||
else
|
||||
# unexpected DNS message ignored
|
||||
end
|
||||
end
|
||||
return msg, s.data
|
||||
end
|
||||
|
||||
def close
|
||||
thread, sock, @thread, @sock = @thread, @sock
|
||||
begin
|
||||
if thread
|
||||
thread.kill
|
||||
thread.join
|
||||
end
|
||||
ensure
|
||||
sock = @sock
|
||||
@sock = nil
|
||||
sock.close if sock
|
||||
end
|
||||
end
|
||||
|
||||
def delete(arg)
|
||||
case arg
|
||||
when Sender
|
||||
@senders.delete_if {|k, s| s == arg }
|
||||
when Queue
|
||||
@senders.delete_if {|k, s| s.queue == arg }
|
||||
else
|
||||
raise ArgumentError.new("neither Sender or Queue: #{arg}")
|
||||
end
|
||||
end
|
||||
|
||||
class Sender # :nodoc:
|
||||
def initialize(msg, data, sock, queue)
|
||||
def initialize(msg, data, sock)
|
||||
@msg = msg
|
||||
@data = data
|
||||
@sock = sock
|
||||
@queue = queue
|
||||
end
|
||||
attr_reader :queue
|
||||
|
||||
def recv(msg)
|
||||
@queue.push([msg, @data])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -585,45 +643,38 @@ class Resolv
|
|||
def initialize
|
||||
super()
|
||||
@sock = UDPSocket.new
|
||||
@sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
|
||||
@id = {}
|
||||
@id.default = -1
|
||||
@thread = Thread.new {
|
||||
DNSThreadGroup.add Thread.current
|
||||
loop {
|
||||
reply, from = @sock.recvfrom(UDPSize)
|
||||
msg = begin
|
||||
Message.decode(reply)
|
||||
rescue DecodeError
|
||||
STDERR.print("DNS message decoding error: #{reply.inspect}\n")
|
||||
next
|
||||
end
|
||||
if s = @senders[[[from[3],from[1]],msg.id]]
|
||||
s.recv msg
|
||||
else
|
||||
#STDERR.print("non-handled DNS message: #{msg.inspect} from #{from.inspect}\n")
|
||||
end
|
||||
}
|
||||
}
|
||||
@sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD
|
||||
DNS.bind_random_port(@sock)
|
||||
end
|
||||
|
||||
def sender(msg, data, queue, host, port=Port)
|
||||
def recv_reply
|
||||
reply, from = @sock.recvfrom(UDPSize)
|
||||
return reply, [from[3],from[1]]
|
||||
end
|
||||
|
||||
def sender(msg, data, host, port=Port)
|
||||
service = [host, port]
|
||||
id = Thread.exclusive {
|
||||
@id[service] = (@id[service] + 1) & 0xffff
|
||||
}
|
||||
id = DNS.allocate_request_id(host, port)
|
||||
request = msg.encode
|
||||
request[0,2] = [id].pack('n')
|
||||
return @senders[[service, id]] =
|
||||
Sender.new(request, data, @sock, host, port, queue)
|
||||
Sender.new(request, data, @sock, host, port)
|
||||
end
|
||||
|
||||
def close
|
||||
super
|
||||
@senders.each_key {|service, id|
|
||||
DNS.free_request_id(service[0], service[1], id)
|
||||
}
|
||||
end
|
||||
|
||||
class Sender < Requester::Sender # :nodoc:
|
||||
def initialize(msg, data, sock, host, port, queue)
|
||||
super(msg, data, sock, queue)
|
||||
def initialize(msg, data, sock, host, port)
|
||||
super(msg, data, sock)
|
||||
@host = host
|
||||
@port = port
|
||||
end
|
||||
attr_reader :data
|
||||
|
||||
def send
|
||||
@sock.send(@msg, 0, @host, @port)
|
||||
|
@ -637,42 +688,38 @@ class Resolv
|
|||
@host = host
|
||||
@port = port
|
||||
@sock = UDPSocket.new(host.index(':') ? Socket::AF_INET6 : Socket::AF_INET)
|
||||
DNS.bind_random_port(@sock)
|
||||
@sock.connect(host, port)
|
||||
@sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
|
||||
@id = -1
|
||||
@thread = Thread.new {
|
||||
DNSThreadGroup.add Thread.current
|
||||
loop {
|
||||
reply = @sock.recv(UDPSize)
|
||||
msg = begin
|
||||
Message.decode(reply)
|
||||
rescue DecodeError
|
||||
STDERR.print("DNS message decoding error: #{reply.inspect}")
|
||||
next
|
||||
end
|
||||
if s = @senders[msg.id]
|
||||
s.recv msg
|
||||
else
|
||||
#STDERR.print("non-handled DNS message: #{msg.inspect}")
|
||||
end
|
||||
}
|
||||
}
|
||||
@sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD
|
||||
end
|
||||
|
||||
def sender(msg, data, queue, host=@host, port=@port)
|
||||
def recv_reply
|
||||
reply = @sock.recv(UDPSize)
|
||||
return reply, nil
|
||||
end
|
||||
|
||||
def sender(msg, data, host=@host, port=@port)
|
||||
unless host == @host && port == @port
|
||||
raise RequestError.new("host/port don't match: #{host}:#{port}")
|
||||
end
|
||||
id = Thread.exclusive { @id = (@id + 1) & 0xffff }
|
||||
id = DNS.allocate_request_id(@host, @port)
|
||||
request = msg.encode
|
||||
request[0,2] = [id].pack('n')
|
||||
return @senders[id] = Sender.new(request, data, @sock, queue)
|
||||
return @senders[[nil,id]] = Sender.new(request, data, @sock)
|
||||
end
|
||||
|
||||
def close
|
||||
super
|
||||
@senders.each_key {|from, id|
|
||||
DNS.free_request_id(@host, @port, id)
|
||||
}
|
||||
end
|
||||
|
||||
class Sender < Requester::Sender # :nodoc:
|
||||
def send
|
||||
@sock.send(@msg, 0)
|
||||
end
|
||||
attr_reader :data
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -681,39 +728,25 @@ class Resolv
|
|||
super()
|
||||
@host = host
|
||||
@port = port
|
||||
@sock = TCPSocket.new
|
||||
@sock.connect(host, port)
|
||||
@sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
|
||||
@id = -1
|
||||
@sock = TCPSocket.new(@host, @port)
|
||||
@sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD
|
||||
@senders = {}
|
||||
@thread = Thread.new {
|
||||
DNSThreadGroup.add Thread.current
|
||||
loop {
|
||||
len = @sock.read(2).unpack('n')
|
||||
reply = @sock.read(len)
|
||||
msg = begin
|
||||
Message.decode(reply)
|
||||
rescue DecodeError
|
||||
STDERR.print("DNS message decoding error: #{reply.inspect}")
|
||||
next
|
||||
end
|
||||
if s = @senders[msg.id]
|
||||
s.push msg
|
||||
else
|
||||
#STDERR.print("non-handled DNS message: #{msg.inspect}")
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def sender(msg, data, queue, host=@host, port=@port)
|
||||
def recv_reply
|
||||
len = @sock.read(2).unpack('n')[0]
|
||||
reply = @sock.read(len)
|
||||
return reply, nil
|
||||
end
|
||||
|
||||
def sender(msg, data, host=@host, port=@port)
|
||||
unless host == @host && port == @port
|
||||
raise RequestError.new("host/port don't match: #{host}:#{port}")
|
||||
end
|
||||
id = Thread.exclusive { @id = (@id + 1) & 0xffff }
|
||||
id = DNS.allocate_request_id(@host, @port)
|
||||
request = msg.encode
|
||||
request[0,2] = [request.length, id].pack('nn')
|
||||
return @senders[id] = Sender.new(request, data, @sock, queue)
|
||||
return @senders[[nil,id]] = Sender.new(request, data, @sock)
|
||||
end
|
||||
|
||||
class Sender < Requester::Sender # :nodoc:
|
||||
|
@ -721,6 +754,14 @@ class Resolv
|
|||
@sock.print(@msg)
|
||||
@sock.flush
|
||||
end
|
||||
attr_reader :data
|
||||
end
|
||||
|
||||
def close
|
||||
super
|
||||
@senders.each_key {|from,id|
|
||||
DNS.free_request_id(@host, @port, id)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue