8000 gh-94315: Check for DAC override capability (GH-94316) · python/cpython@7e0d98e · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7e0d98e

Browse files
authored
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.
1 parent 1172172 commit 7e0d98e

File tree

6 files changed

+58
-22
lines changed

6 files changed

+58
-22
lines changed

Lib/test/support/os_helper.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def can_chmod():
263263
else:
264264
can = stat.S_IMODE(mode1) != stat.S_IMODE(mode2)
265265
finally:
266-
os.unlink(TESTFN)
266+
unlink(TESTFN)
267267
_can_chmod = can
268268
return can
269269

@@ -278,6 +278,48 @@ def skip_unless_working_chmod(test):
278278
return test if ok else unittest.skip(msg)(test)
279279

280280

281+
# Check whether the current effective user has the capability to override
282+
# DAC (discretionary access control). Typically user root is able to
283+
# bypass file read, write, and execute permission checks. The capability
284+
# is independent of the effective user. See capabilities(7).
285+
_can_dac_override = None
286+
287+
def can_dac_override():
288+
global _can_dac_override
289+
290+
if not can_chmod():
291+
_can_dac_override = False
292+
if _can_dac_override is not None:
293+
return _can_dac_override
294+
295+
try:
296+
with open(TESTFN, "wb") as f:
297+
os.chmod(TESTFN, 0o400)
298+
try:
299+
with open(TESTFN, "wb"):
300+
pass
301+
except OSError:
302+
_can_dac_override = False
303+
else:
304+
_can_dac_override = True
305+
finally:
306+
unlink(TESTFN)
307+
308+
return _can_dac_override
309+
310+
311+
def skip_if_dac_override(test):
312+
ok = not can_dac_override()
313+
msg = "incompatible with CAP_DAC_OVERRIDE"
314+
return test if ok else unittest.skip(msg)(test)
315+
316+
317+
def skip_unless_dac_override(test):
318+
ok = can_dac_override()
319+
msg = "requires CAP_DAC_OVERRIDE"
320+
return test if ok else unittest.skip(msg)(test)
321+
322+
281323
def unlink(filename):
282324
try:
283325
_unlink(filename)

Lib/test/test_argparse.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,8 +1734,7 @@ def __eq__(self, other):
17341734
return self.name == other.name
17351735

17361736

1737-
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
1738-
"non-root user required")
1737+
@os_helper.skip_if_dac_override
17391738
class TestFileTypeW(TempDirMixin, ParserTestCase):
17401739
"""Test the FileType option/argument type for writing files"""
17411740

@@ -1757,8 +1756,8 @@ def setUp(self):
17571756
('-x - -', NS(x=eq_stdout, spam=eq_stdout)),
17581757
]
17591758

1760-
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
1761-
"non-root user required")
1759+
1760+
@os_helper.skip_if_dac_override
17621761
class TestFileTypeX(TempDirMixin, ParserTestCase):
17631762
"""Test the FileType option/argument type for writing new files only"""
17641763

@@ -1778,8 +1777,7 @@ def setUp(self):
17781777
]
17791778

17801779

1781-
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
1782-
"non-root user required")
1780+
@os_helper.skip_if_dac_override
17831781
class TestFileTypeWB(TempDirMixin, ParserTestCase):
17841782
"""Test the FileType option/argument type for writing binary files"""
17851783

@@ -1796,8 +1794,7 @@ class TestFileTypeWB(TempDirMixin, ParserTestCase):
17961794
]
17971795

17981796

1799-
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
1800-
"non-root user required")
1797+
@os_helper.skip_if_dac_override
18011798
class TestFileTypeXB(TestFileTypeX):
18021799
"Test the FileType option/argument type for writing new binary files only"
18031800

Lib/test/test_import/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -885,10 +885,9 @@ def test_import_pyc_path(self):
885885

886886
@unittest.skipUnless(os.name == 'posix',
887887
"test meaningful only on posix systems")
888-
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
889-
"due to varying filesystem permission semantics (issue #11956)")
890888
@skip_if_dont_write_bytecode
891889
@os_helper.skip_unless_working_chmod
890+
@os_helper.skip_if_dac_override
892891
@unittest.skipIf(is_emscripten, "umask is a stub")
893892
def test_unwritable_directory(self):
894893
# When the umask causes the new __pycache__ directory to be

Lib/test/test_py_compile.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,7 @@ def test_relative_path(self):
115115
self.assertTrue(os.path.exists(self.pyc_path))
116116
self.assertFalse(os.path.exists(self.cache_path))
117117

118-
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
119-
'non-root user required')
118+
@os_helper.skip_if_dac_override
120119
@unittest.skipIf(os.name == 'nt',
121120
'cannot control directory permissions on Windows')
122121
@os_helper.skip_unless_working_chmod

Lib/test/test_shutil.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,7 @@ def onerror(*args):
312312

313313
@unittest.skipIf(sys.platform[:6] == 'cygwin',
314314
"This test can't be run on Cygwin (issue #1071513).")
315-
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
316-
"This test can't be run reliably as root (issue #1076467).")
315+
@os_helper.skip_if_dac_override
317316
@os_helper.skip_unless_working_chmod
318317
def test_on_error(self):
319318
self.errorState = 0
@@ -1033,8 +1032,7 @@ def _raise_on_src(fname, *, follow_symlinks=True):
10331032

10341033
@os_helper.skip_unless_symlink
10351034
@os_helper.skip_unless_xattr
1036-
@unittest.skipUnless(hasattr(os, 'geteuid') and os.geteuid() == 0,
1037-
'root privileges required')
1035+
@os_helper.skip_unless_dac_override
10381036
def test_copyxattr_symlinks(self):
10391037
# On Linux, it's only possible to access non-user xattr for symlinks;
10401038
# which in turn require root privileges. This test should be expanded
@@ -1830,8 +1828,7 @@ def test_cwd(self):
18301828
# Other platforms: shouldn't match in the current directory.
18311829
self.assertIsNone(rv)
18321830

1833-
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
1834-
'non-root user required')
1831+
@os_helper.skip_if_dac_override
18351832
def test_non_matching_mode(self):
18361833
# Set the file read-only and ask for writeable files.
18371834
os.chmod(self.temp_file.name, stat.S_IREAD)
@@ -2182,11 +2179,11 @@ def test_move_dir_caseinsensitive(self):
21822179
os.rmdir(dst_dir)
21832180

21842181

2185-
@unittest.skipUnless(hasattr(os, 'geteuid') and os.geteuid() == 0
2186-
and hasattr(os, 'lchflags')
2182+
@os_helper.skip_unless_dac_override
2183+
@unittest.skipUnless(hasattr(os, 'lchflags')
21872184
and hasattr(stat, 'SF_IMMUTABLE')
21882185
and hasattr(stat, 'UF_OPAQUE'),
2189-
'root privileges required')
2186+
'requires lchflags')
21902187
def test_move_dir_permission_denied(self):
21912188
# bpo-42782: shutil.move should not create destination directories
21922189
# if the source directory cannot be removed.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Tests now check for DAC override capability instead of relying on
2+
:func:`os.geteuid`.

0 commit comments

Comments
 (0)
0