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
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;
}