| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1929,6 +1929,10 @@ features: | |||
| 1929 | 1929 | platform-dependent. On some platforms, they are ignored and you should call | |
| 1930 | 1930 | :func:`chmod` explicitly to set them. | |
| 1931 | 1931 | ||
| 1932 | + On Windows, a *mode* of ``0o700`` is specifically handled to apply access | ||
| 1933 | + control to the new directory such that only the current user and | ||
| 1934 | + administrators have access. Other values of *mode* are ignored. | ||
| 1935 | + | ||
| 1932 | 1936 | This function can also support :ref:`paths relative to directory descriptors | |
| 1933 | 1937 | <dir_fd>`. | |
| 1934 | 1938 | ||
@@ -1943,6 +1947,9 @@ features: | |||
| 1943 | 1947 | .. versionchanged:: 3.6 | |
| 1944 | 1948 | Accepts a :term:`path-like object`. | |
| 1945 | 1949 | ||
| 1950 | + .. versionchanged:: 3.9.20 | ||
| 1951 | + Windows now handles a *mode* of ``0o700``. | ||
| 1952 | + | ||
| 1946 | 1953 | ||
| 1947 | 1954 | .. function:: makedirs(name, mode=0o777, exist_ok=False) | |
| 1948 | 1955 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -613,6 +613,13 @@ Added :func:`os.waitstatus_to_exitcode` function: | |||
| 613 | 613 | convert a wait status to an exit code. | |
| 614 | 614 | (Contributed by Victor Stinner in :issue:`40094`.) | |
| 615 | 615 | ||
| 616 | + As of 3.9.20, :func:`os.mkdir` and :func:`os.makedirs` on Windows now support | ||
| 617 | + passing a *mode* value of ``0o700`` to apply access control to the new | ||
| 618 | + directory. This implicitly affects :func:`tempfile.mkdtemp` and is a | ||
| 619 | + mitigation for CVE-2024-4030. Other values for *mode* continue to be | ||
| 620 | + ignored. | ||
| 621 | + (Contributed by Steve Dower in :gh:`118486`.) | ||
| 622 | + | ||
| 616 | 623 | pathlib | |
| 617 | 624 | ------- | |
| 618 | 625 | ||
@@ -704,6 +711,14 @@ Previously, :attr:`sys.stderr` was block-buffered when non-interactive. Now | |||
| 704 | 711 | ``stderr`` defaults to always being line-buffered. | |
| 705 | 712 | (Contributed by Jendrik Seipp in :issue:`13601`.) | |
| 706 | 713 | ||
| 714 | + tempfile | ||
| 715 | + -------- | ||
| 716 | + | ||
| 717 | + As of 3.9.20 on Windows, the default mode ``0o700`` used by | ||
| 718 | + :func:`tempfile.mkdtemp` now limits access to the new directory due to | ||
| 719 | + changes to :func:`os.mkdir`. This is a mitigation for CVE-2024-4030. | ||
| 720 | + (Contributed by Steve Dower in :gh:`118486`.) | ||
| 721 | + | ||
| 707 | 722 | tracemalloc | |
| 708 | 723 | ----------- | |
| 709 | 724 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1493,6 +1493,18 @@ def test_exist_ok_existing_regular_file(self): | |||
| 1493 | 1493 | self.assertRaises(OSError, os.makedirs, path, exist_ok=True) | |
| 1494 | 1494 | os.remove(path) | |
| 1495 | 1495 | ||
| 1496 | + @unittest.skipUnless(os.name == 'nt', "requires Windows") | ||
| 1497 | + def test_win32_mkdir_700(self): | ||
| 1498 | + base = support.TESTFN | ||
| 1499 | + path = os.path.abspath(os.path.join(support.TESTFN, 'dir')) | ||
| 1500 | + os.mkdir(path, mode=0o700) | ||
| 1501 | + out = subprocess.check_output(["cacls.exe", path, "/s"], encoding="oem") | ||
| 1502 | + os.rmdir(path) | ||
| 1503 | + self.assertEqual( | ||
| 1504 | + out.strip(), | ||
| 1505 | + f'{path} "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)"', | ||
| 1506 | + ) | ||
| 1507 | + | ||
| 1496 | 1508 | def tearDown(self): | |
| 1497 | 1509 | path = os.path.join(support.TESTFN, 'dir1', 'dir2', 'dir3', | |
| 1498 | 1510 | 'dir4', 'dir5', 'dir6') | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,6 +11,7 @@ | |||
| 11 | 11 | import stat | |
| 12 | 12 | import types | |
| 13 | 13 | import weakref | |
| 14 | + import subprocess | ||
| 14 | 15 | from unittest import mock | |
| 15 | 16 | ||
| 16 | 17 | import unittest | |
@@ -772,6 +773,33 @@ def test_mode(self): | |||
| 772 | 773 | finally: | |
| 773 | 774 | os.rmdir(dir) | |
| 774 | 775 | ||
| 776 | + @unittest.skipUnless(os.name == "nt", "Only on Windows.") | ||
| 777 | + def test_mode_win32(self): | ||
| 778 | + # Use icacls.exe to extract the users with some level of access | ||
| 779 | + # Main thing we are testing is that the BUILTIN\Users group has | ||
| 780 | + # no access. The exact ACL is going to vary based on which user | ||
| 781 | + # is running the test. | ||
| 782 | + dir = self.do_create() | ||
| 783 | + try: | ||
| 784 | + out = subprocess.check_output(["icacls.exe", dir], encoding="oem").casefold() | ||
| 785 | + finally: | ||
| 786 | + os.rmdir(dir) | ||
| 787 | + | ||
| 788 | + dir = dir.casefold() | ||
| 789 | + users = set() | ||
| 790 | + found_user = False | ||
| 791 | + for line in out.strip().splitlines(): | ||
| 792 | + acl = None | ||
| 793 | + # First line of result includes our directory | ||
| 794 | + if line.startswith(dir): | ||
| 795 | + acl = line.removeprefix(dir).strip() | ||
| 796 | + elif line and line[:1].isspace(): | ||
| 797 | + acl = line.strip() | ||
| 798 | + if acl: | ||
| 799 | + users.add(acl.partition(":")[0]) | ||
| 800 | + | ||
| 801 | + self.assertNotIn(r"BUILTIN\Users".casefold(), users) | ||
| 802 | + | ||
| 775 | 803 | def test_collision_with_existing_file(self): | |
| 776 | 804 | # mkdtemp tries another name when a file with | |
| 777 | 805 | # the chosen name already exists | |
| Original file line number | Diff line number | Diff 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. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,6 +24,12 @@ | |||
| 24 | 24 | #include "pycore_ceval.h" // _PyEval_ReInitThreads() | |
| 25 | 25 | #include "pycore_import.h" // _PyImport_ReInitLock() | |
| 26 | 26 | #include "pycore_pystate.h" // _PyInterpreterState_GET() | |
| 27 | + | ||
| 28 | + #ifdef MS_WINDOWS | ||
| 29 | + # include <aclapi.h> // SetEntriesInAcl | ||
| 30 | + # include <sddl.h> // SDDL_REVISION_1 | ||
| 31 | + #endif | ||
| 32 | + | ||
| 27 | 33 | #include "structmember.h" // PyMemberDef | |
| 28 | 34 | #ifndef MS_WINDOWS | |
| 29 | 35 | # include "posixmodule.h" | |
@@ -4425,7 +4431,6 @@ os__path_splitroot_impl(PyObject *module, path_t *path) | |||
| 4425 | 4431 | ||
| 4426 | 4432 | #endif /* MS_WINDOWS */ | |
| 4427 | 4433 | ||
| 4428 | - | ||
| 4429 | 4434 | /*[clinic input] | |
| 4430 | 4435 | os.mkdir | |
| 4431 | 4436 | ||
@@ -4454,6 +4459,12 @@ os_mkdir_impl(PyObject *module, path_t *path, int mode, int dir_fd) | |||
| 4454 | 4459 | /*[clinic end generated code: output=a70446903abe821f input=e965f68377e9b1ce]*/ | |
| 4455 | 4460 | { | |
| 4456 | 4461 | int result; | |
| 4462 | + #ifdef MS_WINDOWS | ||
| 4463 | + int error = 0; | ||
| 4464 | + int pathError = 0; | ||
| 4465 | + SECURITY_ATTRIBUTES secAttr = { sizeof(secAttr) }; | ||
| 4466 | + SECURITY_ATTRIBUTES *pSecAttr = NULL; | ||
| 4467 | + #endif | ||
| 4457 | 4468 | #ifdef HAVE_MKDIRAT | |
| 4458 | 4469 | int mkdirat_unavailable = 0; | |
| 4459 | 4470 | #endif | |
@@ -4465,11 +4476,38 @@ os_mkdir_impl(PyObject *module, path_t *path, int mode, int dir_fd) | |||
| 4465 | 4476 | ||
| 4466 | 4477 | #ifdef MS_WINDOWS | |
| 4467 | 4478 | Py_BEGIN_ALLOW_THREADS | |
| 4468 | - result = CreateDirectoryW(path->wide, NULL); | ||
| 4479 | + if (mode == 0700 /* 0o700 */) { | ||
| 4480 | + ULONG sdSize; | ||
| 4481 | + pSecAttr = &secAttr; | ||
| 4482 | + // Set a discretionary ACL (D) that is protected (P) and includes | ||
| 4483 | + // inheritable (OICI) entries that allow (A) full control (FA) to | ||
| 4484 | + // SYSTEM (SY), Administrators (BA), and the owner (OW). | ||
| 4485 | + if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( | ||
| 4486 | + L"D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)", | ||
| 4487 | + SDDL_REVISION_1, | ||
| 4488 | + &secAttr.lpSecurityDescriptor, | ||
| 4489 | + &sdSize | ||
| 4490 | + )) { | ||
| 4491 | + error = GetLastError(); | ||
| 4492 | + } | ||
| 4493 | + } | ||
| 4494 | + if (!error) { | ||
| 4495 | + result = CreateDirectoryW(path->wide, pSecAttr); | ||
| 4496 | + if (secAttr.lpSecurityDescriptor && | ||
| 4497 | + // uncommonly, LocalFree returns non-zero on error, but still uses | ||
| 4498 | + // GetLastError() to see what the error code is | ||
| 4499 | + LocalFree(secAttr.lpSecurityDescriptor)) { | ||
| 4500 | + error = GetLastError(); | ||
| 4501 | + } | ||
| 4502 | + } | ||
| 4469 | 4503 | Py_END_ALLOW_THREADS | |
| 4470 | 4504 | ||
| 4471 | - if (!result) | ||
| 4505 | + if (error) { | ||
| 4506 | + return PyErr_SetFromWindowsErr(error); | ||
| 4507 | + } | ||
| 4508 | + if (!result) { | ||
| 4472 | 4509 | return path_error(path); | |
| 4510 | + } | ||
| 4473 | 4511 | #else | |
| 4474 | 4512 | Py_BEGIN_ALLOW_THREADS | |
| 4475 | 4513 | #if HAVE_MKDIRAT | |
| Back | FazBrowse Home | New Git URL |
0 commit comments