changeset 43:1086ca649715

- fixed use after free issue with string handling (keeping strings as copy until after call)
author Tassilo Philipp
date Wed, 15 Apr 2020 21:58:13 +0200
parents 1e3d929e43be
children 0f86a5ecfe61
files python/pydc/pydcext.c python/pydc/setup.py
diffstat 2 files changed, 37 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/python/pydc/pydcext.c	Tue Apr 14 18:30:17 2020 +0200
+++ b/python/pydc/pydcext.c	Wed Apr 15 21:58:13 2020 +0200
@@ -33,6 +33,10 @@
 #  define DcPyCObject_SetVoidPtr(ppobj, ptr)   //@@@ unsure what to do, cannot/shouldn't call this with a null pointer as this wants to call the dtor, so not doing anything: PyCapsule_SetPointer((ppobj), (ptr))  // this might need to call the dtor to behave like PyCObject_SetVoidPtr?
 #endif
 
+#if(PY_VERSION_HEX >= 0x03030000)
+#  define PYUNICODE_CACHES_UTF8
+#endif
+
 #if PY_MAJOR_VERSION >= 3
 #  define EXPECT_LONG_TYPE_STR "an int"
 #  define DcPyString_GET_SIZE PyBytes_GET_SIZE
@@ -169,11 +173,16 @@
 
 DCCallVM* gpCall = NULL;
 
+// helper to temporarily copy string arguments
+#define NUM_AUX_STRS 64
+static int   n_str_aux;
+static char* str_aux[NUM_AUX_STRS]; // hard limit, most likely enough and checked for below @@@ugly though
+
 
 /* call function */
 
 static PyObject*
-pydc_call(PyObject* self, PyObject* in_args)
+pydc_call_impl(PyObject* self, PyObject* in_args) /* implementation, called by wrapper func pydc_call() */
 {
 	PyObject    *pcobj_funcptr, *args;
 	const char  *signature, *ptr;
@@ -360,25 +369,32 @@
 			{
 				PyObject* bo = NULL;
 				const char* p;
-				char* p_;
 				size_t s;
-				if ( PyUnicode_Check(po) ) {
+				if ( PyUnicode_Check(po) )
+				{
+					if(n_str_aux >= NUM_AUX_STRS)
+						return PyErr_Format( PyExc_RuntimeError, "too many arguments (implementation limit of %d new UTF-8 string references reached) - abort", n_str_aux );
+
+#if defined(PYUNICODE_CACHES_UTF8)
+					p = PyUnicode_AsUTF8(po);
+#else
+					// w/o PyUnicode_AsUTF8(), which caches the UTF-8 representation, itself, create new ref we'll dec below
 					if((bo = PyUnicode_AsEncodedString(po, "utf-8", "strict")))  // !new ref!
 						p = PyBytes_AS_STRING(bo); // Borrowed pointer
+#endif
 				} else if ( DcPyString_Check(po) )
-					p = DcPyString_AsString(po); //@@@ must not be modified in any way
+					p = DcPyString_AsString(po);
 				else if ( PyByteArray_Check(po) )
 					p = (DCpointer) PyByteArray_AsString(po); // adds an extra '\0', but that's ok //@@@ not sure if allowed to modify
 				else
 					return PyErr_Format( PyExc_RuntimeError, "arg %d - expecting a str", pos );
 
-				// pointer points in any case to a buffer that shouldn't be modified, so pass a copy of the string to dyncall
+				// p points in any case to a buffer that shouldn't be modified, so pass a copy to dyncall (cleaned up after call)
 				s = strlen(p)+1;
-				p_ = malloc(s);
-				strncpy(p_, p, s);
+				str_aux[n_str_aux] = malloc(s);
+				strncpy(str_aux[n_str_aux], p, s);
 				Py_XDECREF(bo);
-				dcArgPointer(gpCall, (DCpointer)p_);
-				free(p_);
+				dcArgPointer(gpCall, (DCpointer)str_aux[n_str_aux++]);
 			}
 			break;
 
@@ -419,6 +435,17 @@
 }
 
 
+static PyObject*
+pydc_call(PyObject* self, PyObject* in_args)
+{
+	int i;
+	n_str_aux = 0;
+	PyObject* o = pydc_call_impl(self, in_args);
+	for(i = 0; i<n_str_aux; ++i)
+		free(str_aux[i]);
+	return o;
+}
+
 
 // module deinit
 static void deinit_pydc(void* x)
--- a/python/pydc/setup.py	Tue Apr 14 18:30:17 2020 +0200
+++ b/python/pydc/setup.py	Wed Apr 15 21:58:13 2020 +0200
@@ -7,7 +7,7 @@
 
 setup(
   name             = 'pydc'
-, version          = '1.1.2'
+, version          = '1.1.3'
 , author           = 'Daniel Adler, Tassilo Philipp'
 , author_email     = 'dadler@dyncall.org, tphilip@dyncall.org'
 , maintainer       = 'Daniel Adler, Tassilo Philipp'