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 35c799d

Browse filesBrowse files
authored
[3.11] gh-118486: Support mkdir(mode=0o700) on Windows (GH-118488) (GH-118739)
1 parent 4762b36 commit 35c799d
Copy full SHA for 35c799d

File tree

Expand file treeCollapse file tree

6 files changed

+103
-2
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+103
-2
lines changed

‎Doc/library/os.rst

Copy file name to clipboardExpand all lines: Doc/library/os.rst
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,6 +2178,10 @@ features:
21782178
platform-dependent. On some platforms, they are ignored and you should call
21792179
:func:`chmod` explicitly to set them.
21802180

2181+
On Windows, a *mode* of ``0o700`` is specifically handled to apply access
2182+
control to the new directory such that only the current user and
2183+
administrators have access. Other values of *mode* are ignored.
2184+
21812185
This function can also support :ref:`paths relative to directory descriptors
21822186
<dir_fd>`.
21832187

@@ -2192,6 +2196,9 @@ features:
21922196
.. versionchanged:: 3.6
21932197
Accepts a :term:`path-like object`.
21942198

2199+
.. versionchanged:: 3.11.10
2200+
Windows now handles a *mode* of ``0o700``.
2201+
21952202

21962203
.. function:: makedirs(name, mode=0o777, exist_ok=False)
21972204

‎Doc/whatsnew/3.11.rst

Copy file name to clipboardExpand all lines: Doc/whatsnew/3.11.rst
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,13 @@ os
899899
instead of ``CryptGenRandom()`` which is deprecated.
900900
(Contributed by Donghee Na in :issue:`44611`.)
901901

902+
* As of 3.11.10, :func:`os.mkdir` and :func:`os.makedirs` on Windows
903+
now support passing a *mode* value of ``0o700`` to apply access
904+
control to the new directory. This implicitly affects
905+
:func:`tempfile.mkdtemp` and is a mitigation for CVE-2024-4030.
906+
Other values for *mode* continue to be ignored.
907+
(Contributed by Steve Dower in :gh:`118486`.)
908+
902909

903910
.. _whatsnew311-pathlib:
904911

@@ -1059,6 +1066,11 @@ tempfile
10591066
such as compression modules.
10601067
(Contributed by Carey Metcalfe in :gh:`70363`.)
10611068

1069+
* As of 3.11.10 on Windows, the default mode ``0o700`` used by
1070+
:func:`tempfile.mkdtemp` now limits access to the new directory due to
1071+
changes to :func:`os.mkdir`. This is a mitigation for CVE-2024-4030.
1072+
(Contributed by Steve Dower in :gh:`118486`.)
1073+
10621074

10631075
.. _whatsnew311-threading:
10641076

‎Lib/test/test_os.py

Copy file name to clipboardExpand all lines: Lib/test/test_os.py
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,6 +1743,18 @@ def test_exist_ok_existing_regular_file(self):
17431743
self.assertRaises(OSError, os.makedirs, path, exist_ok=True)
17441744
os.remove(path)
17451745

1746+
@unittest.skipUnless(os.name == 'nt', "requires Windows")
1747+
def test_win32_mkdir_700(self):
1748+
base = os_helper.TESTFN
1749+
path = os.path.abspath(os.path.join(os_helper.TESTFN, 'dir'))
1750+
os.mkdir(path, mode=0o700)
1751+
out = subprocess.check_output(["cacls.exe", path, "/s"], encoding="oem")
1752+
os.rmdir(path)
1753+
self.assertEqual(
1754+
out.strip(),
1755+
f'{path} "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)"',
1756+
)
1757+
17461758
def tearDown(self):
17471759
path = os.path.join(os_helper.TESTFN, 'dir1', 'dir2', 'dir3',
17481760
'dir4', 'dir5', 'dir6')

‎Lib/test/test_tempfile.py

Copy file name to clipboardExpand all lines: Lib/test/test_tempfile.py
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import stat
1212
import types
1313
import weakref
14+
import subprocess
1415
from unittest import mock
1516

1617
import unittest
@@ -801,6 +802,33 @@ def test_mode(self):
801802
finally:
802803
os.rmdir(dir)
803804

