summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2024-05-24 17:27:01 (GMT)
committerGitHub <noreply@github.com>2024-05-24 17:27:01 (GMT)
commit5130731c9e779b97d00a24f54cdce73ce9975dfd (patch)
tree9e87cdf79ffcba46651eed0928c66b87b7056084
parentb228655c227b2ca298a8ffac44d14ce3d22f6faa (diff)
downloadcpython-5130731c9e779b97d00a24f54cdce73ce9975dfd.zip
cpython-5130731c9e779b97d00a24f54cdce73ce9975dfd.tar.gz
cpython-5130731c9e779b97d00a24f54cdce73ce9975dfd.tar.bz2
[3.9] gh-118486: Support mkdir(mode=0o700) on Windows (GH-118488) (GH-118741)
Co-authored-by: Ɓukasz Langa <lukasz@langa.pl>
-rw-r--r--Doc/library/os.rst7
-rw-r--r--Doc/whatsnew/3.9.rst15
-rw-r--r--Lib/test/test_os.py12
-rw-r--r--Lib/test/test_tempfile.py28
-rw-r--r--Misc/NEWS.d/next/Security/2024-05-01-20-57-09.gh-issue-118486.K44KJG.rst4
-rw-r--r--Modules/posixmodule.c44
6 files changed, 107 insertions, 3 deletions
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index 35a7e1e..1d10ae7 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -1929,6 +1929,10 @@ features:
platform-dependent. On some platforms, they are ignored and you should call
:func:`chmod` explicitly to set them.
+ On Windows, a *mode* of ``0o700`` is specifically handled to apply access
+ control to the new directory such that only the current user and
+ administrators have access. Other values of *mode* are ignored.
+
This function can also support :ref:`paths relative to directory descriptors
<dir_fd>`.
@@ -1943,6 +1947,9 @@ features:
.. versionchanged:: 3.6
Accepts a :term:`path-like object`.
+ .. versionchanged:: 3.9.20
+ Windows now handles a *mode* of ``0o700``.
+
.. function:: makedirs(name, mode=0o777, exist_ok=False)
diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst
index 1756a37..9383047 100644
--- a/Doc/whatsnew/3.9.rst
+++ b/Doc/whatsnew/3.9.rst
@@ -613,6 +613,13 @@ Added :func:`os.waitstatus_to_exitcode` function:
convert a wait status to an exit code.
(Contributed by Victor Stinner in :issue:`40094`.)
+As of 3.9.20, :func:`os.mkdir` and :func:`os.makedirs` on Windows now support
+passing a *mode* value of ``0o700`` to apply access control to the new
+directory. This implicitly affects :func:`tempfile.mkdtemp` and is a
+mitigation for CVE-2024-4030. Other values for *mode* continue to be
+ignored.
+(Contributed by Steve Dower in :gh:`118486`.)
+
pathlib
-------
@@ -704,6 +711,14 @@ Previously, :attr:`sys.stderr` was block-buffered when non-interactive. Now
``stderr`` defaults to always being line-buffered.
(Contributed by Jendrik Seipp in :issue:`13601`.)
+tempfile
+--------
+
+As of 3.9.20 on Windows, the default mode ``0o700`` used by
+:func:`tempfile.mkdtemp` now limits access to the new directory due to
+changes to :func:`os.mkdir`. This is a mitigation for CVE-2024-4030.
+(Contributed by Steve Dower in :gh:`118486`.)
+
tracemalloc
-----------
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 78dd315..f8b4e09 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -1493,6 +1493,18 @@ class MakedirTests(unittest.TestCase):
self.assertRaises(OSError, os.makedirs, path, exist_ok=True)
os.remove(path)
+ @unittest.skipUnless(os.name == 'nt', "requires Windows")
+ def test_win32_mkdir_700(self):
+ base = support.TESTFN
+ path = os.path.abspath(os.path.join(support.TESTFN, 'dir'))
+ os.mkdir(path, mode=0o700)
+ out = subprocess.check_output(["cacls.exe", path, "/s"], encoding="oem")
+ os.rmdir(path)
+ self.assertEqual(
+ out.strip(),
+ f'{path} "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)"',
+ )
+
def tearDown(self):
path = os.path.join(support.TESTFN, 'dir1', 'dir2', 'dir3',
'dir4', 'dir5', 'dir6')
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
index 571263d..8d13c22 100644
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -11,6 +11,7 @@ import contextlib
import stat
import types
import weakref
+import subprocess
from unittest import mock
import unittest
@@ -772,6 +773,33 @@ class TestMkdtemp(TestBadTempdir, BaseTestCase):
finally:
os.rmdir(dir)
+ @unittest.skipUnless(os.name == "nt", "Only on Windows.")
+ def test_mode_win32(self):
+ # Use icacls.exe to extract the users with some level of access
+ # Main thing we are testing is that the BUILTIN\Users group has
+ # no access. The exact ACL is going to vary based on which user
+ # is running the test.
+ dir = self.do_create()
+ try:
+ out = subprocess.check_output(["icacls.exe", dir], encoding="oem").casefold()
+ finally:
+ os.rmdir(dir)
+
+ dir = dir.casefold()
+ users = set()
+ found_user = False
+ for line in out.strip().splitlines():
+ acl = None
+ # First line of result includes our directory
+ if line.startswith(dir):
+ acl = line.removeprefix(dir).strip()
+ elif line and line[:1].isspace():
+ acl = line.strip()
+ if acl:
+ users.add(acl.partition(":")[0])
+
+ self.assertNotIn(r"BUILTIN\Users".casefold(), users)
+
def test_collision_with_existing_file(self):
# mkdtemp tries another name when a file with
# the chosen name already exists
diff --git a/Misc/NEWS.d/next/Security/2024-05-01-20-57-09.gh-issue-118486.K44KJG.rst b/Misc/NEWS.d/next/Security/2024-05-01-20-57-09.gh-issue-118486.K44KJG.rst
new file mode 100644
index 0000000..a28a4e5
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2024-05-01-20-57-09.gh-issue-118486.K44KJG.rst
@@ -0,0 +1,4 @@
+:func:`os.mkdir` on Windows now accepts *mode* of ``0o700`` to restrict
+the new directory to the current user. This fixes CVE-2024-4030
+affecting :func:`tempfile.mkdtemp` in scenarios where the base temporary
+directory is more permissive than the default.
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index bf4e648..7645bcf 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -24,6 +24,12 @@
#include "pycore_ceval.h" // _PyEval_ReInitThreads()
#include "pycore_import.h" // _PyImport_ReInitLock()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
+
+#ifdef MS_WINDOWS
+# include <aclapi.h> // SetEntriesInAcl
+# include <sddl.h> // SDDL_REVISION_1
+#endif
+
#include "structmember.h" // PyMemberDef
#ifndef MS_WINDOWS
# include "posixmodule.h"
@@ -4425,7 +4431,6 @@ os__path_splitroot_impl(PyObject *module, path_t *path)
#endif /* MS_WINDOWS */
-
/*[clinic input]
os.mkdir
@@ -4454,6 +4459,12 @@ os_mkdir_impl(PyObject *module, path_t *path, int mode, int dir_fd)
/*[clinic end generated code: output=a70446903abe821f input=e965f68377e9b1ce]*/
{
int result;
+#ifdef MS_WINDOWS
+ int error = 0;
+ int pathError = 0;
+ SECURITY_ATTRIBUTES secAttr = { sizeof(secAttr) };
+ SECURITY_ATTRIBUTES *pSecAttr = NULL;
+#endif
#ifdef HAVE_MKDIRAT
int mkdirat_unavailable = 0;
#endif
@@ -4465,11 +4476,38 @@ os_mkdir_impl(PyObject *module, path_t *path, int mode, int dir_fd)
#ifdef MS_WINDOWS
Py_BEGIN_ALLOW_THREADS
- result = CreateDirectoryW(path->wide, NULL);
+ if (mode == 0700 /* 0o700 */) {
+ ULONG sdSize;
+ pSecAttr = &secAttr;
+ // Set a discretionary ACL (D) that is protected (P) and includes
+ // inheritable (OICI) entries that allow (A) full control (FA) to
+ // SYSTEM (SY), Administrators (BA), and the owner (OW).
+ if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
+ L"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)",
+ SDDL_REVISION_1,
+ &secAttr.lpSecurityDescriptor,
+ &sdSize
+ )) {
+ error = GetLastError();
+ }
+ }
+ if (!error) {
+ result = CreateDirectoryW(path->wide, pSecAttr);
+ if (secAttr.lpSecurityDescriptor &&
+ // uncommonly, LocalFree returns non-zero on error, but still uses
+ // GetLastError() to see what the error code is
+ LocalFree(secAttr.lpSecurityDescriptor)) {
+ error = GetLastError();
+ }
+ }
Py_END_ALLOW_THREADS
- if (!result)
+ if (error) {
+ return PyErr_SetFromWindowsErr(error);
+ }
+ if (!result) {
return path_error(path);
+ }
#else
Py_BEGIN_ALLOW_THREADS
#if HAVE_MKDIRAT