Skip to content

Use-after-free in zoneinfo.get_weak_cache via weak DECREF assumption #142783

@jackfromeast

Description

@jackfromeast

What happened?

get_weak_cache() assumes that _weak_cache is always owned by the object and safe to DECREF. When _weak_cache is a descriptor returning a new object, this assumption breaks, causing the cache object to be freed too early and leading to a use-after-free in zoneinfo_ZoneInfo_impl().

Proof of Concept:

from zoneinfo import ZoneInfo

class Cache:
    def get(self, *args, **kwargs):
        return None
    def setdefault(self, *args, **kwargs):
        return None
    def clear(self, *args, **kwargs):
        pass

class BombDescriptor:
    def __get__(self, obj, owner):
        return Cache()

class EvilZoneInfo(ZoneInfo):
    pass

EvilZoneInfo._weak_cache = BombDescriptor()

EvilZoneInfo("UTC")

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

static PyObject *
get_weak_cache(zoneinfo_state *state, PyTypeObject *type)
{
    if (type == state->ZoneInfoType) {
        return state->ZONEINFO_WEAK_CACHE;
    }
    else {
        PyObject *cache =
            PyObject_GetAttrString((PyObject *)type, "_weak_cache");
        
        // BUG: The following DECREF free the newly assign cache and leads to UAF
        // We are assuming that the type lives at least as long as the function
        // that calls get_weak_cache, and that it holds a reference to the
        // cache, so we'll return a "borrowed reference".
        Py_XDECREF(cache);
        return cache;
    }
}

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

Sanitizer Report

=================================================================
==1448338==ERROR: AddressSanitizer: heap-use-after-free on address 0x513000037278 at pc 0x59e6317834ef bp 0x7ffc3a4859b0 sp 0x7ffc3a4859a0
READ of size 8 at 0x513000037278 thread T0
    #0 0x59e6317834ee in _Py_TYPE Include/object.h:277
    #1 0x59e6317834ee in PyObject_GetAttrString Objects/object.c:1178
    #2 0x59e6316c3b2a in PyObject_CallMethod Objects/call.c:638
    #3 0x7674c13914f2 in zoneinfo_ZoneInfo_impl Modules/_zoneinfo.c:329
    #4 0x7674c1391710 in zoneinfo_ZoneInfo Modules/clinic/_zoneinfo.c.h:64
    #5 0x59e6317e9346 in type_call Objects/typeobject.c:2448
    #6 0x59e6316c2c71 in _PyObject_MakeTpCall Objects/call.c:242
    #7 0x59e6316c2f19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
    #8 0x59e6316c2f72 in PyObject_Vectorcall Objects/call.c:327
    #9 0x59e631941056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #10 0x59e631984e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #11 0x59e631985148 in _PyEval_Vector Python/ceval.c:2001
    #12 0x59e6319853f8 in PyEval_EvalCode Python/ceval.c:884
    #13 0x59e631a7c507 in run_eval_code_obj Python/pythonrun.c:1365
    #14 0x59e631a7c723 in run_mod Python/pythonrun.c:1459
    #15 0x59e631a7d57a in pyrun_file Python/pythonrun.c:1293
    #16 0x59e631a80220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #17 0x59e631a804f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #18 0x59e631ad174d in pymain_run_file_obj Modules/main.c:410
    #19 0x59e631ad19b4 in pymain_run_file Modules/main.c:429
    #20 0x59e631ad31b2 in pymain_run_python Modules/main.c:691
    #21 0x59e631ad3842 in Py_RunMain Modules/main.c:772
    #22 0x59e631ad3a2e in pymain_main Modules/main.c:802
    #23 0x59e631ad3db3 in Py_BytesMain Modules/main.c:826
    #24 0x59e631557645 in main Programs/python.c:15
    #25 0x7674c202a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #26 0x7674c202a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #27 0x59e631557574 in _start (/home/jackfromeast/Desktop/entropy/tasks/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: ff3dc40ea460bd4beb2c3a72283cca525b319bf0)

0x513000037278 is located 56 bytes inside of 352-byte region [0x513000037240,0x5130000373a0)
freed by thread T0 here:
    #0 0x7674c24fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x59e63178996d in _PyMem_RawFree Objects/obmalloc.c:91
    #2 0x59e63178bcd9 in _PyMem_DebugRawFree Objects/obmalloc.c:2955
    #3 0x59e63178bd1a in _PyMem_DebugFree Objects/obmalloc.c:3100
    #4 0x59e6317b406c in PyObject_Free Objects/obmalloc.c:1522
    #5 0x59e6319f2cf7 in PyObject_GC_Del Python/gc.c:2435
    #6 0x59e6317ce1cb in object_dealloc Objects/typeobject.c:7177
    #7 0x59e6317ec663 in subtype_dealloc Objects/typeobject.c:2852
    #8 0x59e631780481 in _Py_Dealloc Objects/object.c:3200
    #9 0x7674c138967b in Py_DECREF Include/refcount.h:401
    #10 0x7674c138969a in Py_XDECREF Include/refcount.h:511
    #11 0x7674c138b07b in get_weak_cache Modules/_zoneinfo.c:303
    #12 0x7674c13914ca in zoneinfo_ZoneInfo_impl Modules/_zoneinfo.c:328
    #13 0x7674c1391710 in zoneinfo_ZoneInfo Modules/clinic/_zoneinfo.c.h:64
    #14 0x59e6317e9346 in type_call Objects/typeobject.c:2448
    #15 0x59e6316c2c71 in _PyObject_MakeTpCall Objects/call.c:242
    #16 0x59e6316c2f19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
    #17 0x59e6316c2f72 in PyObject_Vectorcall Objects/call.c:327
    #18 0x59e631941056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #19 0x59e631984e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #20 0x59e631985148 in _PyEval_Vector Python/ceval.c:2001
    #21 0x59e6319853f8 in PyEval_EvalCode Python/ceval.c:884
    #22 0x59e631a7c507 in run_eval_code_obj Python/pythonrun.c:1365
    #23 0x59e631a7c723 in run_mod Python/pythonrun.c:1459
    #24 0x59e631a7d57a in pyrun_file Python/pythonrun.c:1293
    #25 0x59e631a80220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #26 0x59e631a804f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #27 0x59e631ad174d in pymain_run_file_obj Modules/main.c:410
    #28 0x59e631ad19b4 in pymain_run_file Modules/main.c:429
    #29 0x59e631ad31b2 in pymain_run_python Modules/main.c:691

previously allocated by thread T0 here:
    #0 0x7674c24fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x59e63178a284 in _PyMem_RawMalloc Objects/obmalloc.c:63
    #2 0x59e631789655 in _PyMem_DebugRawAlloc Objects/obmalloc.c:2887
    #3 0x59e6317896bd in _PyMem_DebugRawMalloc Objects/obmalloc.c:2920
    #4 0x59e63178af3b in _PyMem_DebugMalloc Objects/obmalloc.c:3085
    #5 0x59e6317b3f28 in PyObject_Malloc Objects/obmalloc.c:1493
    #6 0x59e6317e603b in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
    #7 0x59e6317e603b in _PyType_AllocNoTrack Objects/typeobject.c:2504
    #8 0x59e6317e61c7 in PyType_GenericAlloc Objects/typeobject.c:2535
    #9 0x59e6317de10e in object_new Objects/typeobject.c:7167
    #10 0x59e6317e9346 in type_call Objects/typeobject.c:2448
    #11 0x59e6316c2c71 in _PyObject_MakeTpCall Objects/call.c:242
    #12 0x59e6316c2f19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
    #13 0x59e6316c2f72 in PyObject_Vectorcall Objects/call.c:327
    #14 0x59e631941056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #15 0x59e631984e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #16 0x59e631985148 in _PyEval_Vector Python/ceval.c:2001
    #17 0x59e6316c29b8 in _PyFunction_Vectorcall Objects/call.c:413
    #18 0x59e6316c2e7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #19 0x59e6316c2f72 in PyObject_Vectorcall Objects/call.c:327
    #20 0x59e6317fbf01 in slot_tp_descr_get Objects/typeobject.c:10799
    #21 0x59e6317fdb53 in _Py_type_getattro_impl Objects/typeobject.c:6377
    #22 0x59e6317fdd37 in _Py_type_getattro Objects/typeobject.c:6419
    #23 0x59e6317831cc in PyObject_GetAttr Objects/object.c:1313
    #24 0x59e63178350e in PyObject_GetAttrString Objects/object.c:1183
    #25 0x7674c138b070 in get_weak_cache Modules/_zoneinfo.c:299
    #26 0x7674c13914ca in zoneinfo_ZoneInfo_impl Modules/_zoneinfo.c:328
    #27 0x7674c1391710 in zoneinfo_ZoneInfo Modules/clinic/_zoneinfo.c.h:64
    #28 0x59e6317e9346 in type_call Objects/typeobject.c:2448
    #29 0x59e6316c2c71 in _PyObject_MakeTpCall Objects/call.c:242
    #30 0x59e6316c2f19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167

SUMMARY: AddressSanitizer: heap-use-after-free Include/object.h:277 in _Py_TYPE
Shadow bytes around the buggy address:
  0x513000036f80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x513000037000: fd fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x513000037080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x513000037100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x513000037180: 00 00 00 00 00 00 00 00 00 00 00 00 fa fa fa fa
=>0x513000037200: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd[fd]
  0x513000037280: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x513000037300: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x513000037380: fd fd fd fd fa fa fa fa fa fa fa fa fa fa fa fa
  0x513000037400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x513000037480: 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
==1448338==ABORTING

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions