Skip to content

Commit a445c16

Browse files
committed
gh-144475: Fix use-after-free in functools.partial.__repr__()
Hold strong references to pto->args, pto->kw, and pto->fn during partial_repr() to prevent them from being freed by a user-defined __repr__() that mutates the partial object via __setstate__(). Previously, partial_repr() iterated over pto->args using a size 'n' captured before the loop, and accessed tuple items via borrowed references. If a __repr__() called during formatting invoked pto.__setstate__() with a new (smaller) args tuple, the original tuple could be freed while the loop was still iterating, leading to a heap-buffer-overflow (out-of-bounds read). The fix takes a new reference (Py_NewRef) to the args tuple, kw dict, and fn callable before using them, ensuring they stay alive regardless of any mutations to the partial object during formatting.
1 parent 41fa2db commit a445c16

File tree

1 file changed

+24
-8
lines changed

1 file changed

+24
-8
lines changed

Modules/_functoolsmodule.c

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -705,26 +705,39 @@ partial_repr(PyObject *self)
705705
arglist = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
706706
if (arglist == NULL)
707707
goto done;
708-
/* Pack positional arguments */
708+
/* Pack positional arguments.
709+
* Hold a strong reference to pto->args across the loop, because
710+
* a user-defined __repr__ called via %R could mutate 'pto' (e.g.
711+
* via __setstate__), freeing the original args tuple while we're
712+
* still iterating over it. See gh-144475. */
709713
assert(PyTuple_Check(pto->args));
710-
n = PyTuple_GET_SIZE(pto->args);
714+
PyObject *args = Py_NewRef(pto->args);
715+
n = PyTuple_GET_SIZE(args);
711716
for (i = 0; i < n; i++) {
712717
Py_SETREF(arglist, PyUnicode_FromFormat("%U, %R", arglist,
713-
PyTuple_GET_ITEM(pto->args, i)));
714-
if (arglist == NULL)
718+
PyTuple_GET_ITEM(args, i)));
719+
if (arglist == NULL) {
720+
Py_DECREF(args);
715721
goto done;
722+
}
716723
}
717-
/* Pack keyword arguments */
724+
Py_DECREF(args);
725+
/* Pack keyword arguments.
726+
* Similarly, hold a strong reference to pto->kw. See gh-144475. */
718727
assert (PyDict_Check(pto->kw));
719-
for (i = 0; PyDict_Next(pto->kw, &i, &key, &value);) {
728+
PyObject *kw = Py_NewRef(pto->kw);
729+
for (i = 0; PyDict_Next(kw, &i, &key, &value);) {
720730
/* Prevent key.__str__ from deleting the value. */
721731
Py_INCREF(value);
722732
Py_SETREF(arglist, PyUnicode_FromFormat("%U, %S=%R", arglist,
723733
key, value));
724734
Py_DECREF(value);
725-
if (arglist == NULL)
735+
if (arglist == NULL) {
736+
Py_DECREF(kw);
726737
goto done;
738+
}
727739
}
740+
Py_DECREF(kw);
728741

729742
mod = PyType_GetModuleName(Py_TYPE(pto));
730743
if (mod == NULL) {
@@ -735,7 +748,10 @@ partial_repr(PyObject *self)
735748
Py_DECREF(mod);
736749
goto error;
737750
}
738-
result = PyUnicode_FromFormat("%S.%S(%R%U)", mod, name, pto->fn, arglist);
751+
/* Hold a strong reference to pto->fn for the same reason as args/kw. */
752+
PyObject *fn = Py_NewRef(pto->fn);
753+
result = PyUnicode_FromFormat("%S.%S(%R%U)", mod, name, fn, arglist);
754+
Py_DECREF(fn);
739755
Py_DECREF(mod);
740756
Py_DECREF(name);
741757
Py_DECREF(arglist);

0 commit comments

Comments
 (0)