From d119dd9a0cb80dcb2f65e277e005e1488e97acc2 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Thu, 4 Oct 2018 04:59:51 -0400 Subject: [PATCH] Rewrite su daemon and client --- native/jni/daemon/daemon.c | 2 +- native/jni/daemon/socket.c | 16 +- native/jni/include/daemon.h | 4 +- native/jni/su/connect.c | 48 ++--- native/jni/su/su.c | 305 ++++++++++++++----------------- native/jni/su/su.h | 27 ++- native/jni/su/su_daemon.c | 354 ++++++++++++++++-------------------- 7 files changed, 346 insertions(+), 410 deletions(-) diff --git a/native/jni/daemon/daemon.c b/native/jni/daemon/daemon.c index 797c3d39d..ce4daabf1 100644 --- a/native/jni/daemon/daemon.c +++ b/native/jni/daemon/daemon.c @@ -67,7 +67,7 @@ static void *request_handler(void *args) { ls_hide_list(client); break; case SUPERUSER: - su_daemon_receiver(client, &credential); + su_daemon_handler(client, &credential); break; case CHECK_VERSION: write_string(client, xstr(MAGISK_VERSION) ":MAGISK"); diff --git a/native/jni/daemon/socket.c b/native/jni/daemon/socket.c index c83bde2a9..fa7b67b4a 100644 --- a/native/jni/daemon/socket.c +++ b/native/jni/daemon/socket.c @@ -39,7 +39,7 @@ int create_rand_socket(struct sockaddr_un *sun) { return fd; } -int socket_accept(int serv_fd, int timeout) { +int socket_accept(int sockfd, int timeout) { struct timeval tv; fd_set fds; int rc; @@ -47,16 +47,16 @@ int socket_accept(int serv_fd, int timeout) { tv.tv_sec = timeout; tv.tv_usec = 0; FD_ZERO(&fds); - FD_SET(serv_fd, &fds); + FD_SET(sockfd, &fds); do { - rc = select(serv_fd + 1, &fds, NULL, NULL, &tv); + rc = select(sockfd + 1, &fds, NULL, NULL, &tv); } while (rc < 0 && errno == EINTR); if (rc < 1) { 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) { int read_int(int fd) { int val; - xxread(fd, &val, sizeof(int)); + if (xxread(fd, &val, sizeof(int)) != sizeof(int)) + return -1; return val; } int read_int_be(int fd) { uint32_t val; - xxread(fd, &val, sizeof(val)); + if (xxread(fd, &val, sizeof(val)) != sizeof(int)) + return -1; return ntohl(val); } diff --git a/native/jni/include/daemon.h b/native/jni/include/daemon.h index 0d20ef223..18d713ea7 100644 --- a/native/jni/include/daemon.h +++ b/native/jni/include/daemon.h @@ -61,7 +61,7 @@ int check_and_start_logger(); socklen_t setup_sockaddr(struct sockaddr_un *sun, daemon_t d); 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); void send_fd(int sockfd, int fd); int read_int(int fd); @@ -98,6 +98,6 @@ void ls_hide_list(int client); * Superuser * *************/ -void su_daemon_receiver(int client, struct ucred *credential); +void su_daemon_handler(int client, struct ucred *credential); #endif diff --git a/native/jni/su/connect.c b/native/jni/su/connect.c index 54cb5d149..098b34700 100644 --- a/native/jni/su/connect.c +++ b/native/jni/su/connect.c @@ -14,86 +14,86 @@ #include "magisk.h" #include "daemon.h" +#include "utils.h" #include "su.h" #define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am" static char *get_command(const struct su_request *to) { - if (to->command) + if (to->command[0]) return to->command; - if (to->shell) + if (to->shell[0]) return to->shell; return DEFAULT_SHELL; } static void silent_run(char * const args[]) { - set_identity(0); - if (fork()) + if (fork_dont_care()) return; int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC); - dup2(zero, 0); + xdup2(zero, 0); int null = open("/dev/null", O_WRONLY | O_CLOEXEC); - dup2(null, 1); - dup2(null, 2); + xdup2(null, 1); + xdup2(null, 2); setenv("CLASSPATH", "/system/framework/am.jar", 1); execv(args[0], args); PLOGE("exec am"); _exit(EXIT_FAILURE); } -static void setup_user(char *user) { - switch (DB_SET(su_ctx->info, SU_MULTIUSER_MODE)) { +static void setup_user(char *user, struct su_info *info) { + switch (DB_SET(info, SU_MULTIUSER_MODE)) { case MULTIUSER_MODE_OWNER_ONLY: case MULTIUSER_MODE_OWNER_MANAGED: sprintf(user, "%d", 0); break; case MULTIUSER_MODE_USER: - sprintf(user, "%d", su_ctx->info->uid / 100000); + sprintf(user, "%d", info->uid / 100000); break; } } -void app_log() { +void app_log(struct su_context *ctx) { char user[8]; - setup_user(user); + setup_user(user, ctx->info); char fromUid[8]; sprintf(fromUid, "%d", - DB_SET(su_ctx->info, SU_MULTIUSER_MODE) == MULTIUSER_MODE_OWNER_MANAGED ? - su_ctx->info->uid % 100000 : su_ctx->info->uid); + DB_SET(ctx->info, SU_MULTIUSER_MODE) == MULTIUSER_MODE_OWNER_MANAGED ? + ctx->info->uid % 100000 : ctx->info->uid); char toUid[8]; - sprintf(toUid, "%d", su_ctx->to.uid); + sprintf(toUid, "%d", ctx->req.uid); char pid[8]; - sprintf(pid, "%d", su_ctx->pid); + sprintf(pid, "%d", ctx->pid); char policy[2]; - sprintf(policy, "%d", su_ctx->info->access.policy); + sprintf(policy, "%d", ctx->info->access.policy); char *cmd[] = { AM_PATH, "broadcast", "-a", "android.intent.action.BOOT_COMPLETED", - "-p", DB_STR(su_ctx->info, SU_MANAGER), + "-p", DB_STR(ctx->info, SU_MANAGER), "--user", user, "--es", "action", "log", "--ei", "from.uid", fromUid, "--ei", "to.uid", toUid, "--ei", "pid", pid, "--ei", "policy", policy, - "--es", "command", get_command(&su_ctx->to), + "--es", "command", get_command(&ctx->req), NULL }; silent_run(cmd); } -void app_connect(const char *socket) { +void app_connect(const char *socket, struct su_info *info) { char user[8]; - setup_user(user); + setup_user(user, info); char *cmd[] = { AM_PATH, "broadcast", "-a", "android.intent.action.BOOT_COMPLETED", - "-p", DB_STR(su_ctx->info, SU_MANAGER), + "-p", DB_STR(info, SU_MANAGER), "--user", user, "--es", "action", "request", "--es", "socket", (char *) socket, @@ -102,8 +102,8 @@ void app_connect(const char *socket) { silent_run(cmd); } -void socket_send_request(int fd) { - write_key_token(fd, "uid", su_ctx->info->uid); +void socket_send_request(int fd, struct su_info *info) { + write_key_token(fd, "uid", info->uid); write_string_be(fd, "eof"); } diff --git a/native/jni/su/su.c b/native/jni/su/su.c index c797ab5ca..781d4e976 100644 --- a/native/jni/su/su.c +++ b/native/jni/su/su.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -27,11 +26,9 @@ #include "daemon.h" #include "utils.h" #include "su.h" -#include "selinux.h" +#include "pts.h" #include "flags.h" -struct su_context *su_ctx; - static void usage(int status) { FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr; @@ -49,7 +46,7 @@ static void usage(int status) { " -V display version code and exit\n" " -mm, -M,\n" " --mount-master force run in the global mount namespace\n"); - exit2(status); + exit(status); } static char *concat_commands(int argc, char *argv[]) { @@ -64,142 +61,103 @@ static char *concat_commands(int argc, char *argv[]) { return strdup(command); } -static void populate_environment() { - struct passwd *pw; +static void sighandler(int sig) { + restore_stdin(); - if (su_ctx->to.keepenv) - return; + // Assume we'll only be called before death + // See note before sigaction() in set_stdin_raw() + // + // Now, close all standard I/O to cause the pumps + // to exit so we can continue and retrieve the exit + // code + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); - pw = getpwuid(su_ctx->to.uid); - if (pw) { - setenv("HOME", pw->pw_dir, 1); - if (su_ctx->to.shell) - setenv("SHELL", su_ctx->to.shell, 1); - else - setenv("SHELL", DEFAULT_SHELL, 1); - if (su_ctx->to.login || su_ctx->to.uid) { - setenv("USER", pw->pw_name, 1); - setenv("LOGNAME", pw->pw_name, 1); - } + // Put back all the default handlers + struct sigaction act; + + 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) { - /* - * Set effective uid back to root, otherwise setres[ug]id will fail - * if uid isn't root. - */ - if (seteuid(0)) { - PLOGE("seteuid (root)"); - } - if (setresgid(uid, uid, uid)) { - PLOGE("setresgid (%u)", uid); - } - if (setresuid(uid, uid, uid)) { - PLOGE("setresuid (%u)", uid); - } -} - -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() { - if (su_ctx->info->access.notify || su_ctx->info->access.log) - app_log(); - - 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) { - // Handle the pipe, or the daemon will get stuck - if (su_ctx->pipefd[0] >= 0) { - xwrite(su_ctx->pipefd[1], &su_ctx->info->access.policy, sizeof(policy_t)); - close(su_ctx->pipefd[0]); - close(su_ctx->pipefd[1]); - } - exit(status); -} - -int su_daemon_main(int argc, char **argv) { +/* + * Connect daemon, send argc, argv, cwd, pts slave + */ +int su_client_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 }, + { "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 }, }; + struct su_request su_req = { + .uid = UID_ROOT, + .login = 0, + .keepenv = 0, + .shell = DEFAULT_SHELL, + .command = "", + }; + + + for (int i = 0; i < argc; i++) { + // Replace -cn with -z, -mm with -M for supporting getopt_long + if (strcmp(argv[i], "-cn") == 0) + strcpy(argv[i], "-z"); + else if (strcmp(argv[i], "-mm") == 0) + strcpy(argv[i], "-M"); + } + while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:M", long_opts, NULL)) != -1) { switch (c) { - case 'c': - su_ctx->to.command = concat_commands(argc, argv); - optind = argc; - break; - case 'h': - usage(EXIT_SUCCESS); - break; - case 'l': - su_ctx->to.login = 1; - break; - case 'm': - case 'p': - su_ctx->to.keepenv = 1; - break; - case 's': - su_ctx->to.shell = optarg; - break; - case 'V': - printf("%d\n", MAGISK_VER_CODE); - exit2(EXIT_SUCCESS); - case 'v': - printf("%s\n", xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)"); - exit2(EXIT_SUCCESS); - case 'z': - // Do nothing, placed here for legacy support :) - break; - case 'M': - DB_SET(su_ctx->info, SU_MNT_NS) = NAMESPACE_MODE_GLOBAL; - break; - default: - /* Bionic getopt_long doesn't terminate its error output by newline */ - fprintf(stderr, "\n"); - usage(2); + case 'c': + su_req.command = concat_commands(argc, argv); + optind = argc; + break; + case 'h': + usage(EXIT_SUCCESS); + break; + case 'l': + su_req.login = 1; + break; + case 'm': + case 'p': + su_req.keepenv = 1; + break; + case 's': + su_req.shell = optarg; + break; + case 'V': + printf("%d\n", MAGISK_VER_CODE); + exit(EXIT_SUCCESS); + case 'v': + printf("%s\n", xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)"); + exit(EXIT_SUCCESS); + case 'z': + // Do nothing, placed here for legacy support :) + break; + case 'M': + /* TODO */ + break; + default: + /* Bionic getopt_long doesn't terminate its error output by newline */ + fprintf(stderr, "\n"); + usage(2); } } if (optind < argc && strcmp(argv[optind], "-") == 0) { - su_ctx->to.login = 1; + su_req.login = 1; optind++; } /* username or uid */ @@ -207,54 +165,69 @@ int su_daemon_main(int argc, char **argv) { struct passwd *pw; pw = getpwnam(argv[optind]); if (pw) - su_ctx->to.uid = pw->pw_uid; + su_req.uid = pw->pw_uid; else - su_ctx->to.uid = atoi(argv[optind]); + su_req.uid = atoi(argv[optind]); optind++; } - // Handle namespaces - switch (DB_SET(su_ctx->info, SU_MNT_NS)) { - 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; + char pts_slave[PATH_MAX]; + int ptmx, fd; + + // Connect to client + fd = connect_daemon(); + + // Tell the daemon we are su + write_int(fd, SUPERUSER); + + // Send su_request + xwrite(fd, &su_req, 3 * sizeof(unsigned)); + write_string(fd, su_req.shell); + write_string(fd, su_req.command); + + // Determine which one of our streams are attached to a TTY + int atty = 0; + if (isatty(STDIN_FILENO)) atty |= ATTY_IN; + if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT; + if (isatty(STDERR_FILENO)) atty |= ATTY_ERR; + + if (atty) { + // We need a PTY. Get one. + ptmx = pts_open(pts_slave, sizeof(pts_slave)); + } else { + pts_slave[0] = '\0'; } - // Change directory to cwd - chdir(su_ctx->cwd); + // Send pts_slave + write_string(fd, pts_slave); - if (su_ctx->pipefd[0] >= 0) { - // Create random socket - struct sockaddr_un addr; - int sockfd = create_rand_socket(&addr); + // 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); - // Connect Magisk Manager - app_connect(addr.sun_path + 1); - int fd = socket_accept(sockfd, 60); - - socket_send_request(fd); - su_ctx->info->access.policy = read_int_be(fd); - - close(fd); - close(sockfd); - - // Report the policy to main daemon - xwrite(su_ctx->pipefd[1], &su_ctx->info->access.policy, sizeof(policy_t)); - close(su_ctx->pipefd[0]); - close(su_ctx->pipefd[1]); + // 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 diff --git a/native/jni/su/su.h b/native/jni/su/su.h index b66df0b19..705046fdb 100644 --- a/native/jni/su/su.h +++ b/native/jni/su/su.h @@ -12,6 +12,11 @@ #define DEFAULT_SHELL "/system/bin/sh" +// Constants for atty +#define ATTY_IN 1 +#define ATTY_OUT 2 +#define ATTY_ERR 4 + struct su_info { unsigned uid; /* Unique key to find su_info */ pthread_mutex_t lock; /* Internal lock */ @@ -33,32 +38,22 @@ struct su_info { struct su_request { unsigned uid; - int login; - int keepenv; + unsigned login; + unsigned keepenv; char *shell; char *command; }; struct su_context { struct su_info *info; - struct su_request to; + struct su_request req; 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 -void app_log(); -void app_connect(const char *socket); -void socket_send_request(int fd); +void app_log(struct su_context *ctx); +void app_connect(const char *socket, struct su_info *info); +void socket_send_request(int fd, struct su_info *info); #endif diff --git a/native/jni/su/su_daemon.c b/native/jni/su/su_daemon.c index f9bf6b33c..f2aa38767 100644 --- a/native/jni/su/su_daemon.c +++ b/native/jni/su/su_daemon.c @@ -2,13 +2,14 @@ */ #define _GNU_SOURCE -#include + #include #include #include #include #include #include +#include #include #include #include @@ -21,44 +22,16 @@ #include "pts.h" #include "selinux.h" -// Constants for the atty bitfield -#define ATTY_IN 1 -#define ATTY_OUT 2 -#define ATTY_ERR 4 - #define TIMEOUT 3 #define LOCK_CACHE() pthread_mutex_lock(&cache_lock) #define LOCK_INFO() pthread_mutex_lock(&info->lock) #define UNLOCK_CACHE() pthread_mutex_unlock(&cache_lock) -#define UNLOCK_INFO() pthread_mutex_unlock(&ctx.info->lock) +#define UNLOCK_INFO() pthread_mutex_unlock(&info->lock) static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER; static struct su_info *cache; -static void sighandler(int sig) { - restore_stdin(); - - // Assume we'll only be called before death - // See note before sigaction() in set_stdin_raw() - // - // Now, close all standard I/O to cause the pumps - // to exit so we can continue and retrieve the exit - // code - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - - // Put back all the default handlers - struct sigaction act; - - memset(&act, 0, sizeof(act)); - act.sa_handler = SIG_DFL; - for (int i = 0; quit_signals[i]; ++i) { - sigaction(quit_signals[i], &act, NULL); - } -} - static void *info_collector(void *node) { struct su_info *info = node; while (1) { @@ -185,48 +158,133 @@ static struct su_info *get_su_info(unsigned uid) { if (info->access.policy == QUERY && DB_STR(info, SU_MANAGER)[0] == '\0') info->access = NO_SU_ACCESS; } + + // If still not determined, ask manager + if (info->access.policy == QUERY) { + // Create random socket + struct sockaddr_un addr; + int sockfd = create_rand_socket(&addr); + + // Connect manager + app_connect(addr.sun_path + 1, info); + int fd = socket_accept(sockfd, 60); + + socket_send_request(fd, info); + int ret = read_int_be(fd); + info->access.policy = ret < 0 ? DENY : ret; + + close(fd); + close(sockfd); + } + + // Unlock + UNLOCK_INFO(); + return info; } -static void su_executor(int client) { - LOGD("su: executor started\n"); +static void populate_environment(struct su_request *req) { + struct passwd *pw; + + if (req->keepenv) + return; + + pw = getpwuid(req->uid); + if (pw) { + setenv("HOME", pw->pw_dir, 1); + if (req->shell) + setenv("SHELL", req->shell, 1); + else + setenv("SHELL", DEFAULT_SHELL, 1); + if (req->login || req->uid) { + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + } + } +} + +static void set_identity(unsigned uid) { + /* + * Set effective uid back to root, otherwise setres[ug]id will fail + * if uid isn't root. + */ + if (seteuid(0)) { + PLOGE("seteuid (root)"); + } + if (setresgid(uid, uid, uid)) { + PLOGE("setresgid (%u)", uid); + } + if (setresuid(uid, uid, uid)) { + PLOGE("setresuid (%u)", uid); + } +} + +void su_daemon_handler(int client, struct ucred *credential) { + LOGD("su: request from client: %d\n", client); + + struct su_info *info = get_su_info(credential->uid); + + // Fail fast + if (info->access.policy == DENY && !info->access.log && !info->access.notify) { + write_int(client, DENY); + return; + } + + /* 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 = xfork(); + if (child) { + // Decrement reference count + --info->ref; + + // Wait result + LOGD("su: waiting child: [%d]\n", child); + int status, code; + + if (waitpid(child, &status, 0) > 0) + code = WEXITSTATUS(status); + else + code = -1; + + LOGD("su: return code: [%d]\n", code); + write(client, &code, sizeof(code)); + close(client); + return; + } + + LOGD("su: fork handler\n"); + + struct su_context ctx = { + .info = info, + .pid = credential->pid + }; // Become session leader xsetsid(); // Migrate environment from client char path[32], buf[4096]; - snprintf(path, sizeof(path), "/proc/%d/cwd", su_ctx->pid); - xreadlink(path, su_ctx->cwd, sizeof(su_ctx->cwd)); - snprintf(path, sizeof(path), "/proc/%d/environ", su_ctx->pid); + snprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid); + xreadlink(path, buf, sizeof(buf)); + chdir(buf); + snprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid); memset(buf, 0, sizeof(buf)); int fd = open(path, O_RDONLY); read(fd, buf, sizeof(buf)); + close(fd); clearenv(); for (size_t pos = 0; buf[pos];) { putenv(buf + pos); pos += strlen(buf + pos) + 1; } - // Let's read some info from the socket - int argc = read_int(client); - if (argc < 0 || argc > 512) { - LOGE("unable to allocate args: %d", argc); - exit2(1); - } - LOGD("su: argc=[%d]\n", argc); - - char **argv = (char**) xmalloc(sizeof(char*) * (argc + 1)); - argv[argc] = NULL; - for (int i = 0; i < argc; i++) { - argv[i] = read_string(client); - LOGD("su: argv[%d]=[%s]\n", i, argv[i]); - // Replace -cn with -z, -mm with -M for supporting getopt_long - if (strcmp(argv[i], "-cn") == 0) - strcpy(argv[i], "-z"); - else if (strcmp(argv[i], "-mm") == 0) - strcpy(argv[i], "-M"); - } + // Read su_request + xxread(client, &ctx.req, 3 * sizeof(unsigned)); + ctx.req.shell = read_string(client); + ctx.req.command = read_string(client); // Get pts_slave char *pts_slave = read_string(client); @@ -244,10 +302,10 @@ static void su_executor(int client) { xstat(pts_slave, &st); // If caller is not root, ensure the owner of pts_slave is the caller - if(st.st_uid != su_ctx->info->uid && su_ctx->info->uid != 0) { + if(st.st_uid != info->uid && info->uid != 0) { LOGE("su: Wrong permission of pts_slave"); - su_ctx->info->access.policy = DENY; - exit2(1); + info->access.policy = DENY; + exit(1); } // Set our pts_slave to devpts, same restriction as adb shell @@ -258,18 +316,12 @@ static void su_executor(int client) { // our controlling TTY and not the daemon's ptsfd = xopen(pts_slave, O_RDWR); - if (infd < 0) { - LOGD("su: stdin using PTY"); - infd = ptsfd; - } - if (outfd < 0) { - LOGD("su: stdout using PTY"); + if (infd < 0) + infd = ptsfd; + if (outfd < 0) outfd = ptsfd; - } - if (errfd < 0) { - LOGD("su: stderr using PTY"); + if (errfd < 0) errfd = ptsfd; - } } free(pts_slave); @@ -285,142 +337,56 @@ static void su_executor(int client) { write_int(client, 0); close(client); - // Run the actual main - su_daemon_main(argc, argv); -} - -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 } - }; - - // Fail fast - if (ctx.info->access.policy == DENY && !ctx.info->access.log && !ctx.info->access.notify) { - UNLOCK_INFO(); - write_int(client, DENY); - return; + // Handle namespaces + switch (DB_SET(info, SU_MNT_NS)) { + case NAMESPACE_MODE_GLOBAL: + LOGD("su: use global namespace\n"); + break; + case NAMESPACE_MODE_REQUESTER: + LOGD("su: use namespace of pid=[%d]\n", ctx.pid); + if (switch_mnt_ns(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; } - // If still not determined, open a pipe and wait for results - if (ctx.info->access.policy == QUERY) - xpipe2(ctx.pipefd, O_CLOEXEC); + if (info->access.policy) { + char* argv[] = { NULL, NULL, NULL, NULL }; - /* 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 = xfork(); - if (child == 0) { - su_ctx = &ctx; - su_executor(client); - } + if (ctx.req.login) + argv[0] = "-"; + else + argv[0] = ctx.req.shell; - // Wait for results - if (ctx.pipefd[0] >= 0) { - xxread(ctx.pipefd[0], &ctx.info->access.policy, sizeof(policy_t)); - close(ctx.pipefd[0]); - close(ctx.pipefd[1]); - } + if (ctx.req.command[0]) { + argv[1] = "-c"; + argv[2] = ctx.req.command; + } - // The policy is determined, unlock - UNLOCK_INFO(); + // Setup shell + umask(022); + populate_environment(&ctx.req); + set_identity(ctx.req.uid); - // Info is now useless to us, decrement reference count - --ctx.info->ref; + if (info->access.notify || info->access.log) + app_log(&ctx); - // Wait result - LOGD("su: waiting child: [%d]\n", child); - int status, code; - - if (waitpid(child, &status, 0) > 0) - code = WEXITSTATUS(status); - else - code = -1; - - LOGD("su: return code: [%d]\n", code); - write(client, &code, sizeof(code)); - close(client); - - return; -} - -/* - * Connect daemon, send argc, argv, cwd, pts slave - */ -int su_client_main(int argc, char *argv[]) { - char pts_slave[PATH_MAX]; - int ptmx, socketfd; - - // Connect to client - socketfd = connect_daemon(); - - // Tell the daemon we are su - write_int(socketfd, SUPERUSER); - - // Number of command line arguments - write_int(socketfd, argc); - - // Command line arguments - for (int i = 0; i < argc; i++) { - write_string(socketfd, argv[i]); - } - - // Determine which one of our streams are attached to a TTY - int atty = 0; - if (isatty(STDIN_FILENO)) atty |= ATTY_IN; - if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT; - if (isatty(STDERR_FILENO)) atty |= ATTY_ERR; - - if (atty) { - // We need a PTY. Get one. - ptmx = pts_open(pts_slave, sizeof(pts_slave)); + execvp(ctx.req.shell, argv); + fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell, strerror(errno)); + PLOGE("exec"); + exit(EXIT_FAILURE); } else { - pts_slave[0] = '\0'; - } + if (info->access.notify || info->access.log) + app_log(&ctx); - // Send the pts_slave path to the daemon - write_string(socketfd, pts_slave); - - // Send stdin - send_fd(socketfd, (atty & ATTY_IN) ? -1 : STDIN_FILENO); - // Send stdout - send_fd(socketfd, (atty & ATTY_OUT) ? -1 : STDOUT_FILENO); - // Send stderr - send_fd(socketfd, (atty & ATTY_ERR) ? -1 : STDERR_FILENO); - - // Wait for acknowledgement from daemon - if (read_int(socketfd)) { - // Fast fail + LOGW("su: request rejected (%u->%u)", info->uid, ctx.req.uid); fprintf(stderr, "%s\n", strerror(EACCES)); - return DENY; + exit(EXIT_FAILURE); } - - 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(socketfd); - close(socketfd); - - return code; } +