Skip to content

heap-buffer-overflow in functools.partial.__repr__() #144475

@Qanux

Description

@Qanux

Bug report

Bug description:

A heap-buffer-overflow vulnerability exists in CPython's functools.partial.__repr__() method due to improper handling of borrowed references during iteration over pto->args.

Running with a Python interpreter compiled with ASAN (./configure --with-address-sanitizer):

import gc 
from functools import partial 

g_partial = None 

class EvilObject: 
    def __init__(self, name, is_trigger=False): 
        self.name = name 
        self.is_trigger = is_trigger 
        self.triggered = False 
    
    def __repr__(self): 
        global g_partial 
        if self.is_trigger and not self.triggered and g_partial is not None: 
            self.triggered = True 
            # Replace args via __setstate__, this frees the old tuple 
            new_state = (lambda x: x, ("replaced",), {}, None) 
            g_partial.__setstate__(new_state) 
            gc.collect() 
        return f"EvilObject({self.name})" 
 
evil1 = EvilObject("trigger", is_trigger=True) 
evil2 = EvilObject("victim1") 
evil3 = EvilObject("victim2") 
evil4 = EvilObject("victim3") 
evil5 = EvilObject("victim4") 

p = partial(lambda: None, evil1, evil2, evil3, evil4, evil5) 
g_partial = p 

del evil1, evil2, evil3, evil4, evil5 

repr(p) 

Asan output:

=================================================================
==430879==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5060000d4878 at pc 0x5c4743c6d881 bp 0x7ffc1076d330 sp 0x7ffc1076d320
READ of size 8 at 0x5060000d4878 thread T0
    #0 0x5c4743c6d880 in partial_repr Modules/_functoolsmodule.c:712
    #1 0x5c47436f8158 in PyObject_Repr Objects/object.c:779
    #2 0x5c47436f8158 in PyObject_Repr Objects/object.c:754
    #3 0x5c47435da5ef in _PyObject_VectorcallTstate Include/internal/pycore_call.h:136
    #4 0x5c47435da5ef in PyObject_Vectorcall Objects/call.c:327
    #5 0x5c474394258e in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:762
    #6 0x5c4743498fc3 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1809
    #7 0x5c474394f069 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:118
    #8 0x5c474394f069 in _PyEval_Vector Python/ceval.c:2097
    #9 0x5c474394f069 in PyEval_EvalCode Python/ceval.c:673
    #10 0x5c4743a8f6ed in run_eval_code_obj Python/pythonrun.c:1366
    #11 0x5c4743a8f6ed in run_mod Python/pythonrun.c:1469
    #12 0x5c4743a9456e in pyrun_file Python/pythonrun.c:1294
    #13 0x5c4743a9456e in _PyRun_SimpleFileObject Python/pythonrun.c:518
    #14 0x5c4743a94fbb in _PyRun_AnyFileObject Python/pythonrun.c:81
    #15 0x5c4743b0a0e9 in pymain_run_file_obj Modules/main.c:410
    #16 0x5c4743b0a0e9 in pymain_run_file Modules/main.c:429
    #17 0x5c4743b0a0e9 in pymain_run_python Modules/main.c:691
    #18 0x5c4743b0b7de in Py_RunMain Modules/main.c:772
    #19 0x5c4743b0b7de in pymain_main Modules/main.c:802
    #20 0x5c4743b0b7de in Py_BytesMain Modules/main.c:826
    #21 0x7005d3e29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #22 0x7005d3e29e3f in __libc_start_main_impl ../csu/libc-start.c:392
    #23 0x5c47434b8d34 in _start (/home/or4nge/cpython/python+0x20cd34)

