-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
Open
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
What happened?
The zoneinfo_ZoneInfo_impl() looks up an entry via zone_from_strong_cache, which traverses the strong-cache list and compares keys using PyObject_RichCompareBool(key, node->key, Py_EQ). A crafted str subclass overrides __eq__ to call ZoneInfo.clear_cache(only_keys=(self,)) during the comparison, freeing the currently visited StrongCacheNode. Traversal then proceeds and moves the (now freed) node to the LRU front, triggering a heap use-after-free (ASan crash in remove_from_strong_cache).
Proof of Concept:
from zoneinfo import ZoneInfo
class EvilStr(str):
def __new__(cls, value: str):
obj = super().__new__(cls, value)
obj._triggered = False
return obj
def __eq__(self, other):
if not self._triggered:
self._triggered = True
ZoneInfo.clear_cache(only_keys=(self,))
return super().__eq__(other)
def __hash__(self):
return super().__hash__()
key1 = EvilStr("UTC")
key2 = EvilStr("UTC")
ZoneInfo(key1)
ZoneInfo(key2)Affected Versions:
| Python Version | Status | Exit Code |
|---|---|---|
Python 3.9.24+ (heads/3.9:9c4638d, Oct 17 2025, 11:19:30) |
ASAN | 1 |
Python 3.10.19+ (heads/3.10:0142619, Oct 17 2025, 11:20:05) [GCC 13.3.0] |
ASAN | 1 |
Python 3.11.14+ (heads/3.11:88f3f5b, Oct 17 2025, 11:20:44) [GCC 13.3.0] |
ASAN | 1 |
Python 3.12.12+ (heads/3.12:8cb2092, Oct 17 2025, 11:21:35) [GCC 13.3.0] |
ASAN | 1 |
Python 3.13.9+ (heads/3.13:0760a57, Oct 17 2025, 11:22:25) [GCC 13.3.0] |
ASAN | 1 |
Python 3.14.0+ (heads/3.14:889e918, Oct 17 2025, 11:23:02) [GCC 13.3.0] |
ASAN | 1 |
Python 3.15.0a1+ (heads/main:fbf0843, Oct 17 2025, 11:23:37) [GCC 13.3.0] |
ASAN | 1 |
Related Code Snippet
static PyObject *
zoneinfo_ZoneInfo_impl(PyTypeObject *type, PyObject *key)
/*[clinic end generated code: output=95e61dab86bb95c3 input=ef73d7a83bf8790e]*/
{
zoneinfo_state *state = zoneinfo_get_state_by_self(type);
PyObject *instance = zone_from_strong_cache(state, type, key); // Trigged __eq__ method
if (instance != NULL || PyErr_Occurred()) {
return instance;
}
PyObject *weak_cache = get_weak_cache(state, type);
instance = PyObject_CallMethod(weak_cache, "get", "O", key, Py_None);
if (instance == NULL) {
return NULL;
}
if (instance == Py_None) {
Py_DECREF(instance);
PyObject *tmp = zoneinfo_new_instance(state, type, key);
if (tmp == NULL) {
return NULL;
}
instance =
PyObject_CallMethod(weak_cache, "setdefault", "OO", key, tmp);
Py_DECREF(tmp);
if (instance == NULL) {
return NULL;
}
((PyZoneInfo_ZoneInfo *)instance)->source = SOURCE_CACHE;
}
update_strong_cache(state, type, key, instance);
return instance;
}
/* Retrieves a ZoneInfo from the strong cache if it's present.
*
* This function finds the ZoneInfo by key and if found will move the node to
* the front of the LRU cache and return a new reference to it. It returns NULL
* if the key is not in the cache.
*
* The strong cache is currently only implemented for the base class, so this
* always returns a cache miss for subclasses.
*/
static PyObject *
zone_from_strong_cache(zoneinfo_state *state, const PyTypeObject *const type,
PyObject *const key)
{
if (type != state->ZoneInfoType) {
return NULL; // Strong cache currently only implemented for base class
}
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(state->ZoneInfoType);
StrongCacheNode *cache = state->ZONEINFO_STRONG_CACHE;
StrongCacheNode *node = find_in_strong_cache(cache, key);
if (node != NULL) {
StrongCacheNode **root = &(state->ZONEINFO_STRONG_CACHE);
// Node has been accessed while it has been freed already
move_strong_cache_node_to_front(state, root, node);
return Py_NewRef(node->zone);
}
return NULL; // Cache miss
}
/* Retrieves the node associated with a key, if it exists.
*
* This traverses the strong cache until it finds a matching key and returns a
* pointer to the relevant node if found. Returns NULL if no node is found.
*
* root may be NULL, indicating an empty cache.
*/
static StrongCacheNode *
find_in_strong_cache(const StrongCacheNode *const root, PyObject *const key)
{
const StrongCacheNode *node = root;
while (node != NULL) {
// Trigged __eq__ method where the strong cache node has been freed due to the clear
int rv = PyObject_RichCompareBool(key, node->key, Py_EQ);
if (rv < 0) {
return NULL;
}
if (rv) {
return (StrongCacheNode *)node;
}
node = node->next;
}
return NULL;
}
Sanitizer Report
=================================================================
==1443306==ERROR: AddressSanitizer: heap-use-after-free on address 0x506000079ef8 at pc 0x7108671893f8 bp 0x7fff61acc1b0 sp 0x7fff61acc1a0
READ of size 8 at 0x506000079ef8 thread T0
#0 0x7108671893f7 in remove_from_strong_cache Modules/_zoneinfo.c:2379
#1 0x710867189444 in move_strong_cache_node_to_front Modules/_zoneinfo.c:2457
#2 0x71086718ce56 in zone_from_strong_cache Modules/_zoneinfo.c:2492
#3 0x71086719149b in zoneinfo_ZoneInfo_impl Modules/_zoneinfo.c:323
#4 0x710867191710 in zoneinfo_ZoneInfo Modules/clinic/_zoneinfo.c.h:64
#5 0x5703b3b3f346 in type_call Objects/typeobject.c:2448
#6 0x5703b3a18c71 in _PyObject_MakeTpCall Objects/call.c:242
#7 0x5703b3a18f19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
#8 0x5703b3a18f72 in PyObject_Vectorcall Objects/call.c:327
#9 0x5703b3c97056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#10 0x5703b3cdae54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#11 0x5703b3cdb148 in _PyEval_Vector Python/ceval.c:2001
#12 0x5703b3cdb3f8 in PyEval_EvalCode Python/ceval.c:884
#13 0x5703b3dd2507 in run_eval_code_obj Python/pythonrun.c:1365
#14 0x5703b3dd2723 in run_mod Python/pythonrun.c:1459
#15 0x5703b3dd357a in pyrun_file Python/pythonrun.c:1293
#16 0x5703b3dd6220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#17 0x5703b3dd64f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
#18 0x5703b3e2774d in pymain_run_file_obj Modules/main.c:410
#19 0x5703b3e279b4 in pymain_run_file Modules/main.c:429
#20 0x5703b3e291b2 in pymain_run_python Modules/main.c:691
#21 0x5703b3e29842 in Py_RunMain Modules/main.c:772
#22 0x5703b3e29a2e in pymain_main Modules/main.c:802
#23 0x5703b3e29db3 in Py_BytesMain Modules/main.c:826
#24 0x5703b38ad645 in main Programs/python.c:15
#25 0x710867c2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#26 0x710867c2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#27 0x5703b38ad574 in _start (/home/jackfromeast/Desktop/entropy/tasks/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: ff3dc40ea460bd4beb2c3a72283cca525b319bf0)
0x506000079ef8 is located 24 bytes inside of 56-byte region [0x506000079ee0,0x506000079f18)
freed by thread T0 here:
#0 0x7108680fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0x5703b3adf96d in _PyMem_RawFree Objects/obmalloc.c:91
#2 0x5703b3ae1cd9 in _PyMem_DebugRawFree Objects/obmalloc.c:2955
#3 0x5703b3ae1d1a in _PyMem_DebugFree Objects/obmalloc.c:3100
#4 0x5703b3b09348 in PyMem_Free Objects/obmalloc.c:1070
#5 0x710867190c1a in strong_cache_node_free Modules/_zoneinfo.c:2344
#6 0x710867190c93 in eject_from_strong_cache Modules/_zoneinfo.c:2435
#7 0x710867191079 in zoneinfo_ZoneInfo_clear_cache_impl Modules/_zoneinfo.c:524
#8 0x7108671912f3 in zoneinfo_ZoneInfo_clear_cache Modules/clinic/_zoneinfo.c.h:255
#9 0x5703b3acb0b6 in cfunction_vectorcall_FASTCALL_KEYWORDS_METHOD Objects/methodobject.c:481
#10 0x5703b3a18e7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#11 0x5703b3a18f72 in PyObject_Vectorcall Objects/call.c:327
#12 0x5703b3c9ec60 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:2920
#13 0x5703b3cdae54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#14 0x5703b3cdb148 in _PyEval_Vector Python/ceval.c:2001
#15 0x5703b3a189b8 in _PyFunction_Vectorcall Objects/call.c:413
#16 0x5703b3b2b56b in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#17 0x5703b3b479a6 in vectorcall_unbound Objects/typeobject.c:3033
#18 0x5703b3b479a6 in maybe_call_special_one_arg Objects/typeobject.c:3175
#19 0x5703b3b47ad3 in _PyObject_MaybeCallSpecialOneArg Objects/typeobject.c:3190
#20 0x5703b3b47b19 in slot_tp_richcompare Objects/typeobject.c:10729
#21 0x5703b3ad8687 in do_richcompare Objects/object.c:1059
#22 0x5703b3ad893d in PyObject_RichCompare Objects/object.c:1108
#23 0x5703b3ad89ad in PyObject_RichCompareBool Objects/object.c:1130
#24 0x71086718af2a in find_in_strong_cache Modules/_zoneinfo.c:2403
#25 0x71086718ce3f in zone_from_strong_cache Modules/_zoneinfo.c:2488
#26 0x71086719149b in zoneinfo_ZoneInfo_impl Modules/_zoneinfo.c:323
#27 0x710867191710 in zoneinfo_ZoneInfo Modules/clinic/_zoneinfo.c.h:64
#28 0x5703b3b3f346 in type_call Objects/typeobject.c:2448
#29 0x5703b3a18c71 in _PyObject_MakeTpCall Objects/call.c:242
#30 0x5703b3a18f19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
previously allocated by thread T0 here:
#0 0x7108680fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x5703b3ae0284 in _PyMem_RawMalloc Objects/obmalloc.c:63
#2 0x5703b3adf655 in _PyMem_DebugRawAlloc Objects/obmalloc.c:2887
#3 0x5703b3adf6bd in _PyMem_DebugRawMalloc Objects/obmalloc.c:2920
#4 0x5703b3ae0f3b in _PyMem_DebugMalloc Objects/obmalloc.c:3085
#5 0x5703b3b09204 in PyMem_Malloc Objects/obmalloc.c:1041
#6 0x71086718d3c9 in strong_cache_node_new Modules/_zoneinfo.c:2324
#7 0x7108671913ba in update_strong_cache Modules/_zoneinfo.c:2514
#8 0x710867191514 in zoneinfo_ZoneInfo_impl Modules/_zoneinfo.c:350
#9 0x710867191710 in zoneinfo_ZoneInfo Modules/clinic/_zoneinfo.c.h:64
#10 0x5703b3b3f346 in type_call Objects/typeobject.c:2448
#11 0x5703b3a18c71 in _PyObject_MakeTpCall Objects/call.c:242
#12 0x5703b3a18f19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
#13 0x5703b3a18f72 in PyObject_Vectorcall Objects/call.c:327
#14 0x5703b3c97056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#15 0x5703b3cdae54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#16 0x5703b3cdb148 in _PyEval_Vector Python/ceval.c:2001
#17 0x5703b3cdb3f8 in PyEval_EvalCode Python/ceval.c:884
#18 0x5703b3dd2507 in run_eval_code_obj Python/pythonrun.c:1365
#19 0x5703b3dd2723 in run_mod Python/pythonrun.c:1459
#20 0x5703b3dd357a in pyrun_file Python/pythonrun.c:1293
#21 0x5703b3dd6220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#22 0x5703b3dd64f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
#23 0x5703b3e2774d in pymain_run_file_obj Modules/main.c:410
#24 0x5703b3e279b4 in pymain_run_file Modules/main.c:429
#25 0x5703b3e291b2 in pymain_run_python Modules/main.c:691
#26 0x5703b3e29842 in Py_RunMain Modules/main.c:772
#27 0x5703b3e29a2e in pymain_main Modules/main.c:802
#28 0x5703b3e29db3 in Py_BytesMain Modules/main.c:826
#29 0x5703b38ad645 in main Programs/python.c:15
SUMMARY: AddressSanitizer: heap-use-after-free Modules/_zoneinfo.c:2379 in remove_from_strong_cache
Shadow bytes around the buggy address:
0x506000079c00: fd fd fd fd fa fa fa fa fd fd fd fd fd fd fd fa
0x506000079c80: fa fa fa fa fd fd fd fd fd fd fd fd fa fa fa fa
0x506000079d00: fd fd fd fd fd fd fd fd fa fa fa fa fd fd fd fd
0x506000079d80: fd fd fd fd fa fa fa fa fd fd fd fd fd fd fd fa
0x506000079e00: fa fa fa fa fd fd fd fd fd fd fd fd fa fa fa fa
=>0x506000079e80: fd fd fd fd fd fd fd fd fa fa fa fa fd fd fd[fd]
0x506000079f00: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fd
0x506000079f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50600007a000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50600007a080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x50600007a100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==1443306==ABORTINGMetadata
Metadata
Assignees
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Projects
Status
No status