diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py index 814f945aac7453c..4c5082831632db1 100644 --- a/Lib/test/test_os/test_posix.py +++ b/Lib/test/test_os/test_posix.py @@ -1109,6 +1109,30 @@ def test_fchmod_file(self): self.check_chmod(posix.fchmod, f.fileno()) self.check_chmod(posix.chmod, f.fileno()) + @os_helper.skip_unless_working_chmod + @unittest.skipUnless(os.chmod in os.supports_fd, + "test needs fd support in os.chmod()") + @unittest.skipUnless(os.chmod in os.supports_dir_fd, + "test needs dir_fd support in os.chmod()") + def test_chmod_fd_with_dir_fd(self): + with open(os_helper.TESTFN, 'wb+') as f: + dir_fd = os.open(os.curdir, os.O_RDONLY) + self.addCleanup(os.close, dir_fd) + with self.assertRaisesRegex(ValueError, + 'can\'t specify both dir_fd and fd'): + posix.chmod(f.fileno(), 0o600, dir_fd=dir_fd) + + @os_helper.skip_unless_working_chmod + @unittest.skipUnless(os.chmod in os.supports_fd, + "test needs fd support in os.chmod()") + @unittest.skipIf(os.name == 'nt', + 'follow_symlinks defaults to False on Windows') + def test_chmod_fd_follow_symlinks(self): + with open(os_helper.TESTFN, 'wb+') as f: + with self.assertRaisesRegex(ValueError, + 'cannot use fd and follow_symlinks together'): + posix.chmod(f.fileno(), 0o600, follow_symlinks=False) + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') def test_lchmod_file(self): self.check_chmod(posix.lchmod, os_helper.TESTFN) diff --git a/Misc/NEWS.d/next/Library/2026-08-23-08-41-00.gh-issue-156264.hJ8qJn.rst b/Misc/NEWS.d/next/Library/2026-08-23-08-41-00.gh-issue-156264.hJ8qJn.rst new file mode 100644 index 000000000000000..836e804182276c8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-23-08-41-00.gh-issue-156264.hJ8qJn.rst @@ -0,0 +1,3 @@ +:func:`os.chmod` now raises :exc:`ValueError` when *dir_fd* or +*follow_symlinks* is used together with a file descriptor *path*, matching +the documented behaviour and :func:`os.chown`. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index bec305a4042c49d..148abb170c30984 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4113,6 +4113,20 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd, return NULL; #endif + if (dir_fd_and_fd_invalid("chmod", dir_fd, path->fd)) { + return NULL; + } + +#ifndef MS_WINDOWS + /* On Windows, follow_symlinks defaults to False, so doing this check + would reject the valid os.chmod(fd, mode). */ + if (fd_and_follow_symlinks_invalid("chmod", + path->is_fd, + follow_symlinks)) { + return NULL; + } +#endif + if (PySys_Audit("os.chmod", "Oii", path->object, mode, dir_fd == DEFAULT_DIR_FD ? -1 : dir_fd) < 0) { return NULL;