0x5060000d4878 is located 0 bytes to the right of 56-byte region [0x5060000d4840,0x5060000d4878)
allocated by thread T0 here:
    #0 0x7005d42b4887 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
    #1 0x5c47439e5079 in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
    #2 0x5c47439e5079 in gc_alloc Python/gc.c:2352
    #3 0x5c47439e5079 in _PyObject_GC_NewVar Python/gc.c:2394
    #4 0x5c4743762d6a in tuple_alloc Objects/tupleobject.c:57
    #5 0x5c47437657b7 in PyTuple_New Objects/tupleobject.c:81
    #6 0x5c47437657b7 in PyTuple_New Objects/tupleobject.c:75
    #7 0x5c47439cad7f in fold_tuple_of_constants Python/flowgraph.c:1470
    #8 0x5c47439cad7f in optimize_basic_block Python/flowgraph.c:2337
    #9 0x5c47439cf342 in optimize_cfg Python/flowgraph.c:2546
    #10 0x5c47439cf342 in _PyCfg_OptimizeCodeUnit Python/flowgraph.c:3658
    #11 0x5c4743992c7a in optimize_and_assemble_code_unit Python/compile.c:1439
    #12 0x5c4743992c7a in _PyCompile_OptimizeAndAssemble Python/compile.c:1481
    #13 0x5c4743988122 in codegen_function_body Python/codegen.c:1411
    #14 0x5c4743988122 in codegen_function Python/codegen.c:1503
    #15 0x5c4743979224 in codegen_visit_stmt Python/codegen.c:3029
    #16 0x5c474397fa1a in codegen_body Python/codegen.c:929
    #17 0x5c474398142f in codegen_class_body Python/codegen.c:1592
    #18 0x5c474398142f in codegen_class Python/codegen.c:1685
    #19 0x5c474397b86a in codegen_visit_stmt Python/codegen.c:3031
    #20 0x5c474397fa1a in codegen_body Python/codegen.c:929
    #21 0x5c474398ade2 in _PyCodegen_Module Python/codegen.c:892
    #22 0x5c474398d148 in compiler_codegen Python/compile.c:841
    #23 0x5c47439930f6 in compiler_mod Python/compile.c:862
    #24 0x5c47439930f6 in _PyAST_Compile Python/compile.c:1494
    #25 0x5c4743a8f4d0 in run_mod Python/pythonrun.c:1419
    #26 0x5c4743a9456e in pyrun_file Python/pythonrun.c:1294
    #27 0x5c4743a9456e in _PyRun_SimpleFileObject Python/pythonrun.c:518
    #28 0x5c4743a94fbb in _PyRun_AnyFileObject Python/pythonrun.c:81
    #29 0x5c4743b0a0e9 in pymain_run_file_obj Modules/main.c:410
    #30 0x5c4743b0a0e9 in pymain_run_file Modules/main.c:429
    #31 0x5c4743b0a0e9 in pymain_run_python Modules/main.c:691
    #32 0x5c4743b0b7de in Py_RunMain Modules/main.c:772
    #33 0x5c4743b0b7de in pymain_main Modules/main.c:802
    #34 0x5c4743b0b7de in Py_BytesMain Modules/main.c:826
    #35 0x7005d3e29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58

SUMMARY: AddressSanitizer: heap-buffer-overflow Modules/_functoolsmodule.c:712 in partial_repr
Shadow bytes around the buggy address:
  0x0a0c800128b0: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa
  0x0a0c800128c0: 00 00 00 00 00 00 00 00 fa fa fa fa fd fd fd fd
  0x0a0c800128d0: fd fd fd fd fa fa fa fa 00 00 00 00 00 00 00 00
  0x0a0c800128e0: fa fa fa fa 00 00 00 00 00 00 00 00 fa fa fa fa
  0x0a0c800128f0: 00 00 00 00 00 00 00 fa fa fa fa fa 00 00 00 00
=>0x0a0c80012900: 00 00 00 00 fa fa fa fa 00 00 00 00 00 00 00[fa]
  0x0a0c80012910: fa fa fa fa 00 00 00 00 00 00 00 fa fa fa fa fa
  0x0a0c80012920: 00 00 00 00 00 00 00 00 fa fa fa fa fd fd fd fd
  0x0a0c80012930: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fa
  0x0a0c80012940: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa
  0x0a0c80012950: 00 00 00 00 00 00 00 00 fa fa fa fa 00 00 00 00
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
  Shadow gap:              cc
==430879==ABORTING

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions