fix: root related mounts leak in KernelSU in isolated services

This commit fixes the leak of mounts in KernelSU with global umount disabled in isolated services. This happens because KernelSU doesn't handle isolated services in kernel side, so we must find the main app UID and see if that UID is in denylist instead. With that, also improve APatch detection to take advantage of faster integer/UID comparison rather than always check process/string comparison, and only fallback to process name based if UID is not found.

Co-Authored-By: nampud <nampud@users.noreply.github.com>
This commit is contained in:
ThePedroo
2025-06-24 19:18:45 -03:00
parent fa9adcf3b5
commit 48238521df
3 changed files with 69 additions and 16 deletions

View File

@@ -4,6 +4,9 @@
extern "C" {
#endif /* __cplusplus */
#define IS_ISOLATED_SERVICE(uid) \
((uid) >= 90000 && (uid) < 1000000)
/*
* Bionic's atoi runs through strtol().
* Use our own implementation for faster conversion.

View File

@@ -722,7 +722,48 @@ void ZygiskContext::run_modules_post() {
void ZygiskContext::app_specialize_pre() {
flags[APP_SPECIALIZE] = true;
info_flags = rezygiskd_get_process_flags(g_ctx->args.app->uid, (const char *const)process);
/* INFO: Isolated services have different UIDs than the main apps. Because
numerous root implementations base themselves in the UID of the
app, we need to ensure that the UID sent to ReZygiskd to search
is the app's and not the isolated service, or else it will be
able to bypass DenyList.
All apps, and isolated processes, of *third-party* applications will
have their app_data_dir set. The system applications might not have
one, however it is unlikely they will create an isolated process,
and even if so, it should not impact in detections, performance or
any area.
*/
uid_t uid = args.app->uid;
if (IS_ISOLATED_SERVICE(uid) && args.app->app_data_dir) {
/* INFO: If the app is an isolated service, we use the UID of the
app's process data directory, which is the UID of the
app itself, which root implementations actually use.
*/
const char *data_dir = env->GetStringUTFChars(args.app->app_data_dir, NULL);
if (!data_dir) {
LOGE("Failed to get app data directory");
return;
}
struct stat st;
if (stat(data_dir, &st) == -1) {
PLOGE("Failed to stat app data directory [%s]", data_dir);
env->ReleaseStringUTFChars(args.app->app_data_dir, data_dir);
return;
}
uid = st.st_uid;
LOGD("Isolated service being related to UID %d, app data dir: %s", uid, data_dir);
env->ReleaseStringUTFChars(args.app->app_data_dir, data_dir);
}
info_flags = rezygiskd_get_process_flags(uid, (const char *const)process);
if (info_flags & PROCESS_IS_FIRST_STARTED) {
/* INFO: To ensure we are really using a clean mount namespace, we use
the first process it as reference for clean mount namespace,

View File

@@ -168,22 +168,8 @@ bool apatch_uid_should_umount(uid_t uid, const char *const process) {
return false;
}
/* INFO: Some can take advantage of the UID being different in an app's
isolated service, bypassing this check, so we must check against
process name in case it is an isolated service. This can happen in
all root implementations. */
size_t targeted_process_length = 0;
if (IS_ISOLATED_SERVICE(uid)) targeted_process_length = strlen(process);
for (size_t i = 0; i < config.size; i++) {
if (IS_ISOLATED_SERVICE(uid)) {
size_t config_process_length = strlen(config.configs[i].process);
size_t smallest_process_length = targeted_process_length < config_process_length ? targeted_process_length : config_process_length;
if (strncmp(config.configs[i].process, process, smallest_process_length) != 0) continue;
} else {
if (config.configs[i].uid != uid) continue;
}
if (config.configs[i].uid != uid) continue;
/* INFO: This allow us to copy the information to avoid use-after-free */
bool umount_needed = config.configs[i].umount_needed;
@@ -193,6 +179,29 @@ bool apatch_uid_should_umount(uid_t uid, const char *const process) {
return umount_needed;
}
/* INFO: Isolated services have different UIDs than the main app, and
while libzygisk.so has code to send the UID of the app related
to the isolated service, we add this so that in case it fails,
this should avoid it pass through as Mounted.
*/
if (IS_ISOLATED_SERVICE(uid)) {
size_t targeted_process_length = strlen(process);
for (size_t i = 0; i < config.size; i++) {
size_t config_process_length = strlen(config.configs[i].process);
size_t smallest_process_length = targeted_process_length < config_process_length ? targeted_process_length : config_process_length;
if (strncmp(config.configs[i].process, process, smallest_process_length) != 0) continue;
/* INFO: This allow us to copy the information to avoid use-after-free */
bool umount_needed = config.configs[i].umount_needed;
_apatch_free_package_config(&config);
return umount_needed;
}
}
_apatch_free_package_config(&config);
return false;