Skip to content

C stack limit detection is disabled on musl, so deep C recursion crashes instead of raising RecursionError #159876

Open

Description

@serhiy-storchaka

Bug report

Bug description:

On Alpine Linux (musl libc), test_lazy_import.LazyImportTypeTests.test_deeply_dotted_name terminates the
interpreter with SIGSEGV instead of raising RecursionError:

$ ./python -m unittest test.test_lazy_import.LazyImportTypeTests.test_deeply_dotted_name
test_deeply_dotted_name (...)
A long dotted name must not overflow the C stack. ... Segmentation fault

The crash is a C stack overflow in the prologue of a recursive call:

Thread 2 "Thread-1 (run)" received signal SIGSEGV, Segmentation fault.
0x00005555556bc2dc in lazy_import_replay_from (...) at ../Objects/lazyimportobject.c:280
#1  0x00005555556bc331 in lazy_import_replay_from (...) at ../Objects/lazyimportobject.c:287
#2  0x00005555556bc331 in lazy_import_replay_from (...) at ../Objects/lazyimportobject.c:287
... (repeats)

lazy_import_replay_from() does guard itself with _Py_EnterRecursiveCallTstate(), so the guard is simply
not firing.

Cause

The test runs its body in a thread with a 256 KiB stack
(@support.run_with_limited_c_stack(depth=10_000, size=256 * 1024)), but CPython never learns that.
hardware_stack_limits() in Python/ceval.c only queries the real stack bounds when:

#  if defined(HAVE_PTHREAD_GETATTR_NP) && !defined(_AIX) && \
        !defined(__NetBSD__) && \
        (defined(__GLIBC__) || defined(__ANDROID__) || !defined(__linux__))

(Python/ceval.c:150-152 on main, :162-164 on 3.15, :462-464 on 3.14.)

On musl, __linux__ is defined while __GLIBC__ and __ANDROID__ are not, so the probe is skipped and
CPython falls back to guessing base = sp - Py_C_STACK_SIZE. With the -Wl,-z,stack-size= that configure
adds, Py_C_STACK_SIZE is _Py_LINKER_THREAD_STACK_SIZE = 1048576, so the interpreter assumes 1 MiB on
every thread regardless of the thread's real size
.

Measured with _testinternalcapi.get_c_recursion_remaining():

import threading, _testinternalcapi as T
for size in (256 * 1024, 8 * 1024 * 1024):
    threading.stack_size(size)
    def show(size=size):
        print("  thread stack %7d B -> c_recursion_remaining=%d"
              % (size, T.get_c_recursion_remaining()))
    t = threading.Thread(target=show); t.start(); t.join()
Alpine 3.24 / musl:  thread stack  262144 B -> c_recursion_remaining=1450
                     thread stack 8388608 B -> c_recursion_remaining=1450   # constant
Debian / glibc:      thread stack  262144 B -> c_recursion_remaining=250
                     thread stack 8388608 B -> c_recursion_remaining=12650  # tracks reality

So on a 256 KiB thread the interpreter believes it has roughly 4x the stack it actually has, the soft limit
lands below the real stack base, and the guard can never trigger.

The exclusion looks broader than the problem it was added for

It came from GH-134336 ("Disable computed stack limit checks on non-glibc linux", gh-131338). But musl's
pthread_getattr_np() is accurate for secondary threads and only wrong for the main thread:

/* cc -o stackprobe stackprobe.c */
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
static void report(const char *tag) {
    pthread_attr_t attr; void *addr = NULL; size_t size = 0, guard = 0;
    if (pthread_getattr_np(pthread_self(), &attr)) { printf("%s: failed\n", tag); return; }
    pthread_attr_getstack(&attr, &addr, &size);
    pthread_attr_getguardsize(&attr, &guard);
    pthread_attr_destroy(&attr);
    printf("%-14s base=%p size=%zu (%.2f MiB) guard=%zu\n", tag, addr, size, size/1048576.0, guard);
}
static void *thr(void *a) { report((const char *)a); return NULL; }
int main(void) {
    pthread_attr_t a; pthread_t t;
    report("main");
    pthread_attr_init(&a); pthread_attr_setstacksize(&a, 8u<<20);
    pthread_create(&t, &a, thr, (void *)"thread-8MiB"); pthread_join(t, NULL);
    pthread_attr_destroy(&a);
    pthread_create(&t, NULL, thr, (void *)"thread-default"); pthread_join(t, NULL);
    return 0;
}
Alpine 3.24 / musl 1.2:
main           base=0x7ffeda154000 size=131072 (0.12 MiB) guard=0        <-- wrong, RLIMIT_STACK is 8 MiB
thread-8MiB    base=0x7f772ee70000 size=8391456 (8.00 MiB) guard=8192    <-- correct
thread-default base=0x7f772f650000 size=133920  (0.13 MiB) guard=8192    <-- correct

Debian / glibc 2.41:
main           base=0x7fffa3c90000 size=8380416 (7.99 MiB) guard=0
thread-8MiB    base=0x7909e3800000 size=8388608 (8.00 MiB) guard=4096
thread-default base=0x7909e3800000 size=8388608 (8.00 MiB) guard=4096

This is the same shape as gh-159320 / GH-159321, which re-enabled detection for Android after it had been
caught by the same blanket condition.

Scope

The broken detection is not specific to test_lazy_import or to 3.15+: the guard is identical on 3.14, and
the probe above returns the same constant 1450 on a 3.14 build. 3.14 is only green because
test_lazy_import does not exist there and no other test asks for a thread smaller than the assumed
Py_C_STACK_SIZE. Any embedder or stdlib caller creating a musl thread with less than ~1 MiB of stack can
overflow silently.

Apart from this one test, the suite is clean on Alpine: main 550 test files run / 1 failed, 3.15 548 /
1 failed (same test), 3.14 525 / 0 failed.

I appreciate musl is PEP 11 tier 0 (configure says so explicitly) and that gh-151546, gh-139467 and gh-76488
were all closed as OS-unsupported. Filing this one anyway because it is a hard crash where a guard exists
and is expected to work, and because the Android case was treated as worth fixing.

CPython versions tested on:

3.14, 3.15, CPython main branch

Operating systems tested on:

Linux

Linked PRs

Activity

  1. serhiy-storchaka commented on Oct 20, 2026

    @serhiy-storchaka
    MemberAuthor

    My apologies, the report was created by Claude, and I wasn't familiar enough with this to shorten it.

  2. added a commit that references this issue on Oct 20, 2026
  3. added
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    type-bugAn unexpected behavior, bug, or error
    on Oct 20, 2026
  4. added a commit that references this issue on Oct 20, 2026
  5. added
    3.14bugs and security fixes
    3.15bugs and security fixes
    3.16new features, bugs and security fixes
    on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.14bugs and security fixes3.15bugs and security fixes3.16new features, bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions