Skip to content

Instantly share code, notes, and snippets.

@sbz

sbz/sbz-cpython-crash.md Secret

Last active Nov 21, 2020
Embed
What would you like to do?
Cpython interpreter crash

Python Code Objects crash

Py_DECREF on value crash the interpreter in Python/ceval.c:1104

Report of bugs.python.org

https://bugs.python.org/issue42422

Pull Request

https://github.com/python/cpython/pull/23448

Preparation

Build Python with Debug

export CFLAGS+="-g -O0"
./configure --with-memory-sanitizer --with-address-sanitizer --with-pymalloc --with-pydebug
[sbz@devbox ~/github/cpython]$ elfdbg -v ./python
HAS DEBUG
7 ELF debug sections:
.debug_str
.debug_loc
.debug_abbrev
.debug_info
.debug_ranges
.debug_line
.debug_aranges

Debugging in GDB session

[sbz@devbox ~/github/cpython]$ gdb ./python
gdb$ source python-gdb.py
gdb$ source Tools/gdb/libpython.py
gdb$ b*_Py_UnixMain
gdb$ r crash.py

Compile bytecode

python2.7 -mcompileall crash.py
python3.6 -mcompileall crash.py
python3.7 -mcompileall crash.py
python3.8 -mcompileall crash.py
python3.9 -mcompileall crash.py
ls __pycache__

Analysis

Comments

This PoC is crashing python interpreters version 2.7,3.6,3.7,3.8 and above 3.9+.

By creating a code object of size 0 with a POP_TOP opcode this lead to a segmentation fault of the python interpreter in Python/ceval.c.

It was tested on all python3.x versions against a fresh compilation of the git clone github.com/python/cpython.git on branches and master. You need to adapt the code() constructor because the parameters are different across versions but crash remains.

I will just cover the version 3.7 in following text

Reproduction steps

$ git clone --depth 1 https://github.com/python/cpython.git
$ git checkout -b 3.7 origin/3.7
$ export CFLAGS+="-g -O0"
$ ./configure
$ make
$ ./python -V
Python 3.7.9+
$ ./python -c 'import sys; print(sys.version)'
3.7.9+ (heads/3.7-dirty:08ba61dade, Nov 21 2020, 04:57:20) 
[Clang 10.0.1 (git@github.com:llvm/llvm-project.git llvmorg-10.0.1-0-gef32c611a
$ ./python crash.py

Running the python3.7 execution into gdb, helped me to locate the crash for python3.7 https://github.com/python/cpython/blob/3.7/Python/ceval.c#L1104

$ gdb --batch --silent ./python -ex 'r crash.py' -ex 'bt'

Program received signal SIGSEGV, Segmentation fault.
0x000000000033794a in _PyEval_EvalFrameDefault (f=0x800b7d098, throwflag=0) at Python/ceval.c:1104
warning: Source file is more recent than executable.
1104                Py_DECREF(value);

Also I have executed the PoC in different platforms Linux, FreeBSD and MacOSX. The behaviour is the same and the interpreter keep getting SIGSEGV signal.

I have located the issue in the source code but I'm wondering what will be the best way to fix it? Open to python developers suggestions.

I have noticed that one assertion handle this case (link in master) https://github.com/python/cpython/blob/master/Python/ceval.c#L1430 but most of the interpreters are built without --with-assertions enabled (i.e. -with -DNDEBUG compiler flag), so the crash will still persist.

Crash root cause

$ gdb python3.7
(gdb) source python-gdb.py
(gdb) source Tools/gdb/libpython.py
(gdb) r crash.py
Starting program: /usr/home/sbz/github/cpython/python crash.py
  0           0 POP_TOP

Program received signal SIGSEGV, Segmentation fault.
0x000000000033794a in _PyEval_EvalFrameDefault (f=0x800b7d098, throwflag=0) at Python/ceval.c:1104
warning: Source file is more recent than executable.
1104                Py_DECREF(value);
(gdb) bt
#0  0x000000000033794a in _PyEval_EvalFrameDefault (f=0x800b7d098, throwflag=0) at Python/ceval.c:1104
#1  0x0000000000336f6a in PyEval_EvalFrameEx (f=0x800b7d098, throwflag=0) at Python/ceval.c:547
#2  0x000000000034a5d8 in _PyEval_EvalCodeWithName (_co=0x8013a69c0, globals=0x800be0bd0, locals=0x800be0bd0, args=0x0, argcount=0, kwnames=0x0, kwargs=0x0, kwcount=0, kwstep=2, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0, name=0x0, qualname=0x0)
    at Python/ceval.c:3930
#3  0x0000000000336f00 in PyEval_EvalCodeEx (_co=0x8013a69c0, globals=0x800be0bd0, locals=0x800be0bd0, args=0x0, argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0) at Python/ceval.c:3959
#4  0x0000000000336d8e in PyEval_EvalCode (co=0x8013a69c0, globals=0x800be0bd0, locals=0x800be0bd0) at Python/ceval.c:524
#5  0x000000000045202f in builtin_exec_impl (module=0x800adcc28, source=0x8013a69c0, globals=0x800be0bd0, locals=0x800be0bd0) at Python/bltinmodule.c:1079
#6  0x000000000044fa1a in builtin_exec (module=0x800adcc28, args=0x8012cfbb8, nargs=1) at Python/clinic/bltinmodule.c.h:283
#7  0x000000000032319e in _PyMethodDef_RawFastCallKeywords (method=0x544340 <builtin_methods+480>, self=0x800adcc28, args=0x8012cfbb8, nargs=1, kwnames=0x0) at Objects/call.c:651
#8  0x0000000000322323 in _PyCFunction_FastCallKeywords (func=0x800b35d38, args=0x8012cfbb8, nargs=1, kwnames=0x0) at Objects/call.c:730
#9  0x0000000000348b71 in call_function (pp_stack=0x7fffffffcec0, oparg=1, kwnames=0x0) at Python/ceval.c:4568
#10 0x000000000034415e in _PyEval_EvalFrameDefault (f=0x8012cfa48, throwflag=0) at Python/ceval.c:3124
#11 0x0000000000336f6a in PyEval_EvalFrameEx (f=0x8012cfa48, throwflag=0) at Python/ceval.c:547
#12 0x000000000034a5d8 in _PyEval_EvalCodeWithName (_co=0x8012979c0, globals=0x800be0bd0, locals=0x800be0bd0, args=0x0, argcount=0, kwnames=0x0, kwargs=0x0, kwcount=0, kwstep=2, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0, name=0x0, qualname=0x0)
    at Python/ceval.c:3930
#13 0x0000000000336f00 in PyEval_EvalCodeEx (_co=0x8012979c0, globals=0x800be0bd0, locals=0x800be0bd0, args=0x0, argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0) at Python/ceval.c:3959
#14 0x0000000000336d8e in PyEval_EvalCode (co=0x8012979c0, globals=0x800be0bd0, locals=0x800be0bd0) at Python/ceval.c:524
#15 0x00000000003cadfa in run_mod (mod=0x800b11658, filename=0x80130c070, globals=0x800be0bd0, locals=0x800be0bd0, flags=0x7fffffffe940, arena=0x800ae40a8) at Python/pythonrun.c:1035
#16 0x00000000003c9d72 in PyRun_FileExFlags (fp=0x8009f96c0, filename_str=0x8012504d0 "crash.py", start=257, globals=0x800be0bd0, locals=0x800be0bd0, closeit=1, flags=0x7fffffffe940) at Python/pythonrun.c:988
#17 0x00000000003c8c66 in PyRun_SimpleFileExFlags (fp=0x8009f96c0, filename=0x8012504d0 "crash.py", closeit=1, flags=0x7fffffffe940) at Python/pythonrun.c:429
#18 0x00000000003c861c in PyRun_AnyFileExFlags (fp=0x8009f96c0, filename=0x8012504d0 "crash.py", closeit=1, flags=0x7fffffffe940) at Python/pythonrun.c:84
#19 0x00000000002b0167 in pymain_run_file (fp=0x8009f96c0, filename=0x800a39060 L"crash.py", p_cf=0x7fffffffe940) at Modules/main.c:427
#20 0x00000000002af93c in pymain_run_filename (pymain=0x7fffffffe990, cf=0x7fffffffe940) at Modules/main.c:1606
#21 0x00000000002acd83 in pymain_run_python (pymain=0x7fffffffe990) at Modules/main.c:2867
#22 0x00000000002abf30 in pymain_main (pymain=0x7fffffffe990) at Modules/main.c:3028
#23 0x00000000002abfbc in _Py_UnixMain (argc=2, argv=0x7fffffffea80) at Modules/main.c:3063
#24 0x00000000002a66e2 in main (argc=2, argv=0x7fffffffea80) at ./Programs/python.c:15
(gdb) up 0
#0  _PyEval_EvalFrameDefault (f=0x800ac45d0, throwflag=0) at Python/ceval.c:1104
1104                Py_DECREF(value);
(gdb) ptype value
type = struct _object {
        Py_ssize_t ob_refcnt;
            struct _typeobject *ob_type;
} *
(gdb) print *(PyObject *)value
$6 = {
      ob_refcnt = 4230,
      ob_type = 0x533018 <_PyNone_Type>
}

The following is a condensed version one-liner of crash.py to reproduce the crash. Below I pasted in a gdb session the progam crash stacktrace

(gdb) run
>>> exec((lambda f:f).__code__.__class__(0,0,0,0,0,"\x01".encode(),(),(),(),"","",0,"".encode()))

Program received signal SIGSEGV, Segmentation fault.
0x000000000033872a in _PyEval_EvalFrameDefault (f=0x8012b5500, throwflag=0) at Python/ceval.c:1104
1104                Py_DECREF(value);
(gdb) bt
#0  0x000000000033872a in _PyEval_EvalFrameDefault (f=0x8012b5500, throwflag=0) at Python/ceval.c:1104
#1  0x0000000000337d4a in PyEval_EvalFrameEx (f=0x8012b5500, throwflag=0) at Python/ceval.c:547
#2  0x000000000034b3b8 in _PyEval_EvalCodeWithName (_co=0x801317a50, globals=0x800bdc0a0, locals=0x800bdc0a0, args=0x0, argcount=0, kwnames=0x0, kwargs=0x0, kwcount=0, kwstep=2, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0, name=0x0, qualname=0x0)
    at Python/ceval.c:3930
#3  0x0000000000337ce0 in PyEval_EvalCodeEx (_co=0x801317a50, globals=0x800bdc0a0, locals=0x800bdc0a0, args=0x0, argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0) at Python/ceval.c:3959
#4  0x0000000000337b6e in PyEval_EvalCode (co=0x801317a50, globals=0x800bdc0a0, locals=0x800bdc0a0) at Python/ceval.c:524
#5  0x0000000000454d0f in builtin_exec_impl (module=0x800ae7d10, source=0x801317a50, globals=0x800bdc0a0, locals=0x800bdc0a0) at Python/bltinmodule.c:1079
#6  0x00000000004526fa in builtin_exec (module=0x800ae7d10, args=0x80137a790, nargs=1) at Python/clinic/bltinmodule.c.h:283
#7  0x0000000000323f7e in _PyMethodDef_RawFastCallKeywords (method=0x547060 <builtin_methods+480>, self=0x800ae7d10, args=0x80137a790, nargs=1, kwnames=0x0) at Objects/call.c:654
#8  0x0000000000323033 in _PyCFunction_FastCallKeywords (func=0x800aeedc0, args=0x80137a790, nargs=1, kwnames=0x0) at Objects/call.c:732
#9  0x0000000000349951 in call_function (pp_stack=0x7fffffffceb0, oparg=1, kwnames=0x0) at Python/ceval.c:4568
#10 0x0000000000344f3e in _PyEval_EvalFrameDefault (f=0x80137a620, throwflag=0) at Python/ceval.c:3124
#11 0x0000000000337d4a in PyEval_EvalFrameEx (f=0x80137a620, throwflag=0) at Python/ceval.c:547
#12 0x000000000034b3b8 in _PyEval_EvalCodeWithName (_co=0x8013174b0, globals=0x800bdc0a0, locals=0x800bdc0a0, args=0x0, argcount=0, kwnames=0x0, kwargs=0x0, kwcount=0, kwstep=2, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0, name=0x0, qualname=0x0)
    at Python/ceval.c:3930
#13 0x0000000000337ce0 in PyEval_EvalCodeEx (_co=0x8013174b0, globals=0x800bdc0a0, locals=0x800bdc0a0, args=0x0, argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0, kwdefs=0x0, closure=0x0) at Python/ceval.c:3959
#14 0x0000000000337b6e in PyEval_EvalCode (co=0x8013174b0, globals=0x800bdc0a0, locals=0x800bdc0a0) at Python/ceval.c:524
#15 0x00000000003cbeca in run_mod (mod=0x800b13520, filename=0x80129fdb0, globals=0x800bdc0a0, locals=0x800bdc0a0, flags=0x7fffffffe950, arena=0x8012d41b0) at Python/pythonrun.c:1035
#16 0x00000000003ca506 in PyRun_InteractiveOneObjectEx (fp=0x8007d5330, filename=0x80129fdb0, flags=0x7fffffffe950) at Python/pythonrun.c:256
#17 0x00000000003c98c8 in PyRun_InteractiveLoopFlags (fp=0x8007d5330, filename_str=0x240c34 "<stdin>", flags=0x7fffffffe950) at Python/pythonrun.c:120
#18 0x00000000003c96b7 in PyRun_AnyFileExFlags (fp=0x8007d5330, filename=0x240c34 "<stdin>", closeit=0, flags=0x7fffffffe950) at Python/pythonrun.c:78
#19 0x00000000002b09c7 in pymain_run_file (fp=0x8007d5330, filename=0x0, p_cf=0x7fffffffe950) at Modules/main.c:456
#20 0x00000000002b019c in pymain_run_filename (pymain=0x7fffffffe9a0, cf=0x7fffffffe950) at Modules/main.c:1646
#21 0x00000000002ad5e3 in pymain_run_python (pymain=0x7fffffffe9a0) at Modules/main.c:2907
#22 0x00000000002ac790 in pymain_main (pymain=0x7fffffffe9a0) at Modules/main.c:3068
#23 0x00000000002ac81c in _Py_UnixMain (argc=1, argv=0x7fffffffea90) at Modules/main.c:3103
#24 0x00000000002a6f42 in main (argc=1, argv=0x7fffffffea90) at ./Programs/python.c:15

Versions payloads

Crash payloads for the different python versions

  • Python 2.7
exec((lambda f:f).func_code.__class__(0,0,0,0,"\x01",(),(),(),"","",0,""))
  • Python 3.5
exec((lambda f:f).__code__.__class__(0,0,0,0,0,"\x01".encode(),(),(),(),"","",0,"".encode()))
  • Python 3.6 and 3.7
exec((lambda f:f).__code__.__class__(0,0,0,0,0,"\x01".encode(),(),(),(),"","",0,"".encode()))
  • Python 3.8 and +
exec((lambda f:f).__code__.__class__(0,0,0,0,0,0,"\x01".encode(),(),(),(),"","",0,"".encode()))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.