diff -u python-dns-2.3.0/DNS/Base.py python-dns-2.3.0/DNS/Base.py --- python-dns-2.3.0/DNS/Base.py +++ python-dns-2.3.0/DNS/Base.py @@ -12,6 +12,11 @@ import socket, string, types, time import Type,Class,Opcode import asyncore +try: + from random import SystemRandom + random = SystemRandom() +except: + import random class DNSError(Exception): pass @@ -60,6 +65,7 @@ self.defaults = {} self.argparse(name,args) self.defaults = self.args + self.tid = 0 def argparse(self,name,args): if not name and self.defaults.has_key('name'): @@ -89,7 +95,7 @@ r,w,e = select.select([self.s],[],[],self.args['timeout']) if not len(r): raise DNSError, 'Timeout' - self.reply = self.s.recv(1024) + (self.reply, self.from_address) = self.s.recvfrom(65535) self.time_finish=time.time() self.args['server']=self.ns return self.processReply() @@ -135,7 +141,19 @@ # u = Lib.Munpacker(reply) # Lib.dumpM(u) + def getSource(self): + # Get random source port to avoid DNS cache poisoning attack. + try: + source = random.randint(1024,65535) + self.s.bind(('', source)) + except socket.error, msg: + # Error 98, 'Address already in use' + if msg[0] == 98: + self.getSource() + def conn(self): + # Source is source port we'll take a reply from. + self.getSource() self.s.connect((self.ns,self.port)) def req(self,*name,**args): @@ -146,6 +164,7 @@ # raise DNSError,'reinitialize request before reuse' protocol = self.args['protocol'] self.port = self.args['port'] + self.tid = random.randint(0,65535) opcode = self.args['opcode'] rd = self.args['rd'] server=self.args['server'] @@ -166,7 +185,7 @@ #print 'QTYPE %d(%s)' % (qtype, Type.typestr(qtype)) m = Lib.Mpacker() # jesus. keywords and default args would be good. TODO. - m.addHeader(0, + m.addHeader(self.tid, 0, opcode, 0, 0, rd, 0, 0, 0, 1, 0, 0, 0) m.addQuestion(qname, qtype, Class.IN) @@ -190,20 +209,31 @@ self.socketInit(socket.AF_INET, socket.SOCK_DGRAM) for self.ns in server: try: - # TODO. Handle timeouts &c correctly (RFC) - #self.s.connect((self.ns, self.port)) - self.conn() - self.time_start=time.time() - if not self.async: - self.s.send(self.request) - self.response=self.processUDPReply() - #except socket.error: - except None: - continue + try: + # TODO. Handle timeouts &c correctly (RFC) + #self.s.connect((self.ns, self.port)) + self.conn() + self.s.setblocking(0) + self.time_start=time.time() + if not self.async: + self.s.send(self.request) + r=self.processUDPReply() + # Since we bind to the source port, we don't need to check that + # here, but do make sure it's actually a DNS request that the packet + # is in reply to. + while r.header['id'] != self.tid or self.from_address[1] != 53: + r=self.processUDPReply() + self.response = r + # FIXME: check waiting async queries + #except socket.error: + except None: + continue + finally: + self.s.close() break if not self.response: if not self.async: - raise DNSError,'no working nameservers found' + raise DNSError,('no working nameservers found') def sendTCPRequest(self, server): " do the work of sending a TCP request " @@ -212,14 +242,21 @@ self.response=None for self.ns in server: try: - self.socketInit(socket.AF_INET, socket.SOCK_STREAM) - self.time_start=time.time() - self.conn() - self.s.send(Lib.pack16bit(len(self.request))+self.request) - self.s.shutdown(1) - self.response=self.processTCPReply() - except socket.error: - continue + try: + # TODO. Handle timeouts &c correctly (RFC) + self.socketInit(socket.AF_INET, socket.SOCK_STREAM) + self.time_start=time.time() + self.conn() + self.s.setblocking(0) + self.s.sendall(Lib.pack16bit(len(self.request))+self.request) + self.s.shutdown(socket.SHUT_WR) + r=self.processTCPReply() + if r.header['id'] != self.tid: continue + self.response = r + except socket.error: + continue + finally: + self.s.close() break if not self.response: raise DNSError,'no working nameservers found' @@ -238,6 +275,8 @@ self.async=1 def conn(self): import time + # Source is source port we'll take a reply from. + self.getSource() self.connect((self.ns,self.port)) self.time_start=time.time() if self.args.has_key('start') and self.args['start']: diff -u python-dns-2.3.0/debian/changelog python-dns-2.3.0/debian/changelog --- python-dns-2.3.0/debian/changelog +++ python-dns-2.3.0/debian/changelog @@ -1,3 +1,11 @@ +python-dns (2.3.0-5.1ubuntu2.1) feisty-security; urgency=high + + * SECURITY UPDATE: Add source port and TID randomization (LP: #247409) + * References + * CVE-2008-1447 DNS source port guessable + + -- Scott Kitterman Sat, 26 Jul 2008 02:17:03 -0400 + python-dns (2.3.0-5.1ubuntu2) feisty; urgency=low [ Scott Kitterman ]