summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorChristian Heimes <christian@python.org>2022-06-27 18:27:19 (GMT)
committerGitHub <noreply@github.com>2022-06-27 18:27:19 (GMT)
commit7e0d98ecb3f049841de9854e7a3eca3e2638e4b2 (patch)
treedbc35d553d10f812f9f4dd479c76715824a6799a /Lib
parent117217245390ed3e6fe166180f052e022d296921 (diff)
downloadcpython-7e0d98ecb3f049841de9854e7a3eca3e2638e4b2.zip
cpython-7e0d98ecb3f049841de9854e7a3eca3e2638e4b2.tar.gz
cpython-7e0d98ecb3f049841de9854e7a3eca3e2638e4b2.tar.bz2
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.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/support/os_helper.py44
-rw-r--r--Lib/test/test_argparse.py13
-rw-r--r--Lib/test/test_import/__init__.py3
-rw-r--r--Lib/test/test_py_compile.py3
-rw-r--r--Lib/test/test_shutil.py15
5 files changed, 56 insertions, 22 deletions
diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py
index 589ef19..4edb1ab 100644
--- a/Lib/test/support/os_helper.py
+++ b/Lib/test/support/os_helper.py
@@ -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)
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 673ef89..425b6bb 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -1734,8 +1734,7 @@ class WFile(object):
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 @@ class TestFileTypeW(TempDirMixin, ParserTestCase):
('-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 @@ class TestFileTypeX(TempDirMixin, ParserTestCase):
]
-@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"
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index ab7f887..6c5b80b 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -885,10 +885,9 @@ class PycacheTests(unittest.TestCase):
@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
diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
index f494aed..a4a52b1 100644
--- a/Lib/test/test_py_compile.py
+++ b/Lib/test/test_py_compile.py
@@ -115,8 +115,7 @@ class PyCompileTestsBase:
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
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index 6fa0739..a2c4ab5 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -312,8 +312,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
@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 @@ class TestCopy(BaseTest, unittest.TestCase):
@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 @@ class TestWhich(BaseTest, unittest.TestCase):
# 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 @@ class TestMove(BaseTest, unittest.TestCase):
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.