From d7de9cc7b6518571eeb7f8ed5fffe9f287b27474 Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Sat, 7 Nov 2026 03:06:55 +0100 Subject: [PATCH] android: open the core path that passed the folder check runloop_core_dylib_load resolved the core's path to check that it lies in the app's own folders, then handed the original, unresolved path to dlopen. Any symlink on that path could be repointed between the check and the open. android_core_path_allowed becomes android_core_path_resolve, which hands back the resolved path it checked, and that path is the one opened. Which cores are allowed is unchanged. --- frontend/drivers/platform_unix.c | 20 +++++++++++++++----- frontend/drivers/platform_unix.h | 5 ++++- runloop.c | 11 ++++++++--- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/frontend/drivers/platform_unix.c b/frontend/drivers/platform_unix.c index 747011b63e..093c2c39dc 100644 --- a/frontend/drivers/platform_unix.c +++ b/frontend/drivers/platform_unix.c @@ -1558,16 +1558,12 @@ static void android_env_derive_application_info(JNIEnv *env, jobject activity) (*env)->DeleteLocalRef(env, appinfo); } -bool android_core_path_allowed(const char *path) +static bool android_core_real_path_allowed(const char *real_path) { - char real_path[PATH_MAX_LENGTH]; const char *s; size_t _len; size_t i; - strlcpy(real_path, path, sizeof(real_path)); - if (!path_resolve_realpath(real_path, sizeof(real_path), true)) - *real_path = '\0'; for (i = 0; i < ARRAY_SIZE(android_core_dirs); i++) { _len = strlen(android_core_dirs[i]); @@ -1595,6 +1591,20 @@ bool android_core_path_allowed(const char *path) && strchr(s + _len, '/')); } +bool android_core_path_resolve(const char *path, char *s, size_t len) +{ + if ( !path || !*path + || strlcpy(s, path, len) >= len + || !path_resolve_realpath(s, len, true) + || !android_core_real_path_allowed(s)) + { + if (len) + *s = '\0'; + return false; + } + return true; +} + /* getPackageManager().getPackageInfo(getPackageName(), 0).versionCode. */ static unsigned android_env_derive_version_code(JNIEnv *env, jobject activity) { diff --git a/frontend/drivers/platform_unix.h b/frontend/drivers/platform_unix.h index 300422582c..3041f66720 100644 --- a/frontend/drivers/platform_unix.h +++ b/frontend/drivers/platform_unix.h @@ -583,7 +583,10 @@ void frontend_android_get_version(int32_t *major, int32_t *minor, int32_t *rel); void frontend_android_get_version_sdk(int32_t *sdk); -bool android_core_path_allowed(const char *path); +/* Resolves @path, symlinks included, into @s; true only when the + * result lies in one of the app's own core folders. Open @s, not + * @path: the checked path is then the opened one. */ +bool android_core_path_resolve(const char *path, char *s, size_t len); bool is_screen_reader_enabled(void); diff --git a/runloop.c b/runloop.c index 78ce3c53f1..c32bdb787f 100644 --- a/runloop.c +++ b/runloop.c @@ -842,8 +842,11 @@ void libretro_get_environment_info( dylib_t runloop_core_dylib_load(const char *path, bool quiet) { #if defined(ANDROID) - /* Another app's intent can name the core */ - if (!android_core_path_allowed(path)) + /* Another app's intent can name the core. The resolved path that + * passed the check is the one opened, so no symlink on the way can + * be swapped between the check and the open. */ + char real_path[PATH_MAX_LENGTH]; + if (!android_core_path_resolve(path, real_path, sizeof(real_path))) { if (!quiet) RARCH_ERR("[Core] Not loading \"%s\": not found in the app's own folders.\n", @@ -852,8 +855,10 @@ dylib_t runloop_core_dylib_load(const char *path, bool quiet) dylib_error(); return NULL; } -#endif + return dylib_load(real_path); +#else return dylib_load(path); +#endif } /* Whether the core path is resolved through symlinks before it is