linux: Fix stack-related sub-process shutdown crashes (fixes #3912)

On Linux systems the stack frame reference canary will be purposely
changed when forking sub-processes (see https://crbug.com/40181003).
To avoid sub-process shutdown crashes the NO_STACK_PROTECTOR
annotation must be added to all functions in the call stack leading to
CefExecuteProcess(). Applications that cannot add this annotation must
instead pass the `--change-stack-guard-on-fork=disable` command-line
flag.
This commit is contained in:
Marshall Greenblatt
2025-03-31 14:03:57 -04:00
parent b2f5ab6cd2
commit 5b18ca7d3f
10 changed files with 41 additions and 6 deletions

View File

@ -313,6 +313,30 @@
#define STACK_UNINITIALIZED #define STACK_UNINITIALIZED
#endif #endif
// Attribute "no_stack_protector" disables -fstack-protector for the specified
// function.
//
// "stack_protector" is enabled on most POSIX builds. The flag adds a canary
// to each stack frame, which on function return is checked against a reference
// canary. If the canaries do not match, it's likely that a stack buffer
// overflow has occurred, so immediately crashing will prevent exploitation in
// many cases.
//
// In some cases it's desirable to remove this, e.g. on hot functions, or if
// we have purposely changed the reference canary.
//
// On Linux systems the reference canary will be purposely changed when forking
// sub-processes (see https://crbug.com/40181003). To avoid sub-process shutdown
// crashes the NO_STACK_PROTECTOR annotation must be added to all functions in
// the call stack leading to CefExecuteProcess(). Applications that cannot add
// this annotation must instead pass the `--change-stack-guard-on-fork=disable`
// command-line flag.
#if defined(COMPILER_GCC) || defined(__clang__)
#define NO_STACK_PROTECTOR __attribute__((no_stack_protector))
#else
#define NO_STACK_PROTECTOR
#endif
// The ANALYZER_ASSUME_TRUE(bool arg) macro adds compiler-specific hints // The ANALYZER_ASSUME_TRUE(bool arg) macro adds compiler-specific hints
// to Clang which control what code paths are statically analyzed, // to Clang which control what code paths are statically analyzed,
// and is meant to be used in conjunction with assert & assert-like functions. // and is meant to be used in conjunction with assert & assert-like functions.

View File

@ -59,7 +59,7 @@ class CefApp;
/// |windows_sandbox_info| parameter is only used on Windows and may be NULL /// |windows_sandbox_info| parameter is only used on Windows and may be NULL
/// (see cef_sandbox_win.h for details). /// (see cef_sandbox_win.h for details).
/// ///
/*--cef(api_hash_check,optional_param=application, /*--cef(api_hash_check,no_stack_protector,optional_param=application,
optional_param=windows_sandbox_info)--*/ optional_param=windows_sandbox_info)--*/
int CefExecuteProcess(const CefMainArgs& args, int CefExecuteProcess(const CefMainArgs& args,
CefRefPtr<CefApp> application, CefRefPtr<CefApp> application,

View File

@ -291,6 +291,7 @@ int RunMainWithPreferredStackSize(FiberState& fiber_state) {
} // namespace } // namespace
NO_STACK_PROTECTOR
int CefExecuteProcess(const CefMainArgs& args, int CefExecuteProcess(const CefMainArgs& args,
CefRefPtr<CefApp> application, CefRefPtr<CefApp> application,
void* windows_sandbox_info) { void* windows_sandbox_info) {

View File

@ -206,6 +206,7 @@ void CefMainRunner::QuitMessageLoop() {
} }
// static // static
NO_STACK_PROTECTOR
int CefMainRunner::RunAsHelperProcess(const CefMainArgs& args, int CefMainRunner::RunAsHelperProcess(const CefMainArgs& args,
CefRefPtr<CefApp> application, CefRefPtr<CefApp> application,
void* windows_sandbox_info) { void* windows_sandbox_info) {

View File

@ -48,6 +48,7 @@ void TerminationSignalHandler(int signatl) {
MainContext::Get()->GetRootWindowManager()->CloseAllWindows(true); MainContext::Get()->GetRootWindowManager()->CloseAllWindows(true);
} }
NO_STACK_PROTECTOR
int RunMain(int argc, char* argv[]) { int RunMain(int argc, char* argv[]) {
// Create a copy of |argv| on Linux because Chromium mangles the value // Create a copy of |argv| on Linux because Chromium mangles the value
// internally (see issue #620). // internally (see issue #620).
@ -158,6 +159,7 @@ int RunMain(int argc, char* argv[]) {
} // namespace client } // namespace client
// Program entry point function. // Program entry point function.
NO_STACK_PROTECTOR
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
return client::RunMain(argc, argv); return client::RunMain(argc, argv);
} }

View File

@ -31,6 +31,7 @@ int XIOErrorHandlerImpl(Display* display) {
#endif // defined(CEF_X11) #endif // defined(CEF_X11)
// Entry point function for all processes. // Entry point function for all processes.
NO_STACK_PROTECTOR
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
// Provide CEF with command-line arguments. // Provide CEF with command-line arguments.
CefMainArgs main_args(argc, argv); CefMainArgs main_args(argc, argv);

View File

@ -134,6 +134,7 @@ class ScopedPlatformSetup final {
} // namespace } // namespace
NO_STACK_PROTECTOR
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
int exit_code; int exit_code;

View File

@ -522,9 +522,9 @@ def dict_to_str(dict):
# Attribute keys allowed in CEF metadata comments. # Attribute keys allowed in CEF metadata comments.
COMMON_ATTRIB_KEYS = ('added', 'removed') COMMON_ATTRIB_KEYS = ('added', 'removed')
CLASS_ATTRIB_KEYS = COMMON_ATTRIB_KEYS + ('no_debugct_check', 'source') CLASS_ATTRIB_KEYS = COMMON_ATTRIB_KEYS + ('no_debugct_check', 'source')
FUNCTION_ATTRIB_KEYS = COMMON_ATTRIB_KEYS + ('api_hash_check', 'capi_name', FUNCTION_ATTRIB_KEYS = COMMON_ATTRIB_KEYS + (
'count_func', 'default_retval', 'api_hash_check', 'capi_name', 'count_func', 'default_retval',
'index_param', 'optional_param') 'index_param', 'no_stack_protector', 'optional_param')
# regex for matching comment-formatted attributes # regex for matching comment-formatted attributes
_cre_attrib = r'/\*--cef\(([A-Za-z0-9_ ,=:\n]{0,})\)--\*/' _cre_attrib = r'/\*--cef\(([A-Za-z0-9_ ,=:\n]{0,})\)--\*/'

View File

@ -9,10 +9,13 @@ import functools
def make_cpptoc_impl_proto(name, func, parts): def make_cpptoc_impl_proto(name, func, parts):
proto = ''
if func.has_attrib('no_stack_protector'):
proto += 'NO_STACK_PROTECTOR '
if isinstance(func, obj_function_virtual): if isinstance(func, obj_function_virtual):
proto = parts['retval'] + ' CEF_CALLBACK' proto += parts['retval'] + ' CEF_CALLBACK'
else: else:
proto = 'CEF_EXPORT ' + parts['retval'] proto += 'CEF_EXPORT ' + parts['retval']
proto += ' ' + name + '(' + ', '.join(parts['args']) + ')' proto += ' ' + name + '(' + ', '.join(parts['args']) + ')'
return proto return proto

View File

@ -11,6 +11,8 @@ def make_ctocpp_impl_proto(clsname, name, func, parts):
const = '' const = ''
proto = 'NO_SANITIZE("cfi-icall") ' proto = 'NO_SANITIZE("cfi-icall") '
if func.has_attrib('no_stack_protector'):
proto += 'NO_STACK_PROTECTOR '
if clsname is None: if clsname is None:
proto += 'CEF_GLOBAL ' + parts['retval'] + ' ' proto += 'CEF_GLOBAL ' + parts['retval'] + ' '
else: else: