Skip to content

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

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

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions