From 4845d5a7edbfcacd8b8567c921691e2486b518b0 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 10 Apr 2026 22:17:00 +0200 Subject: [PATCH 1/2] ref: make SENTRY_HANDLER_STACK_SIZE overridable via env var Allow overriding the compile-time SENTRY_HANDLER_STACK_SIZE default at runtime via environment variable. The stack-overflow integration tests previously parametrized SENTRY_HANDLER_STACK_SIZE as a CMake option, forcing 5 extra configure+build cycles on Windows (crashpad x2, inproc x1, breakpad x2). Switching to a runtime env var lets every parametrization reuse the cached build, saving a minute or two on Windows. The env var is read via `GetEnvironmentVariableA` (a kernel32 export) rather than the CRT `getenv`/`strtod`, because the lookup happens in `sentry__set_default_thread_stack_guarantee`, which is called from `DllMain` on `DLL_PROCESS_ATTACH` / `DLL_THREAD_ATTACH`. CRT calls under the loader lock are a latent source of instability and must be avoided. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/sentry_os.c | 29 ++++++++++++++++++++++++--- tests/test_integration_crashpad.py | 15 +++++++------- tests/test_integration_stdout.py | 32 ++++++++++++++++++------------ 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/src/sentry_os.c b/src/sentry_os.c index d7cc2d58d6..de28db22a1 100644 --- a/src/sentry_os.c +++ b/src/sentry_os.c @@ -263,6 +263,29 @@ sentry_set_thread_stack_guarantee(uint32_t stack_guarantee_in_bytes) return 1; } +// Resolves the handler stack size (in KiB) from the `SENTRY_HANDLER_STACK_SIZE` +// environment variable, falling back to the compile-time default. This is +// called from `DllMain` via `sentry__set_default_thread_stack_guarantee`, so +// it must avoid CRT (`getenv`, `strtod`, ...) and only use kernel32 exports. +static size_t +sentry__handler_stack_size_kib(void) +{ + char buf[12]; + DWORD n = GetEnvironmentVariableA( + "SENTRY_HANDLER_STACK_SIZE", buf, sizeof(buf)); + if (n == 0 || n >= sizeof(buf)) { + return SENTRY_HANDLER_STACK_SIZE; + } + size_t val = 0; + for (DWORD i = 0; i < n; i++) { + if (buf[i] < '0' || buf[i] > '9') { + return SENTRY_HANDLER_STACK_SIZE; + } + val = val * 10 + (size_t)(buf[i] - '0'); + } + return val > 0 ? val : SENTRY_HANDLER_STACK_SIZE; +} + void sentry__set_default_thread_stack_guarantee(void) { @@ -270,8 +293,9 @@ sentry__set_default_thread_stack_guarantee(void) return; } + const size_t stack_size_kib = sentry__handler_stack_size_kib(); const unsigned long expected_stack_guarantee - = SENTRY_HANDLER_STACK_SIZE * 1024; + = (unsigned long)stack_size_kib * 1024; DWORD thread_id = GetThreadId(GetCurrentThread()); ULONG_PTR high = 0; ULONG_PTR low = 0; @@ -284,8 +308,7 @@ sentry__set_default_thread_stack_guarantee(void) SENTRY_WARNF( "Cannot set handler stack guarantee of %zuKiB for thread %lu " "(stack reserve: %zuKiB, expected factor: %zux, actual: %.2fx)", - (size_t)SENTRY_HANDLER_STACK_SIZE, thread_id, - thread_stack_reserve / 1024, + stack_size_kib, thread_id, thread_stack_reserve / 1024, (size_t)SENTRY_THREAD_STACK_GUARANTEE_FACTOR, expected_stack_reserve / (double)expected_stack_guarantee); return; diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index a2dca88174..adaf3ccff7 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -440,18 +440,18 @@ def test_crashpad_dumping_crash(cmake, httpserver, run_args, build_args): @pytest.mark.parametrize( - "build_args", + "stack_size", [ - ({}), # uses default of 64KiB + None, # uses default of 64KiB pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "16"}, + "16", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", ), ), pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "32"}, + "32", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", @@ -459,11 +459,12 @@ def test_crashpad_dumping_crash(cmake, httpserver, run_args, build_args): ), ], ) -def test_crashpad_dumping_stack_overflow(cmake, httpserver, build_args): - build_args.update({"SENTRY_BACKEND": "crashpad"}) - tmp_path = cmake(["sentry_example"], build_args) +def test_crashpad_dumping_stack_overflow(cmake, httpserver, stack_size): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + if stack_size: + env["SENTRY_HANDLER_STACK_SIZE"] = stack_size httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK") httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") diff --git a/tests/test_integration_stdout.py b/tests/test_integration_stdout.py index ecbdac1a5c..83ff40656a 100644 --- a/tests/test_integration_stdout.py +++ b/tests/test_integration_stdout.py @@ -158,13 +158,13 @@ def test_multi_process(cmake): assert len(runs) == 0 -def run_stdout_for(backend, cmake, example_args, build_args=None): +def run_stdout_for(backend, cmake, example_args, build_args=None, env=None): build_args = dict(build_args or {}) build_args.update({"SENTRY_BACKEND": backend, "SENTRY_TRANSPORT": "none"}) tmp_path = cmake(["sentry_example"], build_args) - run(tmp_path, "sentry_example", example_args, expect_failure=True) + run(tmp_path, "sentry_example", example_args, expect_failure=True, env=env) return tmp_path, check_output(tmp_path, "sentry_example", ["stdout", "no-setup"]) @@ -243,12 +243,12 @@ def test_inproc_crash_stdout_before_send_and_on_crash(cmake): @pytest.mark.parametrize( - "build_args", + "stack_size", [ - ({}), # uses default of 64KiB + None, # uses default of 64KiB # no test with 16KiB since `inproc` fails with that handler stack size pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "32"}, + "32", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", @@ -256,9 +256,12 @@ def test_inproc_crash_stdout_before_send_and_on_crash(cmake): ), ], ) -def test_inproc_stack_overflow_stdout(cmake, build_args): +def test_inproc_stack_overflow_stdout(cmake, stack_size): + env = dict(os.environ) + if stack_size: + env["SENTRY_HANDLER_STACK_SIZE"] = stack_size tmp_path, output = run_stdout_for( - "inproc", cmake, ["log", "attachment", "stack-overflow"], build_args + "inproc", cmake, ["log", "attachment", "stack-overflow"], env=env ) envelope = Envelope.deserialize(output) @@ -328,18 +331,18 @@ def test_breakpad_crash_stdout_before_send_and_on_crash(cmake): @pytest.mark.parametrize( - "build_args", + "stack_size", [ - ({}), # uses default of 64KiB + None, # uses default of 64KiB pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "16"}, + "16", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", ), ), pytest.param( - {"SENTRY_HANDLER_STACK_SIZE": "32"}, + "32", marks=pytest.mark.skipif( sys.platform != "win32", reason="handler stack size parameterization tests stack guarantee on windows only", @@ -348,9 +351,12 @@ def test_breakpad_crash_stdout_before_send_and_on_crash(cmake): ], ) @pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") -def test_breakpad_stack_overflow_stdout(cmake, build_args): +def test_breakpad_stack_overflow_stdout(cmake, stack_size): + env = dict(os.environ) + if stack_size: + env["SENTRY_HANDLER_STACK_SIZE"] = stack_size tmp_path, output = run_stdout_for( - "breakpad", cmake, ["attachment", "stack-overflow"], build_args + "breakpad", cmake, ["attachment", "stack-overflow"], env=env ) envelope = Envelope.deserialize(output) From 9a5bfc8692dbb9cc434b0d88cf6e6efa25874e4e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 11 Apr 2026 11:18:19 +0200 Subject: [PATCH 2/2] prevent overflow --- src/sentry_os.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sentry_os.c b/src/sentry_os.c index de28db22a1..fefcf01ebb 100644 --- a/src/sentry_os.c +++ b/src/sentry_os.c @@ -283,7 +283,8 @@ sentry__handler_stack_size_kib(void) } val = val * 10 + (size_t)(buf[i] - '0'); } - return val > 0 ? val : SENTRY_HANDLER_STACK_SIZE; + return (val > 0 && val <= 0xFFFFFFFFu / 1024) ? val + : SENTRY_HANDLER_STACK_SIZE; } void