805+
@unittest.skipUnless(os.name == "nt", "Only on Windows.")
806+
def test_mode_win32(self):
807+
# Use icacls.exe to extract the users with some level of access
808+
# Main thing we are testing is that the BUILTIN\Users group has
809+
# no access. The exact ACL is going to vary based on which user
810+
# is running the test.
811+
dir = self.do_create()
812+
try:
813+
out = subprocess.check_output(["icacls.exe", dir], encoding="oem").casefold()
814+
finally:
815+
os.rmdir(dir)
816+
817+
dir = dir.casefold()
818+
users = set()
819+
found_user = False
820+
for line in out.strip().splitlines():
821+
acl = None
822+
# First line of result includes our directory
823+
if line.startswith(dir):
824+
acl = line.removeprefix(dir).strip()
825+
elif line and line[:1].isspace():
826+
acl = line.strip()
827+
if acl:
828+
users.add(acl.partition(":")[0])
829+
830+
self.assertNotIn(r"BUILTIN\Users".casefold(), users)
831+
804832
def test_collision_with_existing_file(self):
805833
# mkdtemp tries another name when a file with
806834
# the chosen name already exists
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:func:`os.mkdir` on Windows now accepts *mode* of ``0o700`` to restrict
2+
the new directory to the current user. This fixes CVE-2024-4030
3+
affecting :func:`tempfile.mkdtemp` in scenarios where the base temporary
4+
directory is more permissive than the default.

‎Modules/posixmodule.c

Copy file name to clipboardExpand all lines: Modules/posixmodule.c
+40-2Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
#include "pycore_pystate.h" // _PyInterpreterState_GET()
3535
#include "pycore_signal.h" // Py_NSIG
3636

37+
#ifdef MS_WINDOWS
38+
# include <aclapi.h> // SetEntriesInAcl
39+
# include <sddl.h> // SDDL_REVISION_1
40+
#endif
41+
3742
#include "structmember.h" // PyMemberDef
3843
#ifndef MS_WINDOWS
3944
# include "posixmodule.h"
@@ -4611,6 +4616,12 @@ os_mkdir_impl(PyObject *module, path_t *path, int mode, int dir_fd)
46114616
/*[clinic end generated code: output=a70446903abe821f input=a61722e1576fab03]*/
46124617
{
46134618
int result;
4619+
#ifdef MS_WINDOWS
4620+
int error = 0;
4621+
int pathError = 0;
4622+
SECURITY_ATTRIBUTES secAttr = { sizeof(secAttr) };
4623+
SECURITY_ATTRIBUTES *pSecAttr = NULL;
4624+
#endif
46144625
#ifdef HAVE_MKDIRAT
46154626
int mkdirat_unavailable = 0;
46164627
#endif
@@ -4622,11 +4633,38 @@ os_mkdir_impl(PyObject *module, path_t *path, int mode, int dir_fd)
46224633

46234634
#ifdef MS_WINDOWS
46244635
Py_BEGIN_ALLOW_THREADS
4625-
result = CreateDirectoryW(path->wide, NULL);
4636+
if (mode == 0700 /* 0o700 */) {
4637+
ULONG sdSize;
4638+
pSecAttr = &secAttr;
4639+
// Set a discretionary ACL (D) that is protected (P) and includes
4640+
// inheritable (OICI) entries that allow (A) full control (FA) to
4641+
// SYSTEM (SY), Administrators (BA), and the owner (OW).
4642+
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
4643+
L"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)",
4644+
SDDL_REVISION_1,
4645+
&secAttr.lpSecurityDescriptor,
4646+
&sdSize
4647+
)) {
4648+
error = GetLastError();
4649+
}
4650+
}
4651+
if (!error) {
4652+
result = CreateDirectoryW(path->wide, pSecAttr);
4653+
if (secAttr.lpSecurityDescriptor &&
4654+
// uncommonly, LocalFree returns non-zero on error, but still uses
4655+
// GetLastError() to see what the error code is
4656+
LocalFree(secAttr.lpSecurityDescriptor)) {
4657+
error = GetLastError();
4658+
}
4659+
}
46264660
Py_END_ALLOW_THREADS
46274661

4628-
if (!result)
4662+
if (error) {
4663+
return PyErr_SetFromWindowsErr(error);
4664+
}
4665+
if (!result) {
46294666
return path_error(path);
4667+
}
46304668
#else
46314669
Py_BEGIN_ALLOW_THREADS
46324670
#if HAVE_MKDIRAT

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.