[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Freeze exception request for python-dns 2.3.2-1



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.


Reply to: