mirror of
https://github.com/python/cpython.git
synced 2026-04-20 10:51:00 +00:00
gh-145244: Fix use-after-free on borrowed dict key in json encoder (GH-145245)
In encoder_encode_key_value(), key is a borrowed reference from
PyDict_Next(). If the default callback mutates or clears the dict,
key becomes a dangling pointer. The error path then calls
_PyErr_FormatNote("%R", key) on freed memory.
Fix by holding strong references to key and value unconditionally
during encoding, not just in the free-threading build.
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
This commit is contained in:
parent
daa2578dc0
commit
8a466fa3d9
3 changed files with 29 additions and 7 deletions
|
|
@ -77,6 +77,29 @@ def __lt__(self, o):
|
|||
d[1337] = "true.dat"
|
||||
self.assertEqual(self.dumps(d, sort_keys=True), '{"1337": "true.dat"}')
|
||||
|
||||
# gh-145244: UAF on borrowed key when default callback mutates dict
|
||||
def test_default_clears_dict_key_uaf(self):
|
||||
class Evil:
|
||||
pass
|
||||
|
||||
class AlsoEvil:
|
||||
pass
|
||||
|
||||
# Use a non-interned string key so it can actually be freed
|
||||
key = "A" * 100
|
||||
target = {key: Evil()}
|
||||
del key
|
||||
|
||||
def evil_default(obj):
|
||||
if isinstance(obj, Evil):
|
||||
target.clear()
|
||||
return AlsoEvil()
|
||||
raise TypeError("not serializable")
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
self.json.dumps(target, default=evil_default,
|
||||
check_circular=False)
|
||||
|
||||
def test_dumps_str_subclass(self):
|
||||
# Don't call obj.__str__() on str subclasses
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Fixed a use-after-free in :mod:`json` encoder when a ``default`` callback
|
||||
mutates the dictionary being serialized.
|
||||
|
|
@ -1784,24 +1784,21 @@ _encoder_iterate_dict_lock_held(PyEncoderObject *s, PyUnicodeWriter *writer,
|
|||
PyObject *key, *value;
|
||||
Py_ssize_t pos = 0;
|
||||
while (PyDict_Next(dct, &pos, &key, &value)) {
|
||||
#ifdef Py_GIL_DISABLED
|
||||
// gh-119438: in the free-threading build the critical section on dct can get suspended
|
||||
// gh-119438, gh-145244: key and value are borrowed refs from
|
||||
// PyDict_Next(). encoder_encode_key_value() may invoke user
|
||||
// Python code (the 'default' callback) that can mutate or
|
||||
// clear the dict, so we must hold strong references.
|
||||
Py_INCREF(key);
|
||||
Py_INCREF(value);
|
||||
#endif
|
||||
if (encoder_encode_key_value(s, writer, first, dct, key, value,
|
||||
indent_level, indent_cache,
|
||||
separator) < 0) {
|
||||
#ifdef Py_GIL_DISABLED
|
||||
Py_DECREF(key);
|
||||
Py_DECREF(value);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
#ifdef Py_GIL_DISABLED
|
||||
Py_DECREF(key);
|
||||
Py_DECREF(value);
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue