Call the debuggerd first chance handler in sigchain for recoverable GWP-ASan
Recoverable GWP-ASan is a new feature in Android U that allows GWP-ASan
to detect use-after-free and heap-buffer-overflow (like it currently
does), and for a bug report to be printed, but *now* the app that
contained the bug won't crash.
This may allow us to deploy GWP-ASan as an opt-out feature, rather than
an opt-in feature as it currently is. Previously, AppCompat meant that
we couldn't have opt-in GWP-ASan, because an OS upgrade could now cause
"working before" (just with silent memory corruption) apps to suddenly
crash.
Bug: 247012630
Test: Patch an OS build with aosp/2394588, then use `setprop
libc.debug.gwp_asan.recoverable.com.sanitizers.app.none true` (where
com.sanitizers.app.none is an app that can trigger a use-after-free) to
enable recoverable GWP-ASan. Then, trigger the use-after-free, and make
sure that the sigchain handler calls the debuggerd handler first,
returning from sigchain.
Change-Id: I01ff2ad6bf16cbebec4e86156d5a0357b1f98e59
diff --git a/sigchainlib/sigchain.cc b/sigchainlib/sigchain.cc
index 668d75e..208f7ed 100644
--- a/sigchainlib/sigchain.cc
+++ b/sigchainlib/sigchain.cc
@@ -340,6 +340,12 @@
static bool is_signal_hook_debuggable = false;
+// Weak linkage, as the ART APEX might be deployed on devices where this symbol doesn't exist (i.e.
+// all OS's before Android U). This symbol comes from libdl.
+__attribute__((weak)) extern "C" bool android_handle_signal(int signal_number,
+ siginfo_t* info,
+ void* context);
+
void SignalChain::Handler(int signo, siginfo_t* siginfo, void* ucontext_raw) {
// Try the special handlers first.
// If one of them crashes, we'll reenter this handler and pass that crash onto the user handler.
@@ -369,6 +375,25 @@
}
}
+ // In Android 14, there's a special feature called "recoverable" GWP-ASan. GWP-ASan is a tool that
+ // finds heap-buffer-overflow and heap-use-after-free on native heap allocations (e.g. malloc()
+ // inside of JNI, not the ART heap). The way it catches buffer overflow (roughly) is by rounding
+ // up the malloc() so that it's page-sized, and mapping an inaccessible page on the left- and
+ // right-hand side. It catches use-after-free by mprotecting the allocation page to be PROT_NONE
+ // on free(). The new "recoverable" mode is designed to allow debuggerd to print a crash report,
+ // but for the app or process in question to not crash (i.e. recover) and continue even after the
+ // bug is detected. Sigchain thus must allow debuggerd to handle the signal first, and if
+ // debuggerd has promised that it can recover, and it's done the steps to allow recovery (as
+ // identified by android_handle_signal returning true), then we should return from this handler
+ // and let the app continue.
+ //
+ // For all non-GWP-ASan-recoverable crashes, or crashes where recovery is not possible,
+ // android_handle_signal returns false, and we will continue to the rest of the sigchain handler
+ // logic.
+ if (android_handle_signal != nullptr && android_handle_signal(signo, siginfo, ucontext_raw)) {
+ return;
+ }
+
// Forward to the user's signal handler.
int handler_flags = chains[signo].action_.sa_flags;
ucontext_t* ucontext = static_cast<ucontext_t*>(ucontext_raw);