Skip to content

fix(android): mask signal during CHAIN_AT_START to survive chained Mono handler re-raises#1572

Open
jpnurmi wants to merge 10 commits intomasterfrom
jpnurmi/fix/sa-nodefer-chain-at-start
Open

fix(android): mask signal during CHAIN_AT_START to survive chained Mono handler re-raises#1572
jpnurmi wants to merge 10 commits intomasterfrom
jpnurmi/fix/sa-nodefer-chain-at-start

Conversation

@jpnurmi
Copy link
Collaborator

@jpnurmi jpnurmi commented Mar 11, 2026

Summary

  • On Android, mask the signal before invoking the chained handler so SA_NODEFER doesn't let re-raises kill the process
  • Use raw rt_sigprocmask syscall to bypass Android's libsigchain, whose sigprocmask guard is only active inside its own special handlers
  • After the chain: reinstall our handler if it was reset to SIG_DFL, consume any pending signal via sigtimedwait, and unmask

Context

With SA_NODEFER, the chained Mono handler can reset the signal handler to SIG_DFL and re-raise. The re-raised signal is delivered immediately and kills the process before inproc can capture the crash.

This is gated to Android because only the Mono runtime (used on Android) does the reset+raise pattern. On desktop Linux, CoreCLR modifies the ucontext and returns without re-raising.

jpnurmi and others added 2 commits March 11, 2026 13:27
SA_NODEFER (added in #1446) is incompatible with the CHAIN_AT_START
signal handler strategy. When chaining to the runtime's signal handler
(e.g. Mono), the runtime may reset the signal to SIG_DFL and re-raise.
With SA_NODEFER the re-raised signal is delivered immediately, killing
the process before our handler can regain control.

Without SA_NODEFER, the re-raised signal is blocked during handler
execution, allowing the runtime handler to return and sentry-native
to proceed with crash capture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jpnurmi jpnurmi force-pushed the jpnurmi/fix/sa-nodefer-chain-at-start branch from 89200fa to ab26763 Compare March 11, 2026 12:40
@jpnurmi jpnurmi requested a review from supervacuus March 11, 2026 12:52
@supervacuus
Copy link
Collaborator

Mono's mono_handle_native_crash resets the crashing signal to SIG_DFL and re-raises it as part of its crash handling flow. With SA_NODEFER, the re-raised signal is delivered immediately

Yeah, this makes sense, but is there no Mono test in the downstream integration tests? It is quite painful that the two runtimes differ so severely, given that we seem to lack any early warning for that particular config. Or did this only happen with .NET 10?

Recursive crash detection (the reason SA_NODEFER was added) is not critical for CHAIN_AT_START because:

Not critical is an understatement. It is as critical as with all other use cases, when the signal actually comes from code that the Native SDK should handle.

Wouldn't it be better to just mask the incoming signal before we invoke the handler at start? This way, a raise inside the .NET handler is blocked until we sigreturn, and we retain the recursive reentrancy semantics of our handler (which are critical because we execute user-provided code from the handler). Disable the unmask once we know the .NET handler didn't feel responsible for the signal.

jpnurmi and others added 3 commits March 11, 2026 14:27
…ng process

With SA_NODEFER, the chained handler's re-raise is delivered immediately
and kills the process before we regain control. Mask the signal via raw
rt_sigprocmask (to bypass Android's libsigchain), then after the chain:
reinstall our handler if it was reset to SIG_DFL, consume any pending
signal with sigtimedwait, and unmask.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jpnurmi
Copy link
Collaborator Author

jpnurmi commented Mar 11, 2026

Yeah, this makes sense, but is there no Mono test in the downstream integration tests? It is quite painful that the two runtimes differ so severely, given that we seem to lack any early warning for that particular config. Or did this only happen with .NET 10?

This happens with both .NET 9 and .NET 10. Unfortunately, the PR that takes chained signal handling into use and would have revealed this problem, has been on hold until now due to other issues in .NET 10.

@jpnurmi jpnurmi changed the title fix: skip SA_NODEFER when CHAIN_AT_START is active fix: mask signal during CHAIN_AT_START to survive chained handler re-raises Mar 11, 2026
sigtimedwait is not declared without _POSIX_C_SOURCE >= 199309L, so use
the raw SYS_rt_sigtimedwait syscall instead. Also replace sizeof(sigset_t)
with _NSIG/8 since the kernel expects 8 bytes, not glibc's 128.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
libsigchain's sigprocmask guard is only active inside its own special
handlers, so our signal handler still gets filtered. Keep the raw
syscall on Android and use the standard sigprocmask on other platforms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jpnurmi and others added 2 commits March 12, 2026 10:04
…raises

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jpnurmi jpnurmi changed the title fix: mask signal during CHAIN_AT_START to survive chained handler re-raises fix(android): mask signal during CHAIN_AT_START to survive chained Mono handler re-raises Mar 12, 2026
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants