Move database logic outside of MagiskSU

This commit is contained in:
topjohnwu 2018-06-13 04:33:32 +08:00
parent 7e2ba41c64
commit b2f719989d
5 changed files with 134 additions and 289 deletions

View File

@ -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,

148
db.c
View File

@ -1,148 +0,0 @@
/*
** Copyright 2017, John Wu (@topjohnwu)
** Copyright 2013, Koushik Dutta (@koush)
**
*/
#include <stdlib.h>
#include <stdio.h>
#include <sqlite3.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#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));
}
}
}

48
su.c
View File

@ -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();

48
su.h
View File

@ -8,57 +8,26 @@
#include <sys/types.h>
#include <sys/stat.h>
#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

View File

@ -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);
}