1
0
mirror of https://github.com/samba-team/samba.git synced 2025-12-19 12:23:49 +03:00
Files
samba-mirror/python/samba/tests/safe_tarfile.py
Douglas Bagnall 7a5b22e422 pytest: safe_tarfile: accept NotADirectoryError as bad path rejection
After recent upstream Python fixes for various path escape and symlink
CVEs in tarfile, in particular this commit:

  commit 3612d8f51741b11f36f8fb0494d79086bac9390a
  Author: Łukasz Langa <lukasz@langa.pl>
  Date:   Tue Jun 3 12:42:11 2025 +0200

  gh-135034: Normalize link targets in tarfile, add `os.path.realpath(strict='allow_missing')` (#135037)

  Addresses CVEs 2024-12718, 2025-4138, 2025-4330, and 2025-4517.

  Signed-off-by: Łukasz Langa <lukasz@langa.pl>
Reviewed-by: Jennifer Sutton <jennifersutton@catalyst.net.nz>
  Co-authored-by: Petr Viktorin <encukou@gmail.com>
  Co-authored-by: Seth Michael Larson <seth@python.org>
  Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
  Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>

our ../../ test that looks for a tarfile.OutsideDestinationError now
meets a NotADirectoryError in recent Python versions (this from 3.13,
Fedora 42):

UNEXPECTED(error): samba.tests.safe_tarfile.samba.tests.safe_tarfile.SafeTarFileTestCase.test_dots(none)
REASON: Exception: Exception: Traceback (most recent call last):
  File "/tmp/samba-testbase/b1/samba-o3/bin/python/samba/tests/safe_tarfile.py", line 48, in test_dots
    self.assertRaises(tarfile.OutsideDestinationError,
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                      stf.extractall,
                      ^^^^^^^^^^^^^^^
                      tarname)
                      ^^^^^^^^
  File "/usr/lib64/python3.13/unittest/case.py", line 795, in assertRaises
    return context.handle('assertRaises', args, kwargs)
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.13/unittest/case.py", line 238, in handle
    callable_obj(*args, **kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.13/tarfile.py", line 2343, in extractall
    tarinfo, unfiltered = self._get_extract_tarinfo(
                          ~~~~~~~~~~~~~~~~~~~~~~~~~^
        member, filter_function, path)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.13/tarfile.py", line 2432, in _get_extract_tarinfo
    self._handle_fatal_error(e)
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^
  File "/usr/lib64/python3.13/tarfile.py", line 2430, in _get_extract_tarinfo
    filtered = filter_function(unfiltered, path)
  File "/usr/lib64/python3.13/tarfile.py", line 842, in tar_filter
    new_attrs = _get_filtered_attrs(member, dest_path, False)
  File "/usr/lib64/python3.13/tarfile.py", line 783, in _get_filtered_attrs
    target_path = os.path.realpath(os.path.join(dest_path, name),
                                   strict=os.path.ALLOW_MISSING)
  File "<frozen posixpath>", line 457, in realpath
NotADirectoryError: [Errno 20] Not a directory: '/tmp/samba-testbase/b1/samba-o3/bin/ab/tmp/tmpbn6e69ci/tar.tar'

In this commit, we say that a NotADirectoryError is OK.

When we started safe_tarfile we were acting in advance of upstream
Python, but now they are well ahead of us. If we trust their work in
recent versions and accept the error conditions they choose, we can
more easily get rid of our safe_tarfile when the time is right.

For the moment we still support as far back as Python 3.6 for some old
enterprise distros, and it is for those that we continue to maintain
safe_tarfile. In versions before 3.11 we will see
tarfile.ExtractError, and the test for that is unaffected by this
change.

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
2025-08-26 22:42:39 +00:00

83 lines
2.6 KiB
Python

# Unix SMB/CIFS implementation.
# Copyright (C) Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import tarfile
from samba import safe_tarfile
import os
from samba.tests import TestCaseInTempDir
def filterer(prefix):
def f(info):
info.name = prefix + info.name
return info
return f
class SafeTarFileTestCase(TestCaseInTempDir):
def test_dots(self):
filename = os.path.join(self.tempdir, 'x')
tarname = os.path.join(self.tempdir, 'tar.tar')
f = open(filename, 'w')
f.write('x')
f.close()
tf = tarfile.open(tarname, 'w')
tf.add(filename, filter=filterer('../../'))
tf.close()
stf = safe_tarfile.open(tarname)
# If we have data_filter, we have a patched python to address
# CVE-2007-4559.
if hasattr(tarfile, "data_filter"):
self.assertRaises((tarfile.OutsideDestinationError,
NotADirectoryError),
stf.extractall,
tarname)
else:
self.assertRaises(tarfile.ExtractError,
stf.extractall,
tarname)
self.rm_files('x', 'tar.tar')
def test_slash(self):
filename = os.path.join(self.tempdir, 'x')
tarname = os.path.join(self.tempdir, 'tar.tar')
f = open(filename, 'w')
f.write('x')
f.close()
tf = tarfile.open(tarname, 'w')
tf.add(filename, filter=filterer('/'))
tf.close()
stf = safe_tarfile.open(tarname)
# If we have data_filter, we have a patched python to address
# CVE-2007-4559.
if hasattr(tarfile, "data_filter"):
self.assertRaises(NotADirectoryError,
stf.extractall,
tarname)
else:
self.assertRaises(tarfile.ExtractError,
stf.extractall,
tarname)
self.rm_files('x', 'tar.tar')