Skip to content

Commit c30b178

Browse files
Issue #16038: CVE-2013-1752: ftplib: Limit amount of data read by
limiting the call to readline(). Original patch by Michał Jastrzębski and Giampaolo Rodola.
1 parent 0abb218 commit c30b178

File tree

3 files changed

+43
-6
lines changed

3 files changed

+43
-6
lines changed

Lib/ftplib.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949

5050
# The standard FTP server control port
5151
FTP_PORT = 21
52+
# The sizehint parameter passed to readline() calls
53+
MAXLINE = 8192
5254

5355

5456
# Exception raised when an error or invalid response is received
@@ -96,6 +98,7 @@ class FTP:
9698
debugging = 0
9799
host = ''
98100
port = FTP_PORT
101+
maxline = MAXLINE
99102
sock = None
100103
file = None
101104
welcome = None
@@ -194,7 +197,9 @@ def putcmd(self, line):
194197
# Internal: return one line from the server, stripping CRLF.
195198
# Raise EOFError if the connection is closed
196199
def getline(self):
197-
line = self.file.readline()
200+
line = self.file.readline(self.maxline + 1)
201+
if len(line) > self.maxline:
202+
raise Error("got more than %d bytes" % self.maxline)
198203
if self.debugging > 1:
199204
print('*get*', self.sanitize(line))
200205
if not line: raise EOFError
@@ -446,7 +451,9 @@ def retrlines(self, cmd, callback = None):
446451
with self.transfercmd(cmd) as conn, \
447452
conn.makefile('r', encoding=self.encoding) as fp:
448453
while 1:
449-
line = fp.readline()
454+
line = fp.readline(self.maxline + 1)
455+
if len(line) > self.maxline:
456+
raise Error("got more than %d bytes" % self.maxline)
450457
if self.debugging > 2: print('*retr*', repr(line))
451458
if not line:
452459
break
@@ -496,7 +503,9 @@ def storlines(self, cmd, fp, callback=None):
496503
self.voidcmd('TYPE A')
497504
with self.transfercmd(cmd) as conn:
498505
while 1:
499-
buf = fp.readline()
506+
buf = fp.readline(self.maxline + 1)
507+
if len(buf) > self.maxline:
508+
raise Error("got more than %d bytes" % self.maxline)
500509
if not buf: break
501510
if buf[-2:] != B_CRLF:
502511
if buf[-1] in B_CRLF: buf = buf[:-1]
@@ -773,7 +782,9 @@ def retrlines(self, cmd, callback = None):
773782
fp = conn.makefile('r', encoding=self.encoding)
774783
with fp, conn:
775784
while 1:
776-
line = fp.readline()
785+
line = fp.readline(self.maxline + 1)
786+
if len(line) > self.maxline:
787+
raise Error("got more than %d bytes" % self.maxline)
777788
if self.debugging > 2: print('*retr*', repr(line))
778789
if not line:
779790
break
@@ -804,7 +815,9 @@ def storlines(self, cmd, fp, callback=None):
804815
self.voidcmd('TYPE A')
805816
with self.transfercmd(cmd) as conn:
806817
while 1:
807-
buf = fp.readline()
818+
buf = fp.readline(self.maxline + 1)
819+
if len(buf) > self.maxline:
820+
raise Error("got more than %d bytes" % self.maxline)
808821
if not buf: break
809822
if buf[-2:] != B_CRLF:
810823
if buf[-1] in B_CRLF: buf = buf[:-1]

Lib/test/test_ftplib.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def __init__(self, conn):
9191
self.next_response = ''
9292
self.next_data = None
9393
self.rest = None
94+
self.next_retr_data = RETR_DATA
9495
self.push('220 welcome')
9596

9697
def collect_incoming_data(self, data):
@@ -220,7 +221,7 @@ def cmd_retr(self, arg):
220221
offset = int(self.rest)
221222
else:
222223
offset = 0
223-
self.dtp.push(RETR_DATA[offset:])
224+
self.dtp.push(self.next_retr_data[offset:])
224225
self.dtp.close_when_done()
225226
self.rest = None
226227

@@ -242,6 +243,11 @@ def cmd_mlsd(self, arg):
242243
self.dtp.push(MLSD_DATA)
243244
self.dtp.close_when_done()
244245

246+
def cmd_setlongretr(self, arg):
247+
# For testing. Next RETR will return long line.
248+
self.next_retr_data = 'x' * int(arg)
249+
self.push('125 setlongretr ok')
250+
245251

246252
class DummyFTPServer(asyncore.dispatcher, threading.Thread):
247253

@@ -758,6 +764,20 @@ def test_parse257(self):
758764
self.assertEqual(ftplib.parse257('257 "/foo/b""ar"'), '/foo/b"ar')
759765
self.assertEqual(ftplib.parse257('257 "/foo/b""ar" created'), '/foo/b"ar')
760766

767+
def test_line_too_long(self):
768+
self.assertRaises(ftplib.Error, self.client.sendcmd,
769+
'x' * self.client.maxline * 2)
770+
771+
def test_retrlines_too_long(self):
772+
self.client.sendcmd('SETLONGRETR %d' % (self.client.maxline * 2))
773+
received = []
774+
self.assertRaises(ftplib.Error,
775+
self.client.retrlines, 'retr', received.append)
776+
777+
def test_storlines_too_long(self):
778+
f = io.BytesIO(b'x' * self.client.maxline * 2)
779+
self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f)
780+
761781

762782
class TestIPv6Environment(TestCase):
763783

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ Core and Builtins
7878
Library
7979
-------
8080

81+
- Issue #16038: CVE-2013-1752: ftplib: Limit amount of data read by
82+
limiting the call to readline(). Original patch by Michał
83+
Jastrzębski and Giampaolo Rodola.
84+
8185
- Issue #18235: Fix the sysconfig variables LDSHARED and BLDSHARED under AIX.
8286
Patch by David Edelsohn.
8387

0 commit comments

Comments
 (0)