Skip to content

mallopt: fix M_BIONIC_RESTORE_DEFAULT_SIGABRT_HANDLER being a no-op#65

Open
thomasbuilds wants to merge 1 commit intoGrapheneOS:16-qpr2from
thomasbuilds:fix-restore-default-sigabrt-handler
Open

mallopt: fix M_BIONIC_RESTORE_DEFAULT_SIGABRT_HANDLER being a no-op#65
thomasbuilds wants to merge 1 commit intoGrapheneOS:16-qpr2from
thomasbuilds:fix-restore-default-sigabrt-handler

Conversation

@thomasbuilds
Copy link
Copy Markdown
Contributor

Summary

mallopt(M_BIONIC_RESTORE_DEFAULT_SIGABRT_HANDLER, …) (added in 43d1f48) is silently a no-op in the case it was designed to handle, so callers that rely on it to escape a debuggerd / ART sigchainlib SIGABRT handler still go through that handler instead of crashing with the default disposition.

Details

The saved SIGABRT disposition is captured in __libc_preinit_impl (libc_init_dynamic.cpp):

sigaction(SIGABRT, nullptr, &globals->saved_sigabrt_handler);

__libc_preinit is declared __attribute__((constructor(1))) (libc_init_dynamic.cpp L223), i.e. it runs before any other constructor or .preinit_array entry. At that point execve(2) has just reset SIGABRT to SIG_DFL, so the kernel fills saved_sigabrt_handler with a zero-initialized struct. Because sa_handler and sa_sigaction share a union (see signal_types.h), saved_sigabrt_handler.sa_sigaction is NULL.

The restore path then short-circuits on that NULL:

if (param == M_BIONIC_RESTORE_DEFAULT_SIGABRT_HANDLER) {
  if (__libc_globals->saved_sigabrt_handler.sa_sigaction != nullptr) {
    sigaction(SIGABRT, &__libc_globals->saved_sigabrt_handler, nullptr);
    return 1;
  }
  return 0;
}

The sigaction() call is never reached and the function returns 0 — the option does nothing in the very scenario it is meant for: switching SIGABRT back to SIG_DFL after debuggerd / sigchainlib have installed their own handler later in startup.

Fix

Drop the null guard. Restoring a struct sigaction whose sa_handler is SIG_DFL is exactly the documented intent of this option, and sigaction() accepts that just fine.

if (param == M_BIONIC_RESTORE_DEFAULT_SIGABRT_HANDLER) {
  sigaction(SIGABRT, &__libc_globals->saved_sigabrt_handler, nullptr);
  return 1;
}

Impact

Any caller using mallopt(M_BIONIC_RESTORE_DEFAULT_SIGABRT_HANDLER, …) to bypass an installed SIGABRT handler — the documented purpose of the API — currently silently fails. After this change, the call performs the intended sigaction(SIGABRT, …) and returns 1.

The saved SIGABRT disposition is captured in __libc_preinit_impl, which
runs as __attribute__((constructor(1))) — before any other constructor
or .preinit_array entry. At that moment execve(2) has just reset
SIGABRT to SIG_DFL, so the saved 'struct sigaction' is zero-initialized
and saved_sigabrt_handler.sa_handler / .sa_sigaction (a union) is NULL.

The mallopt M_BIONIC_RESTORE_DEFAULT_SIGABRT_HANDLER handler then
short-circuits on 'sa_sigaction != nullptr' and never calls sigaction(),
so the option is a permanent no-op in the very case it is designed for:
restoring SIG_DFL after debuggerd / sigchainlib have installed a
SIGABRT handler at a later point during process startup.

Restoring a struct that contains SIG_DFL is exactly the documented
intent of this option, so drop the null guard and always perform the
sigaction() call.
@julesh85
Copy link
Copy Markdown

Hey @thomasbuilds, we’re impressed with your contributions to GrapheneOS! I'd love to connect and chat about a potential role. If you're open to it, please message me on Matrix here: @jules:grapheneos.org. Cheers!

@thomasbuilds
Copy link
Copy Markdown
Contributor Author

Hi, I've sent you a dm on matrix as user MrPump

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