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

Reply via email to