]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-47162: Add call trampoline to mitigate bad fpcasts on Emscripten (GH-32189)
authorChristian Heimes <christian@python.org>
Wed, 30 Mar 2022 19:28:33 +0000 (22:28 +0300)
committerGitHub <noreply@github.com>
Wed, 30 Mar 2022 19:28:33 +0000 (12:28 -0700)
Include/internal/pycore_object.h
Misc/NEWS.d/next/Core and Builtins/2022-03-30-13-13-25.bpo-47162.yDJMUm.rst [new file with mode: 0644]
Objects/call.c
Objects/descrobject.c
Objects/methodobject.c
Python/import.c
Python/importdl.c
Python/importdl.h

index 06671b5057afab50db45f96ccf4bf3d5ff5b2429..177b06e2dd4f76b58e05e0ecef0efdbf39d66aeb 100644 (file)
@@ -242,6 +242,33 @@ extern PyObject* _PyType_GetSubclasses(PyTypeObject *);
 
 PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, PyObject *);
 
+/* C function call trampolines to mitigate bad function pointer casts.
+ *
+ * Typical native ABIs ignore additional arguments or fill in missing
+ * values with 0/NULL in function pointer cast. Compilers do not show
+ * warnings when a function pointer is explicitly casted to an
+ * incompatible type.
+ *
+ * Bad fpcasts are an issue in WebAssembly. WASM's indirect_call has strict
+ * function signature checks. Argument count, types, and return type must
+ * match.
+ *
+ * Third party code unintentionally rely on problematic fpcasts. The call
+ * trampoline mitigates common occurences of bad fpcasts on Emscripten.
+ */
+#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
+#define _PyCFunction_TrampolineCall(meth, self, args) \
+    _PyCFunctionWithKeywords_TrampolineCall( \
+        (*(PyCFunctionWithKeywords)(void(*)(void))meth), self, args, NULL)
+extern PyObject* _PyCFunctionWithKeywords_TrampolineCall(
+    PyCFunctionWithKeywords meth, PyObject *, PyObject *, PyObject *);
+#else
+#define _PyCFunction_TrampolineCall(meth, self, args) \
+    (meth)((self), (args))
+#define _PyCFunctionWithKeywords_TrampolineCall(meth, self, args, kw) \
+    (meth)((self), (args), (kw))
+#endif // __EMSCRIPTEN__ && PY_CALL_TRAMPOLINE
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-03-30-13-13-25.bpo-47162.yDJMUm.rst b/Misc/NEWS.d/next/Core and Builtins/2022-03-30-13-13-25.bpo-47162.yDJMUm.rst
new file mode 100644 (file)
index 0000000..7ecbfb3
--- /dev/null
@@ -0,0 +1,4 @@
+WebAssembly cannot deal with bad function pointer casts (different count
+or types of arguments). Python can now use call trampolines to mitigate
+the problem. Define :c:macro:`PY_CALL_TRAMPOLINE` to enable call
+trampolines.
index cf8fa1eeffe1c5defb9ea1996ee98b27524b3cef..448476223adc0431f825b8827f54d7a41562949b 100644 (file)
@@ -211,7 +211,8 @@ _PyObject_MakeTpCall(PyThreadState *tstate, PyObject *callable,
     PyObject *result = NULL;
     if (_Py_EnterRecursiveCall(tstate, " while calling a Python object") == 0)
     {
-        result = call(callable, argstuple, kwdict);
+        result = _PyCFunctionWithKeywords_TrampolineCall(
+            (PyCFunctionWithKeywords)call, callable, argstuple, kwdict);
         _Py_LeaveRecursiveCall(tstate);
     }
 
index e255d4ae5f86f6cea6c89062b816700e203c8474..7cbfe8d9c19407739ef356bfddd5b5654f640074 100644 (file)
@@ -13,6 +13,25 @@ class property "propertyobject *" "&PyProperty_Type"
 [clinic start generated code]*/
 /*[clinic end generated code: output=da39a3ee5e6b4b0d input=556352653fd4c02e]*/
 
+// see pycore_object.h
+#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
+#include <emscripten.h>
+EM_JS(PyObject*, descr_set_trampoline_call, (setter set, PyObject *obj, PyObject *value, void *closure), {
+    return wasmTable.get(set)(obj, value, closure);
+});
+
+EM_JS(PyObject*, descr_get_trampoline_call, (getter get, PyObject *obj, void *closure), {
+    return wasmTable.get(get)(obj, closure);
+});
+#else
+#define descr_set_trampoline_call(set, obj, value, closure) \
+    (set)((obj), (value), (closure))
+
+#define descr_get_trampoline_call(get, obj, closure) \
+    (get)((obj), (closure))
+
+#endif // __EMSCRIPTEN__ && PY_CALL_TRAMPOLINE
+
 static void
 descr_dealloc(PyDescrObject *descr)
 {
@@ -180,7 +199,8 @@ getset_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type)
         return NULL;
     }
     if (descr->d_getset->get != NULL)
