Package: python3-mrgingham Version: 1.24-2 Severity: important Tags: upstream patch X-Debbugs-Cc: debian-glibc@lists.debian.org User: debian-glibc@lists.debian.org Usertags: glibc2.41 dlopen-executable-stack
Dear maintainer, Starting with glibc 2.41, the dlopen and dlmopen functions no longer make the stack executable if a shared library requires it and instead just fail. This change aims to improve security, as the previous behaviour was used as a vector for RCE (CVE-2023-38408). Unfortunately the python3-mrgingham provides a Python module with an executable stack, and Python uses dlopen() to open module. With the glibc change, the mrgingham module can't be loaded anymore, causing the testsuite to fail during build or autopkgtest: | 58s autopkgtest [18:26:56]: test autodep8-python3: [----------------------- | 58s Testing with python3.12: | 58s Traceback (most recent call last): | 58s File "<string>", line 1, in <module> | 58s ImportError: /usr/lib/python3/dist-packages/mrgingham.cpython-312-x86_64-linux-gnu.so: cannot enable executable stack as shared object requires: Invalid argument | 58s autopkgtest [18:26:56]: test autodep8-python3: -----------------------] For a full log, see: https://ci.debian.net/packages/m/mrgingham/unstable/amd64/58113030/ I have tracked the issue to the use of nested functions require trampoline in the python wrapper code (the library is not affected and does not need an executable stack). While GCC provides a '-ftrampoline-impl=heap' option, it does not work on all architectures. Fortunately the API is defined by the C++ bridge and specific to the wrapper. Therefore it can be changed to pass the result variable as an opaque pointer (opaque to keep the bridge outside of the Python world) instead of relying on a trampoline for it. You will find attached a proposed patch, I guess other alternatives are possible to reach the same result. Regards Aurelien
--- mrgingham-1.24.orig/mrgingham_pywrap.c +++ mrgingham-1.24/mrgingham_pywrap.c @@ -160,14 +160,15 @@ static PyObject* find_points(PyObject* N goto done; } - bool add_points(int* xy, int N, double scale) + bool add_points(int* xy, int N, double scale, void *opaque) { - result = PyArray_SimpleNew(2, - ((npy_intp[]){N, 2}), - NPY_DOUBLE); - if(result == NULL) return false; + PyObject** resultp = (PyObject**) opaque; + *resultp = PyArray_SimpleNew(2, + ((npy_intp[]){N, 2}), + NPY_DOUBLE); + if(*resultp == NULL) return false; - double* out_data = (double*)PyArray_BYTES((PyArrayObject*)result); + double* out_data = (double*)PyArray_BYTES((PyArrayObject*)*resultp); for(int i=0; i<2*N; i++) out_data[i] = scale * (double)xy[i]; return true; @@ -179,7 +180,7 @@ static PyObject* find_points(PyObject* N image_pyramid_level, blobs, - &add_points) ) + &add_points, &result) ) { if(result == NULL) { @@ -260,14 +261,15 @@ static PyObject* find_board(PyObject* NP goto done; } - bool add_points(double* xy, int N) + bool add_points(double* xy, int N, void *opaque) { - result = PyArray_SimpleNew(2, - ((npy_intp[]){N, 2}), - NPY_DOUBLE); - if(result == NULL) return false; + PyObject **resultp = (PyObject **)opaque; + *resultp = PyArray_SimpleNew(2, + ((npy_intp[]){N, 2}), + NPY_DOUBLE); + if(*resultp == NULL) return false; - double* out_data = (double*)PyArray_BYTES((PyArrayObject*)result); + double* out_data = (double*)PyArray_BYTES((PyArrayObject*)*resultp); memcpy(out_data, xy, 2*N*sizeof(double)); return true; } @@ -279,7 +281,7 @@ static PyObject* find_board(PyObject* NP gridn, image_pyramid_level, blobs, - &add_points) ) + &add_points, &result) ) { // This is allowed to fail. We possibly found no chessboard. This is // sloppy since it ignore other potential errors, but there shouldn't be --- mrgingham-1.24.orig/mrgingham_pywrap_cplusplus_bridge.cc +++ mrgingham-1.24/mrgingham_pywrap_cplusplus_bridge.cc @@ -37,7 +37,8 @@ bool find_chessboard_corners_from_image_ int image_pyramid_level, bool doblobs, - bool (*add_points)(int* xy, int N, double scale) ) + bool (*add_points)(int* xy, int N, double scale, void *opaque), + void *opaque ) { cv::Mat cvimage(Nrows, Ncols, CV_8UC1, imagebuffer, stride); @@ -62,7 +63,7 @@ bool find_chessboard_corners_from_image_ "add_points() assumes PointInt is simply 2 ints"); return (*add_points)( &out_points[0].x, (int)out_points.size(), - 1. / (double)FIND_GRID_SCALE); + 1. / (double)FIND_GRID_SCALE, opaque); } extern "C" @@ -79,7 +80,8 @@ bool find_chessboard_from_image_array_C( int image_pyramid_level, bool doblobs, - bool (*add_points)(double* xy, int N) ) + bool (*add_points)(double* xy, int N, void *opaque), + void *opaque ) { cv::Mat cvimage(Nrows, Ncols, CV_8UC1, imagebuffer, stride); @@ -111,5 +113,5 @@ bool find_chessboard_from_image_array_C( static_assert( sizeof(mrgingham::PointDouble) == 2*sizeof(double), "add_points() assumes PointDouble is simply 2 doubles"); return - (*add_points)( &out_points[0].x, (int)out_points.size() ); + (*add_points)( &out_points[0].x, (int)out_points.size(), opaque ); } --- mrgingham-1.24.orig/mrgingham_pywrap_cplusplus_bridge.h +++ mrgingham-1.24/mrgingham_pywrap_cplusplus_bridge.h @@ -18,7 +18,8 @@ bool find_chessboard_corners_from_image_ int image_pyramid_level, bool doblobs, - bool (*add_points)(int* xy, int N, double scale) ); + bool (*add_points)(int* xy, int N, double scale, void *opaque), + void *opaque ); bool find_chessboard_from_image_array_C( // in int Nrows, int Ncols, @@ -33,7 +34,9 @@ bool find_chessboard_from_image_array_C( int image_pyramid_level, bool doblobs, - bool (*add_points)(double* xy, int N) ); + bool (*add_points)(double* xy, int N, void *opaque), + void *opaque ); + #ifdef __cplusplus } #endif