Title: test_code.CoExtra leads to double-free when ce_size >1
Type: crash Stage:
Components: Tests Versions: Python 3.9, Python 3.8, Python 3.7, Python 3.6
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: dino.viehland, slebedev
Priority: normal Keywords:

Created on 2020-01-16 11:58 by slebedev, last changed 2020-01-16 12:00 by slebedev.

Messages (1)
msg360117 - (view) Author: Sergei Lebedev (slebedev) Date: 2020-01-16 11:58
tl;dr Passing a Python function as a freefunc to _PyEval_RequestCodeExtraIndex leads to double-free. In general, I think that freefunc should not be allowed to call other Python functions.


test_code.CoExtra registers a new co_extra slot with a ctypes-wrapped freefunc

    # All defined in globals().
    LAST_FREED = None

    def myfree(ptr):
        global LAST_FREED
        LAST_FREED = ptr

    FREE_FUNC = freefunc(myfree)
    FREE_INDEX = RequestCodeExtraIndex(FREE_FUNC)

Imagine that we have registered another co_extra slot FOO_INDEX of type Foo and a freefunc FreeFoo. Furthermore, assume that 

* FOO_INDEX is set on any executed code object including myfree. 

Consider what happens when we collect the globals() of the test_code module. myfree is referenced by globals() and FREE_FUNC. If FREE_FUNC is DECREF'd first, then by the time we get to myfree it has a refcount of 1 and DECREF'ing it leads to a code_dealloc call. Recall that the code object corresponding to myfree has two co_extra slots: 

* FOO_INDEX pointing to some Foo*, and
* FREE_INDEX with a value of NULL.

So, code_dealloc will first call FreeFoo (because FOO_INDEX < FREE_INDEX) and then the ctypes wrapper of myfree. The following sequence of calls looks roughly like this

code_dealloc # !

The argument of the last code_dealloc call is *the same* myfree code object (!). This means that code_dealloc will attempt to call FreeFoo on an already free'd pointer leading to a crash.
Date User Action Args
2020-01-16 12:00:40slebedevsetnosy: + dino.viehland
2020-01-16 11:58:29slebedevcreate