From fe629262c0db7aa18bad8bf3ac524acd8695739b Mon Sep 17 00:00:00 2001 From: VanshAgarwal24036 <148854295+VanshAgarwal24036@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:35:42 +0530 Subject: [PATCH 1/4] gh-144050: Fix stat.filemode pure Python file type detection (GH-144059) --- Lib/stat.py | 11 ++++++++--- Lib/test/test_stat.py | 5 +++++ .../2026-01-20-16-35-55.gh-issue-144050.0kKFbF.rst | 2 ++ 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-01-20-16-35-55.gh-issue-144050.0kKFbF.rst diff --git a/Lib/stat.py b/Lib/stat.py index ab1b25b9d6351c..214c7917b5e048 100644 --- a/Lib/stat.py +++ b/Lib/stat.py @@ -166,9 +166,14 @@ def filemode(mode): perm = [] for index, table in enumerate(_filemode_table): for bit, char in table: - if mode & bit == bit: - perm.append(char) - break + if index == 0: + if S_IFMT(mode) == bit: + perm.append(char) + break + else: + if mode & bit == bit: + perm.append(char) + break else: if index == 0: # Unknown filetype diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index 5fd25d5012c425..a83f7d076f027e 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -163,6 +163,11 @@ def test_mode(self): self.statmod.S_IFREG) self.assertEqual(self.statmod.S_IMODE(st_mode), 0o666) + def test_filemode_does_not_misclassify_random_bits(self): + # gh-144050 regression test + self.assertEqual(self.statmod.filemode(0o77777)[0], "?") + self.assertEqual(self.statmod.filemode(0o177777)[0], "?") + @os_helper.skip_unless_working_chmod def test_directory(self): os.mkdir(TESTFN) diff --git a/Misc/NEWS.d/next/Library/2026-01-20-16-35-55.gh-issue-144050.0kKFbF.rst b/Misc/NEWS.d/next/Library/2026-01-20-16-35-55.gh-issue-144050.0kKFbF.rst new file mode 100644 index 00000000000000..dfc062d023c8f1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-20-16-35-55.gh-issue-144050.0kKFbF.rst @@ -0,0 +1,2 @@ +Fix :func:`stat.filemode` in the pure-Python implementation to avoid misclassifying +invalid mode values as block devices. From 31c81ab0a2fb7009a76426617991a9b539ab0180 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 20 Jan 2026 15:09:20 +0300 Subject: [PATCH 2/4] Update struct.__doc__: _Bool available unconditionally (#143716) This amends commit a9296e7f3be4d6c22271b25c86467ff867c63bbb. --- Modules/_struct.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index c3f8359ac39e4e..74ae9e4c33d31f 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -2662,8 +2662,7 @@ The optional first format char indicates byte order, size and alignment:\n\ The remaining chars indicate types of args and must match exactly;\n\ these can be preceded by a decimal repeat count:\n\ x: pad byte (no data); c:char; b:signed byte; B:unsigned byte;\n\ - ?: _Bool (requires C99; if not available, char is used instead)\n\ - h:short; H:unsigned short; i:int; I:unsigned int;\n\ + ?:_Bool; h:short; H:unsigned short; i:int; I:unsigned int;\n\ l:long; L:unsigned long; f:float; d:double; e:half-float.\n\ F:float complex; D:double complex.\n\ Special cases (preceding decimal count indicates length):\n\ From 795d5c5b44c5ac4b7d1619800f341ec2d0332430 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Tue, 20 Jan 2026 05:45:12 -0800 Subject: [PATCH 3/4] gh-144054: shutdown fix for deferred ref counting (GH-144055) When shutting down, disable deferred refcounting for all GC objects. It is important to do this also for untracked objects, which before this change were getting missed. Small code cleanup: We can remove the shutdown case disable_deferred_refcounting() call inside scan_heap_visitor() if we are careful about it. The key is that frame_disable_deferred_refcounting() might fail if the object is untracked. --- Python/gc_free_threading.c | 52 +++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 51261cea0cfe2c..beb3fa588f40e7 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -308,17 +308,18 @@ disable_deferred_refcounting(PyObject *op) // should also be disabled when we turn off deferred refcounting. _PyObject_DisablePerThreadRefcounting(op); } - - // Generators and frame objects may contain deferred references to other - // objects. If the pointed-to objects are part of cyclic trash, we may - // have disabled deferred refcounting on them and need to ensure that we - // use strong references, in case the generator or frame object is - // resurrected by a finalizer. - if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) { - frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe); - } - else if (PyFrame_Check(op)) { - frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame); + if (_PyObject_GC_IS_TRACKED(op)) { + // Generators and frame objects may contain deferred references to other + // objects. If the pointed-to objects are part of cyclic trash, we may + // have disabled deferred refcounting on them and need to ensure that we + // use strong references, in case the generator or frame object is + // resurrected by a finalizer. + if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) { + frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe); + } + else if (PyFrame_Check(op)) { + frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame); + } } } @@ -1240,19 +1241,30 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, return true; } - if (state->reason == _Py_GC_REASON_SHUTDOWN) { - // Disable deferred refcounting for reachable objects as well during - // interpreter shutdown. This ensures that these objects are collected - // immediately when their last reference is removed. - disable_deferred_refcounting(op); - } - // object is reachable, restore `ob_tid`; we're done with these objects gc_restore_tid(op); gc_clear_alive(op); return true; } +// Disable deferred refcounting for reachable objects during interpreter +// shutdown. This ensures that these objects are collected immediately when +// their last reference is removed. This needs to consider both tracked and +// untracked GC objects, since either might have deferred refcounts enabled. +static bool +scan_heap_disable_deferred(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block_all_gc(block, args); + if (op == NULL) { + return true; + } + if (!_Py_IsImmortal(op)) { + disable_deferred_refcounting(op); + } + return true; +} + static int move_legacy_finalizer_reachable(struct collection_state *state); @@ -1487,6 +1499,10 @@ deduce_unreachable_heap(PyInterpreterState *interp, // Restores ob_tid for reachable objects. gc_visit_heaps(interp, &scan_heap_visitor, &state->base); + if (state->reason == _Py_GC_REASON_SHUTDOWN) { + gc_visit_heaps(interp, &scan_heap_disable_deferred, &state->base); + } + if (state->legacy_finalizers.head) { // There may be objects reachable from legacy finalizers that are in // the unreachable set. We need to mark them as reachable. From a126893fa80c4ee5f0bac8a84a49491c19edd511 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:25:31 +0200 Subject: [PATCH 4/4] gh-143960: Add support for OpenSSL 3.6, drop EOL 3.2 (#143961) Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- ...-01-17-15-31-19.gh-issue-143960.Zi0EqR.rst | 1 + Modules/_ssl.c | 2 +- Modules/{_ssl_data_35.h => _ssl_data_36.h} | 24 +++++++++++++++++-- Tools/ssl/make_ssl_data.py | 4 ++-- Tools/ssl/multissltests.py | 3 ++- 6 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2026-01-17-15-31-19.gh-issue-143960.Zi0EqR.rst rename Modules/{_ssl_data_35.h => _ssl_data_36.h} (99%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2dc610ce37cc4c..e7f7aa5172e082 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -261,7 +261,7 @@ jobs: # Keep 1.1.1w in our list despite it being upstream EOL and otherwise # unsupported as it most resembles other 1.1.1-work-a-like ssl APIs # supported by important vendors such as AWS-LC. - openssl_ver: [1.1.1w, 3.0.18, 3.2.6, 3.3.5, 3.4.3, 3.5.4] + openssl_ver: [1.1.1w, 3.0.18, 3.3.5, 3.4.3, 3.5.4, 3.6.0] # See Tools/ssl/make_ssl_data.py for notes on adding a new version env: OPENSSL_VER: ${{ matrix.openssl_ver }} diff --git a/Misc/NEWS.d/next/Build/2026-01-17-15-31-19.gh-issue-143960.Zi0EqR.rst b/Misc/NEWS.d/next/Build/2026-01-17-15-31-19.gh-issue-143960.Zi0EqR.rst new file mode 100644 index 00000000000000..2b8e01f937db76 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2026-01-17-15-31-19.gh-issue-143960.Zi0EqR.rst @@ -0,0 +1 @@ +Add support for OpenSSL 3.6, drop EOL 3.2. Patch by Hugo van Kemenade. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 7dd57e7892af41..2bcf864e759b91 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -150,7 +150,7 @@ static void _PySSLFixErrno(void) { /* Include generated data (error codes) */ /* See Tools/ssl/make_ssl_data.py for notes on adding a new version. */ #if (OPENSSL_VERSION_NUMBER >= 0x30401000L) -#include "_ssl_data_35.h" +#include "_ssl_data_36.h" #elif (OPENSSL_VERSION_NUMBER >= 0x30100000L) #include "_ssl_data_340.h" #elif (OPENSSL_VERSION_NUMBER >= 0x30000000L) diff --git a/Modules/_ssl_data_35.h b/Modules/_ssl_data_36.h similarity index 99% rename from Modules/_ssl_data_35.h rename to Modules/_ssl_data_36.h index e4919b550e3a89..02b8b66e80fce2 100644 --- a/Modules/_ssl_data_35.h +++ b/Modules/_ssl_data_36.h @@ -1,6 +1,6 @@ /* File generated by Tools/ssl/make_ssl_data.py */ -/* Generated on 2025-10-04T17:49:19.148321+00:00 */ -/* Generated from Git commit openssl-3.5.4-0-gc1eeb9406 */ +/* Generated on 2026-01-17T13:03:49.335767+00:00 */ +/* Generated from Git commit openssl-3.6.0-0-g7b371d80d9 */ /* generated from args.lib2errnum */ static struct py_ssl_library_code library_codes[] = { @@ -1863,6 +1863,11 @@ static struct py_ssl_error_code error_codes[] = { #else {"NOT_KEK", 46, 123}, #endif + #ifdef CMS_R_NOT_KEM + {"NOT_KEM", ERR_LIB_CMS, CMS_R_NOT_KEM}, + #else + {"NOT_KEM", 46, 197}, + #endif #ifdef CMS_R_NOT_KEY_AGREEMENT {"NOT_KEY_AGREEMENT", ERR_LIB_CMS, CMS_R_NOT_KEY_AGREEMENT}, #else @@ -2058,6 +2063,11 @@ static struct py_ssl_error_code error_codes[] = { #else {"UNKNOWN_ID", 46, 150}, #endif + #ifdef CMS_R_UNKNOWN_KDF_ALGORITHM + {"UNKNOWN_KDF_ALGORITHM", ERR_LIB_CMS, CMS_R_UNKNOWN_KDF_ALGORITHM}, + #else + {"UNKNOWN_KDF_ALGORITHM", 46, 198}, + #endif #ifdef CMS_R_UNSUPPORTED_COMPRESSION_ALGORITHM {"UNSUPPORTED_COMPRESSION_ALGORITHM", ERR_LIB_CMS, CMS_R_UNSUPPORTED_COMPRESSION_ALGORITHM}, #else @@ -2078,6 +2088,11 @@ static struct py_ssl_error_code error_codes[] = { #else {"UNSUPPORTED_ENCRYPTION_TYPE", 46, 192}, #endif + #ifdef CMS_R_UNSUPPORTED_KDF_ALGORITHM + {"UNSUPPORTED_KDF_ALGORITHM", ERR_LIB_CMS, CMS_R_UNSUPPORTED_KDF_ALGORITHM}, + #else + {"UNSUPPORTED_KDF_ALGORITHM", 46, 199}, + #endif #ifdef CMS_R_UNSUPPORTED_KEK_ALGORITHM {"UNSUPPORTED_KEK_ALGORITHM", ERR_LIB_CMS, CMS_R_UNSUPPORTED_KEK_ALGORITHM}, #else @@ -5763,6 +5778,11 @@ static struct py_ssl_error_code error_codes[] = { #else {"PSS_SALTLEN_TOO_SMALL", 57, 172}, #endif + #ifdef PROV_R_REPEATED_PARAMETER + {"REPEATED_PARAMETER", ERR_LIB_PROV, PROV_R_REPEATED_PARAMETER}, + #else + {"REPEATED_PARAMETER", 57, 252}, + #endif #ifdef PROV_R_REQUEST_TOO_LARGE_FOR_DRBG {"REQUEST_TOO_LARGE_FOR_DRBG", ERR_LIB_PROV, PROV_R_REQUEST_TOO_LARGE_FOR_DRBG}, #else diff --git a/Tools/ssl/make_ssl_data.py b/Tools/ssl/make_ssl_data.py index 286f0e5f54a779..439dbaf882db68 100755 --- a/Tools/ssl/make_ssl_data.py +++ b/Tools/ssl/make_ssl_data.py @@ -17,8 +17,8 @@ git tag --list 'openssl-*' git switch --detach openssl-3.4.1 -After generating the definitions, compare the result with newest pre-existing file. -You can use a command like: +After generating the definitions, compare the result with the newest +pre-existing file. You can use a command like: git diff --no-index Modules/_ssl_data_340.h Modules/_ssl_data_341.h diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 56976de49989ec..828fb8b44f9b08 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -45,14 +45,15 @@ OPENSSL_OLD_VERSIONS = [ "1.1.1w", "3.1.8", + "3.2.6", ] OPENSSL_RECENT_VERSIONS = [ "3.0.18", - "3.2.6", "3.3.5", "3.4.3", "3.5.4", + "3.6.0", # See make_ssl_data.py for notes on adding a new version. ]