Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 1 addition & 22 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_emscripten_trampoline.h" // _PyCFunction_TrampolineCall()
#include "pycore_wasm_trampoline.h" // _PyCFunction_TrampolineCall()
#include "pycore_gc.h" // _PyObject_GC_TRACK()
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_ACQUIRE()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
Expand Down Expand Up @@ -977,27 +977,6 @@ extern PyObject* _PyObject_NextNotImplemented(PyObject *);
// Export for '_datetime' shared extension
PyAPI_FUNC(PyObject*) _PyObject_GetState(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 occurrences of bad fpcasts on Emscripten.
*/
#if !(defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE))
#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

// Export these 2 symbols for '_pickle' shared extension
PyAPI_DATA(PyTypeObject) _PyNone_Type;
PyAPI_DATA(PyTypeObject) _PyNotImplemented_Type;
Expand Down
10 changes: 0 additions & 10 deletions Include/internal/pycore_runtime_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,6 @@ struct pyruntimestate {
struct _types_runtime_state types;
struct _Py_time_runtime_state time;

#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
// Used in "Python/emscripten_trampoline.c" to choose between wasm-gc
// trampoline and JavaScript trampoline.
PyObject* (*emscripten_trampoline)(int* success,
PyCFunctionWithKeywords func,
PyObject* self,
PyObject* args,
PyObject* kw);
#endif

/* All the objects that are shared by the runtime's interpreters. */
struct _Py_cached_objects cached_objects;
struct _Py_static_objects static_objects;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#ifndef Py_EMSCRIPTEN_TRAMPOLINE_H
#define Py_EMSCRIPTEN_TRAMPOLINE_H
#ifndef Py_WASM_TRAMPOLINE_H
#define Py_WASM_TRAMPOLINE_H

#include "pycore_typedefs.h" // _PyRuntimeState
#include "Python.h"

/**
* C function call trampolines to mitigate bad function pointer casts.
Expand All @@ -18,37 +18,44 @@
* 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
* 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 occurrences of bad fpcasts on Emscripten.
* trampoline mitigates common occurrences of bad fpcasts on Wasm targets.
*/

#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
#if defined(__wasm__) && defined(PY_CALL_TRAMPOLINE)

PyObject*
_PyEM_TrampolineCall(PyCFunctionWithKeywords func,
PyObject* self,
PyObject* args,
PyObject* kw);
_PyWasm_TrampolineCall(PyCFunctionWithKeywords func,
PyObject* self,
PyObject* args,
PyObject* kw);

int
_PyWasm_TrampolineCallSetter(setter func,
PyObject* self,
PyObject* val,
void* closure);

#define _PyCFunction_TrampolineCall(meth, self, args) \
_PyEM_TrampolineCall(*_PyCFunctionWithKeywords_CAST(meth), (self), (args), NULL)
_PyWasm_TrampolineCall(*_PyCFunctionWithKeywords_CAST(meth), (self), (args), NULL)

#define _PyCFunctionWithKeywords_TrampolineCall(meth, self, args, kw) \
_PyEM_TrampolineCall((meth), (self), (args), (kw))
_PyWasm_TrampolineCall((meth), (self), (args), (kw))

#define descr_set_trampoline_call(set, obj, value, closure) \
((int)_PyEM_TrampolineCall(_PyCFunctionWithKeywords_CAST(set), (obj), \
(value), (PyObject*)(closure)))
_PyWasm_TrampolineCallSetter((set), (obj), (value), (closure))

#define descr_get_trampoline_call(get, obj, closure) \
_PyEM_TrampolineCall(_PyCFunctionWithKeywords_CAST(get), (obj), \
(PyObject*)(closure), NULL)
_PyWasm_TrampolineCall(_PyCFunctionWithKeywords_CAST(get), (obj), \
(PyObject*)(closure), NULL)


#else // defined(__wasm__) && defined(PY_CALL_TRAMPOLINE)

#else // defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
// Disable trampolines by directly calling the method.

#define _PyCFunction_TrampolineCall(meth, self, args) \
(meth)((self), (args))
Expand All @@ -62,6 +69,6 @@ _PyEM_TrampolineCall(PyCFunctionWithKeywords func,
#define descr_get_trampoline_call(get, obj, closure) \
(get)((obj), (closure))

#endif // defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
#endif // defined(__wasm__) && defined(PY_CALL_TRAMPOLINE)

#endif // ndef Py_EMSCRIPTEN_SIGNAL_H
#endif // ndef Py_WASM_TRAMPOLINE_H
77 changes: 77 additions & 0 deletions Lib/test/test_capi/test_fpcast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Tests for calling C functions through mis-cast function pointers.

Extension modules frequently cast C functions with the wrong number of
arguments to PyCFunction, getter, setter or ternaryfunc. Native ABIs tolerate
this; on WebAssembly, call_indirect checks the signature, so CPython routes
these calls through a trampoline (Python/wasm_trampoline.c) that detects the
real signature. Calling each variant must work everywhere.
"""
import unittest
from test.support import check_sanitizer, import_helper, is_wasm32

_testcapi = import_helper.import_module('_testcapi')


# Native ABIs tolerate these calls but they are implementation-defined behavior.
# -fsanitize=undefined correctly trips on them.
@unittest.skipIf(check_sanitizer(ub=True),
"calls through mis-cast function pointers are UB natively")
class FpcastTest(unittest.TestCase):
def check_calls(self, obj, prefix):
for arity in range(4):
with self.subTest(kind="noargs", arity=arity):
self.assertIsNone(getattr(obj, f"{prefix}noargs{arity}")())
with self.subTest(kind="o", arity=arity):
self.assertIsNone(getattr(obj, f"{prefix}o{arity}")(1))
with self.subTest(kind="varargs", arity=arity):
self.assertIsNone(getattr(obj, f"{prefix}varargs{arity}")())
self.assertIsNone(getattr(obj, f"{prefix}varargs{arity}")(1, 2))
with self.subTest(kind="kwargs", arity=arity):
self.assertIsNone(getattr(obj, f"{prefix}kwargs{arity}")())
self.assertIsNone(getattr(obj, f"{prefix}kwargs{arity}")(1, x=2))

def test_module_functions(self):
self.check_calls(_testcapi, "fpcast_")

def test_methods(self):
self.check_calls(_testcapi.FpcastTestType(), "")

def test_tp_call(self):
for arity in range(4):
with self.subTest(arity=arity):
cls = getattr(_testcapi, f"FpcastCallable{arity}")
self.assertIsNone(cls()())
self.assertIsNone(cls()(1, x=2))

def test_getset(self):
t = _testcapi.FpcastTestType()
self.assertIsNone(t.getset0)
self.assertIsNone(t.getset1)
self.assertIsNone(t.getset2)
t.getset1 = 5
sentinel = object()
t.getset2 = sentinel
self.assertIs(_testcapi.fpcast_last_set_value(), sentinel)
with self.assertRaises(AttributeError):
t.getset0 = 1

@unittest.skipUnless(is_wasm32, "requires the wasm call trampoline")
def test_unsupported_signature(self):
# A function with four pointer arguments matches none of the
# signatures the trampoline knows about: it must raise SystemError
# rather than trap.
t = _testcapi.FpcastTestType()
with self.assertRaises(SystemError):
_testcapi.fpcast_noargs4()
with self.assertRaises(SystemError):
t.noargs4()
with self.assertRaises(SystemError):
_testcapi.FpcastCallable4()()
with self.assertRaises(SystemError):
t.getset4
with self.assertRaises(SystemError):
t.getset4 = 1


if __name__ == "__main__":
unittest.main()
8 changes: 1 addition & 7 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/internal/pycore_uop.h \
$(srcdir)/Include/internal/pycore_uop_ids.h \
$(srcdir)/Include/internal/pycore_uop_metadata.h \
$(srcdir)/Include/internal/pycore_wasm_trampoline.h \
$(srcdir)/Include/internal/pycore_warnings.h \
$(srcdir)/Include/internal/pycore_weakref.h \
$(DTRACE_HEADERS) \
Expand Down Expand Up @@ -3153,13 +3154,6 @@ Python/asm_trampoline_universal2.o: $(srcdir)/Python/asm_trampoline_aarch64.S $(
rm -f Python/asm_trampoline_arm64-apple-darwin.o \
Python/asm_trampoline_x86_64-apple-darwin.o

Python/emscripten_trampoline_inner.wasm: $(srcdir)/Python/emscripten_trampoline_inner.c
# emcc has a path that ends with emsdk/upstream/emscripten/emcc, we're looking for emsdk/upstream/bin/clang.
$$(em-config LLVM_ROOT)/clang -o $@ $< -mgc -O2 -Wl,--no-entry -Wl,--import-table -Wl,--import-memory -target wasm32-unknown-unknown -nostdlib

Python/emscripten_trampoline_wasm.c: Python/emscripten_trampoline_inner.wasm
$(PYTHON_FOR_REGEN) $(srcdir)/Platforms/emscripten/prepare_external_wasm.py $< $@ getWasmTrampolineModule

JIT_SHIM_BUILD_OBJS= @JIT_SHIM_BUILD_O@
JIT_UNWIND_INFO_H= $(if $(JIT_OBJS),jit_unwind_info.h $(patsubst jit_stencils-%.h,jit_unwind_info-%.h,@JIT_STENCILS_H@))
JIT_BUILD_TARGETS= jit_stencils.h @JIT_STENCILS_H@ $(JIT_UNWIND_INFO_H) $(JIT_SHIM_BUILD_OBJS)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Added support for function call adaptor trampolines for wasi to prevent
traps when C handlers take the wrong number of arguments. Dropped Emscripten
function call adaptors support for JS runtimes that don't support wasm-gc.
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c _testcapi/fpcast.c
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c _testlimitedcapi/build.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
Expand Down
Loading
Loading