# 3617 for (i = 0; i < len; i++) { # (gdb) print *(PyListObject*)tmp # $1 = {ob_base = {ob_base = {_ob_next = 0x4056f8f4, _ob_prev = 0x4057329c, ob_refcnt = 2, ob_type = 0x830e1c0 }, # ob_size = 1337}, ob_item = 0x8491ae0, allocated = 1432} # (gdb) n # 3619 res = obj2ast_stmt(PyList_GET_ITEM(tmp, i), &value, arena); # (gdb) n # 3620 if (res != 0) goto failed; # (gdb) print *(PyListObject*)tmp # $2 = {ob_base = {ob_base = {_ob_next = 0x4056f8f4, _ob_prev = 0x4057329c, ob_refcnt = 2, ob_type = 0x830e1c0 }, # ob_size = 1}, ob_item = 0x8491ae0, allocated = 4} # (gdb) c # Continuing. # # Program received signal SIGSEGV, Segmentation fault. # 0x080f2c17 in PyObject_GetAttr (v=, name='lineno') at Objects/object.c:872 # 872 if (tp->tp_getattro != NULL) # # Objects freed in __getattr__ are used later in the loop above. There are two # bugs actually. One is the use-after-free and the second is using a stale size # variable "len" to control the for(...) loop. "body" can be mutated inside # obj2ast_stmt. import ast class X(ast.FunctionDef): def __init__(self): m = ast.parse("def f(): pass") self.fun_def = m.body[0] def __getattr__(self, attr): global m print("__getattr__:", attr) if attr in ["col_offset", "lineno"]: m.body[1:] = [] return 1 x = getattr(self.fun_def, attr) return x m = ast.Module(body=[X() for i in range(1337)]) compile(m, "", "exec")