]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-139748: fix leaks in AC error paths when using unicode FS-b… (#139789)
authorKumar Aditya <kumaraditya@python.org>
Wed, 8 Oct 2025 16:46:21 +0000 (22:16 +0530)
committerGitHub <noreply@github.com>
Wed, 8 Oct 2025 16:46:21 +0000 (22:16 +0530)
* [3.14] gh-139748: fix leaks in AC error paths when using unicode FS-based converters (GH-139765)
(cherry picked from commit b04a57deef66ce08233be57d1ab5873388df2cea)

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
14 files changed:
Lib/test/test_clinic.py
Lib/test/test_compile.py
Lib/test/test_symtable.py
Misc/NEWS.d/next/Core_and_Builtins/2025-10-08-13-52-00.gh-issue-139748.jq0yFJ.rst [new file with mode: 0644]
Modules/_ssl.c
Modules/clinic/_ssl.c.h
Modules/clinic/socketmodule.c.h
Modules/clinic/symtablemodule.c.h
Modules/posixmodule.c
Modules/socketmodule.c
Modules/symtablemodule.c
Python/bltinmodule.c
Python/clinic/bltinmodule.c.h
Tools/clinic/libclinic/converters.py

index d5b751eafc5bbb0373988895337816d8a14e4346..b25108e0ff79035f0325633e63cd913ae81ceac0 100644 (file)
@@ -2833,6 +2833,8 @@ class ClinicExternalTest(TestCase):
             "uint64",
             "uint8",
             "unicode",
+            "unicode_fs_decoded",
+            "unicode_fs_encoded",
             "unsigned_char",
             "unsigned_int",
             "unsigned_long",
index 8a66be9b3312624c4275c2eced9bddb43827743c..7d7409ef93aa5f1f7001bf138dbdf2fa897c7a91 100644 (file)
@@ -651,6 +651,21 @@ class TestSpecifics(unittest.TestCase):
                 compile('pass', filename, 'exec')
         self.assertRaises(TypeError, compile, 'pass', list(b'file.py'), 'exec')
 
+    def test_compile_filename_refleak(self):
+        # Regression tests for reference leak in PyUnicode_FSDecoder.
+        # See https://github.com/python/cpython/issues/139748.
+        mortal_str = 'this is a mortal string'
+        # check error path when 'mode' AC conversion failed
+        self.assertRaises(TypeError, compile, b'', mortal_str, mode=1234)
+        # check error path when 'optimize' AC conversion failed
+        self.assertRaises(OverflowError, compile, b'', mortal_str,
+                          'exec', optimize=1 << 1000)
+        # check error path when 'dont_inherit' AC conversion failed
+        class EvilBool:
+            def __bool__(self): raise ValueError
+        self.assertRaises(ValueError, compile, b'', mortal_str,
+                          'exec', dont_inherit=EvilBool())
+
     @support.cpython_only
     def test_same_filename_used(self):
         s = """def f(): pass\ndef g(): pass"""
index 24d89b09d946ad3b5a85de33f583b66bd8cdc42a..9537de3756fd32f66e35a6912ddb87fcd3bd9f3d 100644 (file)
@@ -527,6 +527,13 @@ class SymtableTest(unittest.TestCase):
         expected = f"<symtable entry top({self.top.get_id()}), line {self.top.get_lineno()}>"
         self.assertEqual(repr(self.top._table), expected)
 
+    def test__symtable_refleak(self):
+        # Regression test for reference leak in PyUnicode_FSDecoder.
+        # See https://github.com/python/cpython/issues/139748.
+        mortal_str = 'this is a mortal string'
+        # check error path when 'compile_type' AC conversion failed
+        self.assertRaises(TypeError, symtable.symtable, '', mortal_str, 1)
+
 
 class ComprehensionTests(unittest.TestCase):
     def get_identifiers_recursive(self, st, res):
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-08-13-52-00.gh-issue-139748.jq0yFJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-08-13-52-00.gh-issue-139748.jq0yFJ.rst
new file mode 100644 (file)
index 0000000..3b190f3
--- /dev/null
@@ -0,0 +1,2 @@
+Fix reference leaks in error branches of functions accepting path strings or
+bytes such as :func:`compile` and :func:`os.system`. Patch by Bénédikt Tran.
index 5af0c3f8668fbbf4aa441b98107c8878f497cbc0..6898d73b8ffb2ae708047fbb4cd82881fdb6c2b4 100644 (file)
@@ -1831,14 +1831,14 @@ _certificate_to_der(_sslmodulestate *state, X509 *certificate)
 
 /*[clinic input]
 _ssl._test_decode_cert
-    path: object(converter="PyUnicode_FSConverter")
+    path: unicode_fs_encoded
     /
 
 [clinic start generated code]*/
 
 static PyObject *
 _ssl__test_decode_cert_impl(PyObject *module, PyObject *path)
-/*[clinic end generated code: output=96becb9abb23c091 input=cdeaaf02d4346628]*/
+/*[clinic end generated code: output=96becb9abb23c091 input=cb4988d5e651a4f8]*/
 {
     PyObject *retval = NULL;
     X509 *x=NULL;
@@ -1868,7 +1868,6 @@ _ssl__test_decode_cert_impl(PyObject *module, PyObject *path)
     X509_free(x);
 
   fail0:
-    Py_DECREF(path);
     if (cert != NULL) BIO_free(cert);
     return retval;
 }
index c6e2abd4d93474980d772c78956ab1ef61cd5c21..6c6ef4cebd7998318d0fd34edefef9c253907ee7 100644 (file)
@@ -47,7 +47,7 @@ static PyObject *
 _ssl__test_decode_cert(PyObject *module, PyObject *arg)
 {
     PyObject *return_value = NULL;
-    PyObject *path;
+    PyObject *path = NULL;
 
     if (!PyUnicode_FSConverter(arg, &path)) {
         goto exit;
@@ -55,6 +55,9 @@ _ssl__test_decode_cert(PyObject *module, PyObject *arg)
     return_value = _ssl__test_decode_cert_impl(module, path);
 
 exit:
+    /* Cleanup for path */
+    Py_XDECREF(path);
+
     return return_value;
 }
 
@@ -2900,4 +2903,4 @@ exit:
 #ifndef _SSL_ENUM_CRLS_METHODDEF
     #define _SSL_ENUM_CRLS_METHODDEF
 #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */
-/*[clinic end generated code: output=748650909fec8906 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=cbd8526cd625c433 input=a9049054013a1b77]*/
index 573903be87e6ea85e1a728560049beed402e114c..2b27640cd0aaf37865a6c58adce96e1b0d3b5969 100644 (file)
@@ -315,7 +315,7 @@ static PyObject *
 _socket_if_nametoindex(PyObject *module, PyObject *arg)
 {
     PyObject *return_value = NULL;
-    PyObject *oname;
+    PyObject *oname = NULL;
 
     if (!PyUnicode_FSConverter(arg, &oname)) {
         goto exit;
@@ -323,6 +323,9 @@ _socket_if_nametoindex(PyObject *module, PyObject *arg)
     return_value = _socket_if_nametoindex_impl(module, oname);
 
 exit:
+    /* Cleanup for oname */
+    Py_XDECREF(oname);
+
     return return_value;
 }
 
@@ -370,4 +373,4 @@ exit:
 #ifndef _SOCKET_IF_INDEXTONAME_METHODDEF
     #define _SOCKET_IF_INDEXTONAME_METHODDEF
 #endif /* !defined(_SOCKET_IF_INDEXTONAME_METHODDEF) */
-/*[clinic end generated code: output=07776dd21d1e3b56 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=1bbfda36b02c29c2 input=a9049054013a1b77]*/
index 2ecd3afc00d2be493c849e879cd0f7c074c0fb3a..bd55d77c5409e9391f108f7eb1f830b3d8ebddb4 100644 (file)
@@ -22,7 +22,7 @@ _symtable_symtable(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
 {
     PyObject *return_value = NULL;
     PyObject *source;
-    PyObject *filename;
+    PyObject *filename = NULL;
     const char *startstr;
 
     if (!_PyArg_CheckPositional("symtable", nargs, 3, 3)) {
@@ -48,6 +48,9 @@ _symtable_symtable(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
     return_value = _symtable_symtable_impl(module, source, filename, startstr);
 
 exit:
+    /* Cleanup for filename */
+    Py_XDECREF(filename);
+
     return return_value;
 }
-/*[clinic end generated code: output=931964a76a72f850 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=7a8545d9a1efe837 input=a9049054013a1b77]*/
index 06a40db1078057906dbff97dd1c67410f2464ccc..3dc62240952f68da5ef9abfce11adbc532112fd4 100644 (file)
@@ -3122,17 +3122,6 @@ class dev_t_return_converter(unsigned_long_return_converter):
     conversion_fn = '_PyLong_FromDev'
     unsigned_cast = '(dev_t)'
 
-class FSConverter_converter(CConverter):
-    type = 'PyObject *'
-    converter = 'PyUnicode_FSConverter'
-    def converter_init(self):
-        if self.default is not unspecified:
-            fail("FSConverter_converter does not support default values")
-        self.c_default = 'NULL'
-
-    def cleanup(self):
-        return "Py_XDECREF(" + self.name + ");\n"
-
 class pid_t_converter(CConverter):
     type = 'pid_t'
     format_unit = '" _Py_PARSE_PID "'
@@ -3196,7 +3185,7 @@ class confname_converter(CConverter):
         """, argname=argname, converter=self.converter, table=self.table)
 
 [python start generated code]*/
-/*[python end generated code: output=da39a3ee5e6b4b0d input=8189d5ae78244626]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=d2759f2332cd39b3]*/
 
 /*[clinic input]
 
@@ -6098,14 +6087,14 @@ os_system_impl(PyObject *module, const wchar_t *command)
 /*[clinic input]
 os.system -> long
 
-    command: FSConverter
+    command: unicode_fs_encoded
 
 Execute the command in a subshell.
 [clinic start generated code]*/
 
 static long
 os_system_impl(PyObject *module, PyObject *command)
-/*[clinic end generated code: output=290fc437dd4f33a0 input=86a58554ba6094af]*/
+/*[clinic end generated code: output=290fc437dd4f33a0 input=47c6f24b6dc92881]*/
 {
     long result;
     const char *bytes = PyBytes_AsString(command);
@@ -9285,7 +9274,7 @@ error:
 /*[clinic input]
 os.initgroups
 
-    username as oname: FSConverter
+    username as oname: unicode_fs_encoded
     gid: int
     /
 
@@ -9298,12 +9287,12 @@ group id.
 
 static PyObject *
 os_initgroups_impl(PyObject *module, PyObject *oname, int gid)
-/*[clinic end generated code: output=7f074d30a425fd3a input=df3d54331b0af204]*/
+/*[clinic end generated code: output=7f074d30a425fd3a input=984e60c7fed88cb4]*/
 #else
 /*[clinic input]
 os.initgroups
 
-    username as oname: FSConverter
+    username as oname: unicode_fs_encoded
     gid: gid_t
     /
 
@@ -9316,7 +9305,7 @@ group id.
 
 static PyObject *
 os_initgroups_impl(PyObject *module, PyObject *oname, gid_t gid)
-/*[clinic end generated code: output=59341244521a9e3f input=0cb91bdc59a4c564]*/
+/*[clinic end generated code: output=59341244521a9e3f input=17d8fbe2dea42ca4]*/
 #endif
 {
     const char *username = PyBytes_AS_STRING(oname);
@@ -13067,8 +13056,8 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
 /*[clinic input]
 os.putenv
 
-    name: FSConverter
-    value: FSConverter
+    name: unicode_fs_encoded
+    value: unicode_fs_encoded
     /
 
 Change or add an environment variable.
@@ -13076,7 +13065,7 @@ Change or add an environment variable.
 
 static PyObject *
 os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
-/*[clinic end generated code: output=d29a567d6b2327d2 input=a97bc6152f688d31]*/
+/*[clinic end generated code: output=d29a567d6b2327d2 input=84fcd30f873c8c45]*/
 {
     const char *name_string = PyBytes_AS_STRING(name);
     const char *value_string = PyBytes_AS_STRING(value);
@@ -13119,7 +13108,7 @@ os_unsetenv_impl(PyObject *module, PyObject *name)
 #else
 /*[clinic input]
 os.unsetenv
-    name: FSConverter
+    name: unicode_fs_encoded
     /
 
 Delete an environment variable.
@@ -13127,7 +13116,7 @@ Delete an environment variable.
 
 static PyObject *
 os_unsetenv_impl(PyObject *module, PyObject *name)
-/*[clinic end generated code: output=54c4137ab1834f02 input=2bb5288a599c7107]*/
+/*[clinic end generated code: output=54c4137ab1834f02 input=78ff12e505ade80a]*/
 {
     if (PySys_Audit("os.unsetenv", "(O)", name) < 0) {
         return NULL;
@@ -15185,14 +15174,14 @@ os_urandom_impl(PyObject *module, Py_ssize_t size)
 /*[clinic input]
 os.memfd_create
 
-    name: FSConverter
+    name: unicode_fs_encoded
     flags: unsigned_int(bitwise=True, c_default="MFD_CLOEXEC") = MFD_CLOEXEC
 
 [clinic start generated code]*/
 
 static PyObject *
 os_memfd_create_impl(PyObject *module, PyObject *name, unsigned int flags)
-/*[clinic end generated code: output=6681ede983bdb9a6 input=a42cfc199bcd56e9]*/
+/*[clinic end generated code: output=6681ede983bdb9a6 input=cd0eb092cfac474b]*/
 {
     int fd;
     const char *bytes = PyBytes_AS_STRING(name);
index 92c9aa8b510dca34095b01063fbc017546a810ea..c90b693dd2d62b39a66dfe2e72454fea8fc4e46c 100644 (file)
@@ -7261,7 +7261,7 @@ Returns a list of network interface information (index, name) tuples.");
 
 /*[clinic input]
 _socket.if_nametoindex
-    oname: object(converter="PyUnicode_FSConverter")
+    oname: unicode_fs_encoded
     /
 
 Returns the interface index corresponding to the interface name if_name.
@@ -7269,7 +7269,7 @@ Returns the interface index corresponding to the interface name if_name.
 
 static PyObject *
 _socket_if_nametoindex_impl(PyObject *module, PyObject *oname)
-/*[clinic end generated code: output=289a411614f30244 input=01e0f1205307fb77]*/
+/*[clinic end generated code: output=289a411614f30244 input=6125dc20683560cf]*/
 {
 #ifdef MS_WINDOWS
     NET_IFINDEX index;
@@ -7278,7 +7278,6 @@ _socket_if_nametoindex_impl(PyObject *module, PyObject *oname)
 #endif
 
     index = if_nametoindex(PyBytes_AS_STRING(oname));
-    Py_DECREF(oname);
     if (index == 0) {
         /* if_nametoindex() doesn't set errno */
         PyErr_SetString(PyExc_OSError, "no interface with this name");
index d0d5223e5acea85e447433189ea85a38f2d580a7..d353f406831ecd283e37fcb66bba8601d4685e0f 100644 (file)
@@ -13,7 +13,7 @@ module _symtable
 _symtable.symtable
 
     source:    object
-    filename:  object(converter='PyUnicode_FSDecoder')
+    filename:  unicode_fs_decoded
     startstr:  str
     /
 
@@ -23,7 +23,7 @@ Return symbol and scope dictionaries used internally by compiler.
 static PyObject *
 _symtable_symtable_impl(PyObject *module, PyObject *source,
                         PyObject *filename, const char *startstr)
-/*[clinic end generated code: output=59eb0d5fc7285ac4 input=9dd8a50c0c36a4d7]*/
+/*[clinic end generated code: output=59eb0d5fc7285ac4 input=436ffff90d02e4f6]*/
 {
     struct symtable *st;
     PyObject *t;
@@ -47,12 +47,10 @@ _symtable_symtable_impl(PyObject *module, PyObject *source,
     else {
         PyErr_SetString(PyExc_ValueError,
            "symtable() arg 3 must be 'exec' or 'eval' or 'single'");
-        Py_DECREF(filename);
         Py_XDECREF(source_copy);
         return NULL;
     }
     st = _Py_SymtableStringObjectFlags(str, filename, start, &cf);
-    Py_DECREF(filename);
     Py_XDECREF(source_copy);
     if (st == NULL) {
         return NULL;
index 1ca23626ea52fd3bd2e8b1e86821d8bcf154034f..4ab67bdee1d6bac72dc7c7a628786866629e5471 100644 (file)
@@ -745,7 +745,7 @@ builtin_chr(PyObject *module, PyObject *i)
 compile as builtin_compile
 
     source: object
-    filename: object(converter="PyUnicode_FSDecoder")
+    filename: unicode_fs_decoded
     mode: str
     flags: int = 0
     dont_inherit: bool = False
@@ -771,7 +771,7 @@ static PyObject *
 builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
                      const char *mode, int flags, int dont_inherit,
                      int optimize, int feature_version)
-/*[clinic end generated code: output=b0c09c84f116d3d7 input=cc78e20e7c7682ba]*/
+/*[clinic end generated code: output=b0c09c84f116d3d7 input=8f0069edbdac381b]*/
 {
     PyObject *source_copy;
     const char *str;
@@ -889,7 +889,6 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
 error:
     result = NULL;
 finally:
-    Py_DECREF(filename);
     return result;
 }
 
index c1fcbbba8e2f5786efc214d34327c06ae8bd8576..b44a8d1e2972e46e574ed9977a1d59950c933c00 100644 (file)
@@ -296,7 +296,7 @@ builtin_compile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj
     PyObject *argsbuf[7];
     Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 3;
     PyObject *source;
-    PyObject *filename;
+    PyObject *filename = NULL;
     const char *mode;
     int flags = 0;
     int dont_inherit = 0;
@@ -367,6 +367,9 @@ skip_optional_kwonly:
     return_value = builtin_compile_impl(module, source, filename, mode, flags, dont_inherit, optimize, feature_version);
 
 exit:
+    /* Cleanup for filename */
+    Py_XDECREF(filename);
+
     return return_value;
 }
 
@@ -1274,4 +1277,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=cd5f80e3dc3082d5 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b5d2231033c7591c input=a9049054013a1b77]*/
index 39d0ac557a60f5660d5a4dba57c9912cc2bc9c5c..8c92b766ba0862a6fb7f6cf525283242fc3622f0 100644 (file)
@@ -920,6 +920,26 @@ class unicode_converter(CConverter):
         return super().parse_arg(argname, displayname, limited_capi=limited_capi)
 
 
+class _unicode_fs_converter_base(CConverter):
+    type = 'PyObject *'
+
+    def converter_init(self) -> None:
+        if self.default is not unspecified:
+            fail(f"{self.__class__.__name__} does not support default values")
+        self.c_default = 'NULL'
+
+    def cleanup(self) -> str:
+        return f"Py_XDECREF({self.parser_name});"
+
+
+class unicode_fs_encoded_converter(_unicode_fs_converter_base):
+    converter = 'PyUnicode_FSConverter'
+
+
+class unicode_fs_decoded_converter(_unicode_fs_converter_base):
+    converter = 'PyUnicode_FSDecoder'
+
+
 @add_legacy_c_converter('u')
 @add_legacy_c_converter('u#', zeroes=True)
 @add_legacy_c_converter('Z', accept={str, NoneType})