-        return descr->d_getset->get(obj, descr->d_getset->closure);
+        return descr_get_trampoline_call(
+            descr->d_getset->get, obj, descr->d_getset->closure);
     PyErr_Format(PyExc_AttributeError,
                  "attribute '%V' of '%.100s' objects is not readable",
                  descr_name((PyDescrObject *)descr), "?",
@@ -232,8 +252,9 @@ getset_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value)
         return -1;
     }
     if (descr->d_getset->set != NULL) {
-        return descr->d_getset->set(obj, value,
-                                    descr->d_getset->closure);
+        return descr_set_trampoline_call(
+            descr->d_getset->set, obj, value,
+            descr->d_getset->closure);
     }
     PyErr_Format(PyExc_AttributeError,
                  "attribute '%V' of '%.100s' objects is not writable",
@@ -306,7 +327,8 @@ method_vectorcall_VARARGS(
         Py_DECREF(argstuple);
         return NULL;
     }
-    PyObject *result = meth(args[0], argstuple);
+    PyObject *result = _PyCFunction_TrampolineCall(
+        meth, args[0], argstuple);
     Py_DECREF(argstuple);
     _Py_LeaveRecursiveCall(tstate);
     return result;
@@ -339,7 +361,8 @@ method_vectorcall_VARARGS_KEYWORDS(
     if (meth == NULL) {
         goto exit;
     }
-    result = meth(args[0], argstuple, kwdict);
+    result = _PyCFunctionWithKeywords_TrampolineCall(
+        meth, args[0], argstuple, kwdict);
     _Py_LeaveRecursiveCall(tstate);
 exit:
     Py_DECREF(argstuple);
@@ -427,7 +450,7 @@ method_vectorcall_NOARGS(
     if (meth == NULL) {
         return NULL;
     }
-    PyObject *result = meth(args[0], NULL);
+    PyObject *result = _PyCFunction_TrampolineCall(meth, args[0], NULL);
     _Py_LeaveRecursiveCall(tstate);
     return result;
 }
@@ -455,7 +478,7 @@ method_vectorcall_O(
     if (meth == NULL) {
         return NULL;
     }
-    PyObject *result = meth(args[0], args[1]);
+    PyObject *result = _PyCFunction_TrampolineCall(meth, args[0], args[1]);
     _Py_LeaveRecursiveCall(tstate);
     return result;
 }
index 93fac22ec437c1f7b9703ffab9315750771d0307..8bcb1e0fadb8c01ee3195f9cf6f83f92e859f6c2 100644 (file)
@@ -483,7 +483,8 @@ cfunction_vectorcall_NOARGS(
     if (meth == NULL) {
         return NULL;
     }
-    PyObject *result = meth(PyCFunction_GET_SELF(func), NULL);
+    PyObject *result = _PyCFunction_TrampolineCall(
+        meth, PyCFunction_GET_SELF(func), NULL);
     _Py_LeaveRecursiveCall(tstate);
     return result;
 }
@@ -510,7 +511,8 @@ cfunction_vectorcall_O(
     if (meth == NULL) {
         return NULL;
     }
-    PyObject *result = meth(PyCFunction_GET_SELF(func), args[0]);
+    PyObject *result = _PyCFunction_TrampolineCall(
+        meth, PyCFunction_GET_SELF(func), args[0]);
     _Py_LeaveRecursiveCall(tstate);
     return result;
 }
@@ -537,7 +539,9 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs)
 
     PyObject *result;
     if (flags & METH_KEYWORDS) {
-        result = (*(PyCFunctionWithKeywords)(void(*)(void))meth)(self, args, kwargs);
+        result = _PyCFunctionWithKeywords_TrampolineCall(
+            (*(PyCFunctionWithKeywords)(void(*)(void))meth),
+            self, args, kwargs);
     }
     else {
         if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
@@ -546,7 +550,15 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs)
                           ((PyCFunctionObject*)func)->m_ml->ml_name);
             return NULL;
         }
