I pushed my version of the fix for #490217 into Sid just before the freeze. http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=490217 python-dns vulnerable to CVE-2008-1447 DNS source port guessable Upstream has released their version of the fix in version 2.3.2 and I think it's better and would like to see it included in Lenny if possible. Specifically they used a while loop instead of recursion when trying to bind to a new socket so it can't go on too long (very low probability) and also they caught that you don't want to close the socket if the async option is in use (AFAIK, no packages in Debian use this, but there appear to be unpackaged users of this module based on popcon). The functional changes not related to #490217 are 9 lines of code and present very minimal risk (if they were wrong, the package just wouldn't work right away - there isn't a risk of a subtle problem that emerges later) Additionally, #492996 was reported today with a patch. It's not an RC bug, but the fix is very small and it's helpfull for IPv4/v6 interoperability. http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=492996 python-dns: Should safely ignore IPv6 "nameserver" entries in resolv.conf as long as queyring those is not supported I've tested the proposed fix for #492996 on an IPv6 connected server that has and IPv6 address in resolv.conf and it works. Additionally, upstream has reviewed the patch and agrees with it (as an interim - they are planning on proper IPv6 support in the next release). Additionally, I have my draft package running successfully on one of my servers now. I would like to get this into Lenny. If the freeze exception is approved, I'll upload to Sid so it can be properly aged. If it's not, I'll upload to experimental. Debdiff aimed at Lenny attached. Thank you, Scott Kittterman
diff -Nru python-dns-2.3.1/debian/changelog python-dns-2.3.2/debian/changelog --- python-dns-2.3.1/debian/changelog 2008-07-30 12:37:35.000000000 -0400 +++ python-dns-2.3.2/debian/changelog 2008-07-30 12:37:35.000000000 -0400 @@ -1,3 +1,16 @@ +python-dns (2.3.2-1) unstable; urgency=low + + * New upstream release + - Upstream fix for source port and TID randomization + - Drop debian/patches/source-tid-random.patch (upstream incorporated + a fix for this) + * Add debian/patches/ignore-ipv6-ns.patch so python-dns ignores IPv6 + name servers and works in a mixed environment (Closes: #492996) + - Thanks to Julian Mehnle for the patch + * Added missing final newline in debian/copyright + + -- Scott Kitterman <scott@kitterman.com> Wed, 30 Jul 2008 12:25:06 -0400 + python-dns (2.3.1-6) unstable; urgency=high * Fix debian/patches/source-tid-random.patch so it doesn't lose socket diff -Nru python-dns-2.3.1/debian/copyright python-dns-2.3.2/debian/copyright --- python-dns-2.3.1/debian/copyright 2008-07-30 12:37:35.000000000 -0400 +++ python-dns-2.3.2/debian/copyright 2008-07-30 12:37:35.000000000 -0400 @@ -153,4 +153,5 @@ 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License -Agreement. \ No newline at end of file +Agreement. + diff -Nru python-dns-2.3.1/debian/patches/ignore-ipv6-ns.patch python-dns-2.3.2/debian/patches/ignore-ipv6-ns.patch --- python-dns-2.3.1/debian/patches/ignore-ipv6-ns.patch 1969-12-31 19:00:00.000000000 -0500 +++ python-dns-2.3.2/debian/patches/ignore-ipv6-ns.patch 2008-07-30 12:37:35.000000000 -0400 @@ -0,0 +1,16 @@ +diff -Nur -x '*.orig' -x '*~' python-dns-2.3.2/DNS/Base.py python-dns-2.3.2.new/DNS/Base.py +--- python-dns-2.3.2/DNS/Base.py 2008-07-27 21:27:00.000000000 -0400 ++++ python-dns-2.3.2.new/DNS/Base.py 2008-07-30 12:21:44.000000000 -0400 +@@ -55,7 +55,11 @@ + if fields[0]=='sortlist': + pass + if fields[0]=='nameserver': +- defaults['server'].append(fields[1]) ++ if fields[1].count(':'): ++ """ Ignore IPv6 nameservers as we currently do not support querying them. """ ++ pass ++ else: ++ defaults['server'].append(fields[1]) + + def DiscoverNameServers(): + import sys diff -Nru python-dns-2.3.1/debian/patches/source-tid-random.patch python-dns-2.3.2/debian/patches/source-tid-random.patch --- python-dns-2.3.1/debian/patches/source-tid-random.patch 2008-07-30 12:37:35.000000000 -0400 +++ python-dns-2.3.2/debian/patches/source-tid-random.patch 1969-12-31 19:00:00.000000000 -0500 @@ -1,153 +0,0 @@ -diff -Nur -x '*.orig' -x '*~' python-dns-2.3.1/DNS/Base.py python-dns-2.3.1.new/DNS/Base.py ---- python-dns-2.3.1/DNS/Base.py 2007-05-22 16:28:31.000000000 -0400 -+++ python-dns-2.3.1.new/DNS/Base.py 2008-07-26 22:08:21.000000000 -0400 -@@ -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 - -@@ -58,6 +63,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'): -@@ -87,7 +93,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() -@@ -133,7 +139,21 @@ - # 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() -+ else: -+ raise -+ - 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): -@@ -144,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'] -@@ -164,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) -@@ -187,20 +208,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 " -@@ -208,14 +240,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' -@@ -234,6 +273,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 -Nru python-dns-2.3.1/DNS/Base.py python-dns-2.3.2/DNS/Base.py --- python-dns-2.3.1/DNS/Base.py 2007-05-22 16:28:31.000000000 -0400 +++ python-dns-2.3.2/DNS/Base.py 2008-07-27 21:27:00.000000000 -0400 @@ -1,5 +1,5 @@ """ -$Id: Base.py,v 1.12.2.4 2007/05/22 20:28:31 customdesigned Exp $ +$Id: Base.py,v 1.12.2.7 2008/07/28 01:27:00 customdesigned Exp $ This file is part of the pydns project. Homepage: http://pydns.sourceforge.net @@ -9,12 +9,27 @@ Base functionality. Request and Response classes, that sort of thing. """ -import socket, string, types, time +import socket, string, types, time, select import Type,Class,Opcode import asyncore +# +# This random generator is used for transaction ids and port selection. This +# is important to prevent spurious results from lost packets, and malicious +# cache poisoning. This doesn't matter if you are behind a caching nameserver +# or your app is a primary DNS server only. To install your own generator, +# replace DNS.Base.random. SystemRandom uses /dev/urandom or similar source. +# +try: + from random import SystemRandom + random = SystemRandom() +except: + import random class DNSError(Exception): pass +# Lib uses DNSError, so import after defining. +import Lib + defaults= { 'protocol':'udp', 'port':53, 'opcode':Opcode.QUERY, 'qtype':Type.A, 'rd':1, 'timing':1, 'timeout': 30 } @@ -58,6 +73,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'): @@ -82,18 +98,16 @@ self.s = socket.socket(a,b) def processUDPReply(self): - import time,select if self.args['timeout'] > 0: 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() def processTCPReply(self): - import time, Lib self.f = self.s.makefile('r') header = self.f.read(2) if len(header) < 2: @@ -107,7 +121,6 @@ return self.processReply() def processReply(self): - import Lib self.args['elapsed']=(self.time_finish-self.time_start)*1000 u = Lib.Munpacker(self.reply) r=Lib.DnsResult(u,self.args) @@ -133,17 +146,29 @@ # u = Lib.Munpacker(reply) # Lib.dumpM(u) + def getSource(self): + "Pick random source port to avoid DNS cache poisoning attack." + while True: + try: + source_port = random.randint(1024,65535) + self.s.bind(('', source_port)) + break + except socket.error, msg: + # Error 98, 'Address already in use' + if msg[0] != 98: raise + def conn(self): + self.getSource() self.s.connect((self.ns,self.port)) def req(self,*name,**args): " needs a refactoring " - import time, Lib self.argparse(name,args) #if not self.args: # 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'] @@ -164,7 +189,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) @@ -187,38 +212,56 @@ 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() + try: + # TODO. Handle timeouts &c correctly (RFC) + self.conn() + self.time_start=time.time() + if not self.async: + self.s.send(self.request) + r=self.processUDPReply() + # Since we bind to the source port and connect to the + # destination 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] != self.port: + r=self.processUDPReply() + self.response = r + # FIXME: check waiting async queries + #except socket.error: + except None: + continue + break + finally: if not self.async: - self.s.send(self.request) - self.response=self.processUDPReply() - #except socket.error: - except None: - continue - break - if not self.response: - if not self.async: - raise DNSError,'no working nameservers found' + self.s.close() + if not self.response and not self.async: + raise DNSError,'no working nameservers found' def sendTCPRequest(self, server): " do the work of sending a TCP request " - import time, Lib 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 - break + 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) + buf = Lib.pack16bit(len(self.request))+self.request + self.s.sendall(buf) + self.s.shutdown(socket.SHUT_WR) + r=self.processTCPReply() + if r.header['id'] == self.tid: + self.response = r + break + except socket.error: + continue + finally: + self.s.close() if not self.response: - raise DNSError,'no working nameservers found' + raise DNSError, 'no working nameservers found' #class DnsAsyncRequest(DnsRequest): class DnsAsyncRequest(DnsRequest,asyncore.dispatcher_with_send): @@ -233,7 +276,7 @@ #self.realinit(name,args) # XXX todo self.async=1 def conn(self): - import time + self.getSource() self.connect((self.ns,self.port)) self.time_start=time.time() if self.args.has_key('start') and self.args['start']: @@ -256,6 +299,15 @@ # # $Log: Base.py,v $ +# Revision 1.12.2.7 2008/07/28 01:27:00 customdesigned +# Check configured port. +# +# Revision 1.12.2.6 2008/07/28 00:17:10 customdesigned +# Randomize source ports. +# +# Revision 1.12.2.5 2008/07/24 20:10:55 customdesigned +# Randomize tid in requests, and check in response. +# # Revision 1.12.2.4 2007/05/22 20:28:31 customdesigned # Missing import Lib # diff -Nru python-dns-2.3.1/DNS/__init__.py python-dns-2.3.2/DNS/__init__.py --- python-dns-2.3.1/DNS/__init__.py 2007-05-22 17:06:52.000000000 -0400 +++ python-dns-2.3.2/DNS/__init__.py 2008-07-27 22:11:07.000000000 -0400 @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- -# $Id: __init__.py,v 1.8.2.2 2007/05/22 21:06:52 customdesigned Exp $ +# $Id: __init__.py,v 1.8.2.5 2008/07/28 02:11:07 customdesigned Exp $ # # This file is part of the pydns project. # Homepage: http://pydns.sourceforge.net @@ -9,7 +9,7 @@ # __init__.py for DNS class. -__version__ = '2.3.1' +__version__ = '2.3.2' import Type,Opcode,Status,Class from Base import DnsRequest, DNSError @@ -21,8 +21,18 @@ Request = DnsRequest Result = DnsResult + # # $Log: __init__.py,v $ +# Revision 1.8.2.5 2008/07/28 02:11:07 customdesigned +# Bump version. +# +# Revision 1.8.2.4 2008/07/28 00:17:10 customdesigned +# Randomize source ports. +# +# Revision 1.8.2.3 2008/07/24 20:10:55 customdesigned +# Randomize tid in requests, and check in response. +# # Revision 1.8.2.2 2007/05/22 21:06:52 customdesigned # utf-8 in __init__.py # diff -Nru python-dns-2.3.1/PKG-INFO python-dns-2.3.2/PKG-INFO --- python-dns-2.3.1/PKG-INFO 2007-05-22 17:07:14.000000000 -0400 +++ python-dns-2.3.2/PKG-INFO 2008-07-27 22:11:26.000000000 -0400 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: pydns -Version: 2.3.1 +Version: 2.3.2 Summary: Python DNS library Home-page: http://pydns.sourceforge.net/ Author: Anthony Baxter and others diff -Nru python-dns-2.3.1/pydns.spec python-dns-2.3.2/pydns.spec --- python-dns-2.3.1/pydns.spec 2007-05-22 16:38:32.000000000 -0400 +++ python-dns-2.3.2/pydns.spec 2008-07-24 16:10:08.000000000 -0400 @@ -1,5 +1,5 @@ %define name pydns -%define version 2.3.1 +%define version 2.3.2 %define release 2.4 Summary: Python DNS library @@ -35,5 +35,18 @@ %defattr(-,root,root) %changelog +* Thu Jul 24 2008 Stuart Gathman <stuart@bmsi.com> 2.3.2-1 +- Randomize TID * Tue May 22 2007 Stuart Gathman <stuart@bmsi.com> 2.3.1-1 - Bug fix release +- BTS Patches: +- 01resolv-conf-parse patch, thanks to Arnaud Fontaine <arnaud@andesi.org> + (closes: #378991) +- Changes from Ubuntu (SF = Sourceforge project bug #) (closes: #411138): +- 02utf-8 patch for files with UTF-8 content +- 03socket-error-trap patch, Added DNSError trap for socket.error. +- 04lazy-init SF 1563723 lazy should initilize defaults['server'] +- 05addr2bin2addr SF 863364 Mac OS X, Win2000 DHCP, addr2bin and bin2addr. +- 06win32-fix SF 1180344 win32dns.py fails on windows server 2003 +- 07unpacker SF 954095 Bug in DNS.Lib.Unpacker.getbyte() +- 08import-lib SF 658601 Missing "import Lib"; for TCP protocol diff -Nru python-dns-2.3.1/python-pydns.spec python-dns-2.3.2/python-pydns.spec --- python-dns-2.3.1/python-pydns.spec 2007-05-22 16:36:49.000000000 -0400 +++ python-dns-2.3.2/python-pydns.spec 2008-07-24 16:10:14.000000000 -0400 @@ -1,7 +1,7 @@ %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} Name: python-pydns -Version: 2.3.1 +Version: 2.3.2 Release: 1%{?dist} Summary: Python module for DNS (Domain Name Service). @@ -47,7 +47,20 @@ %{python_sitelib}/DNS/*.py* %changelog +* Thu Jul 24 2008 Stuart Gathman <stuart@bmsi.com> 2.3.2-1 +- Randomize TID * Tue May 22 2007 Stuart Gathman <stuart@bmsi.com> 2.3.1-1 - Bug fix release +- BTS Patches: +- 01resolv-conf-parse patch, thanks to Arnaud Fontaine <arnaud@andesi.org> + (closes: #378991) +- Changes from Ubuntu (SF = Sourceforge project bug #) (closes: #411138): +- 02utf-8 patch for files with UTF-8 content +- 03socket-error-trap patch, Added DNSError trap for socket.error. +- 04lazy-init SF 1563723 lazy should initilize defaults['server'] +- 05addr2bin2addr SF 863364 Mac OS X, Win2000 DHCP, addr2bin and bin2addr. +- 06win32-fix SF 1180344 win32dns.py fails on windows server 2003 +- 07unpacker SF 954095 Bug in DNS.Lib.Unpacker.getbyte() +- 08import-lib SF 658601 Missing "import Lib"; for TCP protocol * Tue Aug 29 2006 Sean Reifschneider <jafo@tummy.com> 2.3.0-1 - Initial RPM spec file.
Attachment:
signature.asc
Description: This is a digitally signed message part.