diff --git a/src/mono/mono/mini/mini-exceptions.c b/src/mono/mono/mini/mini-exceptions.c index 188fa9d340ede1..f3fe6c2c211d7d 100644 --- a/src/mono/mono/mini/mini-exceptions.c +++ b/src/mono/mono/mini/mini-exceptions.c @@ -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) { diff --git a/src/tasks/AndroidAppBuilder/Templates/monodroid.c b/src/tasks/AndroidAppBuilder/Templates/monodroid.c index 5ba3952a43d78a..10dd1c1cfcef0a 100644 --- a/src/tasks/AndroidAppBuilder/Templates/monodroid.c +++ b/src/tasks/AndroidAppBuilder/Templates/monodroid.c @@ -24,6 +24,9 @@ #include #include #include +#include +#include +#include #include /********* exported symbols *********/ @@ -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; @@ -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); @@ -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, ¤t_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, ¤t_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; +} diff --git a/src/tests/FunctionalTests/Android/Device_Emulator/CrashChaining/Android.Device_Emulator.CrashChaining.Test.csproj b/src/tests/FunctionalTests/Android/Device_Emulator/CrashChaining/Android.Device_Emulator.CrashChaining.Test.csproj new file mode 100644 index 00000000000000..d897abbba713a1 --- /dev/null +++ b/src/tests/FunctionalTests/Android/Device_Emulator/CrashChaining/Android.Device_Emulator.CrashChaining.Test.csproj @@ -0,0 +1,18 @@ + + + Exe + false + true + $(NetCoreAppCurrent) + Android.Device_Emulator.CrashChaining.Test.dll + 42 + + + + + + + + + + diff --git a/src/tests/FunctionalTests/Android/Device_Emulator/CrashChaining/Program.cs b/src/tests/FunctionalTests/Android/Device_Emulator/CrashChaining/Program.cs new file mode 100644 index 00000000000000..bbe31e77e45834 --- /dev/null +++ b/src/tests/FunctionalTests/Android/Device_Emulator/CrashChaining/Program.cs @@ -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; + +/// +/// 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. +/// +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; + } +}