-        result = meth(self, args);
+        result = _PyCFunction_TrampolineCall(meth, self, args);
     }
     return _Py_CheckFunctionResult(tstate, func, result, NULL);
 }
+
+#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
+#include <emscripten.h>
+
+EM_JS(PyObject*, _PyCFunctionWithKeywords_TrampolineCall, (PyCFunctionWithKeywords func, PyObject *self, PyObject *args, PyObject *kw), {
+    return wasmTable.get(func)(self, args, kw);
+});
+#endif
index 982ec8cfe631a6be84794e124d19baa3bfdd838d..4b6d6d16821a9430ce896137474532e618872da7 100644 (file)
@@ -527,7 +527,7 @@ import_find_extension(PyThreadState *tstate, PyObject *name,
     else {
         if (def->m_base.m_init == NULL)
             return NULL;
-        mod = def->m_base.m_init();
+        mod = _PyImport_InitFunc_TrampolineCall(def->m_base.m_init);
         if (mod == NULL)
             return NULL;
         if (PyObject_SetItem(modules, name, mod) == -1) {
@@ -958,6 +958,13 @@ PyImport_GetImporter(PyObject *path)
     return get_path_importer(tstate, path_importer_cache, path_hooks, path);
 }
 
+#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
+#include <emscripten.h>
+EM_JS(PyObject*, _PyImport_InitFunc_TrampolineCall, (PyModInitFunction func), {
+    return wasmTable.get(func)();
+});
+#endif // __EMSCRIPTEN__ && PY_CALL_TRAMPOLINE
+
 static PyObject*
 create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
 {
@@ -973,8 +980,7 @@ create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
                 /* Cannot re-init internal module ("sys" or "builtins") */
                 return PyImport_AddModuleObject(name);
             }
-
-            mod = (*p->initfunc)();
+            mod = _PyImport_InitFunc_TrampolineCall(*p->initfunc);
             if (mod == NULL) {
                 return NULL;
             }
index f66c6013d2c9892d33a58f95c5c81d73ec821c26..870ae2730071bb4524d314314184a364dfbbedd2 100644 (file)
@@ -102,7 +102,7 @@ _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp)
     const char *oldcontext;
     dl_funcptr exportfunc;
     PyModuleDef *def;
-    PyObject *(*p0)(void);
+    PyModInitFunction p0;
 
     name_unicode = PyObject_GetAttrString(spec, "name");
     if (name_unicode == NULL) {
@@ -157,7 +157,7 @@ _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp)
         goto error;
     }
 
-    p0 = (PyObject *(*)(void))exportfunc;
+    p0 = (PyModInitFunction)exportfunc;
 
     /* Package context is needed for single-phase init */
     oldcontext = _Py_PackageContext;
@@ -166,7 +166,7 @@ _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp)
         _Py_PackageContext = oldcontext;
         goto error;
     }
-    m = p0();
+    m = _PyImport_InitFunc_TrampolineCall(p0);
     _Py_PackageContext = oldcontext;
 
     if (m == NULL) {
index 9847652b1f1b31ceec1e9390fbbbce1702348a6e..26d18b626df05235350cb221951b764a47af6cc9 100644 (file)
@@ -10,6 +10,14 @@ extern const char *_PyImport_DynLoadFiletab[];
 
 extern PyObject *_PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *);
 
+typedef PyObject *(*PyModInitFunction)(void);
+
+#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
+extern PyObject *_PyImport_InitFunc_TrampolineCall(PyModInitFunction func);
+#else
+#define _PyImport_InitFunc_TrampolineCall(func) (func)()
+#endif
+
 /* Max length of module suffix searched for -- accommodates "module.slb" */
 #define MAXSUFFIXSIZE 12