gh-94315: Check for DAC override capability (GH-94316)

``os.geteuid() == 0`` is not a reliable check whether the current user
has the capability to bypass permission checks. Tests now probe for DAC
override.
This commit is contained in:
Christian Heimes 2022-06-27 20:27:19 +02:00 committed by GitHub
parent 1172172453
commit 7e0d98ecb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 22 deletions

View file

@ -263,7 +263,7 @@ def can_chmod():
else:
can = stat.S_IMODE(mode1) != stat.S_IMODE(mode2)
finally:
os.unlink(TESTFN)
unlink(TESTFN)
_can_chmod = can
return can
@ -278,6 +278,48 @@ def skip_unless_working_chmod(test):
return test if ok else unittest.skip(msg)(test)
# Check whether the current effective user has the capability to override
# DAC (discretionary access control). Typically user root is able to
# bypass file read, write, and execute permission checks. The capability
# is independent of the effective user. See capabilities(7).
_can_dac_override = None
def can_dac_override():
global _can_dac_override
if not can_chmod():
_can_dac_override = False
if _can_dac_override is not None:
return _can_dac_override
try:
with open(TESTFN, "wb") as f:
os.chmod(TESTFN, 0o400)
try:
with open(TESTFN, "wb"):
pass
except OSError:
_can_dac_override = False
else:
_can_dac_override = True
finally:
unlink(TESTFN)
return _can_dac_override
def skip_if_dac_override(test):
ok = not can_dac_override()
msg = "incompatible with CAP_DAC_OVERRIDE"
return test if ok else unittest.skip(msg)(test)
def skip_unless_dac_override(test):
ok = can_dac_override()
msg = "requires CAP_DAC_OVERRIDE"
return test if ok else unittest.skip(msg)(test)
def unlink(filename):
try:
_unlink(filename)

View file

@ -1734,8 +1734,7 @@ def __eq__(self, other):
return self.name == other.name
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"non-root user required")
@os_helper.skip_if_dac_override
class TestFileTypeW(TempDirMixin, ParserTestCase):
"""Test the FileType option/argument type for writing files"""
@ -1757,8 +1756,8 @@ def setUp(self):
('-x - -', NS(x=eq_stdout, spam=eq_stdout)),
]
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"non-root user required")
@os_helper.skip_if_dac_override
class TestFileTypeX(TempDirMixin, ParserTestCase):
"""Test the FileType option/argument type for writing new files only"""
@ -1778,8 +1777,7 @@ def setUp(self):
]
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"non-root user required")
@os_helper.skip_if_dac_override
class TestFileTypeWB(TempDirMixin, ParserTestCase):
"""Test the FileType option/argument type for writing binary files"""
@ -1796,8 +1794,7 @@ class TestFileTypeWB(TempDirMixin, ParserTestCase):
]
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"non-root user required")
@os_helper.skip_if_dac_override
class TestFileTypeXB(TestFileTypeX):
"Test the FileType option/argument type for writing new binary files only"

View file

@ -885,10 +885,9 @@ def test_import_pyc_path(self):
@unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems")
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"due to varying filesystem permission semantics (issue #11956)")
@skip_if_dont_write_bytecode
@os_helper.skip_unless_working_chmod
@os_helper.skip_if_dac_override
@unittest.skipIf(is_emscripten, "umask is a stub")
def test_unwritable_directory(self):
# When the umask causes the new __pycache__ directory to be

View file

@ -115,8 +115,7 @@ def test_relative_path(self):
self.assertTrue(os.path.exists(self.pyc_path))
self.assertFalse(os.path.exists(self.cache_path))
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
'non-root user required')
@os_helper.skip_if_dac_override
@unittest.skipIf(os.name == 'nt',
'cannot control directory permissions on Windows')
@os_helper.skip_unless_working_chmod

View file

@ -312,8 +312,7 @@ def onerror(*args):
@unittest.skipIf(sys.platform[:6] == 'cygwin',
"This test can't be run on Cygwin (issue #1071513).")
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"This test can't be run reliably as root (issue #1076467).")
@os_helper.skip_if_dac_override
@os_helper.skip_unless_working_chmod
def test_on_error(self):
self.errorState = 0
@ -1033,8 +1032,7 @@ def _raise_on_src(fname, *, follow_symlinks=True):
@os_helper.skip_unless_symlink
@os_helper.skip_unless_xattr
@unittest.skipUnless(hasattr(os, 'geteuid') and os.geteuid() == 0,
'root privileges required')
@os_helper.skip_unless_dac_override
def test_copyxattr_symlinks(self):
# On Linux, it's only possible to access non-user xattr for symlinks;
# which in turn require root privileges. This test should be expanded
@ -1830,8 +1828,7 @@ def test_cwd(self):
# Other platforms: shouldn't match in the current directory.
self.assertIsNone(rv)
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
'non-root user required')
@os_helper.skip_if_dac_override
def test_non_matching_mode(self):
# Set the file read-only and ask for writeable files.
os.chmod(self.temp_file.name, stat.S_IREAD)
@ -2182,11 +2179,11 @@ def test_move_dir_caseinsensitive(self):
os.rmdir(dst_dir)
@unittest.skipUnless(hasattr(os, 'geteuid') and os.geteuid() == 0
and hasattr(os, 'lchflags')
@os_helper.skip_unless_dac_override
@unittest.skipUnless(hasattr(os, 'lchflags')
and hasattr(stat, 'SF_IMMUTABLE')
and hasattr(stat, 'UF_OPAQUE'),
'root privileges required')
'requires lchflags')
def test_move_dir_permission_denied(self):
# bpo-42782: shutil.move should not create destination directories
# if the source directory cannot be removed.

View file

@ -0,0 +1,2 @@
Tests now check for DAC override capability instead of relying on
:func:`os.geteuid`.