Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions src/mono/mono/mini/mini-exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -2955,24 +2955,29 @@ mono_handle_native_crash (const char *signal, MonoContext *mctx, MONO_SIG_HANDLE
MonoJitTlsData *jit_tls = mono_tls_get_jit_tls ();

#ifdef MONO_ARCH_USE_SIGACTION
struct sigaction sa;
sa.sa_handler = SIG_DFL;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
// When crash chaining is enabled, keep our signal handlers installed so
// that secondary signals (e.g. SIGABRT from FORTIFY on other threads)
// don't kill the process with SIG_DFL before we can chain the original
// crash to the previous handler.
if (!mono_do_crash_chaining) {
struct sigaction sa;
sa.sa_handler = SIG_DFL;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;

/* Remove our SIGABRT handler */
g_assert (sigaction (SIGABRT, &sa, NULL) != -1);
/* Remove our SIGABRT handler */
g_assert (sigaction (SIGABRT, &sa, NULL) != -1);

/* On some systems we get a SIGILL when calling abort (), because it might
* fail to raise SIGABRT */
g_assert (sigaction (SIGILL, &sa, NULL) != -1);
/* On some systems we get a SIGILL when calling abort (), because it might
* fail to raise SIGABRT */
g_assert (sigaction (SIGILL, &sa, NULL) != -1);

/* Remove SIGCHLD, it uses the finalizer thread */
g_assert (sigaction (SIGCHLD, &sa, NULL) != -1);

/* Remove SIGQUIT, we are already dumping threads */
g_assert (sigaction (SIGQUIT, &sa, NULL) != -1);
/* Remove SIGCHLD, it uses the finalizer thread */
g_assert (sigaction (SIGCHLD, &sa, NULL) != -1);

/* Remove SIGQUIT, we are already dumping threads */
g_assert (sigaction (SIGQUIT, &sa, NULL) != -1);
}
#endif

if (mini_debug_options.suspend_on_native_crash) {
Expand Down
120 changes: 120 additions & 0 deletions src/tasks/AndroidAppBuilder/Templates/monodroid.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#include <sys/system_properties.h>
#include <sys/mman.h>
#include <assert.h>
#include <setjmp.h>
#include <signal.h>
#include <ucontext.h>
#include <unistd.h>

/********* exported symbols *********/
Expand All @@ -46,6 +49,12 @@ Java_net_dot_MonoRunner_freeNativeResources (JNIEnv* env, jobject thiz);
void
invoke_external_native_api (void (*callback)(void));

int
test_crash_chaining (void);

void
test_crash_chaining_install_pre_mono_handler (void);

/********* implementation *********/

static const char* g_bundle_path = NULL;
Expand Down Expand Up @@ -296,6 +305,11 @@ mono_droid_runtime_init (const char* executable, int local_date_time_offset)
mono_set_signal_chaining (true);
mono_set_crash_chaining (true);

// Install the crash chaining test handler before mono_jit_init
// only when the crash chaining test is running.
if (getenv("TEST_CRASH_CHAINING"))
test_crash_chaining_install_pre_mono_handler();

if (wait_for_debugger) {
char* options[] = { "--debugger-agent=transport=dt_socket,server=y,address=0.0.0.0:55556" };
mono_jit_parse_options (1, options);
Expand Down Expand Up @@ -467,3 +481,109 @@ invoke_external_native_api (void (*callback)(void))
if (callback)
callback();
}

/*
* Test for crash chaining: verify that mono_handle_native_crash does not
* reset SIGABRT to SIG_DFL when crash_chaining is enabled.
*
* Strategy:
* 1. Install a custom SIGSEGV handler that saves Mono's handler and
* checks whether SIGABRT is still handled (not SIG_DFL).
* 2. Trigger a SIGSEGV from a non-JIT native function.
* 3. Mono chains to our handler (because signal_chaining is enabled).
* 4. Our handler checks SIGABRT disposition and reports the result.
*
* Returns 0 on success, non-zero on failure.
*/
static volatile sig_atomic_t g_test_crash_chain_result = -1;
static volatile sig_atomic_t g_test_sigabrt_received = 0;
static sigjmp_buf g_test_jmpbuf;

__attribute__((noinline))
static void do_test_crash(void)
{
volatile int *ptr = 0;
*ptr = 42;
}

static void
test_sigabrt_handler(int signum)
{
(void)signum;
g_test_sigabrt_received = 1;
}

/**
* Pre-Mono SIGSEGV handler installed before mono_jit_init. Mono's
* signal chaining saves this handler and calls it via mono_chain_signal
* when a native crash occurs.
*
* After Mono's crash diagnostics (mono_handle_native_crash) run, this
* handler verifies that SIGABRT was not reset to SIG_DFL. Without the
* fix, mono_handle_native_crash unconditionally resets SIGABRT to
* SIG_DFL, so raise(SIGABRT) kills the process (the test crashes).
* With the fix, SIGABRT retains Mono's handler, and we can catch the
* raised SIGABRT with a temporary handler (the test passes).
*/
static void
test_pre_mono_sigsegv_handler(int signum, siginfo_t *info, void *context)
{
(void)signum;
(void)info;
(void)context;

// By the time Mono chains to us, mono_handle_native_crash has
// already run. Check if SIGABRT was reset to SIG_DFL.
struct sigaction current_abrt;
sigaction(SIGABRT, NULL, &current_abrt);

if (current_abrt.sa_handler == SIG_DFL) {
// Bug: SIGABRT is SIG_DFL. Demonstrate the failure by raising
// SIGABRT — just like a FORTIFY abort on another thread would.
// This kills the process (test crashes).
raise(SIGABRT);
_exit(1); // unreachable
}

// SIGABRT is still handled — the fix works. Install a temporary
// catcher and raise SIGABRT to prove it's actually catchable.
struct sigaction tmp_abrt = { .sa_handler = test_sigabrt_handler };
sigaction(SIGABRT, &tmp_abrt, NULL);

g_test_sigabrt_received = 0;
raise(SIGABRT);

sigaction(SIGABRT, &current_abrt, NULL);

g_test_crash_chain_result = g_test_sigabrt_received ? 0 : 1;

// Jump back to test_crash_chaining to report the result.
siglongjmp(g_test_jmpbuf, 1);
}

void
test_crash_chaining_install_pre_mono_handler(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = test_pre_mono_sigsegv_handler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
}

int
test_crash_chaining(void)
{
g_test_crash_chain_result = -1;
if (sigsetjmp(g_test_jmpbuf, 1) == 0) {
do_test_crash();
}

if (g_test_crash_chain_result == -1) {
LOG_ERROR("test_crash_chaining: handler was not called");
return 3;
}

return g_test_crash_chain_result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RunAOTCompilation>false</RunAOTCompilation>
<TestRuntime>true</TestRuntime>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<MainLibraryFileName>Android.Device_Emulator.CrashChaining.Test.dll</MainLibraryFileName>
<ExpectedExitCode>42</ExpectedExitCode>
</PropertyGroup>

<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>

<ItemGroup>
<AndroidEnv Include="TEST_CRASH_CHAINING" Value="1" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

/// <summary>
/// Test that crash chaining preserves signal handlers during
/// mono_handle_native_crash. When crash_chaining is enabled,
/// mono_handle_native_crash should NOT reset SIGABRT etc. to SIG_DFL,
/// because that would let secondary signals (e.g. FORTIFY aborts on
/// other threads) kill the process before the crash can be chained.
/// </summary>
public static class Program
{
// Returns 0 on success (SIGABRT handler preserved during crash chaining),
// non-zero on failure.
[DllImport("__Internal")]
private static extern int test_crash_chaining();

public static int Main()
{
int result = test_crash_chaining();
Console.WriteLine(result == 0
? "PASS: crash chaining preserved signal handlers"
: $"FAIL: crash chaining test returned {result}");
return result == 0 ? 42 : result;
}
}
Loading