Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit addebca

Browse files
committed
merge
2 parents da9cf0e + ee9b172 commit addebca

6 files changed

Lines changed: 123 additions & 28 deletions

File tree

Doc/library/zipfile.rst

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,16 @@ ZipFile Objects
242242
to extract to. *member* can be a filename or a :class:`ZipInfo` object.
243243
*pwd* is the password used for encrypted files.
244244

245+
.. note::
246+
247+
If a member filename is an absolute path, a drive/UNC sharepoint and
248+
leading (back)slashes will be stripped, e.g.: ``///foo/bar`` becomes
249+
``foo/bar`` on Unix, and ``С:\foo\bar`` becomes ``foo\bar`` on Windows.
250+
And all ``".."`` components in a member filename will be removed, e.g.:
251+
``../../foo../../ba..r`` becomes ``foo../ba..r``. On Windows illegal
252+
characters (``:``, ``<``, ``>``, ``|``, ``"``, ``?``, and ``*``)
253+
replaced by underscore (``_``).
254+
245255

246256
.. method:: ZipFile.extractall(path=None, members=None, pwd=None)
247257

@@ -250,12 +260,9 @@ ZipFile Objects
250260
be a subset of the list returned by :meth:`namelist`. *pwd* is the password
251261
used for encrypted files.
252262

253-
.. warning::
263+
.. note::
254264

255-
Never extract archives from untrusted sources without prior inspection.
256-
It is possible that files are created outside of *path*, e.g. members
257-
that have absolute filenames starting with ``"/"`` or filenames with two
258-
dots ``".."``.
265+
See :meth:`extract` note.
259266

260267

261268
.. method:: ZipFile.printdir()

Lib/test/test_zipfile.py

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
SMALL_TEST_DATA = [('_ziptest1', '1q2w3e4r5t'),
2626
('ziptest2dir/_ziptest2', 'qawsedrftg'),
27-
('/ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'),
27+
('ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'),
2828
('ziptest2dir/ziptest3dir/ziptest4dir/_ziptest3', '6y7u8i9o0p')]
2929

3030

@@ -501,10 +501,7 @@ def test_extract(self):
501501
writtenfile = zipfp.extract(fpath)
502502

503503
# make sure it was written to the right place
504-
if os.path.isabs(fpath):
505-
correctfile = os.path.join(os.getcwd(), fpath[1:])
506-
else:
507-
correctfile = os.path.join(os.getcwd(), fpath)
504+
correctfile = os.path.join(os.getcwd(), fpath)
508505
correctfile = os.path.normpath(correctfile)
509506

510507
self.assertEqual(writtenfile, correctfile)
@@ -526,10 +523,7 @@ def test_extract_all(self):
526523
with zipfile.ZipFile(TESTFN2, "r") as zipfp:
527524
zipfp.extractall()
528525
for fpath, fdata in SMALL_TEST_DATA:
529-
if os.path.isabs(fpath):
530-
outfile = os.path.join(os.getcwd(), fpath[1:])
531-
else:
532-
outfile = os.path.join(os.getcwd(), fpath)
526+
outfile = os.path.join(os.getcwd(), fpath)
533527

534528
with open(outfile, "rb") as f:
535529
self.assertEqual(fdata.encode(), f.read())
@@ -539,6 +533,80 @@ def test_extract_all(self):
539533
# remove the test file subdirectories
540534
shutil.rmtree(os.path.join(os.getcwd(), 'ziptest2dir'))
541535

536+
def check_file(self, filename, content):
537+
self.assertTrue(os.path.isfile(filename))
538+
with open(filename, 'rb') as f:
539+
self.assertEqual(f.read(), content)
540+
541+
def test_extract_hackers_arcnames(self):
542+
hacknames = [
543+
('../foo/bar', 'foo/bar'),
544+
('foo/../bar', 'foo/bar'),
545+
('foo/../../bar', 'foo/bar'),
546+
('foo/bar/..', 'foo/bar'),
547+
('./../foo/bar', 'foo/bar'),
548+
('/foo/bar', 'foo/bar'),
549+
('/foo/../bar', 'foo/bar'),
550+
('/foo/../../bar', 'foo/bar'),
551+
('//foo/bar', 'foo/bar'),
552+
('../../foo../../ba..r', 'foo../ba..r'),
553+
]
554+
if os.path.sep == '\\': # Windows.
555+
hacknames.extend([
556+
(r'..\foo\bar', 'foo/bar'),
557+
(r'..\/foo\/bar', 'foo/bar'),
558+
(r'foo/\..\/bar', 'foo/bar'),
559+
(r'foo\/../\bar', 'foo/bar'),
560+
(r'C:foo/bar', 'foo/bar'),
561+
(r'C:/foo/bar', 'foo/bar'),
562+
(r'C://foo/bar', 'foo/bar'),
563+
(r'C:\foo\bar', 'foo/bar'),
564+
(r'//conky/mountpoint/foo/bar', 'foo/bar'),
565+
(r'\\conky\mountpoint\foo\bar', 'foo/bar'),
566+
(r'///conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
567+
(r'\\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
568+
(r'//conky//mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
569+
(r'\\conky\\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
570+
(r'//?/C:/foo/bar', 'foo/bar'),
571+
(r'\\?\C:\foo\bar', 'foo/bar'),
572+
(r'C:/../C:/foo/bar', 'C_/foo/bar'),
573+
(r'a:b\c<d>e|f"g?h*i', 'b/c_d_e_f_g_h_i'),
574+
])
575+
576+
for arcname, fixedname in hacknames:
577+
content = b'foobar' + arcname.encode()
578+
with zipfile.ZipFile(TESTFN2, 'w', zipfile.ZIP_STORED) as zipfp:
579+
zipfp.writestr(arcname, content)
580+
581+
targetpath = os.path.join('target', 'subdir', 'subsub')
582+
correctfile = os.path.join(targetpath, *fixedname.split('/'))
583+
584+
with zipfile.ZipFile(TESTFN2, 'r') as zipfp:
585+
writtenfile = zipfp.extract(arcname, targetpath)
586+
self.assertEqual(writtenfile, correctfile)
587+
self.check_file(correctfile, content)
588+
shutil.rmtree('target')
589+
590+
with zipfile.ZipFile(TESTFN2, 'r') as zipfp:
591+
zipfp.extractall(targetpath)
592+
self.check_file(correctfile, content)
593+
shutil.rmtree('target')
594+
595+
correctfile = os.path.join(os.getcwd(), *fixedname.split('/'))
596+
597+
with zipfile.ZipFile(TESTFN2, 'r') as zipfp:
598+
writtenfile = zipfp.extract(arcname)
599+
self.assertEqual(writtenfile, correctfile)
600+
self.check_file(correctfile, content)
601+
shutil.rmtree(fixedname.split('/')[0])
602+
603+
with zipfile.ZipFile(TESTFN2, 'r') as zipfp:
604+
zipfp.extractall()
605+
self.check_file(correctfile, content)
606+
shutil.rmtree(fixedname.split('/')[0])
607+
608+
os.remove(TESTFN2)
609+
542610
def test_writestr_compression_stored(self):
543611
zipfp = zipfile.ZipFile(TESTFN2, "w")
544612
zipfp.writestr("a.txt", "hello world", compress_type=zipfile.ZIP_STORED)

Lib/zipfile.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,17 +1229,22 @@ def _extract_member(self, member, targetpath, pwd):
12291229
"""
12301230
# build the destination pathname, replacing
12311231
# forward slashes to platform specific separators.
1232-
# Strip trailing path separator, unless it represents the root.
1233-
if (targetpath[-1:] in (os.path.sep, os.path.altsep)
1234-
and len(os.path.splitdrive(targetpath)[1]) > 1):
1235-
targetpath = targetpath[:-1]
1236-
1237-
# don't include leading "/" from file name if present
1238-
if member.filename[0] == '/':
1239-
targetpath = os.path.join(targetpath, member.filename[1:])
1240-
else:
1241-
targetpath = os.path.join(targetpath, member.filename)
1242-
1232+
arcname = member.filename.replace('/', os.path.sep)
1233+
1234+
if os.path.altsep:
1235+
arcname = arcname.replace(os.path.altsep, os.path.sep)
1236+
# interpret absolute pathname as relative, remove drive letter or
1237+
# UNC path, redundant separators, "." and ".." components.
1238+
arcname = os.path.splitdrive(arcname)[1]
1239+
arcname = os.path.sep.join(x for x in arcname.split(os.path.sep)
1240+
if x not in ('', os.path.curdir, os.path.pardir))
1241+
# filter illegal characters on Windows
1242+
if os.path.sep == '\\':
1243+
illegal = ':<>|"?*'
1244+
table = str.maketrans(illegal, '_' * len(illegal))
1245+
arcname = arcname.translate(table)
1246+
1247+
targetpath = os.path.join(targetpath, arcname)
12431248
targetpath = os.path.normpath(targetpath)
12441249

12451250
# Create all upper directories if necessary.

Misc/NEWS

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ Core and Builtins
167167
Library
168168
-------
169169

170+
- Issue #6972: The zipfile module no longer overwrites files outside of
171+
its destination path when extracting malicious zip files.
172+
170173
- Issue #4844: ZipFile now raises BadZipFile when opens a ZIP file with an
171174
incomplete "End of Central Directory" record. Original patch by Guilherme
172175
Polo and Alan McIntyre.
@@ -503,6 +506,12 @@ Library
503506
- Issue #15906: Fix a regression in `argparse` caused by the preceding change,
504507
when ``action='append'``, ``type='str'`` and ``default=[]``.
505508

509+
Extension Modules
510+
-----------------
511+
512+
- Issue #12268: The io module file object write methods no longer abort early
513+
when one of its write system calls is interrupted (EINTR).
514+
506515
Tests
507516
-----
508517

Modules/_io/iobase.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,10 @@ iobase_writelines(PyObject *self, PyObject *args)
669669
break; /* Stop Iteration */
670670
}
671671

672-
res = PyObject_CallMethodObjArgs(self, _PyIO_str_write, line, NULL);
672+
res = NULL;
673+
do {
674+
res = PyObject_CallMethodObjArgs(self, _PyIO_str_write, line, NULL);
675+
} while (res == NULL && _PyIO_trap_eintr());
673676
Py_DECREF(line);
674677
if (res == NULL) {
675678
Py_DECREF(iter);

Modules/_io/textio.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,8 +1247,11 @@ _textiowrapper_writeflush(textio *self)
12471247
Py_DECREF(pending);
12481248
if (b == NULL)
12491249
return -1;
1250-
ret = PyObject_CallMethodObjArgs(self->buffer,
1251-
_PyIO_str_write, b, NULL);
1250+
ret = NULL;
1251+
do {
1252+
ret = PyObject_CallMethodObjArgs(self->buffer,
1253+
_PyIO_str_write, b, NULL);
1254+
} while (ret == NULL && _PyIO_trap_eintr());
12521255
Py_DECREF(b);
12531256
if (ret == NULL)
12541257
return -1;

0 commit comments

Comments
 (0)