diff --git a/activity.c b/activity.c index e65ab136a..54c376f5f 100644 --- a/activity.c +++ b/activity.c @@ -45,7 +45,7 @@ static void silent_run(char* const args[]) { } static int setup_user(struct su_context *ctx, char* user) { - switch (ctx->info->multiuser_mode) { + switch (ctx->info->dbs.v[SU_MULTIUSER_MODE]) { case MULTIUSER_MODE_OWNER_ONLY: /* Should already be denied if not owner */ case MULTIUSER_MODE_OWNER_MANAGED: sprintf(user, "%d", 0); @@ -59,7 +59,7 @@ static int setup_user(struct su_context *ctx, char* user) { void app_send_result(struct su_context *ctx, policy_t policy) { char fromUid[16]; - if (ctx->info->multiuser_mode == MULTIUSER_MODE_OWNER_MANAGED) + if (ctx->info->dbs.v[SU_MULTIUSER_MODE] == MULTIUSER_MODE_OWNER_MANAGED) sprintf(fromUid, "%d", ctx->info->uid % 100000); else sprintf(fromUid, "%d", ctx->info->uid); @@ -74,7 +74,7 @@ void app_send_result(struct su_context *ctx, policy_t policy) { int notify = setup_user(ctx, user); char activity[128]; - sprintf(activity, ACTION_RESULT, ctx->info->pkg_name); + sprintf(activity, ACTION_RESULT, ctx->info->str.s[SU_REQUESTER]); // Send notice to manager, enable logging char *result_command[] = { @@ -113,7 +113,7 @@ void app_send_request(struct su_context *ctx) { int notify = setup_user(ctx, user); char activity[128]; - sprintf(activity, ACTION_REQUEST, ctx->info->pkg_name); + sprintf(activity, ACTION_REQUEST, ctx->info->str.s[SU_REQUESTER]); char *request_command[] = { AM_PATH, "start", "-n", @@ -128,7 +128,7 @@ void app_send_request(struct su_context *ctx) { // Send notice to user to tell them root is managed by owner if (notify) { sprintf(user, "%d", notify); - sprintf(activity, ACTION_RESULT, ctx->info->pkg_name); + sprintf(activity, ACTION_RESULT, ctx->info->str.s[SU_REQUESTER]); char *notify_command[] = { AM_PATH, "broadcast", "-n", activity, diff --git a/db.c b/db.c deleted file mode 100644 index cc4d42589..000000000 --- a/db.c +++ /dev/null @@ -1,148 +0,0 @@ -/* -** Copyright 2017, John Wu (@topjohnwu) -** Copyright 2013, Koushik Dutta (@koush) -** -*/ - -#include -#include -#include -#include -#include -#include - -#include "magisk.h" -#include "su.h" - -static int policy_callback(void *v, int argc, char **argv, char **azColName) { - struct su_context *ctx = (struct su_context *) v; - policy_t policy = QUERY; - time_t until = 0; - for (int i = 0; i < argc; i++) { - if (strcmp(azColName[i], "policy") == 0) - policy = atoi(argv[i]); - else if (strcmp(azColName[i], "until") == 0) - until = atol(argv[i]); - } - - if (policy == DENY) - ctx->info->policy = DENY; - else if (policy == ALLOW && (until == 0 || until > time(NULL))) - ctx->info->policy = ALLOW; - - LOGD("su_db: query policy=[%d]\n", ctx->info->policy); - - return 0; -} - -static int settings_callback(void *v, int argc, char **argv, char **azColName) { - struct su_context *ctx = (struct su_context *) v; - int *target, value; - char *entry; - for (int i = 0; i < argc; ++i) { - if (strcmp(azColName[i], "key") == 0) { - if (strcmp(argv[i], ROOT_ACCESS_ENTRY) == 0) - target = &ctx->info->root_access; - else if (strcmp(argv[i], MULTIUSER_MODE_ENTRY) == 0) - target = &ctx->info->multiuser_mode; - else if (strcmp(argv[i], NAMESPACE_MODE_ENTRY) == 0) - target = &ctx->info->mnt_ns; - entry = argv[i]; - } else if (strcmp(azColName[i], "value") == 0) { - value = atoi(argv[i]); - } - } - LOGD("su_db: query %s=[%d]\n", entry, value); - *target = value; - return 0; -} - -static int strings_callback(void *v, int argc, char **argv, char **azColName) { - struct su_context *ctx = (struct su_context *) v; - char *entry, *target, *value; - for (int i = 0; i < argc; ++i) { - if (strcmp(azColName[i], "key") == 0) { - if (strcmp(argv[i], REQUESTER_ENTRY) == 0) - target = ctx->info->pkg_name; - entry = argv[i]; - } else if (strcmp(azColName[i], "value") == 0) { - value = argv[i]; - } - } - LOGD("su_db: query %s=[%s]\n", entry, value); - strcpy(target, value); - return 0; -} - -void database_check(struct su_context *ctx) { - sqlite3 *db = NULL; - int ret; - char buffer[PATH_MAX], *err = NULL; - const char *base = access("/data/user_de", F_OK) == 0 ? "/data/user_de" : "/data/user"; - - // Set default values - ctx->info->root_access = ROOT_ACCESS_APPS_AND_ADB; - ctx->info->multiuser_mode = MULTIUSER_MODE_OWNER_ONLY; - ctx->info->mnt_ns = NAMESPACE_MODE_REQUESTER; - strcpy(ctx->info->pkg_name, "???"); /* bad string so it doesn't exist */ - - // Open database - ret = sqlite3_open_v2(MAGISKDB, &db, SQLITE_OPEN_READONLY, NULL); - if (ret) { - LOGE("sqlite3 open failure: %s\n", sqlite3_errstr(ret)); - sqlite3_close(db); - goto stat_requester; - } - - // Query for strings - sqlite3_exec(db, "SELECT key, value FROM strings", strings_callback, ctx, &err); - if (err) - LOGE("sqlite3_exec: %s\n", err); - err = NULL; - - // Query for settings - sqlite3_exec(db, "SELECT key, value FROM settings", settings_callback, ctx, &err); - if (err) - LOGE("sqlite3_exec: %s\n", err); - err = NULL; - - // Query for policy - int uid = -1; - switch (ctx->info->multiuser_mode) { - case MULTIUSER_MODE_OWNER_ONLY: - if (ctx->info->uid / 100000) { - uid = -1; - ctx->info->policy = DENY; - ctx->notify = 0; - } else { - uid = ctx->info->uid; - } - break; - case MULTIUSER_MODE_OWNER_MANAGED: - uid = ctx->info->uid % 100000; - break; - case MULTIUSER_MODE_USER: - uid = ctx->info->uid; - break; - } - - sprintf(buffer, "SELECT policy, until FROM policies WHERE uid=%d", uid); - sqlite3_exec(db, buffer, policy_callback, ctx, &err); - if (err) - LOGE("sqlite3_exec: %s\n", err); - - sqlite3_close(db); - -stat_requester: - // We prefer the original name - sprintf(buffer, "%s/0/" JAVA_PACKAGE_NAME, base); - if (stat(buffer, &ctx->info->st) == 0) { - strcpy(ctx->info->pkg_name, JAVA_PACKAGE_NAME); - } else { - sprintf(buffer, "%s/0/%s", base, ctx->info->pkg_name); - if (stat(buffer, &ctx->info->st) == -1) { - LOGE("su: cannot find requester"); - memset(&ctx->info->st, 0, sizeof(ctx->info->st)); - } - } -} diff --git a/su.c b/su.c index f11431a50..4841b057e 100644 --- a/su.c +++ b/su.c @@ -104,7 +104,7 @@ void set_identity(unsigned uid) { static __attribute__ ((noreturn)) void allow() { char* argv[] = { NULL, NULL, NULL, NULL }; - if (su_ctx->notify) + if (su_ctx->info->access.notify || su_ctx->info->access.log) app_send_result(su_ctx, ALLOW); if (su_ctx->to.login) @@ -129,7 +129,7 @@ static __attribute__ ((noreturn)) void allow() { } static __attribute__ ((noreturn)) void deny() { - if (su_ctx->notify) + if (su_ctx->info->access.notify || su_ctx->info->access.log) app_send_result(su_ctx, DENY); LOGW("su: request rejected (%u->%u)", su_ctx->info->uid, su_ctx->to.uid); @@ -140,7 +140,7 @@ static __attribute__ ((noreturn)) void deny() { static void socket_cleanup() { if (su_ctx && su_ctx->sock_path[0]) { unlink(su_ctx->sock_path); - su_ctx->sock_path[0] = 0; + su_ctx->sock_path[0] = '\0'; } } @@ -151,8 +151,9 @@ static void cleanup_signal(int sig) { __attribute__ ((noreturn)) void exit2(int status) { // Handle the pipe, or the daemon will get stuck - if (su_ctx->info->policy == QUERY) { - xwrite(su_ctx->pipefd[1], &su_ctx->info->policy, sizeof(su_ctx->info->policy)); + if (su_ctx->pipefd[0] >= 0) { + int i = DENY; + xwrite(su_ctx->pipefd[1], &i, sizeof(i)); close(su_ctx->pipefd[0]); close(su_ctx->pipefd[1]); } @@ -215,7 +216,6 @@ int su_daemon_main(int argc, char **argv) { case 'c': su_ctx->to.command = concat_commands(argc, argv); optind = argc; - su_ctx->notify = 1; break; case 'h': usage(EXIT_SUCCESS); @@ -240,7 +240,7 @@ int su_daemon_main(int argc, char **argv) { // Do nothing, placed here for legacy support :) break; case 'M': - su_ctx->info->mnt_ns = NAMESPACE_MODE_GLOBAL; + su_ctx->info->dbs.v[SU_MNT_NS] = NAMESPACE_MODE_GLOBAL; break; default: /* Bionic getopt_long doesn't terminate its error output by newline */ @@ -265,7 +265,7 @@ int su_daemon_main(int argc, char **argv) { } // Handle namespaces - switch (su_ctx->info->mnt_ns) { + switch (su_ctx->info->dbs.v[SU_MNT_NS]) { case NAMESPACE_MODE_GLOBAL: LOGD("su: use global namespace\n"); break; @@ -285,30 +285,8 @@ int su_daemon_main(int argc, char **argv) { // Change directory to cwd chdir(su_ctx->cwd); - // Check root_access configuration - switch (su_ctx->info->root_access) { - case ROOT_ACCESS_DISABLED: - LOGE("Root access is disabled!\n"); - exit(EXIT_FAILURE); - case ROOT_ACCESS_APPS_ONLY: - if (su_ctx->info->uid == UID_SHELL) { - LOGE("Root access is disabled for ADB!\n"); - exit(EXIT_FAILURE); - } - break; - case ROOT_ACCESS_ADB_ONLY: - if (su_ctx->info->uid != UID_SHELL) { - LOGE("Root access limited to ADB only!\n"); - exit(EXIT_FAILURE); - } - break; - case ROOT_ACCESS_APPS_AND_ADB: - default: - break; - } - // New request or no db exist, notify user for response - if (su_ctx->info->policy == QUERY && su_ctx->info->st.st_uid != 0) { + if (su_ctx->pipefd[0] >= 0) { socket_serv_fd = socket_create_temp(su_ctx->sock_path, sizeof(su_ctx->sock_path)); setup_sighandlers(cleanup_signal); @@ -326,17 +304,17 @@ int su_daemon_main(int argc, char **argv) { socket_cleanup(); if (strcmp(result, "socket:ALLOW") == 0) - su_ctx->info->policy = ALLOW; + su_ctx->info->access.policy = ALLOW; else - su_ctx->info->policy = DENY; + su_ctx->info->access.policy = DENY; // Report the policy to main daemon - xwrite(su_ctx->pipefd[1], &su_ctx->info->policy, sizeof(su_ctx->info->policy)); + xwrite(su_ctx->pipefd[1], &su_ctx->info->access.policy, sizeof(policy_t)); close(su_ctx->pipefd[0]); close(su_ctx->pipefd[1]); } - if (su_ctx->info->policy == ALLOW) + if (su_ctx->info->access.policy == ALLOW) allow(); else deny(); diff --git a/su.h b/su.h index 89b6c93dc..20127ba74 100644 --- a/su.h +++ b/su.h @@ -8,57 +8,26 @@ #include #include +#include "db.h" #include "list.h" #define MAGISKSU_VER_STR xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)" -// DB settings for root access -#define ROOT_ACCESS_ENTRY "root_access" -#define ROOT_ACCESS_DISABLED 0 -#define ROOT_ACCESS_APPS_ONLY 1 -#define ROOT_ACCESS_ADB_ONLY 2 -#define ROOT_ACCESS_APPS_AND_ADB 3 - -// DB settings for multiuser -#define MULTIUSER_MODE_ENTRY "multiuser_mode" -#define MULTIUSER_MODE_OWNER_ONLY 0 -#define MULTIUSER_MODE_OWNER_MANAGED 1 -#define MULTIUSER_MODE_USER 2 - -// DB settings for namespace seperation -#define NAMESPACE_MODE_ENTRY "mnt_ns" -#define NAMESPACE_MODE_GLOBAL 0 -#define NAMESPACE_MODE_REQUESTER 1 -#define NAMESPACE_MODE_ISOLATE 2 - -// DB entry for requester -#define REQUESTER_ENTRY "requester" - -// DO NOT CHANGE LINE BELOW, java package name will always be the same -#define JAVA_PACKAGE_NAME "com.topjohnwu.magisk" // This is used if wrapping the fragment classes and activities // with classes in another package. #define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser" #define DEFAULT_SHELL "/system/bin/sh" -typedef enum { - QUERY = 0, - DENY = 1, - ALLOW = 2, -} policy_t; - struct su_info { - unsigned uid; /* Key to find su_info */ + unsigned uid; /* Unique key to find su_info */ pthread_mutex_t lock; /* Internal lock */ - int count; /* Just a count for debugging purpose */ + int count; /* Just a count for debugging purpose */ /* These values should be guarded with internal lock */ - policy_t policy; - int multiuser_mode; - int root_access; - int mnt_ns; - char pkg_name[PATH_MAX]; + struct db_settings dbs; + struct db_strings str; + struct su_access access; struct stat st; /* These should be guarded with global list lock */ @@ -79,7 +48,6 @@ struct su_context { struct su_info *info; struct su_request to; pid_t pid; - int notify; char cwd[PATH_MAX]; char sock_path[PATH_MAX]; int pipefd[2]; @@ -105,8 +73,4 @@ void socket_receive_result(int fd, char *result, ssize_t result_len); void app_send_result(struct su_context *ctx, policy_t policy); void app_send_request(struct su_context *ctx); -// db.c - -void database_check(struct su_context *ctx); - #endif diff --git a/su_daemon.c b/su_daemon.c index 4955df71a..ea0704c32 100644 --- a/su_daemon.c +++ b/su_daemon.c @@ -31,7 +31,7 @@ #define LOCK_LIST() pthread_mutex_lock(&list_lock) #define LOCK_UID() pthread_mutex_lock(&info->lock) #define UNLOCK_LIST() pthread_mutex_unlock(&list_lock) -#define UNLOCK_UID() pthread_mutex_unlock(&info->lock) +#define UNLOCK_UID() pthread_mutex_unlock(&ctx.info->lock) static struct list_head active_list, waiting_list; static pthread_t su_collector = 0; @@ -88,11 +88,41 @@ static void *collector(void *args) { } } -void su_daemon_receiver(int client, struct ucred *credential) { - LOGD("su: request from client: %d\n", client); +static void database_check(struct su_info *info) { + int uid = info->uid; + sqlite3 *db = get_magiskdb(); + if (db) { + get_db_settings(db, -1, &info->dbs); + get_db_strings(db, -1, &info->str); + // Check multiuser settings + switch (info->dbs.v[SU_MULTIUSER_MODE]) { + case MULTIUSER_MODE_OWNER_ONLY: + if (info->uid / 100000) { + uid = -1; + info->access = NO_SU_ACCESS; + } + break; + case MULTIUSER_MODE_OWNER_MANAGED: + uid = info->uid % 100000; + break; + case MULTIUSER_MODE_USER: + default: + break; + } + + if (uid > 0) + get_uid_policy(db, uid, &info->access); + sqlite3_close(db); + } + + // We need to check our manager + if (info->access.log || info->access.notify) + validate_manager(info->str.s[SU_REQUESTER], uid / 100000, &info->st); +} + +static struct su_info *get_su_info(unsigned uid) { struct su_info *info = NULL, *node; - int new_request = 0; LOCK_LIST(); @@ -104,7 +134,7 @@ void su_daemon_receiver(int client, struct ucred *credential) { // Search for existing in the active list list_for_each(node, &active_list, struct su_info, pos) { - if (node->uid == credential->uid) { + if (node->uid == uid) { info = node; break; } @@ -112,10 +142,11 @@ void su_daemon_receiver(int client, struct ucred *credential) { // If no exist, create a new request if (info == NULL) { - new_request = 1; info = malloc(sizeof(*info)); - info->uid = credential->uid; - info->policy = QUERY; + info->uid = uid; + info->dbs = DEFAULT_DB_SETTINGS; + info->access = DEFAULT_SU_ACCESS; + INIT_DB_STRINGS(&info->str); info->ref = 0; info->count = 0; pthread_mutex_init(&info->lock, NULL); @@ -128,65 +159,85 @@ void su_daemon_receiver(int client, struct ucred *credential) { LOGD("su: request from uid=[%d] (#%d)\n", info->uid, ++info->count); - // Default values - struct su_context ctx = { - .info = info, - .to = { - .uid = UID_ROOT, - .login = 0, - .keepenv = 0, - .shell = DEFAULT_SHELL, - .command = NULL, - }, - .pid = credential->pid, - .notify = new_request, - }; - // Lock before the policy is determined LOCK_UID(); - // Not cached, do the checks - if (info->policy == QUERY) { - // Get data from database - database_check(&ctx); + if (info->access.policy == QUERY) { + // Not cached, get data from database + database_check(info); - // Check requester - if (info->policy == QUERY) { - if (info->st.st_gid != info->st.st_uid) { - LOGE("Bad uid/gid %d/%d for Superuser Requestor", info->st.st_gid, info->st.st_uid); - info->policy = DENY; - ctx.notify = 0; - } else if ((info->uid % 100000) == (info->st.st_uid % 100000)) { - info->policy = ALLOW; - info->root_access = ROOT_ACCESS_APPS_AND_ADB; - ctx.notify = 0; - } + // Check su access settings + switch (info->dbs.v[ROOT_ACCESS]) { + case ROOT_ACCESS_DISABLED: + LOGE("Root access is disabled!\n"); + info->access = NO_SU_ACCESS; + break; + case ROOT_ACCESS_ADB_ONLY: + if (info->uid != UID_SHELL) { + LOGE("Root access is disabled for ADB!\n"); + info->access = NO_SU_ACCESS; + } + break; + case ROOT_ACCESS_APPS_ONLY: + if (info->uid == UID_SHELL) { + LOGE("Root access limited to ADB only!\n"); + info->access = NO_SU_ACCESS; + } + break; + case ROOT_ACCESS_APPS_AND_ADB: + default: + break; } - // always allow if it's root - if (info->uid == UID_ROOT) { - info->policy = ALLOW; - info->root_access = ROOT_ACCESS_APPS_AND_ADB; - ctx.notify = 0; - } + // If it's the manager, allow it silently + if ((info->uid % 100000) == (info->st.st_uid % 100000)) + info->access = SILENT_SU_ACCESS; - // If still not determined, open a pipe and wait for results - if (info->policy == QUERY) - xpipe2(ctx.pipefd, O_CLOEXEC); + // Allow if it's root + if (info->uid == UID_ROOT) + info->access = SILENT_SU_ACCESS; + + // If still not determined, check if manager exists + if (info->access.policy == QUERY && info->str.s[SU_REQUESTER][0] == '\0') + info->access = NO_SU_ACCESS; } + return info; +} - // Fork a new process, the child process will need to setsid, - // open a pseudo-terminal if needed, and will eventually run exec - // The parent process will wait for the result and - // send the return code back to our client +void su_daemon_receiver(int client, struct ucred *credential) { + LOGD("su: request from client: %d\n", client); + + // Default values + struct su_context ctx = { + .info = get_su_info(credential->uid), + .to = { + .uid = UID_ROOT, + .login = 0, + .keepenv = 0, + .shell = DEFAULT_SHELL, + .command = NULL, + }, + .pid = credential->pid, + .pipefd = { -1, -1 } + }; + + // If still not determined, open a pipe and wait for results + if (ctx.info->access.policy == QUERY) + xpipe2(ctx.pipefd, O_CLOEXEC); + + /* Fork a new process, the child process will need to setsid, + * open a pseudo-terminal if needed, and will eventually run exec + * The parent process will wait for the result and + * send the return code back to our client + */ int child = fork(); if (child < 0) PLOGE("fork"); if (child) { // Wait for results - if (info->policy == QUERY) { - xxread(ctx.pipefd[0], &info->policy, sizeof(info->policy)); + if (ctx.pipefd[0] >= 0) { + xxread(ctx.pipefd[0], &ctx.info->access.policy, sizeof(policy_t)); close(ctx.pipefd[0]); close(ctx.pipefd[1]); } @@ -223,7 +274,7 @@ void su_daemon_receiver(int client, struct ucred *credential) { // Decrement reference count LOCK_LIST(); - --info->ref; + --ctx.info->ref; UNLOCK_LIST(); return; @@ -276,7 +327,7 @@ void su_daemon_receiver(int client, struct ucred *credential) { char *pts_slave = read_string(client); LOGD("su: pts_slave=[%s]\n", pts_slave); - // The the FDs for each of the streams + // The FDs for each of the streams int infd = recv_fd(client); int outfd = recv_fd(client); int errfd = recv_fd(client); @@ -286,12 +337,12 @@ void su_daemon_receiver(int client, struct ucred *credential) { close(client); if (pts_slave[0]) { - //Check pts_slave file is owned by daemon_from_uid - struct stat stbuf; - xstat(pts_slave, &stbuf); + // Check pts_slave file is owned by daemon_from_uid + struct stat st; + xstat(pts_slave, &st); - //If caller is not root, ensure the owner of pts_slave is the caller - if(stbuf.st_uid != info->uid && info->uid != 0) { + // If caller is not root, ensure the owner of pts_slave is the caller + if(st.st_uid != credential->uid && credential->uid != 0) { LOGE("su: Wrong permission of pts_slave"); exit2(1); }