From 7b03a5a51476a9ad784ebf3ea6912f04e6707335 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 23 Sep 2025 14:47:27 +0100 Subject: [PATCH 1/2] GH-137573: Add test to check that the margin used for overflow protection is larger than the stack space used by the interpreter (GH-137724) (cherry picked from commit 16eae6d90d49ef036b010777ceffd130cfa96126) Co-authored-by: Mark Shannon --- Lib/test/test_call.py | 16 ++++++++++++++++ Modules/_testinternalcapi.c | 14 ++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 2c28f106ec7cb6..31e58e825be422 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -12,6 +12,10 @@ import _testlimitedcapi except ImportError: _testlimitedcapi = None +try: + import _testinternalcapi +except ImportError: + _testinternalcapi = None import struct import collections import itertools @@ -1037,6 +1041,18 @@ def test_unexpected_keyword_suggestion_via_getargs(self): @cpython_only class TestRecursion(unittest.TestCase): + def test_margin_is_sufficient(self): + + def get_sp(): + return _testinternalcapi.get_stack_pointer() + + this_sp = _testinternalcapi.get_stack_pointer() + lower_sp = _testcapi.pyobject_vectorcall(get_sp, (), ()) + self.assertLess(lower_sp, this_sp) + # Add an (arbitrary) extra 25% for safety + safe_margin = (this_sp - lower_sp) * 5 / 4 + self.assertLess(safe_margin, _testinternalcapi.get_stack_margin()) + @skip_on_s390x @unittest.skipIf(is_wasi and Py_DEBUG, "requires deep stack") @skip_if_sanitizer("requires deep stack", thread=True) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index f84cf1a4263a2d..afb72d3df83236 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -125,6 +125,18 @@ get_c_recursion_remaining(PyObject *self, PyObject *Py_UNUSED(args)) return PyLong_FromLong(remaining); } +static PyObject* +get_stack_pointer(PyObject *self, PyObject *Py_UNUSED(args)) +{ + uintptr_t here_addr = _Py_get_machine_stack_pointer(); + return PyLong_FromSize_t(here_addr); +} + +static PyObject* +get_stack_margin(PyObject *self, PyObject *Py_UNUSED(args)) +{ + return PyLong_FromSize_t(_PyOS_STACK_MARGIN_BYTES); +} static PyObject* test_bswap(PyObject *self, PyObject *Py_UNUSED(args)) @@ -2381,6 +2393,8 @@ static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, {"get_c_recursion_remaining", get_c_recursion_remaining, METH_NOARGS}, + {"get_stack_pointer", get_stack_pointer, METH_NOARGS}, + {"get_stack_margin", get_stack_margin, METH_NOARGS}, {"test_bswap", test_bswap, METH_NOARGS}, {"test_popcount", test_popcount, METH_NOARGS}, {"test_bit_length", test_bit_length, METH_NOARGS}, From 898adc72227bef86adfebe8bb02b94ca0665850c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 30 Sep 2025 16:51:40 +0200 Subject: [PATCH 2/2] Decrease safe_margin extra space to 20% --- Lib/test/test_call.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 31e58e825be422..fcbef1ae86bed5 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -1049,8 +1049,8 @@ def get_sp(): this_sp = _testinternalcapi.get_stack_pointer() lower_sp = _testcapi.pyobject_vectorcall(get_sp, (), ()) self.assertLess(lower_sp, this_sp) - # Add an (arbitrary) extra 25% for safety - safe_margin = (this_sp - lower_sp) * 5 / 4 + # Add an (arbitrary) extra 20% for safety + safe_margin = (this_sp - lower_sp) * 6 / 5 self.assertLess(safe_margin, _testinternalcapi.get_stack_margin()) @skip_on_s390x