Skip to content

feat(native): Add Android support for out-of-process crash handling - #1725

Open
jpnurmi wants to merge 3 commits into
jpnurmi/feat/stdout-transportfrom
jpnurmi/feat/native-android
Open

jpnurmi wants to merge 3 commits into
jpnurmi/feat/stdout-transportfrom
jpnurmi/feat/native-android

Conversation

@jpnurmi

@jpnurmi jpnurmi commented May 15, 2026 •

Copy link
Copy Markdown
Collaborator

Warning

WIP    🚧🔨⏳⛔

This enables the out-of-process native crash daemon on Android, including file-backed IPC, daemon packaging as libsentry-crash.so, backend-owned daemon path resolution, and preload support for managed runtimes.

The NDK integration now selects native crash reporting mode from the Android SDK tombstone merging setting: tombstone-enabled builds avoid minidumps because minidumps take precedence over native envelopes, while tombstone-disabled builds keep native-with-minidump for richer crash data.

This also adds Android daemon support for signal-handler chaining, crash envelope queueing, libunwindstack unwinding, and focused Android integration coverage.

Comment thread src/backends/native/sentry_crash_ipc.c Outdated
Comment thread src/backends/native/sentry_crash_ipc.c Outdated
Comment thread src/backends/native/sentry_crash_ipc.c
Comment thread ndk/lib/src/main/jni/sentry_android_transport.c Outdated
Comment thread ndk/lib/build.gradle.kts Outdated
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 7ea304b to 11f94c3 Compare May 19, 2026 13:36
Comment thread src/backends/native/sentry_crash_ipc.c
@jpnurmi jpnurmi changed the title WIP: feat(native): Add Android support POC: feat(native): Add Android support May 19, 2026
Comment thread ndk/gradle.properties Outdated
Comment thread ndk/gradle.properties Outdated
Comment thread src/backends/native/sentry_crash_ipc.c
Comment thread src/backends/native/sentry_crash_daemon.c
Comment thread src/backends/native/sentry_crash_daemon.c Outdated
Comment thread src/backends/native/sentry_crash_handler.c
Comment thread src/backends/native/sentry_crash_handler.c Outdated
Comment on lines +920 to +924
if (sentry__atomic_fetch(&g_preloaded)) {
g_crash_ipc = NULL;
SENTRY_DEBUG("crash handler deactivated, keeping preloaded handlers");
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The signal stack (g_signal_stack.ss_sp) allocated during preload is never freed on shutdown, causing a memory leak.
Severity: MEDIUM

Suggested Fix

The shutdown logic for the preloaded case should be updated to free the resources it allocated. Specifically, the memory for g_signal_stack.ss_sp should be freed and the signal stack should be disabled before the function returns, even when g_preloaded is true.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/backends/native/sentry_crash_handler.c#L920-L924

Potential issue: When the crash handler is preloaded by calling
`sentry__crash_handler_preload()`, memory is allocated for a signal stack and assigned
to `g_signal_stack.ss_sp`. However, during shutdown
(`sentry__crash_handler_shutdown()`), an early return is taken if the `g_preloaded` flag
is set. This bypasses the cleanup logic that frees the allocated signal stack. As a
result, the memory for `g_signal_stack.ss_sp` is never deallocated, leading to a memory
leak in applications that use the preload feature and then shut down the handler.

Comment on lines +11 to +12
char *serialized = sentry_envelope_serialize(envelope, &size_out);
printf("%s", serialized);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The return value of sentry_envelope_serialize is not checked for NULL before being passed to printf, which can cause a crash in out-of-memory situations.
Severity: LOW

Suggested Fix

Add a null-check for the serialized variable after calling sentry_envelope_serialize. If the variable is NULL, handle the error gracefully, for example by skipping the printf call, to prevent a crash.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/transports/sentry_transport_stdout.c#L11-L12

Potential issue: Under extreme out-of-memory conditions, the function
`sentry_envelope_serialize` can return a `NULL` pointer. This occurs if an internal
1-byte memory allocation for an empty string fails. The code passes this `NULL` return
value directly to `printf` without a check. This results in undefined behavior and is
known to cause a segmentation fault and crash on platforms like Android using the Bionic
libc.

Comment thread src/transports/sentry_transport_stdout.c
@jpnurmi jpnurmi changed the title POC: feat(native): Add Android support poc(native): Add Android support for out-of-process crash handling May 20, 2026

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread src/backends/native/sentry_crash_daemon.c
Comment thread src/backends/native/sentry_crash_daemon.c
Comment on lines +117 to +124
fail:
if (ipc->init_mutex) {
sentry__mutex_unlock(ipc->init_mutex);
}
if (ipc->shmem) {
munmap(ipc->shmem, SENTRY_CRASH_SHM_SIZE);
}
if (ipc->notify_fd >= 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The native initialization function closes caller-provided file descriptors on failure without notifying the caller, leading to stale file descriptors on the Java side.
Severity: HIGH

Suggested Fix

The callee (sentry__crash_ipc_init_app_with_fds) should not close file descriptors that it does not own. The function should instead return an error code to the caller, sentry_android_crash_daemon_init, which can then propagate the failure status back to the Java layer. The caller, which owns the file descriptors, should be responsible for closing them upon learning of the initialization failure.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/backends/native/sentry_crash_ipc.c#L117-L124

Potential issue: During native crash daemon initialization via `nativeInitCrashDaemon`,
file descriptors (`notify_fd`, `ready_fd`) are passed from Java to the native layer. If
an error occurs during the subsequent initialization in
`sentry__crash_ipc_init_app_with_fds` (e.g., an `mmap()` failure), the error handling
logic closes these file descriptors. However, the original caller on the Java side is
not notified of this failure or that the descriptors have been closed. This leaves the
caller with stale file descriptors, which can lead to data corruption or crashes if they
are used later, as they might have been recycled by the OS for other resources.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread src/backends/native/sentry_crash_ipc.c
Comment thread src/backends/native/sentry_crash_ipc.c
Comment thread src/backends/native/sentry_crash_ipc.c
Comment thread src/backends/native/sentry_crash_daemon.c
Comment thread src/backends/native/sentry_crash_daemon.c
@linear-code

linear-code Bot commented Sep 23, 2026

Copy link
Copy Markdown

NATIVE-231

Comment thread src/backends/native/sentry_crash_daemon.c
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from c451ff8 to 5a7389f Compare September 24, 2026 15:53

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread src/backends/native/sentry_crash_daemon.c
Comment thread src/backends/native/sentry_crash_daemon.c
jpnurmi added a commit that referenced this pull request Sep 29, 2026
The lint failure in #1725 is reproducible locally with
`make -C ndk check`, even on `master`. CI on `master` happens to pass
because it reuses cached `lintAnalyzeDebug` results. Fresh analysis
reports dependency update notices that `warningsAsErrors` makes fatal:

```text
> Task :sentry-native-ndk:lintDebug FAILED
ndk/gradle/wrapper/gradle-wrapper.properties:3: Error: A newer version of Gradle than 9.7.1 is available: 9.8.0 [AndroidGradlePluginVersion]
ndk/lib/build.gradle.kts:134: Error: A newer version of org.jetbrains:annotations than 23.0.0 is available: 24.0.1 [GradleDependency]
```

Disable these checks alongside `NewerVersionAvailable`. Always rerun
lint analysis so CI cannot reuse a passing result when dependency
metadata changes.
jpnurmi added a commit that referenced this pull request Sep 30, 2026
The lint failure in #1725 is reproducible locally with
`make -C ndk check`, even on `master`. CI on `master` happens to pass
because it reuses cached `lintAnalyzeDebug` results. Fresh analysis
reports dependency update notices that `warningsAsErrors` makes fatal:

```text
> Task :sentry-native-ndk:lintDebug FAILED
ndk/gradle/wrapper/gradle-wrapper.properties:3: Error: A newer version of Gradle than 9.7.1 is available: 9.8.0 [AndroidGradlePluginVersion]
ndk/lib/build.gradle.kts:134: Error: A newer version of org.jetbrains:annotations than 23.0.0 is available: 24.0.1 [GradleDependency]
```

Disable these checks alongside `NewerVersionAvailable`. Always rerun
lint analysis so CI cannot reuse a passing result when dependency
metadata changes.
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 5a7389f to b93f5ad Compare September 30, 2026 10:12

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread src/transports/sentry_transport_stdout.c Outdated
Comment on lines +182 to +192
ipc->shmem = NULL;
close(ipc->shm_fd);
sentry_free(ipc);
return NULL;
}

if (ipc->shmem->magic != SENTRY_CRASH_MAGIC) {
SENTRY_WARN("daemon: invalid shared memory magic");
munmap(ipc->shmem, SENTRY_CRASH_SHM_SIZE);
close(ipc->shm_fd);
sentry_free(ipc);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The Android crash daemon leaks file descriptors if sentry__crash_ipc_init_daemon fails during initialization, as the cleanup path is skipped.
Severity: HIGH

Suggested Fix

Modify sentry__crash_ipc_init_daemon to close the notify_eventfd and ready_eventfd file descriptors in all early-return failure paths, such as after a failed mmap call or an invalid magic number check. This ensures that the file descriptors are always cleaned up, even if the IPC initialization does not complete.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/backends/native/sentry_crash_ipc.c#L182-L192

Potential issue: In the Android crash handler, the `sentry__crash_ipc_init_daemon`
function can leak file descriptors. If `mmap` fails, the magic number in shared memory
is invalid, or the initial memory allocation for the `ipc` struct fails, the function
returns `NULL` without closing the `notify_eventfd` and `ready_eventfd` file
descriptors. The caller's cleanup logic in `android_cleanup_pending_crash_daemon` only
frees these resources if the `ipc` struct was successfully initialized, which it is not
in these failure cases. This results in a resource leak that could exhaust the process's
file descriptor limit after multiple crashes, preventing future crash handling.

Also affects:

  • src/backends/native/sentry_crash_ipc.c:175~178

Comment on lines 1112 to +1115

// On Linux, allow the daemon to ptrace this process
// This is required when Yama LSM ptrace_scope is enabled
if (prctl(PR_SET_PTRACER, state->daemon_pid, 0, 0, 0) != 0) {
if (!state->android_service_daemon

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The Android service daemon skips the prctl(PR_SET_PTRACER) call, which can cause stack unwinding to fail and result in incomplete crash reports on some devices.
Severity: MEDIUM

Suggested Fix

Ensure that the service daemon has the necessary ptrace permissions. This could involve making the prctl(PR_SET_PTRACER) call even when in service daemon mode, or implementing an alternative mechanism to grant these permissions before the crash handler is initialized. The goal is to allow process_vm_readv to succeed.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/backends/sentry_backend_native.c#L1112-L1115

Potential issue: When the Android service daemon is enabled, the code intentionally
skips the `prctl(PR_SET_PTRACER)` call. This call is required to grant the daemon
process permission to trace the app process on systems with the Yama LSM enabled and
`ptrace_scope` set to a value greater than 0. Without this permission,
`libunwindstack`'s use of `process_vm_readv` will fail, preventing it from reading
process memory. This causes stack unwinding to fail silently, resulting in crash reports
with only a single frame instead of a full stack trace, significantly reducing their
usefulness.

@jpnurmi jpnurmi changed the title poc(native): Add Android support for out-of-process crash handling feat(native): Add Android support for out-of-process crash handling Oct 9, 2026
@jpnurmi jpnurmi added the v1 Targets 1.0 (v1 branch) label Oct 9, 2026
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch 2 times, most recently from 8ce92af to a99236e Compare October 10, 2026 07:36
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 5042761

Comment on lines +84 to +89
- name: Check out sentry-java
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: jpnurmi/sentry-java ### TODO: getsentry/sentry-java
ref: feat/native-crash-service
path: sentry-java

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Android E2E job runs mutable third-party fork code with secrets

The Android job checks out jpnurmi/sentry-java at branch feat/native-crash-service and executes its Gradle build while SENTRY_E2E_* secrets are present; pin a full commit SHA from a trusted repo (and prefer getsentry/sentry-java) before running that code with credentials.

Evidence
  • The android job checks out repository: jpnurmi/sentry-java with mutable ref: feat/native-crash-service (not a 40-char SHA).
  • The emulator step then runs python scripts/setup-android-e2e.py sentry-java, and that script invokes gradlew from the checked-out tree (subprocess.run(..., cwd=java)), so branch tip code executes on the runner.
  • The same step exports SENTRY_E2E_DSN, SENTRY_E2E_AUTH_TOKEN, SENTRY_E2E_ORG, and SENTRY_E2E_PROJECT from repository secrets into the job environment.
  • Anyone who can push that fork branch (or compromise the account) can change executed build logic while those secrets are available.

Identified by Warden · security-review · S5S-U4D

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread src/backends/native/sentry_crash_daemon.c
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 95b1105 to d4c36e4 Compare October 10, 2026 08:47
jpnurmi added a commit that referenced this pull request Oct 16, 2026
Android e2e testing for #1725 exposed a daemon stack overflow: the roughly
1.2 MB local `minidump_writer_t` exceeds the crash service Java worker's
stack, crashing the daemon before it writes a minidump.

Allocate the writer on the heap to avoid exhausting the daemon's stack.

#1725
jpnurmi added a commit that referenced this pull request Oct 16, 2026
Android e2e testing for #1725 exposed a daemon stack overflow: the roughly
1.2 MB local `minidump_writer_t` exceeds the crash service Java worker's
stack, crashing the daemon before it writes a minidump.

Allocate the writer on the heap to avoid exhausting the daemon's stack.

#1725
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 68e5853 to 3a7ab68 Compare October 16, 2026 15:06

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 3a7ab68. Configure here.

ctx, build_registers_from_ctx(ctx, SIZE_MAX));
enrich_stacktrace(ctx, captured);
return captured;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Android unwind skips libunwindstack

High Severity

The daemon prefers a handler capture for the crashing thread whenever it contains a non-zero fault IP, and returns before sentry__crash_unwind_stack_libunwindstack runs. The Android handler always stores that fault address first and cannot run libunwindstack in-process, so typical crashes keep a one-frame or frame-pointer stack instead of the CFI unwind this change added.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3a7ab68. Configure here.

Android e2e testing for #1725 exposed a daemon stack overflow: the roughly
1.2 MB local `minidump_writer_t` exceeds the crash service Java worker's
stack, crashing the daemon before it writes a minidump.

Allocate the writer on the heap to avoid exhausting the daemon's stack.

#1725
Exercise minidump reporting and tombstone merging through the surviving
crash service without restarting the app. Verify threads, debug images,
user/release metadata, and Java/native frames in merged stacks.

Build the fixture against the local native and Java SDKs in an isolated
Maven repository and run both cases on API 36 in CI.
@jpnurmi
jpnurmi force-pushed the jpnurmi/feat/native-android branch from 3a7ab68 to 5042761 Compare October 16, 2026 16:30
@jpnurmi
jpnurmi changed the base branch from master to jpnurmi/feat/stdout-transport October 16, 2026 16:37
@jpnurmi
jpnurmi added this pull request to stack #2214 October 16, 2026 16:37

This branch has not been deployed

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

Labels

v1 Targets 1.0 (v1 branch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant