PyGLM
PyGLM copied to clipboard
segfault in `vec_getattr` due to memory use after free
Hello @Zuzu-Typ ! I think I've located a bug in PyGLM, something that I've experienced repeatedly and randomly the past month. I had hard times trying to reproduce it, and still I don't know how to acheive it. However I think I located the source of the problem
description
I get each time a backtrace looking like this with gdb
:
$ gdb python
(gdb) run my_special_program.py
...
Thread 43 "python" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fff19fc2700 (LWP 56671)]
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:65
65 ../sysdeps/x86_64/multiarch/strlen-avx2.S: Aucun fichier ou dossier de ce type.
(gdb) bt
#0 __strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:65
#1 0x00007fffe76f56dc in vec_getattr<3, double> (obj=0x7ffeac1b93f0, name=0x7ffff708a710) at PyGLM/type_methods/vec.h:1803
#2 0x00007ffff6ce1fd5 in PyArray_GetPriority ()
from /home/ydejonghe/.local/lib/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so
#3 0x00007ffff6d06ac1 in array_matrix_multiply ()
from /home/ydejonghe/.local/lib/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so
#4 0x000000000050e62c in ?? ()
#5 0x0000000000618a2a in ?? ()
#6 0x00000000004b9d24 in ?? ()
#7 0x00000000005f6836 in _PyFunction_Vectorcall ()
#8 0x000000000056b1da in _PyEval_EvalFrameDefault ()
#9 0x000000000056939a in _PyEval_EvalCodeWithName ()
#10 0x00000000005f6a13 in _PyFunction_Vectorcall ()
#11 0x000000000050aa2c in ?? ()
#12 0x00000000005f3547 in PyObject_Call ()
#13 0x000000000056c8cd in _PyEval_EvalFrameDefault ()
#14 0x000000000056939a in _PyEval_EvalCodeWithName ()
#15 0x00000000005f6a13 in _PyFunction_Vectorcall ()
#16 0x000000000050aa2c in ?? ()
#17 0x00000000005f3547 in PyObject_Call ()
#18 0x000000000056c8cd in _PyEval_EvalFrameDefault ()
#19 0x000000000056939a in _PyEval_EvalCodeWithName ()
#20 0x000000000050aaa0 in ?? ()
#21 0x000000000056c28c in _PyEval_EvalFrameDefault ()
#22 0x000000000056939a in _PyEval_EvalCodeWithName ()
#23 0x00000000005f6a13 in _PyFunction_Vectorcall ()
#24 0x000000000056b1da in _PyEval_EvalFrameDefault ()
#25 0x000000000056939a in _PyEval_EvalCodeWithName ()
#26 0x00000000005f6a13 in _PyFunction_Vectorcall ()
#27 0x00000000005f3547 in PyObject_Call ()
#28 0x000000000056c8cd in _PyEval_EvalFrameDefault ()
#29 0x00000000005f6836 in _PyFunction_Vectorcall ()
#30 0x000000000056b1da in _PyEval_EvalFrameDefault ()
#31 0x00000000005f6836 in _PyFunction_Vectorcall ()
#32 0x000000000056b1da in _PyEval_EvalFrameDefault ()
#33 0x00000000005f6836 in _PyFunction_Vectorcall ()
#34 0x000000000050aa2c in ?? ()
#35 0x00000000005f3547 in PyObject_Call ()
#36 0x0000000000655a9c in ?? ()
#37 0x0000000000675738 in ?? ()
#38 0x00007ffff7d97609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#39 0x00007ffff7ed1163 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
Taking a closer look at PyGLM/type_methods/vec.h:1803
I found this:
char * name_as_ccp = PyGLM_String_AsString(name);
size_t len = strlen(name_as_ccp); // using 'name_as_ccp' which is a pointer to a freed object
this is because PyGLM_String_AsString
returns a pointer to a freed python object
// helper_macros.h
PyObject* asciiString = PyUnicode_AsASCIIString(name);
char* out = PyBytes_AsString(asciiString); // taking the pointer to the internal buffer
Py_DECREF(asciiString); // deallocating the buffer
return out;
This finding confirms the way the bug appears: very randomly because it only appears when the program as handed the memory over to the system, and only in multithreaded programs because it can only happen when the program is multithreaded with a thread allocating memory with an other allocator that python's allocator. I'm using Qt in a dedicated thread so the Qt allocator might hand over the freed memory to the system before the call to strlen
, and so any further reading from this memory raises a memory corruption.
solution
I expanded PyGLM_String_AsString
to moove the deallocation at the end of the function, and since I did not experienced any segfault.
template<int L, typename T>
static PyObject * vec_getattr(PyObject * obj, PyObject * name) {
PyObject* asciiString = PyUnicode_AsASCIIString(name);
char* name_as_ccp = PyBytes_AsString(asciiString);
size_t len = strlen(name_as_ccp);
// ...
Py_DECREF(asciiString);
return PyObject_GenericGetAttr(obj, name);
}
As far as I can see, only vec.h
and mvec.h
are concerned.