android: only load cores from the app's own folders

A launch intent's LIBRETRO, DATADIR and CONFIGFILE extras are chosen by
whoever sends it, so another app could have RetroArch dlopen any .so.
Every core open now resolves the path and refuses it unless it lies
under the app's dataDir or nativeLibraryDir, or in one of the package's
own code folders in its install folder, all read from ApplicationInfo
rather than from the extras. A Play core install moves the package to a
new code folder while RetroArch runs, so a fixed nativeLibraryDir is
not enough.
This commit is contained in:
davey authored and libretroadmin committed 2026-11-07 03:06:17 +01:00
1 parent c4f751bd99
commit d94f129883
6 files changed
+151 -11

No files matched your search

+110 -4
View File
@@ -134,6 +134,12 @@ enum platform_android_flags
static char app_dir[DIR_MAX_LENGTH];
static char apk_dir[DIR_MAX_LENGTH];
/* Where cores may load from: dataDir and nativeLibraryDir, realpath'd */
static char android_core_dirs[2][DIR_MAX_LENGTH];
/* And any "<package>-" code folder of ours in the install folder:
* Play installs move the code folder mid-session. */
static char android_install_dir[DIR_MAX_LENGTH];
static char android_code_prefix[NAME_MAX_LENGTH];
static char android_config_path[PATH_MAX_LENGTH];
/* Set in android_app_create on the UI thread, so the
* permissionsResolved upcall can reach the app state before the
@@ -1447,9 +1453,48 @@ static bool android_env_file_path(JNIEnv *env, jobject file,
return android_env_jstring_copy(env, jpath, buf, len);
}
/* getApplicationInfo().dataDir / .sourceDir for absent DATADIR / APK. */
/* sourceDir is <install dir>/[~~X/]<package>-Y/base.apk. */
static bool android_env_derive_install_dir(JNIEnv *env,
jobject appinfo, jclass appinfo_class)
{
char *s;
size_t _len;
jfieldID fid = (*env)->GetFieldID(env, appinfo_class,
"packageName", "Ljava/lang/String;");
if ( android_env_exception(env, "ApplicationInfo.packageName")
|| !android_env_jstring_copy(env,
(jstring)(*env)->GetObjectField(env, appinfo, fid),
android_code_prefix, sizeof(android_code_prefix)))
return false;
_len = strlcat(android_code_prefix, "-", sizeof(android_code_prefix));
if (_len >= sizeof(android_code_prefix))
return false;
fid = (*env)->GetFieldID(env, appinfo_class,
"sourceDir", "Ljava/lang/String;");
if ( android_env_exception(env, "ApplicationInfo.sourceDir")
|| !android_env_jstring_copy(env,
(jstring)(*env)->GetObjectField(env, appinfo, fid),
android_install_dir, sizeof(android_install_dir))
|| !path_resolve_realpath(android_install_dir,
sizeof(android_install_dir), true)
|| !(s = strrchr(android_install_dir, '/')))
return false;
*s = '\0';
if ( !(s = strrchr(android_install_dir, '/'))
|| strncmp(s + 1, android_code_prefix, _len))
return false;
*s = '\0';
if ((s = strrchr(android_install_dir, '/')) && s[1] == '~' && s[2] == '~')
*s = '\0';
return (*android_install_dir != '\0');
}
/* getApplicationInfo().dataDir / .sourceDir for absent DATADIR / APK,
* and the core folders, which no extra may move. */
static void android_env_derive_application_info(JNIEnv *env, jobject activity)
{
static const char *core_dir_fields[] = { "dataDir", "nativeLibraryDir" };
size_t i;
jclass activity_class;
jclass appinfo_class;
jmethodID get_application_info;
@@ -1484,10 +1529,72 @@ static void android_env_derive_application_info(JNIEnv *env, jobject activity)
(jstring)(*env)->GetObjectField(env, appinfo, fid),
apk_dir, sizeof(apk_dir));
}
for (i = 0; i < ARRAY_SIZE(android_core_dirs); i++)
{
jfieldID fid = (*env)->GetFieldID(env, appinfo_class,
core_dir_fields[i], "Ljava/lang/String;");
if ( android_env_exception(env, core_dir_fields[i])
|| !android_env_jstring_copy(env,
(jstring)(*env)->GetObjectField(env, appinfo, fid),
android_core_dirs[i], sizeof(android_core_dirs[i]))
|| !path_resolve_realpath(android_core_dirs[i],
sizeof(android_core_dirs[i]), true))
*android_core_dirs[i] = '\0';
__android_log_print(ANDROID_LOG_INFO,
"RetroArch", "[ENV] Core folder: \"%s\".\n", android_core_dirs[i]);
}
if (!android_env_derive_install_dir(env, appinfo, appinfo_class))
{
*android_install_dir = '\0';
*android_code_prefix = '\0';
__android_log_print(ANDROID_LOG_INFO, "RetroArch",
"[ENV] Core folder: no install folder found.\n");
}
else
__android_log_print(ANDROID_LOG_INFO, "RetroArch",
"[ENV] Core folder: \"%s\" (%s*).\n",
android_install_dir, android_code_prefix);
(*env)->DeleteLocalRef(env, appinfo_class);
(*env)->DeleteLocalRef(env, appinfo);
}
bool android_core_path_allowed(const char *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]);
if ( _len
&& !strncmp(real_path, android_core_dirs[i], _len)
&& real_path[_len] == '/')
return true;
}
/* <install dir>/[~~X/]<package>-Y/... */
_len = strlen(android_install_dir);
if ( !_len
|| strncmp(real_path, android_install_dir, _len)
|| real_path[_len] != '/')
return false;
s = real_path + _len + 1;
if (s[0] == '~' && s[1] == '~')
{
if (!(s = strchr(s, '/')))
return false;
s++;
}
_len = strlen(android_code_prefix);
return (!strncmp(s, android_code_prefix, _len)
&& s[_len] != '/'
&& strchr(s + _len, '/'));
}
/* getPackageManager().getPackageInfo(getPackageName(), 0).versionCode. */
static unsigned android_env_derive_version_code(JNIEnv *env, jobject activity)
{
@@ -3221,9 +3328,8 @@ static void frontend_unix_get_env(int *argc,
/* Anything the intent did not supply is derived from the
* application context, so a direct launch of the native activity
* behaves like one through the Java launcher. Extras always win:
* every derivation below runs only for a value still unset. */
if (!*app_dir || !*apk_dir)
android_env_derive_application_info(env, android_app->activity->clazz);
* a value an extra can set is derived only while it is still unset. */
android_env_derive_application_info(env, android_app->activity->clazz);
if (!*internal_storage_path || !*internal_storage_app_path)
android_env_derive_storage(env, android_app->activity->clazz);
if (!*android_app->current_ime)
+2
View File
@@ -583,6 +583,8 @@ 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);
bool is_screen_reader_enabled(void);
/* Pushes the live values of the window-affecting settings to the
+1 -1
View File
@@ -755,7 +755,7 @@ static void runahead_copy_task_handler(retro_task_t *task)
* on while the loader maps the copy. A copy that will not
* open is still published; the main thread's own open of it
* then reports why. */
h->lib = dylib_load(h->out_path);
h->lib = runloop_core_dylib_load(h->out_path, true);
}
else
{
+29 -6
View File
@@ -839,6 +839,23 @@ void libretro_get_environment_info(
runloop_st->flags &= ~RUNLOOP_FLAG_IGNORE_ENVIRONMENT_CB;
}
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))
{
if (!quiet)
RARCH_ERR("[Core] Not loading \"%s\": not found in the app's own folders.\n",
path);
/* Leave no stale loader error for the failure path to print */
dylib_error();
return NULL;
}
#endif
return dylib_load(path);
}
/* Whether the core path is resolved through symlinks before it is
* opened (and saved to history) */
static bool load_dynamic_core_resolves_symlinks(void)
@@ -890,7 +907,7 @@ static void core_preload_task_handler(retro_task_t *task)
core_preload_handle_t *h = task ? (core_preload_handle_t*)task->state : NULL;
/* The open: the one thing off the main thread */
if (h && h->path)
h->lib = dylib_load(h->path);
h->lib = runloop_core_dylib_load(h->path, true);
if (task)
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
}
@@ -1051,7 +1068,7 @@ static dylib_t load_dynamic_core(const char *path, char *s,
return pre;
}
#endif
return dylib_load(path);
return runloop_core_dylib_load(path, false);
}
/* @live is set when @path opened the library already loaded as the
@@ -1062,7 +1079,7 @@ static dylib_t libretro_get_system_info_lib(const char *path,
struct retro_system_info *sysinfo, bool *load_no_content,
bool *live)
{
dylib_t lib = dylib_load(path);
dylib_t lib = runloop_core_dylib_load(path, false);
void (*proc)(struct retro_system_info*);
*live = false;
@@ -5025,10 +5042,12 @@ bool libretro_get_system_info(
core_probe_capture = NULL;
if (!lib)
{
const char *err = dylib_error();
RARCH_ERR("[Core] %s: \"%s\"\n",
msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE),
path);
RARCH_ERR("[Core] Error(s): %s\n", dylib_error());
if (err)
RARCH_ERR("[Core] Error(s): %s\n", err);
return false;
}
if (load_no_content)
@@ -5160,7 +5179,11 @@ bool runloop_init_libretro_symbols(
)))
{
const char *_msg = msg_hash_to_str(MSG_FAILED_TO_OPEN_LIBRETRO_CORE);
RARCH_ERR("[Core] %s: \"%s\"\nError(s): %s\n", _msg, path, dylib_error());
const char *err = dylib_error();
if (err)
RARCH_ERR("[Core] %s: \"%s\"\nError(s): %s\n", _msg, path, err);
else
RARCH_ERR("[Core] %s: \"%s\"\n", _msg, path);
runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
return false;
@@ -5176,7 +5199,7 @@ bool runloop_init_libretro_symbols(
* unless the caller has opened it already */
if (!(lib_handle_local = *lib_handle_p))
{
if (!(lib_handle_local = dylib_load(lib_path)))
if (!(lib_handle_local = runloop_core_dylib_load(lib_path, false)))
return false;
*lib_handle_p = lib_handle_local;
}
+4
View File
@@ -1031,6 +1031,10 @@ bool runloop_core_preload_ready(void);
void runloop_core_preload_cancel(void);
#ifdef HAVE_DYNAMIC
/* dylib_load() for a core; on Android only from the app's own folders.
* @quiet: a worker's open, whose refusal the main thread's open logs */
dylib_t runloop_core_dylib_load(const char *path, bool quiet);
/* Frees the records libretro_get_system_info() keeps of the cores it
* has asked; the next question reads them back from disk. */
void runloop_core_probe_cache_free(void);
+5
View File
@@ -130,6 +130,11 @@ dylib_t dylib_load(const char *path)
return (dylib_t)(uintptr_t)(0x1000 + ++lib_loads);
}
dylib_t runloop_core_dylib_load(const char *path, bool quiet)
{
return dylib_load(path);
}
void dylib_close(dylib_t lib)
{
if (lib)