mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
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:
parent
1172172453
commit
7e0d98ecb3
6 changed files with 58 additions and 22 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Tests now check for DAC override capability instead of relying on
|
||||
:func:`os.geteuid`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue