Skip to content

Commit 30cdcac

Browse files
markshannonhroncok
authored andcommitted
GH-139653: Only raise an exception (or fatal error) when the stack pointer is about to overflow the stack. (GH-141711)
Only raises if the stack pointer is both below the limit *and* above the stack base. This prevents false positives for user-space threads, as the stack pointer will be outside those bounds if the stack has been swapped. (cherry picked from commit c25a070)
1 parent b8dfdd2 commit 30cdcac

File tree

3 files changed

+30
-18
lines changed

3 files changed

+30
-18
lines changed

Include/internal/pycore_ceval.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,13 @@ extern void _PyEval_DeactivateOpCache(void);
201201
static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
202202
uintptr_t here_addr = _Py_get_machine_stack_pointer();
203203
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
204+
// Overflow if stack pointer is between soft limit and the base of the hardware stack.
205+
// If it is below the hardware stack base, assume that we have the wrong stack limits, and do nothing.
206+
// We could have the wrong stack limits because of limited platform support, or user-space threads.
204207
#if _Py_STACK_GROWS_DOWN
205-
return here_addr < _tstate->c_stack_soft_limit;
208+
return here_addr < _tstate->c_stack_soft_limit && here_addr >= _tstate->c_stack_soft_limit - 2 * _PyOS_STACK_MARGIN_BYTES;
206209
#else
207-
return here_addr > _tstate->c_stack_soft_limit;
210+
return here_addr > _tstate->c_stack_soft_limit && here_addr <= _tstate->c_stack_soft_limit + 2 * _PyOS_STACK_MARGIN_BYTES;
208211
#endif
209212
}
210213

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Only raise a ``RecursionError`` or trigger a fatal error if the stack
2+
pointer is both below the limit pointer *and* above the stack base. If
3+
outside of these bounds assume that it is OK. This prevents false positives
4+
when user-space threads swap stacks.

Python/ceval.c

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,11 @@ _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int margin_count)
344344
_Py_InitializeRecursionLimits(tstate);
345345
}
346346
#if _Py_STACK_GROWS_DOWN
347-
return here_addr <= _tstate->c_stack_soft_limit + margin_count * _PyOS_STACK_MARGIN_BYTES;
347+
return here_addr <= _tstate->c_stack_soft_limit + margin_count * _PyOS_STACK_MARGIN_BYTES &&
348+
here_addr >= _tstate->c_stack_soft_limit - 2 * _PyOS_STACK_MARGIN_BYTES;
348349
#else
349-
return here_addr > _tstate->c_stack_soft_limit - margin_count * _PyOS_STACK_MARGIN_BYTES;
350+
return here_addr > _tstate->c_stack_soft_limit - margin_count * _PyOS_STACK_MARGIN_BYTES &&
351+
here_addr <= _tstate->c_stack_soft_limit + 2 * _PyOS_STACK_MARGIN_BYTES;
350352
#endif
351353
}
352354

@@ -437,7 +439,7 @@ int pthread_attr_destroy(pthread_attr_t *a)
437439
#endif
438440

439441
static void
440-
hardware_stack_limits(uintptr_t *base, uintptr_t *top)
442+
hardware_stack_limits(uintptr_t *base, uintptr_t *top, uintptr_t sp)
441443
{
442444
#ifdef WIN32
443445
ULONG_PTR low, high;
@@ -473,10 +475,19 @@ hardware_stack_limits(uintptr_t *base, uintptr_t *top)
473475
return;
474476
}
475477
# endif
476-
uintptr_t here_addr = _Py_get_machine_stack_pointer();
477-
uintptr_t top_addr = _Py_SIZE_ROUND_UP(here_addr, 4096);
478+
// Add some space for caller function then round to minimum page size
479+
// This is a guess at the top of the stack, but should be a reasonably
480+
// good guess if called from _PyThreadState_Attach when creating a thread.
481+
// If the thread is attached deep in a call stack, then the guess will be poor.
482+
#if _Py_STACK_GROWS_DOWN
483+
uintptr_t top_addr = _Py_SIZE_ROUND_UP(sp + 8*sizeof(void*), SYSTEM_PAGE_SIZE);
478484
*top = top_addr;
479485
*base = top_addr - Py_C_STACK_SIZE;
486+
# else
487+
uintptr_t base_addr = _Py_SIZE_ROUND_DOWN(sp - 8*sizeof(void*), SYSTEM_PAGE_SIZE);
488+
*base = base_addr;
489+
*top = base_addr + Py_C_STACK_SIZE;
490+
#endif
480491
#endif
481492
}
482493

@@ -505,28 +516,20 @@ tstate_set_stack(PyThreadState *tstate,
505516
#endif
506517
}
507518

508-
509519
void
510520
_Py_InitializeRecursionLimits(PyThreadState *tstate)
511521
{
512522
uintptr_t base, top;
513-
hardware_stack_limits(&base, &top);
523+
uintptr_t here_addr = _Py_get_machine_stack_pointer();
524+
hardware_stack_limits(&base, &top, here_addr);
514525
assert(top != 0);
515526

516527
tstate_set_stack(tstate, base, top);
517528
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
518529
ts->c_stack_init_base = base;
519530
ts->c_stack_init_top = top;
520-
521-
// Test the stack pointer
522-
#if !defined(NDEBUG) && !defined(__wasi__)
523-
uintptr_t here_addr = _Py_get_machine_stack_pointer();
524-
assert(ts->c_stack_soft_limit < here_addr);
525-
assert(here_addr < ts->c_stack_top);
526-
#endif
527531
}
528532

529-
530533
int
531534
PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate,
532535
void *stack_start_addr, size_t stack_size)
@@ -561,7 +564,7 @@ PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate)
561564

562565

563566
/* The function _Py_EnterRecursiveCallTstate() only calls _Py_CheckRecursiveCall()
564-
if the recursion_depth reaches recursion_limit. */
567+
if the stack pointer is between the stack base and c_stack_hard_limit. */
565568
int
566569
_Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
567570
{
@@ -570,10 +573,12 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
570573
assert(_tstate->c_stack_soft_limit != 0);
571574
assert(_tstate->c_stack_hard_limit != 0);
572575
#if _Py_STACK_GROWS_DOWN
576+
assert(here_addr >= _tstate->c_stack_hard_limit - _PyOS_STACK_MARGIN_BYTES);
573577
if (here_addr < _tstate->c_stack_hard_limit) {
574578
/* Overflowing while handling an overflow. Give up. */
575579
int kbytes_used = (int)(_tstate->c_stack_top - here_addr)/1024;
576580
#else
581+
assert(here_addr <= _tstate->c_stack_hard_limit + _PyOS_STACK_MARGIN_BYTES);
577582
if (here_addr > _tstate->c_stack_hard_limit) {
578583
/* Overflowing while handling an overflow. Give up. */
579584
int kbytes_used = (int)(here_addr - _tstate->c_stack_top)/1024;

0 commit comments

Comments
 (0)