Skip to content

Commit 9cadca5

Browse files
serhiy-storchakaaisk
authored andcommitted
pythongh-94692: Only catch OSError in shutil.rmtree() (python#112756)
Previously a symlink attack resistant version of shutil.rmtree() could ignore or pass to the error handler arbitrary exception when invalid arguments were provided.
1 parent d18447d commit 9cadca5

File tree

3 files changed

+22
-19
lines changed

3 files changed

+22
-19
lines changed

Lib/shutil.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -768,13 +768,13 @@ def onexc(*args):
768768
# lstat()/open()/fstat() trick.
769769
try:
770770
orig_st = os.lstat(path, dir_fd=dir_fd)
771-
except Exception as err:
771+
except OSError as err:
772772
onexc(os.lstat, path, err)
773773
return
774774
try:
775775
fd = os.open(path, os.O_RDONLY, dir_fd=dir_fd)
776776
fd_closed = False
777-
except Exception as err:
777+
except OSError as err:
778778
onexc(os.open, path, err)
779779
return
780780
try:

Lib/test/test_shutil.py

+16-17
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ def test_rmtree_works_on_junctions(self):
317317
self.assertTrue(os.path.exists(dir3))
318318
self.assertTrue(os.path.exists(file1))
319319

320-
def test_rmtree_errors_onerror(self):
320+
def test_rmtree_errors(self):
321321
# filename is guaranteed not to exist
322322
filename = tempfile.mktemp(dir=self.mkdtemp())
323323
self.assertRaises(FileNotFoundError, shutil.rmtree, filename)
@@ -326,15 +326,28 @@ def test_rmtree_errors_onerror(self):
326326

327327
# existing file
328328
tmpdir = self.mkdtemp()
329-
write_file((tmpdir, "tstfile"), "")
330329
filename = os.path.join(tmpdir, "tstfile")
330+
write_file(filename, "")
331331
with self.assertRaises(NotADirectoryError) as cm:
332332
shutil.rmtree(filename)
333333
self.assertEqual(cm.exception.filename, filename)
334334
self.assertTrue(os.path.exists(filename))
335335
# test that ignore_errors option is honored
336336
shutil.rmtree(filename, ignore_errors=True)
337337
self.assertTrue(os.path.exists(filename))
338+
339+
self.assertRaises(TypeError, shutil.rmtree, None)
340+
self.assertRaises(TypeError, shutil.rmtree, None, ignore_errors=True)
341+
exc = TypeError if shutil.rmtree.avoids_symlink_attacks else NotImplementedError
342+
with self.assertRaises(exc):
343+
shutil.rmtree(filename, dir_fd='invalid')
344+
with self.assertRaises(exc):
345+
shutil.rmtree(filename, dir_fd='invalid', ignore_errors=True)
346+
347+
def test_rmtree_errors_onerror(self):
348+
tmpdir = self.mkdtemp()
349+
filename = os.path.join(tmpdir, "tstfile")
350+
write_file(filename, "")
338351
errors = []
339352
def onerror(*args):
340353
errors.append(args)
@@ -350,23 +363,9 @@ def onerror(*args):
350363
self.assertEqual(errors[1][2][1].filename, filename)
351364

352365
def test_rmtree_errors_onexc(self):
353-
# filename is guaranteed not to exist
354-
filename = tempfile.mktemp(dir=self.mkdtemp())
355-
self.assertRaises(FileNotFoundError, shutil.rmtree, filename)
356-
# test that ignore_errors option is honored
357-
shutil.rmtree(filename, ignore_errors=True)
358-
359-
# existing file
360366
tmpdir = self.mkdtemp()
361-
write_file((tmpdir, "tstfile"), "")
362367
filename = os.path.join(tmpdir, "tstfile")
363-
with self.assertRaises(NotADirectoryError) as cm:
364-
shutil.rmtree(filename)
365-
self.assertEqual(cm.exception.filename, filename)
366-
self.assertTrue(os.path.exists(filename))
367-
# test that ignore_errors option is honored
368-
shutil.rmtree(filename, ignore_errors=True)
369-
self.assertTrue(os.path.exists(filename))
368+
write_file(filename, "")
370369
errors = []
371370
def onexc(*args):
372371
errors.append(args)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:func:`shutil.rmtree` now only catches OSError exceptions. Previously a
2+
symlink attack resistant version of ``shutil.rmtree()`` could ignore or pass
3+
to the error handler arbitrary exception when invalid arguments were
4+
provided.

0 commit comments

Comments
 (0)