Add multiuser support

This commit is contained in:
topjohnwu 2017-05-27 02:40:12 +08:00
parent 371db886b4
commit 94c2fc80d2
3 changed files with 87 additions and 172 deletions

View File

@ -26,15 +26,8 @@
static void silent_run(char* const args[]) { static void silent_run(char* const args[]) {
set_identity(0); set_identity(0);
pid_t pid; if (fork())
pid = fork();
/* Parent */
if (pid < 0) {
PLOGE("fork");
}
else if (pid > 0) {
return; return;
}
int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC); int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
dup2(zero, 0); dup2(zero, 0);
int null = open("/dev/null", O_WRONLY | O_CLOEXEC); int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
@ -46,79 +39,37 @@ static void silent_run(char* const args[]) {
_exit(EXIT_FAILURE); _exit(EXIT_FAILURE);
} }
static int get_owner_login_user_args(struct su_context *ctx, char* user, int user_len) { static void setup_user(struct su_context *ctx, char* user) {
int needs_owner_login_prompt = 0; switch (ctx->user.multiuser_mode) {
case MULTIUSER_MODE_OWNER_ONLY: /* Should already be denied if not owner */
if (ctx->user.multiuser_mode == MULTIUSER_MODE_OWNER_MANAGED) { case MULTIUSER_MODE_OWNER_MANAGED:
if (0 != ctx->user.android_user_id) { sprintf(user, "%d", 0);
needs_owner_login_prompt = 1; break;
} case MULTIUSER_MODE_USER:
snprintf(user, user_len, "0"); sprintf(user, "%d", ctx->user.android_user_id);
break;
} }
else if (ctx->user.multiuser_mode == MULTIUSER_MODE_USER) {
snprintf(user, user_len, "%d", ctx->user.android_user_id);
}
else if (ctx->user.multiuser_mode == MULTIUSER_MODE_NONE) {
user[0] = '\0';
}
else {
snprintf(user, user_len, "0");
}
return needs_owner_login_prompt;
} }
void app_send_result(struct su_context *ctx, policy_t policy) { void app_send_result(struct su_context *ctx, policy_t policy) {
// char binary_version[256]; char fromUid[16];
// sprintf(binary_version, "%d", VERSION_CODE); sprintf(fromUid, "%d", ctx->from.uid);
char uid[256]; char toUid[16];
sprintf(uid, "%d", ctx->from.uid);
char toUid[256];
sprintf(toUid, "%d", ctx->to.uid); sprintf(toUid, "%d", ctx->to.uid);
char pid[256]; char pid[16];
sprintf(pid, "%d", ctx->from.pid); sprintf(pid, "%d", ctx->from.pid);
char user[64]; char user[16];
get_owner_login_user_args(ctx, user, sizeof(user)); setup_user(ctx, user);
if (0 != ctx->user.android_user_id) {
char android_user_id[256];
sprintf(android_user_id, "%d", ctx->user.android_user_id);
char *user_result_command[] = {
AM_PATH,
ACTION_RESULT,
"--ei",
"from.uid",
uid,
"--ei",
"to.uid",
toUid,
"--ei",
"pid",
pid,
"--es",
"command",
get_command(&ctx->to),
"--es",
"action",
policy == ALLOW ? "allow" : "deny",
user[0] ? "--user" : NULL,
android_user_id,
NULL
};
silent_run(user_result_command);
}
char *result_command[] = { char *result_command[] = {
AM_PATH, AM_PATH,
ACTION_RESULT, ACTION_RESULT,
"--ei", "--ei",
"from.uid", "from.uid",
uid, fromUid,
"--ei", "--ei",
"to.uid", "to.uid",
toUid, toUid,
@ -131,7 +82,7 @@ void app_send_result(struct su_context *ctx, policy_t policy) {
"--es", "--es",
"action", "action",
policy == ALLOW ? "allow" : "deny", policy == ALLOW ? "allow" : "deny",
user[0] ? "--user" : NULL, "--user",
user, user,
NULL NULL
}; };
@ -139,34 +90,8 @@ void app_send_result(struct su_context *ctx, policy_t policy) {
} }
void app_send_request(struct su_context *ctx) { void app_send_request(struct su_context *ctx) {
// if su is operating in MULTIUSER_MODEL_OWNER,
// and the user requestor is not the owner,
// the owner needs to be notified of the request.
// so there will be two activities shown.
char user[64]; char user[64];
int needs_owner_login_prompt = get_owner_login_user_args(ctx, user, sizeof(user)); setup_user(ctx, user);
if (needs_owner_login_prompt) {
char uid[256];
sprintf(uid, "%d", ctx->from.uid);
char android_user_id[256];
sprintf(android_user_id, "%d", ctx->user.android_user_id);
// in multiuser mode, the owner gets the su prompt
char *notify_command[] = {
AM_PATH,
ACTION_NOTIFY,
"--ei",
"caller_uid",
uid,
"--user",
android_user_id,
NULL
};
silent_run(notify_command);
}
char *request_command[] = { char *request_command[] = {
AM_PATH, AM_PATH,
@ -174,7 +99,7 @@ void app_send_request(struct su_context *ctx) {
"--es", "--es",
"socket", "socket",
ctx->sock_path, ctx->sock_path,
user[0] ? "--user" : NULL, "--user",
user, user,
NULL NULL
}; };

53
su.c
View File

@ -62,8 +62,13 @@ static char *concat_commands(int argc, char *argv[]) {
} }
static int get_multiuser_mode() { static int get_multiuser_mode() {
// TODO: Multiuser support char *prop = getprop(MULTIUSER_MODE_PROP);
return MULTIUSER_MODE_NONE; if (prop) {
int ret = atoi(prop);
free(prop);
return ret;
}
return MULTIUSER_MODE_OWNER_ONLY;
} }
static void populate_environment(const struct su_context *ctx) { static void populate_environment(const struct su_context *ctx) {
@ -212,8 +217,8 @@ int su_daemon_main(int argc, char **argv) {
.user = { .user = {
.android_user_id = 0, .android_user_id = 0,
.multiuser_mode = get_multiuser_mode(), .multiuser_mode = get_multiuser_mode(),
.database_path = APPLICATION_DATA_PATH REQUESTOR_DATABASE_PATH, .database_path = APP_DATA_PATH REQUESTOR_DATABASE_PATH,
.base_path = APPLICATION_DATA_PATH REQUESTOR .base_path = APP_DATA_PATH REQUESTOR
}, },
.umask = 022, .umask = 022,
}; };
@ -262,16 +267,13 @@ int su_daemon_main(int argc, char **argv) {
case 'u': case 'u':
switch (ctx.user.multiuser_mode) { switch (ctx.user.multiuser_mode) {
case MULTIUSER_MODE_USER: case MULTIUSER_MODE_USER:
printf("%s\n", MULTIUSER_VALUE_USER); printf("Owner only: Only owner has root access\n");
break; break;
case MULTIUSER_MODE_OWNER_MANAGED: case MULTIUSER_MODE_OWNER_MANAGED:
printf("%s\n", MULTIUSER_VALUE_OWNER_MANAGED); printf("Owner managed: Only owner can manage root access and receive request prompts\n");
break; break;
case MULTIUSER_MODE_OWNER_ONLY: case MULTIUSER_MODE_OWNER_ONLY:
printf("%s\n", MULTIUSER_VALUE_OWNER_ONLY); printf("User independent: The user has its own separate root rules\n");
break;
case MULTIUSER_MODE_NONE:
printf("%s\n", MULTIUSER_VALUE_NONE);
break; break;
} }
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
@ -318,15 +320,20 @@ int su_daemon_main(int argc, char **argv) {
// It's in multiuser mode // It's in multiuser mode
if (ctx.from.uid > 99999) { if (ctx.from.uid > 99999) {
ctx.user.android_user_id = ctx.from.uid / 100000; ctx.user.android_user_id = ctx.from.uid / 100000;
if (ctx.user.multiuser_mode == MULTIUSER_MODE_USER) { ctx.from.uid %= 100000;
switch (ctx.user.multiuser_mode) {
case MULTIUSER_MODE_OWNER_ONLY:
deny();
case MULTIUSER_MODE_USER:
snprintf(ctx.user.database_path, PATH_MAX, "%s/%d/%s", snprintf(ctx.user.database_path, PATH_MAX, "%s/%d/%s",
USER_DATA_PATH, ctx.user.android_user_id, REQUESTOR_DATABASE_PATH); USER_DATA_PATH, ctx.user.android_user_id, REQUESTOR_DATABASE_PATH);
snprintf(ctx.user.base_path, PATH_MAX, "%s/%d/%s", snprintf(ctx.user.base_path, PATH_MAX, "%s/%d/%s",
USER_DATA_PATH, ctx.user.android_user_id, REQUESTOR); USER_DATA_PATH, ctx.user.android_user_id, REQUESTOR);
break;
} }
} }
// verify superuser is installed // verify if Magisk Manager is installed
xstat(ctx.user.base_path, &st); xstat(ctx.user.base_path, &st);
// odd perms on superuser data dir // odd perms on superuser data dir
@ -336,12 +343,6 @@ int su_daemon_main(int argc, char **argv) {
deny(); deny();
} }
// always allow if this is the superuser uid
// superuser needs to be able to reenable itself when disabled...
if (ctx.from.uid == st.st_uid) {
allow();
}
// Check property of root configuration // Check property of root configuration
char *root_prop = getprop(ROOT_ACCESS_PROP); char *root_prop = getprop(ROOT_ACCESS_PROP);
if (root_prop) { if (root_prop) {
@ -361,21 +362,23 @@ int su_daemon_main(int argc, char **argv) {
default: default:
break; break;
} }
free(root_prop);
} else { } else {
exit(EXIT_FAILURE); // Not initialized yet, set the prop to allow everything by default
setprop(ROOT_ACCESS_PROP, xstr(ROOT_ACCESS_APPS_AND_ADB));
}
// always allow if this is the superuser uid
// superuser needs to be able to reenable itself when disabled...
if (ctx.from.uid == st.st_uid) {
allow();
} }
free(root_prop);
// Allow root to start root // Allow root to start root
if (ctx.from.uid == UID_ROOT) { if (ctx.from.uid == UID_ROOT) {
allow(); allow();
} }
// deny if this is a non owner request and owner mode only
if (ctx.user.multiuser_mode == MULTIUSER_MODE_OWNER_ONLY && ctx.user.android_user_id != 0) {
deny();
}
mkdir(REQUESTOR_CACHE_PATH, 0770); mkdir(REQUESTOR_CACHE_PATH, 0770);
if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) { if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) {
PLOGE("chown (%s, %u, %u)", REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid); PLOGE("chown (%s, %u, %u)", REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid);

91
su.h
View File

@ -10,16 +10,22 @@
#define MAGISKSU_VER_STR xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)" #define MAGISKSU_VER_STR xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)"
// Property check for root access // Property check for root access
#define ROOT_ACCESS_PROP "persist.sys.root_access" #define ROOT_ACCESS_PROP "persist.magisk.root"
#define ROOT_ACCESS_DISABLED 0 #define ROOT_ACCESS_DISABLED 0
#define ROOT_ACCESS_APPS_ONLY 1 #define ROOT_ACCESS_APPS_ONLY 1
#define ROOT_ACCESS_ADB_ONLY 2 #define ROOT_ACCESS_ADB_ONLY 2
#define ROOT_ACCESS_APPS_AND_ADB 3 #define ROOT_ACCESS_APPS_AND_ADB 3
// Property for multiuser
#define MULTIUSER_MODE_PROP "persist.magisk.multiuser"
#define MULTIUSER_MODE_OWNER_ONLY 0
#define MULTIUSER_MODE_OWNER_MANAGED 1
#define MULTIUSER_MODE_USER 2
// DO NOT CHANGE LINE BELOW, java package name will always be the same // DO NOT CHANGE LINE BELOW, java package name will always be the same
#define JAVA_PACKAGE_NAME "com.topjohnwu.magisk" #define JAVA_PACKAGE_NAME "com.topjohnwu.magisk"
#define APPLICATION_DATA_PATH "/data/data/" #define APP_DATA_PATH "/data/data/"
#define USER_DATA_PATH "/data/user" #define USER_DATA_PATH "/data/user"
// If --rename-manifest-package is used in AAPT, this // If --rename-manifest-package is used in AAPT, this
@ -29,77 +35,58 @@
// This is used if wrapping the fragment classes and activities // This is used if wrapping the fragment classes and activities
// with classes in another package. // with classes in another package.
#define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser" #define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser"
#define REQUESTOR_FILES_PATH APPLICATION_DATA_PATH REQUESTOR "/files"
#define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR #define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR
// there's no guarantee that the db or files are actually created named as such by // there's no guarantee that the db or files are actually created named as such by
// SQLiteOpenHelper, etc. Though that is the behavior as of current. // SQLiteOpenHelper, etc. Though that is the behavior as of current.
// it is up to the Android application to symlink as appropriate. // it is up to the Android application to symlink as appropriate.
#define REQUESTOR_DATABASE_PATH REQUESTOR "/databases/su.db" #define REQUESTOR_DATABASE_PATH REQUESTOR "/databases/su.db"
#define REQUESTOR_MULTIUSER_MODE REQUESTOR_FILES_PATH "/multiuser_mode"
#define DEFAULT_SHELL "/system/bin/sh" #define DEFAULT_SHELL "/system/bin/sh"
struct su_initiator { struct su_initiator {
pid_t pid; pid_t pid;
unsigned uid; unsigned uid;
}; };
struct su_request { struct su_request {
unsigned uid; unsigned uid;
int login; int login;
int keepenv; int keepenv;
char *shell; char *shell;
char *command; char *command;
char **argv; char **argv;
int argc; int argc;
}; };
struct su_user_info { struct su_user_info {
// the user in android userspace (multiuser) // the user in android userspace (multiuser)
// that invoked this action. // that invoked this action.
unsigned android_user_id; unsigned android_user_id;
// how su behaves with multiuser. see enum below. // how su behaves with multiuser. see enum below.
int multiuser_mode; int multiuser_mode;
// path to superuser directory. this is populated according // path to superuser directory. this is populated according
// to the multiuser mode. // to the multiuser mode.
// this is used to check uid/gid for protecting socket. // this is used to check uid/gid for protecting socket.
// this is used instead of database, as it is more likely // this is used instead of database, as it is more likely
// to exist. db will not exist if su has never launched. // to exist. db will not exist if su has never launched.
char base_path[PATH_MAX]; char base_path[PATH_MAX];
// path to su database. this is populated according // path to su database. this is populated according
// to the multiuser mode. // to the multiuser mode.
char database_path[PATH_MAX]; char database_path[PATH_MAX];
}; };
struct su_context { struct su_context {
struct su_initiator from; struct su_initiator from;
struct su_request to; struct su_request to;
struct su_user_info user; struct su_user_info user;
mode_t umask; mode_t umask;
char sock_path[PATH_MAX]; char sock_path[PATH_MAX];
}; };
// multiuser su behavior
typedef enum { typedef enum {
// only owner can su INTERACTIVE = 0,
MULTIUSER_MODE_OWNER_ONLY = 0, DENY = 1,
// owner gets a su prompt ALLOW = 2,
MULTIUSER_MODE_OWNER_MANAGED = 1,
// user gets a su prompt
MULTIUSER_MODE_USER = 2,
MULTIUSER_MODE_NONE = 3,
} multiuser_mode_t;
#define MULTIUSER_VALUE_OWNER_ONLY "owner"
#define MULTIUSER_VALUE_OWNER_MANAGED "managed"
#define MULTIUSER_VALUE_USER "user"
#define MULTIUSER_VALUE_NONE "none"
typedef enum {
INTERACTIVE = 0,
DENY = 1,
ALLOW = 2,
} policy_t; } policy_t;
extern struct ucred su_credentials; extern struct ucred su_credentials;