mbooth pushed to jython (f22). "Fix CVE-2013-1752 - multiple unbound readline() DoS flaws in python stdlib"

notifications at fedoraproject.org notifications at fedoraproject.org
Mon Apr 13 09:49:54 UTC 2015


>From 22d50e7757bb11e1986f9ba670a9bab01afcb9d6 Mon Sep 17 00:00:00 2001
From: Mat Booth <mat.booth at redhat.com>
Date: Mon, 13 Apr 2015 10:48:58 +0100
Subject: Fix CVE-2013-1752 - multiple unbound readline() DoS flaws in python
 stdlib


diff --git a/jython-CVE-2013-1752.patch b/jython-CVE-2013-1752.patch
new file mode 100644
index 0000000..996973b
--- /dev/null
+++ b/jython-CVE-2013-1752.patch
@@ -0,0 +1,471 @@
+diff --git a/Lib/ftplib.py b/Lib/ftplib.py
+--- a/Lib/ftplib.py
++++ b/Lib/ftplib.py
+@@ -55,6 +55,8 @@ MSG_OOB = 0x1                           
+ 
+ # The standard FTP server control port
+ FTP_PORT = 21
++# The sizehint parameter passed to readline() calls
++MAXLINE = 8192
+ 
+ 
+ # Exception raised when an error or invalid response is received
+@@ -101,6 +103,7 @@ class FTP:
+     debugging = 0
+     host = ''
+     port = FTP_PORT
++    maxline = MAXLINE
+     sock = None
+     file = None
+     welcome = None
+@@ -180,7 +183,9 @@ class FTP:
+     # Internal: return one line from the server, stripping CRLF.
+     # Raise EOFError if the connection is closed
+     def getline(self):
+-        line = self.file.readline()
++        line = self.file.readline(self.maxline + 1)
++        if len(line) > self.maxline:
++            raise Error("got more than %d bytes" % self.maxline)
+         if self.debugging > 1:
+             print '*get*', self.sanitize(line)
+         if not line: raise EOFError
+@@ -432,7 +437,9 @@ class FTP:
+         conn = self.transfercmd(cmd)
+         fp = conn.makefile('rb')
+         while 1:
+-            line = fp.readline()
++            line = fp.readline(self.maxline + 1)
++            if len(line) > self.maxline:
++                raise Error("got more than %d bytes" % self.maxline)
+             if self.debugging > 2: print '*retr*', repr(line)
+             if not line:
+                 break
+@@ -485,7 +492,9 @@ class FTP:
+         self.voidcmd('TYPE A')
+         conn = self.transfercmd(cmd)
+         while 1:
+-            buf = fp.readline()
++            buf = fp.readline(self.maxline + 1)
++            if len(buf) > self.maxline:
++                raise Error("got more than %d bytes" % self.maxline)
+             if not buf: break
+             if buf[-2:] != CRLF:
+                 if buf[-1] in CRLF: buf = buf[:-1]
+@@ -710,7 +719,9 @@ else:
+             fp = conn.makefile('rb')
+             try:
+                 while 1:
+-                    line = fp.readline()
++                    line = fp.readline(self.maxline + 1)
++                    if len(line) > self.maxline:
++                        raise Error("got more than %d bytes" % self.maxline)
+                     if self.debugging > 2: print '*retr*', repr(line)
+                     if not line:
+                         break
+@@ -748,7 +759,9 @@ else:
+             conn = self.transfercmd(cmd)
+             try:
+                 while 1:
+-                    buf = fp.readline()
++                    buf = fp.readline(self.maxline + 1)
++                    if len(buf) > self.maxline:
++                        raise Error("got more than %d bytes" % self.maxline)
+                     if not buf: break
+                     if buf[-2:] != CRLF:
+                         if buf[-1] in CRLF: buf = buf[:-1]
+@@ -905,7 +918,9 @@ class Netrc:
+         fp = open(filename, "r")
+         in_macro = 0
+         while 1:
+-            line = fp.readline()
++            line = fp.readline(self.maxline + 1)
++            if len(line) > self.maxline:
++                raise Error("got more than %d bytes" % self.maxline)
+             if not line: break
+             if in_macro and line.strip():
+                 macro_lines.append(line)
+diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
+--- a/Lib/test/test_ftplib.py
++++ b/Lib/test/test_ftplib.py
+@@ -65,6 +65,7 @@ class DummyFTPHandler(asynchat.async_cha
+         self.last_received_data = ''
+         self.next_response = ''
+         self.rest = None
++        self.next_retr_data = RETR_DATA
+         self.push('220 welcome')
+ 
+     def collect_incoming_data(self, data):
+@@ -189,7 +190,7 @@ class DummyFTPHandler(asynchat.async_cha
+             offset = int(self.rest)
+         else:
+             offset = 0
+-        self.dtp.push(RETR_DATA[offset:])
++        self.dtp.push(self.next_retr_data[offset:])
+         self.dtp.close_when_done()
+         self.rest = None
+ 
+@@ -203,6 +204,11 @@ class DummyFTPHandler(asynchat.async_cha
+         self.dtp.push(NLST_DATA)
+         self.dtp.close_when_done()
+ 
++    def cmd_setlongretr(self, arg):
++        # For testing. Next RETR will return long line.
++        self.next_retr_data = 'x' * int(arg)
++        self.push('125 setlongretr ok')
++
+ 
+ class DummyFTPServer(asyncore.dispatcher, threading.Thread):
+ 
+@@ -558,6 +564,20 @@ class TestFTPClass(TestCase):
+         # IPv4 is in use, just make sure send_epsv has not been used
+         self.assertEqual(self.server.handler.last_received_cmd, 'pasv')
+ 
++    def test_line_too_long(self):
++        self.assertRaises(ftplib.Error, self.client.sendcmd,
++                          'x' * self.client.maxline * 2)
++
++    def test_retrlines_too_long(self):
++        self.client.sendcmd('SETLONGRETR %d' % (self.client.maxline * 2))
++        received = []
++        self.assertRaises(ftplib.Error,
++                          self.client.retrlines, 'retr', received.append)
++
++    def test_storlines_too_long(self):
++        f = StringIO.StringIO('x' * self.client.maxline * 2)
++        self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f)
++
+ 
+ class TestIPv6Environment(TestCase):
+ 
+diff --git a/Lib/imaplib.py b/Lib/imaplib.py
+--- a/Lib/imaplib.py
++++ b/Lib/imaplib.py
+@@ -35,6 +35,15 @@ IMAP4_PORT = 143
+ IMAP4_SSL_PORT = 993
+ AllowedVersions = ('IMAP4REV1', 'IMAP4')        # Most recent first
+ 
++# Maximal line length when calling readline(). This is to prevent
++# reading arbitrary length lines. RFC 3501 and 2060 (IMAP 4rev1)
++# don't specify a line length. RFC 2683 however suggests limiting client
++# command lines to 1000 octets and server command lines to 8000 octets.
++# We have selected 10000 for some extra margin and since that is supposedly
++# also what UW and Panda IMAP does.
++_MAXLINE = 10000
++
++
+ #       Commands
+ 
+ Commands = {
+@@ -237,7 +246,10 @@ class IMAP4:
+ 
+     def readline(self):
+         """Read line from remote."""
+-        return self.file.readline()
++        line = self.file.readline(_MAXLINE + 1)
++        if len(line) > _MAXLINE:
++            raise self.error("got more than %d bytes" % _MAXLINE)
++        return line
+ 
+ 
+     def send(self, data):
+diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py
+--- a/Lib/test/test_imaplib.py
++++ b/Lib/test/test_imaplib.py
+@@ -165,6 +165,16 @@ class BaseThreadedNetworkedTests(unittes
+                               self.imap_class, *server.server_address)
+ 
+ 
++    def test_linetoolong(self):
++        class TooLongHandler(SimpleIMAPHandler):
++            def handle(self):
++                # Send a very long response line
++                self.wfile.write('* OK ' + imaplib._MAXLINE*'x' + '\r\n')
++
++        with self.reaped_server(TooLongHandler) as server:
++            self.assertRaises(imaplib.IMAP4.error,
++                              self.imap_class, *server.server_address)
++
+ class ThreadedNetworkedTests(BaseThreadedNetworkedTests):
+ 
+     server_class = SocketServer.TCPServer
+
+diff --git a/Lib/nntplib.py b/Lib/nntplib.py
+--- a/Lib/nntplib.py
++++ b/Lib/nntplib.py
+@@ -37,6 +37,13 @@ import socket
+            "error_reply","error_temp","error_perm","error_proto",
+            "error_data",]
+ 
++# maximal line length when calling readline(). This is to prevent
++# reading arbitrary lenght lines. RFC 3977 limits NNTP line length to
++# 512 characters, including CRLF. We have selected 2048 just to be on
++# the safe side.
++_MAXLINE = 2048
++
++
+ # Exceptions raised when an error or invalid response is received
+ class NNTPError(Exception):
+     """Base class for all nntplib exceptions"""
+@@ -200,7 +207,9 @@ class NNTP:
+     def getline(self):
+         """Internal: return one line from the server, stripping CRLF.
+         Raise EOFError if the connection is closed."""
+-        line = self.file.readline()
++        line = self.file.readline(_MAXLINE + 1)
++        if len(line) > _MAXLINE:
++            raise NNTPDataError('line too long')
+         if self.debugging > 1:
+             print '*get*', repr(line)
+         if not line: raise EOFError
+diff --git a/Lib/test/test_nntplib.py b/Lib/test/test_nntplib.py
+new file mode 100644
+--- /dev/null
++++ b/Lib/test/test_nntplib.py
+@@ -0,0 +1,65 @@
++import socket
++import threading
++import nntplib
++import time
++
++from unittest import TestCase
++from test import test_support
++
++HOST = test_support.HOST
++
++
++def server(evt, serv, evil=False):
++    serv.listen(5)
++    try:
++        conn, addr = serv.accept()
++    except socket.timeout:
++        pass
++    else:
++        if evil:
++            conn.send("1 I'm too long response" * 3000 + "\n")
++        else:
++            conn.send("1 I'm OK response\n")
++        conn.close()
++    finally:
++        serv.close()
++        evt.set()
++
++
++class BaseServerTest(TestCase):
++    def setUp(self):
++        self.evt = threading.Event()
++        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
++        self.sock.settimeout(3)
++        self.port = test_support.bind_port(self.sock)
++        threading.Thread(
++            target=server,
++            args=(self.evt, self.sock, self.evil)).start()
++        time.sleep(.1)
++
++    def tearDown(self):
++        self.evt.wait()
++
++
++class ServerTests(BaseServerTest):
++    evil = False
++
++    def test_basic_connect(self):
++        nntp = nntplib.NNTP('localhost', self.port)
++        nntp.sock.close()
++
++
++class EvilServerTests(BaseServerTest):
++    evil = True
++
++    def test_too_long_line(self):
++        self.assertRaises(nntplib.NNTPDataError,
++                          nntplib.NNTP, 'localhost', self.port)
++
++
++def test_main(verbose=None):
++    test_support.run_unittest(EvilServerTests)
++    test_support.run_unittest(ServerTests)
++
++if __name__ == '__main__':
++    test_main()
+diff --git a/Lib/poplib.py b/Lib/poplib.py
+--- a/Lib/poplib.py
++++ b/Lib/poplib.py
+@@ -32,6 +32,12 @@ CR = '\r'
+ LF = '\n'
+ CRLF = CR+LF
+ 
++# maximal line length when calling readline(). This is to prevent
++# reading arbitrary length lines. RFC 1939 limits POP3 line length to
++# 512 characters, including CRLF. We have selected 2048 just to be on
++# the safe side.
++_MAXLINE = 2048
++
+ 
+ class POP3:
+ 
+@@ -103,7 +109,9 @@ class POP3:
+     # Raise error_proto('-ERR EOF') if the connection is closed.
+ 
+     def _getline(self):
+-        line = self.file.readline()
++        line = self.file.readline(_MAXLINE + 1)
++        if len(line) > _MAXLINE:
++            raise error_proto('line too long')
+         if self._debugging > 1: print '*get*', repr(line)
+         if not line: raise error_proto('-ERR EOF')
+         octets = len(line)
+@@ -365,6 +373,8 @@ else:
+             match = renewline.match(self.buffer)
+             while not match:
+                 self._fillBuffer()
++                if len(self.buffer) > _MAXLINE:
++                    raise error_proto('line too long')
+                 match = renewline.match(self.buffer)
+             line = match.group(0)
+             self.buffer = renewline.sub('' ,self.buffer, 1)
+diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py
+--- a/Lib/test/test_poplib.py
++++ b/Lib/test/test_poplib.py
+@@ -198,6 +198,10 @@ class TestPOP3Class(TestCase):
+                     113)
+         self.assertEqual(self.client.retr('foo'), expected)
+ 
++    def test_too_long_lines(self):
++        self.assertRaises(poplib.error_proto, self.client._shortcmd,
++                          'echo +%s' % ((poplib._MAXLINE + 10) * 'a'))
++
+     def test_dele(self):
+         self.assertOK(self.client.dele('foo'))
+ 
+diff --git a/Lib/smtplib.py b/Lib/smtplib.py
+--- a/Lib/smtplib.py
++++ b/Lib/smtplib.py
+@@ -57,6 +57,7 @@ from sys import stderr
+ SMTP_PORT = 25
+ SMTP_SSL_PORT = 465
+ CRLF = "\r\n"
++_MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3
+ 
+ OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
+ 
+@@ -179,10 +180,14 @@ else:
+         def __init__(self, sslobj):
+             self.sslobj = sslobj
+ 
+-        def readline(self):
++        def readline(self, size=-1):
++            if size < 0:
++                size = None
+             str = ""
+             chr = None
+             while chr != "\n":
++                if size is not None and len(str) >= size:
++                    break
+                 chr = self.sslobj.read(1)
+                 if not chr:
+                     break
+@@ -353,7 +358,7 @@ class SMTP:
+             self.file = self.sock.makefile('rb')
+         while 1:
+             try:
+-                line = self.file.readline()
++                line = self.file.readline(_MAXLINE + 1)
+             except socket.error as e:
+                 self.close()
+                 raise SMTPServerDisconnected("Connection unexpectedly closed: "
+@@ -363,6 +368,8 @@ class SMTP:
+                 raise SMTPServerDisconnected("Connection unexpectedly closed")
+             if self.debuglevel > 0:
+                 print>>stderr, 'reply:', repr(line)
++            if len(line) > _MAXLINE:
++                raise SMTPResponseException(500, "Line too long.")
+             resp.append(line[4:].strip())
+             code = line[:3]
+             # Check that the error code is syntactically correct.
+diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py
+--- a/Lib/test/test_smtplib.py
++++ b/Lib/test/test_smtplib.py
+@@ -292,6 +292,33 @@ class BadHELOServerTests(unittest.TestCa
+                             HOST, self.port, 'localhost', 3)
+ 
+ 
++ at unittest.skipUnless(threading, 'Threading required for this test.')
++class TooLongLineTests(unittest.TestCase):
++    respdata = '250 OK' + ('.' * smtplib._MAXLINE * 2) + '\n'
++
++    def setUp(self):
++        self.old_stdout = sys.stdout
++        self.output = StringIO.StringIO()
++        sys.stdout = self.output
++
++        self.evt = threading.Event()
++        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
++        self.sock.settimeout(15)
++        self.port = test_support.bind_port(self.sock)
++        servargs = (self.evt, self.respdata, self.sock)
++        threading.Thread(target=server, args=servargs).start()
++        self.evt.wait()
++        self.evt.clear()
++
++    def tearDown(self):
++        self.evt.wait()
++        sys.stdout = self.old_stdout
++
++    def testLineTooLong(self):
++        self.assertRaises(smtplib.SMTPResponseException, smtplib.SMTP,
++                          HOST, self.port, 'localhost', 3)
++
++
+ sim_users = {'Mr.A at somewhere.com':'John A',
+              'Ms.B at somewhere.com':'Sally B',
+              'Mrs.C at somewhereesle.com':'Ruth C',
+@@ -526,7 +553,8 @@ class SMTPSimTests(unittest.TestCase):
+ def test_main(verbose=None):
+     test_support.run_unittest(GeneralTests, DebuggingServerTests,
+                               NonConnectingTests,
+-                              BadHELOServerTests, SMTPSimTests)
++                              BadHELOServerTests, SMTPSimTests,
++                              TooLongLineTests)
+ 
+ if __name__ == '__main__':
+     test_main()
+diff --git a/Lib/httplib.py b/Lib/httplib.py
+--- a/Lib/httplib.py
++++ b/Lib/httplib.py
+@@ -215,6 +215,10 @@ MAXAMOUNT = 1048576
+ # maximal line length when calling readline().
+ _MAXLINE = 65536
+ 
++# maximum amount of headers accepted
++_MAXHEADERS = 100
++
++
+ class HTTPMessage(mimetools.Message):
+ 
+     def addheader(self, key, value):
+@@ -271,6 +275,8 @@ class HTTPMessage(mimetools.Message):
+         elif self.seekable:
+             tell = self.fp.tell
+         while True:
++            if len(hlist) > _MAXHEADERS:
++                raise HTTPException("got more than %d headers" % _MAXHEADERS)
+             if tell:
+                 try:
+                     startofline = tell()
+diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
+--- a/Lib/test/test_httplib.py
++++ b/Lib/test/test_httplib.py
+@@ -262,6 +262,13 @@ class BasicTest(TestCase):
+         if resp.read() != "":
+             self.fail("Did not expect response from HEAD request")
+ 
++    def test_too_many_headers(self):
++        headers = '\r\n'.join('Header%d: foo' % i for i in xrange(200)) + '\r\n'
++        text = ('HTTP/1.1 200 OK\r\n' + headers)
++        s = FakeSocket(text)
++        r = httplib.HTTPResponse(s)
++        self.assertRaises(httplib.HTTPException, r.begin)
++
+     def test_send_file(self):
+         expected = 'GET /foo HTTP/1.1\r\nHost: example.com\r\n' \
+                    'Accept-Encoding: identity\r\nContent-Length:'
diff --git a/jython.spec b/jython.spec
index 1472a5e..e2050ec 100644
--- a/jython.spec
+++ b/jython.spec
@@ -9,7 +9,7 @@
 
 Name:                      jython
 Version:                   2.7
-Release:                   0.6.rc2%{?dist}
+Release:                   0.7.rc2%{?dist}
 Summary:                   A Java implementation of the Python language
 License:                   ASL 1.1 and BSD and CNRI and JPython and Python
 URL:                       http://www.jython.org/
@@ -24,6 +24,9 @@ Patch0:                    jython-cachedir.patch
 # Avoid rebuilding and validating poms when installing maven stuff and don't gpg sign
 Patch1:                    jython-dont-validate-pom.patch
 
+# Fix for CVE-2013-1752, see https://bugzilla.redhat.com/show_bug.cgi?id=1159201
+Patch2:                    jython-CVE-2013-1752.patch
+
 Requires:                  python >= %{cpython_version}
 Requires:                  antlr32-java
 Requires:                  apache-commons-compress
@@ -99,6 +102,10 @@ Demonstrations and samples for %{name}.
 %patch0
 %patch1
 
+pushd lib-python/2.7
+%patch2 -p2
+popd
+
 # Set correct encoding for source to fix javadoc generation
 sed -i -e '723i encoding="UTF-8"' build.xml
 
@@ -196,6 +203,9 @@ EOF
 %{_datadir}/%{name}/Demo
 
 %changelog
+* Mon Apr 13 2015 Mat Booth <mat.booth at redhat.com> - 2.7-0.7.rc2
+- Fix CVE-2013-1752 - multiple unbound readline() DoS flaws in python stdlib
+
 * Thu Apr 09 2015 Mat Booth <mat.booth at redhat.com> - 2.7-0.6.rc2
 - BR/R jnr-posix >= 3.0.9
 
-- 
cgit v0.10.2


	http://pkgs.fedoraproject.org/cgit/jython.git/commit/?h=f22&id=22d50e7757bb11e1986f9ba670a9bab01afcb9d6


More information about the scm-commits mailing list