diff options
author | Christian Heimes <christian@python.org> | 2022-06-27 18:27:19 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-27 18:27:19 (GMT) |
commit | 7e0d98ecb3f049841de9854e7a3eca3e2638e4b2 (patch) | |
tree | dbc35d553d10f812f9f4dd479c76715824a6799a /Lib | |
parent | 117217245390ed3e6fe166180f052e022d296921 (diff) | |
download | cpython-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.py | 44 | ||||
-rw-r--r-- | Lib/test/test_argparse.py | 13 | ||||
-rw-r--r-- | Lib/test/test_import/__init__.py | 3 | ||||
-rw-r--r-- | Lib/test/test_py_compile.py | 3 | ||||
-rw-r--r-- | Lib/test/test_shutil.py | 15 |
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. |