-
-
Notifications
You must be signed in to change notification settings - Fork 33.3k
Add internal doc describing the stack protection mechanism #137663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
markshannon
merged 3 commits into
python:main
from
faster-cpython:document-stack-protection
Aug 13, 2025
+63
−0
Merged
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # Stack Protection | ||
|
|
||
| CPython protects against stack overflow in the form of runaway, or just very deep, recursion by raising a `RecursionError` instead of just crashing. | ||
| Protection against pure Python stack recursion has existed since very early, but in 3.12 we added protection against stack overflow | ||
| in C code. This was initially implemented using a counter and later improved in 3.14 to use the actual stack depth. | ||
| For those platforms that support it (Windows, Mac, and most linuxes) we query the operating system to find the stack bounds. | ||
markshannon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| For other platforms we use conserative estimates. | ||
|
|
||
|
|
||
| The C stack looks like this: | ||
|
|
||
| ``` | ||
| +-------+ <--- Top of machine stack | ||
| | | | ||
| | | | ||
|
|
||
| ~~ | ||
|
|
||
| | | | ||
| | | | ||
| +-------+ <--- Soft limit | ||
| | | | ||
| | | _PyOS_STACK_MARGIN_BYTES | ||
| | | | ||
| +-------+ <--- Hard limit | ||
| | | | ||
| | | _PyOS_STACK_MARGIN_BYTES | ||
| | | | ||
| +-------+ <--- Bottom of machine stack | ||
AA-Turner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ``` | ||
|
|
||
|
|
||
| We get the current stack pointer using compiler intrinsics where available, or by taking the address of a (C) local variable. See `_Py_get_machine_stack_pointer()`. | ||
markshannon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| The soft and hard limits pointers are set by calling `_Py_InitializeRecursionLimits()` during thread initialization. | ||
|
|
||
| Recursion checks are performed by `_Py_EnterRecursiveCall()` or `_Py_EnterRecursiveCallTstate()` which compare the stack pointer to the soft limit. If the stack pointer is lower than the soft limit, then `_Py_CheckRecursiveCall()` is called which checks against both the hard and soft limits: | ||
|
|
||
| ```Py | ||
markshannon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| kb_used = (stack_top - stack_pointer)>>10 | ||
| if stack_pointer < hard_limit: | ||
| FatalError(f"Unrecoverable stack overflow (used {kb_used} kB)") | ||
| elif stack_pointer < soft_limit: | ||
| raise RecursionError(f"Stack overflow (used {kb_used} kB)") | ||
| ``` | ||
|
|
||
| ### Diagnosing and fixing stack overflows. | ||
markshannon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
markshannon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| For stack protection to work correctly the amount of stack consumed between calls to `_Py_EnterRecursiveCall()` must be less than `_PyOS_STACK_MARGIN_BYTES`. | ||
|
|
||
| If you see a traceback ending in: `RecursionError: Stack overflow (used ... kB)` then the stack protection is working as intended. If you don't expect to see the error, then check the amount of stack used. If it seems low then CPython may not be configured properly. | ||
|
|
||
| However, if you see a fatal error or crash, then something is not right. | ||
| Either a recursive call is not checking `_Py_EnterRecursiveCall()`, or the amount of C stack consumed by a single call exceeds `_PyOS_STACK_MARGIN_BYTES`. If a hard crash occurs, it probably means that the amount of C stack consumed is more than double `_PyOS_STACK_MARGIN_BYTES`. | ||
|
|
||
| Likely causes: | ||
| * Recursive code is not calling `_Py_EnterRecursiveCall()` | ||
| * -O0 compilation flags, especially for Clang. With no optimization, C calls can consume a lot of stack space | ||
markshannon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * Giant, complex functions in third-party C extensions. This is unlikely as the function in question would need to be more complicated than the bytecode interpreter. | ||
| * `_PyOS_STACK_MARGIN_BYTES` is just too low. | ||
| * `_Py_InitializeRecursionLimits()` is not setting the soft and hard limits correctly for that platform. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is versioned in the source tree, is it important to discuss legacy/3.12 behaviour here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not important, but a bit of background does no harm.