Commit d119dd9a authored by topjohnwu's avatar topjohnwu

Rewrite su daemon and client

parent 09ef19f7
...@@ -67,7 +67,7 @@ static void *request_handler(void *args) { ...@@ -67,7 +67,7 @@ static void *request_handler(void *args) {
ls_hide_list(client); ls_hide_list(client);
break; break;
case SUPERUSER: case SUPERUSER:
su_daemon_receiver(client, &credential); su_daemon_handler(client, &credential);
break; break;
case CHECK_VERSION: case CHECK_VERSION:
write_string(client, xstr(MAGISK_VERSION) ":MAGISK"); write_string(client, xstr(MAGISK_VERSION) ":MAGISK");
......
...@@ -39,7 +39,7 @@ int create_rand_socket(struct sockaddr_un *sun) { ...@@ -39,7 +39,7 @@ int create_rand_socket(struct sockaddr_un *sun) {
return fd; return fd;
} }
int socket_accept(int serv_fd, int timeout) { int socket_accept(int sockfd, int timeout) {
struct timeval tv; struct timeval tv;
fd_set fds; fd_set fds;
int rc; int rc;
...@@ -47,16 +47,16 @@ int socket_accept(int serv_fd, int timeout) { ...@@ -47,16 +47,16 @@ int socket_accept(int serv_fd, int timeout) {
tv.tv_sec = timeout; tv.tv_sec = timeout;
tv.tv_usec = 0; tv.tv_usec = 0;
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(serv_fd, &fds); FD_SET(sockfd, &fds);
do { do {
rc = select(serv_fd + 1, &fds, NULL, NULL, &tv); rc = select(sockfd + 1, &fds, NULL, NULL, &tv);
} while (rc < 0 && errno == EINTR); } while (rc < 0 && errno == EINTR);
if (rc < 1) { if (rc < 1) {
PLOGE("select"); PLOGE("select");
exit(-1); return -1;
} }
return xaccept4(serv_fd, NULL, NULL, SOCK_CLOEXEC); return xaccept4(sockfd, NULL, NULL, SOCK_CLOEXEC);
} }
/* /*
...@@ -164,13 +164,15 @@ void send_fd(int sockfd, int fd) { ...@@ -164,13 +164,15 @@ void send_fd(int sockfd, int fd) {
int read_int(int fd) { int read_int(int fd) {
int val; int val;
xxread(fd, &val, sizeof(int)); if (xxread(fd, &val, sizeof(int)) != sizeof(int))
return -1;
return val; return val;
} }
int read_int_be(int fd) { int read_int_be(int fd) {
uint32_t val; uint32_t val;
xxread(fd, &val, sizeof(val)); if (xxread(fd, &val, sizeof(val)) != sizeof(int))
return -1;
return ntohl(val); return ntohl(val);
} }
......
...@@ -61,7 +61,7 @@ int check_and_start_logger(); ...@@ -61,7 +61,7 @@ int check_and_start_logger();
socklen_t setup_sockaddr(struct sockaddr_un *sun, daemon_t d); socklen_t setup_sockaddr(struct sockaddr_un *sun, daemon_t d);
int create_rand_socket(struct sockaddr_un *sun); int create_rand_socket(struct sockaddr_un *sun);
int socket_accept(int serv_fd, int timeout); int socket_accept(int sockfd, int timeout);
int recv_fd(int sockfd); int recv_fd(int sockfd);
void send_fd(int sockfd, int fd); void send_fd(int sockfd, int fd);
int read_int(int fd); int read_int(int fd);
...@@ -98,6 +98,6 @@ void ls_hide_list(int client); ...@@ -98,6 +98,6 @@ void ls_hide_list(int client);
* Superuser * * Superuser *
*************/ *************/
void su_daemon_receiver(int client, struct ucred *credential); void su_daemon_handler(int client, struct ucred *credential);
#endif #endif
...@@ -14,86 +14,86 @@ ...@@ -14,86 +14,86 @@
#include "magisk.h" #include "magisk.h"
#include "daemon.h" #include "daemon.h"
#include "utils.h"
#include "su.h" #include "su.h"
#define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am" #define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am"
static char *get_command(const struct su_request *to) { static char *get_command(const struct su_request *to) {
if (to->command) if (to->command[0])
return to->command; return to->command;
if (to->shell) if (to->shell[0])
return to->shell; return to->shell;
return DEFAULT_SHELL; return DEFAULT_SHELL;
} }
static void silent_run(char * const args[]) { static void silent_run(char * const args[]) {
set_identity(0); if (fork_dont_care())
if (fork())
return; return;
int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC); int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
dup2(zero, 0); xdup2(zero, 0);
int null = open("/dev/null", O_WRONLY | O_CLOEXEC); int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
dup2(null, 1); xdup2(null, 1);
dup2(null, 2); xdup2(null, 2);
setenv("CLASSPATH", "/system/framework/am.jar", 1); setenv("CLASSPATH", "/system/framework/am.jar", 1);
execv(args[0], args); execv(args[0], args);
PLOGE("exec am"); PLOGE("exec am");
_exit(EXIT_FAILURE); _exit(EXIT_FAILURE);
} }
static void setup_user(char *user) { static void setup_user(char *user, struct su_info *info) {
switch (DB_SET(su_ctx->info, SU_MULTIUSER_MODE)) { switch (DB_SET(info, SU_MULTIUSER_MODE)) {
case MULTIUSER_MODE_OWNER_ONLY: case MULTIUSER_MODE_OWNER_ONLY:
case MULTIUSER_MODE_OWNER_MANAGED: case MULTIUSER_MODE_OWNER_MANAGED:
sprintf(user, "%d", 0); sprintf(user, "%d", 0);
break; break;
case MULTIUSER_MODE_USER: case MULTIUSER_MODE_USER:
sprintf(user, "%d", su_ctx->info->uid / 100000); sprintf(user, "%d", info->uid / 100000);
break; break;
} }
} }
void app_log() { void app_log(struct su_context *ctx) {
char user[8]; char user[8];
setup_user(user); setup_user(user, ctx->info);
char fromUid[8]; char fromUid[8];
sprintf(fromUid, "%d", sprintf(fromUid, "%d",
DB_SET(su_ctx->info, SU_MULTIUSER_MODE) == MULTIUSER_MODE_OWNER_MANAGED ? DB_SET(ctx->info, SU_MULTIUSER_MODE) == MULTIUSER_MODE_OWNER_MANAGED ?
su_ctx->info->uid % 100000 : su_ctx->info->uid); ctx->info->uid % 100000 : ctx->info->uid);
char toUid[8]; char toUid[8];
sprintf(toUid, "%d", su_ctx->to.uid); sprintf(toUid, "%d", ctx->req.uid);
char pid[8]; char pid[8];
sprintf(pid, "%d", su_ctx->pid); sprintf(pid, "%d", ctx->pid);
char policy[2]; char policy[2];
sprintf(policy, "%d", su_ctx->info->access.policy); sprintf(policy, "%d", ctx->info->access.policy);
char *cmd[] = { char *cmd[] = {
AM_PATH, "broadcast", AM_PATH, "broadcast",
"-a", "android.intent.action.BOOT_COMPLETED", "-a", "android.intent.action.BOOT_COMPLETED",
"-p", DB_STR(su_ctx->info, SU_MANAGER), "-p", DB_STR(ctx->info, SU_MANAGER),
"--user", user, "--user", user,
"--es", "action", "log", "--es", "action", "log",
"--ei", "from.uid", fromUid, "--ei", "from.uid", fromUid,
"--ei", "to.uid", toUid, "--ei", "to.uid", toUid,
"--ei", "pid", pid, "--ei", "pid", pid,
"--ei", "policy", policy, "--ei", "policy", policy,
"--es", "command", get_command(&su_ctx->to), "--es", "command", get_command(&ctx->req),
NULL NULL
}; };
silent_run(cmd); silent_run(cmd);
} }
void app_connect(const char *socket) { void app_connect(const char *socket, struct su_info *info) {
char user[8]; char user[8];
setup_user(user); setup_user(user, info);
char *cmd[] = { char *cmd[] = {
AM_PATH, "broadcast", AM_PATH, "broadcast",
"-a", "android.intent.action.BOOT_COMPLETED", "-a", "android.intent.action.BOOT_COMPLETED",
"-p", DB_STR(su_ctx->info, SU_MANAGER), "-p", DB_STR(info, SU_MANAGER),
"--user", user, "--user", user,
"--es", "action", "request", "--es", "action", "request",
"--es", "socket", (char *) socket, "--es", "socket", (char *) socket,
...@@ -102,8 +102,8 @@ void app_connect(const char *socket) { ...@@ -102,8 +102,8 @@ void app_connect(const char *socket) {
silent_run(cmd); silent_run(cmd);
} }
void socket_send_request(int fd) { void socket_send_request(int fd, struct su_info *info) {
write_key_token(fd, "uid", su_ctx->info->uid); write_key_token(fd, "uid", info->uid);
write_string_be(fd, "eof"); write_string_be(fd, "eof");
} }
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
#include <errno.h> #include <errno.h>
#include <signal.h> #include <signal.h>
#include <sched.h> #include <sched.h>
#include <libgen.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
...@@ -27,11 +26,9 @@ ...@@ -27,11 +26,9 @@
#include "daemon.h" #include "daemon.h"
#include "utils.h" #include "utils.h"
#include "su.h" #include "su.h"
#include "selinux.h" #include "pts.h"
#include "flags.h" #include "flags.h"
struct su_context *su_ctx;
static void usage(int status) { static void usage(int status) {
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr; FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
...@@ -49,7 +46,7 @@ static void usage(int status) { ...@@ -49,7 +46,7 @@ static void usage(int status) {
" -V display version code and exit\n" " -V display version code and exit\n"
" -mm, -M,\n" " -mm, -M,\n"
" --mount-master force run in the global mount namespace\n"); " --mount-master force run in the global mount namespace\n");
exit2(status); exit(status);
} }
static char *concat_commands(int argc, char *argv[]) { static char *concat_commands(int argc, char *argv[]) {
...@@ -64,142 +61,103 @@ static char *concat_commands(int argc, char *argv[]) { ...@@ -64,142 +61,103 @@ static char *concat_commands(int argc, char *argv[]) {
return strdup(command); return strdup(command);
} }
static void populate_environment() { static void sighandler(int sig) {
struct passwd *pw; restore_stdin();
if (su_ctx->to.keepenv) // Assume we'll only be called before death
return; // See note before sigaction() in set_stdin_raw()
//
pw = getpwuid(su_ctx->to.uid); // Now, close all standard I/O to cause the pumps
if (pw) { // to exit so we can continue and retrieve the exit
setenv("HOME", pw->pw_dir, 1); // code
if (su_ctx->to.shell) close(STDIN_FILENO);
setenv("SHELL", su_ctx->to.shell, 1); close(STDOUT_FILENO);
else close(STDERR_FILENO);
setenv("SHELL", DEFAULT_SHELL, 1);
if (su_ctx->to.login || su_ctx->to.uid) { // Put back all the default handlers
setenv("USER", pw->pw_name, 1); struct sigaction act;
setenv("LOGNAME", pw->pw_name, 1);
} memset(&act, 0, sizeof(act));
act.sa_handler = SIG_DFL;
for (int i = 0; quit_signals[i]; ++i) {
sigaction(quit_signals[i], &act, NULL);
} }
} }
void set_identity(unsigned uid) { /*
/* * Connect daemon, send argc, argv, cwd, pts slave
* Set effective uid back to root, otherwise setres[ug]id will fail */
* if uid isn't root. int su_client_main(int argc, char *argv[]) {
*/ int c;
if (seteuid(0)) { struct option long_opts[] = {
PLOGE("seteuid (root)"); { "command", required_argument, NULL, 'c' },
} { "help", no_argument, NULL, 'h' },
if (setresgid(uid, uid, uid)) { { "login", no_argument, NULL, 'l' },
PLOGE("setresgid (%u)", uid); { "preserve-environment", no_argument, NULL, 'p' },
} { "shell", required_argument, NULL, 's' },
if (setresuid(uid, uid, uid)) { { "version", no_argument, NULL, 'v' },
PLOGE("setresuid (%u)", uid); { "context", required_argument, NULL, 'z' },
} { "mount-master", no_argument, NULL, 'M' },
} { NULL, 0, NULL, 0 },
};
static __attribute__ ((noreturn)) void allow() {
char* argv[] = { NULL, NULL, NULL, NULL };
if (su_ctx->to.login)
argv[0] = "-";
else
argv[0] = basename(su_ctx->to.shell);
if (su_ctx->to.command) {
argv[1] = "-c";
argv[2] = su_ctx->to.command;
}
// Setup shell
umask(022);
populate_environment();
set_identity(su_ctx->to.uid);
if (su_ctx->info->access.notify || su_ctx->info->access.log)
app_log();
execvp(su_ctx->to.shell, argv);
fprintf(stderr, "Cannot execute %s: %s\n", su_ctx->to.shell, strerror(errno));
PLOGE("exec");
exit(EXIT_FAILURE);
}
static __attribute__ ((noreturn)) void deny() { struct su_request su_req = {
if (su_ctx->info->access.notify || su_ctx->info->access.log) .uid = UID_ROOT,
app_log(); .login = 0,
.keepenv = 0,
.shell = DEFAULT_SHELL,
.command = "",
};
LOGW("su: request rejected (%u->%u)", su_ctx->info->uid, su_ctx->to.uid);
fprintf(stderr, "%s\n", strerror(EACCES));
exit(EXIT_FAILURE);
}
__attribute__ ((noreturn)) void exit2(int status) { for (int i = 0; i < argc; i++) {
// Handle the pipe, or the daemon will get stuck // Replace -cn with -z, -mm with -M for supporting getopt_long
if (su_ctx->pipefd[0] >= 0) { if (strcmp(argv[i], "-cn") == 0)
xwrite(su_ctx->pipefd[1], &su_ctx->info->access.policy, sizeof(policy_t)); strcpy(argv[i], "-z");
close(su_ctx->pipefd[0]); else if (strcmp(argv[i], "-mm") == 0)
close(su_ctx->pipefd[1]); strcpy(argv[i], "-M");
} }
exit(status);
}
int su_daemon_main(int argc, char **argv) {
int c;
struct option long_opts[] = {
{ "command", required_argument, NULL, 'c' },
{ "help", no_argument, NULL, 'h' },
{ "login", no_argument, NULL, 'l' },
{ "preserve-environment", no_argument, NULL, 'p' },
{ "shell", required_argument, NULL, 's' },
{ "version", no_argument, NULL, 'v' },
{ "context", required_argument, NULL, 'z' },
{ "mount-master", no_argument, NULL, 'M' },
{ NULL, 0, NULL, 0 },
};
while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:M", long_opts, NULL)) != -1) { while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:M", long_opts, NULL)) != -1) {
switch (c) { switch (c) {
case 'c': case 'c':
su_ctx->to.command = concat_commands(argc, argv); su_req.command = concat_commands(argc, argv);
optind = argc; optind = argc;
break; break;
case 'h': case 'h':
usage(EXIT_SUCCESS); usage(EXIT_SUCCESS);
break; break;
case 'l': case 'l':
su_ctx->to.login = 1; su_req.login = 1;
break; break;
case 'm': case 'm':
case 'p': case 'p':
su_ctx->to.keepenv = 1; su_req.keepenv = 1;
break; break;
case 's': case 's':
su_ctx->to.shell = optarg; su_req.shell = optarg;
break; break;
case 'V': case 'V':
printf("%d\n", MAGISK_VER_CODE); printf("%d\n", MAGISK_VER_CODE);
exit2(EXIT_SUCCESS); exit(EXIT_SUCCESS);
case 'v': case 'v':
printf("%s\n", xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)"); printf("%s\n", xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)");
exit2(EXIT_SUCCESS); exit(EXIT_SUCCESS);
case 'z': case 'z':
// Do nothing, placed here for legacy support :) // Do nothing, placed here for legacy support :)
break; break;
case 'M': case 'M':
DB_SET(su_ctx->info, SU_MNT_NS) = NAMESPACE_MODE_GLOBAL; /* TODO */
break; break;
default: default:
/* Bionic getopt_long doesn't terminate its error output by newline */ /* Bionic getopt_long doesn't terminate its error output by newline */
fprintf(stderr, "\n"); fprintf(stderr, "\n");
usage(2); usage(2);
} }
} }
if (optind < argc && strcmp(argv[optind], "-") == 0) { if (optind < argc && strcmp(argv[optind], "-") == 0) {
su_ctx->to.login = 1; su_req.login = 1;
optind++; optind++;
} }
/* username or uid */ /* username or uid */
...@@ -207,54 +165,69 @@ int su_daemon_main(int argc, char **argv) { ...@@ -207,54 +165,69 @@ int su_daemon_main(int argc, char **argv) {
struct passwd *pw; struct passwd *pw;
pw = getpwnam(argv[optind]); pw = getpwnam(argv[optind]);
if (pw) if (pw)
su_ctx->to.uid = pw->pw_uid; su_req.uid = pw->pw_uid;
else else
su_ctx->to.uid = atoi(argv[optind]); su_req.uid = atoi(argv[optind]);
optind++; optind++;
} }
// Handle namespaces char pts_slave[PATH_MAX];
switch (DB_SET(su_ctx->info, SU_MNT_NS)) { int ptmx, fd;
case NAMESPACE_MODE_GLOBAL:
LOGD("su: use global namespace\n");
break;
case NAMESPACE_MODE_REQUESTER:
LOGD("su: use namespace of pid=[%d]\n", su_ctx->pid);
if (switch_mnt_ns(su_ctx->pid)) {
LOGD("su: setns failed, fallback to isolated\n");
xunshare(CLONE_NEWNS);
}
break;
case NAMESPACE_MODE_ISOLATE:
LOGD("su: use new isolated namespace\n");
xunshare(CLONE_NEWNS);
break;
}
// Change directory to cwd // Connect to client
chdir(su_ctx->cwd); fd = connect_daemon();
if (su_ctx->pipefd[0] >= 0) { // Tell the daemon we are su
// Create random socket write_int(fd, SUPERUSER);
struct sockaddr_un addr;
int sockfd = create_rand_socket(&addr);
// Connect Magisk Manager // Send su_request
app_connect(addr.sun_path + 1); xwrite(fd, &su_req, 3 * sizeof(unsigned));
int fd = socket_accept(sockfd, 60); write_string(fd, su_req.shell);
write_string(fd, su_req.command);
socket_send_request(fd); // Determine which one of our streams are attached to a TTY
su_ctx->info->access.policy = read_int_be(fd); int atty = 0;
if (isatty(STDIN_FILENO)) atty |= ATTY_IN;
if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT;
if (isatty(STDERR_FILENO)) atty |= ATTY_ERR;
close(fd); if (atty) {
close(sockfd); // We need a PTY. Get one.
ptmx = pts_open(pts_slave, sizeof(pts_slave));
} else {
pts_slave[0] = '\0';
}
// Report the policy to main daemon // Send pts_slave
xwrite(su_ctx->pipefd[1], &su_ctx->info->access.policy, sizeof(policy_t)); write_string(fd, pts_slave);
close(su_ctx->pipefd[0]);
close(su_ctx->pipefd[1]); // Send stdin
send_fd(fd, (atty & ATTY_IN) ? -1 : STDIN_FILENO);
// Send stdout
send_fd(fd, (atty & ATTY_OUT) ? -1 : STDOUT_FILENO);
// Send stderr
send_fd(fd, (atty & ATTY_ERR) ? -1 : STDERR_FILENO);
// Wait for ack from daemon
if (read_int(fd)) {
// Fast fail
fprintf(stderr, "%s\n", strerror(EACCES));
return DENY;
} }
su_ctx->info->access.policy == ALLOW ? allow() : deny(); if (atty & ATTY_IN) {
} setup_sighandlers(sighandler);
pump_stdin_async(ptmx);
}
if (atty & ATTY_OUT) {
// Forward SIGWINCH
watch_sigwinch_async(STDOUT_FILENO, ptmx);
pump_stdout_blocking(ptmx);
}
// Get the exit code
int code = read_int(fd);
close(fd);
return code;
}
\ No newline at end of file
...@@ -12,6 +12,11 @@ ...@@ -12,6 +12,11 @@
#define DEFAULT_SHELL "/system/bin/sh" #define DEFAULT_SHELL "/system/bin/sh"
// Constants for atty
#define ATTY_IN 1
#define ATTY_OUT 2
#define ATTY_ERR 4
struct su_info { struct su_info {
unsigned uid; /* Unique key to find su_info */ unsigned uid; /* Unique key to find su_info */
pthread_mutex_t lock; /* Internal lock */ pthread_mutex_t lock; /* Internal lock */
...@@ -33,32 +38,22 @@ struct su_info { ...@@ -33,32 +38,22 @@ struct su_info {
struct su_request { struct su_request {
unsigned uid; unsigned uid;
int login; unsigned login;
int keepenv; unsigned keepenv;
char *shell; char *shell;
char *command; char *command;
}; };
struct su_context { struct su_context {
struct su_info *info; struct su_info *info;
struct su_request to; struct su_request req;
pid_t pid; pid_t pid;
char cwd[PATH_MAX];
int pipefd[2];
}; };
extern struct su_context *su_ctx;
// su.c
int su_daemon_main(int argc, char **argv);
__attribute__ ((noreturn)) void exit2(int status);
void set_identity(unsigned uid);
// connect.c // connect.c
void app_log(); void app_log(struct su_context *ctx);
void app_connect(const char *socket); void app_connect(const char *socket, struct su_info *info);
void socket_send_request(int fd); void socket_send_request(int fd, struct su_info *info);
#endif #endif
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment