Skip to content

Commit 9bfa19c

Browse files
committed
retroarch: gate retroarch_fail longjmp on init-in-progress
flag retroarch_fail unconditionally longjmps into global->error_sjlj_context. The author's comment acknowledges the constraint - 'we cannot longjmp unless we're in retroarch_main_init()' - but the code longjmps regardless, and the constraint is not enforced anywhere. retroarch_fail's callers include drivers_init's per-driver find_*_driver failure paths (find_audio_driver, find_video_driver, find_input_driver, find_camera_driver, etc. at retroarch.c:1668-1700) and the bluetooth/wifi RARCH_*_CTL_INIT handlers reached from the same drivers_init path. drivers_init runs not just from retroarch_main_init's single CMD_EVENT_CORE_INIT (where the setjmp is live) but also from command_event_reinit -> video_driver_reinit_context for every CMD_EVENT_REINIT - fullscreen toggle, HDR mode change, video driver swap, AV info change, etc. When retroarch_fail fires from a reinit-time drivers_init, the longjmp lands in error_sjlj_context, which points to stack memory that was unwound when retroarch_main_init returned. Behavior is undefined: typically a crash, sometimes silent corruption. This is a cold path - drivers don't usually vanish at runtime - but the retroarch_fail calls in drivers_init exist precisely because the code thinks they can fail, and the failure handling assumes initialization-time semantics that no longer hold. Add GLOB_FLG_INIT_IN_PROGRESS to global flags. Set it after the setjmp succeeds in retroarch_main_init, clear it on every exit (both success and the error: label). Gate retroarch_fail's longjmp on the flag; if clear, log and return. The non-longjmp return path means the caller (drivers_init) sees the subsystem fail to init but isn't unwound. Driver init code already NULL-checks its own driver pointers downstream of find_*_driver calls (e.g. video_st->current_video gets NULL'd on find_video_driver failure and downstream rendering checks for it), so a 'driver missing' state is survivable - the user ends up in a degraded but not-crashed state. Initialization-time failures still propagate via the existing setjmp path.
1 parent 0760c29 commit 9bfa19c

2 files changed

Lines changed: 39 additions & 5 deletions

File tree

retroarch.c

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8081,6 +8081,12 @@ bool retroarch_main_init(int argc, char *argv[])
80818081
goto error;
80828082
}
80838083

8084+
/* Mark error_sjlj_context as live. retroarch_fail checks this
8085+
* before longjmp'ing; reinit-time driver_init failures that
8086+
* reach retroarch_fail outside this function will log and
8087+
* return rather than landing in a stale jmp_buf. */
8088+
global->flags |= GLOB_FLG_INIT_IN_PROGRESS;
8089+
80848090
global->flags |= GLOB_FLG_ERR_ON_INIT;
80858091

80868092
/* Have to initialise non-file logging once at the start... */
@@ -8425,11 +8431,13 @@ bool retroarch_main_init(int argc, char *argv[])
84258431
game_ai_init();
84268432
#endif
84278433

8434+
global->flags &= ~GLOB_FLG_INIT_IN_PROGRESS;
84288435
return true;
84298436

84308437
error:
84318438
command_event(CMD_EVENT_CORE_DEINIT, NULL);
84328439
runloop_state_get_ptr()->flags &= ~RUNLOOP_FLAG_IS_INITED;
8440+
global->flags &= ~GLOB_FLG_INIT_IN_PROGRESS;
84338441

84348442
return false;
84358443
}
@@ -8840,12 +8848,29 @@ size_t retroarch_get_capabilities(enum rarch_capabilities type,
88408848
void retroarch_fail(int err_code, const char *err)
88418849
{
88428850
global_t *global = global_get_ptr();
8843-
/* We cannot longjmp unless we're in retroarch_main_init().
8844-
* If not, something went very wrong, and we should
8845-
* just exit right away. */
88468851
strlcpy(global->error_string, err,
88478852
sizeof(global->error_string));
8848-
longjmp(global->error_sjlj_context, err_code);
8853+
8854+
/* Only longjmp if retroarch_main_init's setjmp is still live.
8855+
* Outside that scope (e.g. when drivers_init runs from
8856+
* command_event_reinit for a fullscreen toggle, video driver
8857+
* swap, or any other CMD_EVENT_REINIT path) the
8858+
* error_sjlj_context jmp_buf points into stack memory that
8859+
* was unwound long ago. Jumping there is undefined behavior;
8860+
* on most platforms it crashes or silently corrupts state.
8861+
*
8862+
* Driver init failures during reinit are not recoverable in
8863+
* this function (we don't have a clean rollback path for the
8864+
* partial reinit), but they're survivable - the prior driver
8865+
* state is gone but we can leave the user in the menu rather
8866+
* than crashing. The caller (drivers_init) will see the
8867+
* subsystem fail to init and that subsystem's downstream code
8868+
* is expected to NULL-check its driver pointers. */
8869+
if (global->flags & GLOB_FLG_INIT_IN_PROGRESS)
8870+
longjmp(global->error_sjlj_context, err_code);
8871+
8872+
RARCH_ERR("[Core] retroarch_fail outside retroarch_main_init: %s\n",
8873+
err);
88498874
}
88508875

88518876
/* Called on close content, checks if we need to also exit retroarch */

retroarch_types.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,16 @@ enum global_flags
254254
{
255255
GLOB_FLG_ERR_ON_INIT = (1 << 0),
256256
GLOB_FLG_LAUNCHED_FROM_CLI = (1 << 1),
257-
GLOB_FLG_CLI_LOAD_MENU_ON_ERR = (1 << 2)
257+
GLOB_FLG_CLI_LOAD_MENU_ON_ERR = (1 << 2),
258+
/* Set on entry to retroarch_main_init (right after its setjmp
259+
* is established) and cleared on every exit. retroarch_fail
260+
* checks this flag before longjmp'ing - the error_sjlj_context
261+
* jmp_buf is only valid while retroarch_main_init is on the
262+
* stack; calling retroarch_fail from any other context (e.g.
263+
* a reinit-time drivers_init invoked via command_event_reinit)
264+
* with the flag clear means the longjmp would land in stale
265+
* stack memory. */
266+
GLOB_FLG_INIT_IN_PROGRESS = (1 << 3)
258267
};
259268

260269
typedef struct global

0 commit comments

Comments
 (0)