From a92c9fc22695a50b24fd07de7d0c1c63d7bb5b4f Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sat, 15 Apr 2017 03:21:31 +0800 Subject: [PATCH] MagiskSU rewrite for unified binary --- activity.c | 293 ++++++------ daemon.c | 736 ----------------------------- db.c | 111 ++--- misc.c | 54 +++ pts.c | 280 ++++++----- pts.h | 14 +- su.c | 1279 ++++++++++++++++----------------------------------- su.h | 198 ++++---- su_client.c | 217 +++++++++ su_socket.c | 101 ++++ utils.c | 112 ----- utils.h | 30 -- 12 files changed, 1184 insertions(+), 2241 deletions(-) delete mode 100644 daemon.c create mode 100644 misc.c create mode 100644 su_client.c create mode 100644 su_socket.c delete mode 100644 utils.c delete mode 100644 utils.h diff --git a/activity.c b/activity.c index 3f463869d..97f9e2636 100644 --- a/activity.c +++ b/activity.c @@ -3,17 +3,6 @@ ** Copyright 2010, Adam Shanks (@ChainsDD) ** Copyright 2008, Zinx Verituse (@zinxv) ** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. */ #include @@ -25,6 +14,7 @@ #include #include +#include "magisk.h" #include "su.h" /* intent actions */ @@ -34,167 +24,160 @@ #define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am" -// TODO: leverage this with exec_log? -int silent_run(char* const args[]) { - set_identity(0); - pid_t pid; - pid = fork(); - /* Parent */ - if (pid < 0) { - PLOGE("fork"); - return -1; - } - else if (pid > 0) { - return 0; - } - int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC); - dup2(zero, 0); - int null = open("/dev/null", O_WRONLY | O_CLOEXEC); - dup2(null, 1); - dup2(null, 2); - setenv("CLASSPATH", "/system/framework/am.jar", 1); - execv(args[0], args); - PLOGE("exec am"); - _exit(EXIT_FAILURE); - return -1; +static void silent_run(char* const args[]) { + set_identity(0); + pid_t pid; + pid = fork(); + /* Parent */ + if (pid < 0) { + PLOGE("fork"); + } + else if (pid > 0) { + return; + } + int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC); + dup2(zero, 0); + int null = open("/dev/null", O_WRONLY | O_CLOEXEC); + dup2(null, 1); + dup2(null, 2); + setenv("CLASSPATH", "/system/framework/am.jar", 1); + execv(args[0], args); + PLOGE("exec am"); + _exit(EXIT_FAILURE); } -int get_owner_login_user_args(struct su_context *ctx, char* user, int user_len) { - int needs_owner_login_prompt = 0; - - if (ctx->user.multiuser_mode == MULTIUSER_MODE_OWNER_MANAGED) { - if (0 != ctx->user.android_user_id) { - needs_owner_login_prompt = 1; - } - snprintf(user, user_len, "0"); - } - 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; +static int get_owner_login_user_args(struct su_context *ctx, char* user, int user_len) { + int needs_owner_login_prompt = 0; + + if (ctx->user.multiuser_mode == MULTIUSER_MODE_OWNER_MANAGED) { + if (0 != ctx->user.android_user_id) { + needs_owner_login_prompt = 1; + } + snprintf(user, user_len, "0"); + } + 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; } -int send_result(struct su_context *ctx, policy_t policy) { - char binary_version[256]; - sprintf(binary_version, "%d", VERSION_CODE); +void app_send_result(struct su_context *ctx, policy_t policy) { + char binary_version[256]; + sprintf(binary_version, "%d", VERSION_CODE); - char uid[256]; - sprintf(uid, "%d", ctx->from.uid); + char uid[256]; + sprintf(uid, "%d", ctx->from.uid); - char toUid[256]; - sprintf(toUid, "%d", ctx->to.uid); + char toUid[256]; + sprintf(toUid, "%d", ctx->to.uid); - char pid[256]; - sprintf(pid, "%d", ctx->from.pid); + char pid[256]; + sprintf(pid, "%d", ctx->from.pid); - char user[64]; - get_owner_login_user_args(ctx, user, sizeof(user)); + char user[64]; + get_owner_login_user_args(ctx, user, sizeof(user)); - if (0 != ctx->user.android_user_id) { - char android_user_id[256]; - sprintf(android_user_id, "%d", ctx->user.android_user_id); + 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 *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[] = { - 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, - user, - NULL - }; - return silent_run(result_command); + char *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, + user, + NULL + }; + silent_run(result_command); } -int 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]; - int needs_owner_login_prompt = get_owner_login_user_args(ctx, user, sizeof(user)); +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]; + int needs_owner_login_prompt = get_owner_login_user_args(ctx, user, sizeof(user)); - int ret; - if (needs_owner_login_prompt) { - char uid[256]; - sprintf(uid, "%d", ctx->from.uid); + 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); + 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 - }; + // 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 + }; - int ret = silent_run(notify_command); - if (ret) { - return ret; - } - } + silent_run(notify_command); + } - char *request_command[] = { - AM_PATH, - ACTION_REQUEST, - "--es", - "socket", - ctx->sock_path, - user[0] ? "--user" : NULL, - user, - NULL - }; + char *request_command[] = { + AM_PATH, + ACTION_REQUEST, + "--es", + "socket", + ctx->sock_path, + user[0] ? "--user" : NULL, + user, + NULL + }; - return silent_run(request_command); + silent_run(request_command); } diff --git a/daemon.c b/daemon.c deleted file mode 100644 index 1594c419e..000000000 --- a/daemon.c +++ /dev/null @@ -1,736 +0,0 @@ -/* -** Copyright 2017, John Wu (@topjohnwu) -** Copyright 2010, Adam Shanks (@ChainsDD) -** Copyright 2008, Zinx Verituse (@zinxv) -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#define _GNU_SOURCE /* for unshare() */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef SUPERUSER_EMBEDDED -#include -#endif - -#include "su.h" -#include "utils.h" -#include "pts.h" - -int is_daemon = 0; -int daemon_from_uid = 0; -int daemon_from_pid = 0; - -// Constants for the atty bitfield -#define ATTY_IN 1 -#define ATTY_OUT 2 -#define ATTY_ERR 4 - -/* - * Receive a file descriptor from a Unix socket. - * Contributed by @mkasick - * - * Returns the file descriptor on success, or -1 if a file - * descriptor was not actually included in the message - * - * On error the function terminates by calling exit(-1) - */ -static int recv_fd(int sockfd) { - // Need to receive data from the message, otherwise don't care about it. - char iovbuf; - - struct iovec iov = { - .iov_base = &iovbuf, - .iov_len = 1, - }; - - char cmsgbuf[CMSG_SPACE(sizeof(int))]; - - struct msghdr msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = cmsgbuf, - .msg_controllen = sizeof(cmsgbuf), - }; - - if (recvmsg(sockfd, &msg, MSG_WAITALL) != 1) { - goto error; - } - - // Was a control message actually sent? - switch (msg.msg_controllen) { - case 0: - // No, so the file descriptor was closed and won't be used. - return -1; - case sizeof(cmsgbuf): - // Yes, grab the file descriptor from it. - break; - default: - goto error; - } - - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); - - if (cmsg == NULL || - cmsg->cmsg_len != CMSG_LEN(sizeof(int)) || - cmsg->cmsg_level != SOL_SOCKET || - cmsg->cmsg_type != SCM_RIGHTS) { -error: - LOGE("unable to read fd"); - exit(-1); - } - - return *(int *)CMSG_DATA(cmsg); -} - -/* - * Send a file descriptor through a Unix socket. - * Contributed by @mkasick - * - * On error the function terminates by calling exit(-1) - * - * fd may be -1, in which case the dummy data is sent, - * but no control message with the FD is sent. - */ -static void send_fd(int sockfd, int fd) { - // Need to send some data in the message, this will do. - struct iovec iov = { - .iov_base = "", - .iov_len = 1, - }; - - struct msghdr msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - }; - - char cmsgbuf[CMSG_SPACE(sizeof(int))]; - - if (fd != -1) { - // Is the file descriptor actually open? - if (fcntl(fd, F_GETFD) == -1) { - if (errno != EBADF) { - goto error; - } - // It's closed, don't send a control message or sendmsg will EBADF. - } else { - // It's open, send the file descriptor in a control message. - msg.msg_control = cmsgbuf; - msg.msg_controllen = sizeof(cmsgbuf); - - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); - - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - - *(int *)CMSG_DATA(cmsg) = fd; - } - } - - if (sendmsg(sockfd, &msg, 0) != 1) { -error: - PLOGE("unable to send fd"); - exit(-1); - } -} - -static int read_int(int fd) { - int val; - int len = read(fd, &val, sizeof(int)); - if (len != sizeof(int)) { - LOGE("unable to read int: %d", len); - exit(-1); - } - return val; -} - -static void write_int(int fd, int val) { - int written = write(fd, &val, sizeof(int)); - if (written != sizeof(int)) { - PLOGE("unable to write int"); - exit(-1); - } -} - -static char* read_string(int fd) { - int len = read_int(fd); - if (len > PATH_MAX || len < 0) { - LOGE("invalid string length %d", len); - exit(-1); - } - char* val = malloc(sizeof(char) * (len + 1)); - if (val == NULL) { - LOGE("unable to malloc string"); - exit(-1); - } - val[len] = '\0'; - int amount = read(fd, val, len); - if (amount != len) { - LOGE("unable to read string"); - exit(-1); - } - return val; -} - -static void write_string(int fd, char* val) { - int len = strlen(val); - write_int(fd, len); - int written = write(fd, val, len); - if (written != len) { - PLOGE("unable to write string"); - exit(-1); - } -} - -#ifdef SUPERUSER_EMBEDDED -static void mount_emulated_storage(int user_id) { - const char *emulated_source = getenv("EMULATED_STORAGE_SOURCE"); - const char *emulated_target = getenv("EMULATED_STORAGE_TARGET"); - const char* legacy = getenv("EXTERNAL_STORAGE"); - - if (!emulated_source || !emulated_target) { - // No emulated storage is present - return; - } - - // Create a second private mount namespace for our process - if (unshare(CLONE_NEWNS) < 0) { - PLOGE("unshare"); - return; - } - - if (mount("rootfs", "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) { - PLOGE("mount rootfs as slave"); - return; - } - - // /mnt/shell/emulated -> /storage/emulated - if (mount(emulated_source, emulated_target, NULL, MS_BIND, NULL) < 0) { - PLOGE("mount emulated storage"); - } - - char target_user[PATH_MAX]; - snprintf(target_user, PATH_MAX, "%s/%d", emulated_target, user_id); - - // /mnt/shell/emulated/ -> /storage/emulated/legacy - if (mount(target_user, legacy, NULL, MS_BIND | MS_REC, NULL) < 0) { - PLOGE("mount legacy path"); - } -} -#endif - -static int run_daemon_child(int infd, int outfd, int errfd, int argc, char** argv) { - if (-1 == dup2(outfd, STDOUT_FILENO)) { - PLOGE("dup2 child outfd"); - exit(-1); - } - - if (-1 == dup2(errfd, STDERR_FILENO)) { - PLOGE("dup2 child errfd"); - exit(-1); - } - - if (-1 == dup2(infd, STDIN_FILENO)) { - PLOGE("dup2 child infd"); - exit(-1); - } - - close(infd); - close(outfd); - close(errfd); - - return su_main_nodaemon(argc, argv); -} - -static int daemon_accept(int fd) { - is_daemon = 1; - int pid = read_int(fd); - LOGD("remote pid: %d", pid); - char *pts_slave = read_string(fd); - LOGD("remote pts_slave: %s", pts_slave); - daemon_from_uid = read_int(fd); - LOGD("remote uid: %d", daemon_from_uid); - daemon_from_pid = read_int(fd); - LOGD("remote req pid: %d", daemon_from_pid); - int mount_storage = read_int(fd); - char *cwd = read_string(fd); - LOGD("remote cwd: %s", cwd); - - struct ucred credentials; - socklen_t ucred_length = sizeof(struct ucred); - /* fill in the user data structure */ - if(getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) { - LOGE("could obtain credentials from unix domain socket"); - exit(-1); - } - // if the credentials on the other side of the wire are NOT root, - // we can't trust anything being sent. - if (credentials.uid != 0) { - daemon_from_uid = credentials.uid; - pid = credentials.pid; - daemon_from_pid = credentials.pid; - } - - // The the FDs for each of the streams - int infd = recv_fd(fd); - int outfd = recv_fd(fd); - int errfd = recv_fd(fd); - - int argc = read_int(fd); - if (argc < 0 || argc > 512) { - LOGE("unable to allocate args: %d", argc); - exit(-1); - } - LOGD("remote args: %d", argc); - char** argv = (char**)malloc(sizeof(char*) * (argc + 1)); - argv[argc] = NULL; - int i; - for (i = 0; i < argc; i++) { - argv[i] = read_string(fd); - } - - // ack - write_int(fd, 1); - - // Fork the child process. The fork has to happen before calling - // setsid() and opening the pseudo-terminal so that the parent - // is not affected - int child = fork(); - if (child < 0) { - // fork failed, send a return code and bail out - PLOGE("unable to fork"); - write(fd, &child, sizeof(int)); - close(fd); - return child; - } - - if (child != 0) { - // In parent, wait for the child to exit, and send the exit code - // across the wire. - int status, code; - - free(pts_slave); - - LOGD("waiting for child exit"); - if (waitpid(child, &status, 0) > 0) { - code = WEXITSTATUS(status); - } - else { - code = -1; - } - - // Pass the return code back to the client - LOGD("sending code"); - if (write(fd, &code, sizeof(int)) != sizeof(int)) { - PLOGE("unable to write exit code"); - } - - close(fd); - LOGD("child exited"); - return code; - } - - // We are in the child now - // Close the unix socket file descriptor - close (fd); - - // Become session leader - if (setsid() == (pid_t) -1) { - PLOGE("setsid"); - } - - int ptsfd; - if (pts_slave[0]) { - //Check pts_slave file is owned by daemon_from_uid - { - struct stat stbuf; - int res = stat(pts_slave, &stbuf); - if(res) { - PLOGE("stat(pts_slave) daemon"); - exit(-1); - } - - //If caller is not root, ensure the owner of pts_slave is the caller - if(stbuf.st_uid != credentials.uid && - credentials.uid != 0) { - PLOGE("Wrong permission of pts_slave"); - exit(-1); - } - } - - // Opening the TTY has to occur after the - // fork() and setsid() so that it becomes - // our controlling TTY and not the daemon's - ptsfd = open(pts_slave, O_RDWR); - if (ptsfd == -1) { - PLOGE("open(pts_slave) daemon"); - exit(-1); - } - - //Check we haven't been fooled - { - struct stat stbuf; - int res = fstat(ptsfd, &stbuf); - if(res) { - //If we have been fooled DO NOT WRITE ANYTHING - _exit(2); - } - - if(stbuf.st_uid != credentials.uid && - credentials.uid != 0) { - _exit(2); - } - } - - if (infd < 0) { - LOGD("daemon: stdin using PTY"); - infd = ptsfd; - } - if (outfd < 0) { - LOGD("daemon: stdout using PTY"); - outfd = ptsfd; - } - if (errfd < 0) { - LOGD("daemon: stderr using PTY"); - errfd = ptsfd; - } - } else { - // If a TTY was sent directly, make it the CTTY. - if (isatty(infd)) { - ioctl(infd, TIOCSCTTY, 1); - } - } - free(pts_slave); - -#ifdef SUPERUSER_EMBEDDED - if (mount_storage) { - mount_emulated_storage(multiuser_get_user_id(daemon_from_uid)); - } -#endif - - // Change directory to cwd - chdir(cwd); - free(cwd); - - return run_daemon_child(infd, outfd, errfd, argc, argv); -} - -static void unlock_blocks() { -#define DEV_BLOCKS "/dev/block" - char path[PATH_MAX]; - DIR *dir; - struct dirent *entry; - int fd, OFF = 0; - - if (!(dir = opendir(DEV_BLOCKS))) - return; - - while((entry = readdir(dir))) { - if (entry->d_type == DT_BLK && strstr(entry->d_name, "mmc") != NULL) { - sprintf(path, "%s/%s", DEV_BLOCKS, entry->d_name); - fd = open(path, O_RDONLY); - if (fd < 0) { - PLOGE("open"); - continue; - } - if (ioctl(fd, BLKROSET, &OFF) == -1) - PLOGE("ioctl"); - close(fd); - } - } - - closedir(dir); -} - -int run_daemon() { - if (getuid() != AID_ROOT || getgid() != AID_ROOT) { - PLOGE("daemon requires root. uid/gid not root"); - return -1; - } - - switch (fork()) { - case 0: - break; - case -1: - PLOGE("fork"); - return -1; - default: - return 0; - } - - if (setsid() < 0) { - PLOGE("setsid"); - return -1; - } - if (setcon("u:r:su:s0") < 0) { - PLOGE("setcon"); - return -1; - } - - unlock_blocks(); - - int fd; - struct sockaddr_un sun; - - fd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (fd < 0) { - PLOGE("socket"); - return -1; - } - if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { - PLOGE("fcntl FD_CLOEXEC"); - goto err; - } - - memset(&sun, 0, sizeof(sun)); - sun.sun_family = AF_LOCAL; - strcpy(sun.sun_path, REQUESTOR_DAEMON_PATH); - - unlink(sun.sun_path); - - if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) { - PLOGE("daemon bind"); - goto err; - } - - chmod(sun.sun_path, 0777); - - if (listen(fd, 10) < 0) { - PLOGE("daemon listen"); - goto err; - } - - int client; - while ((client = accept(fd, NULL, NULL)) > 0) { - if (fork_zero_fucks() == 0) { - close(fd); - return daemon_accept(client); - } - else { - close(client); - } - } - - LOGE("daemon exiting"); -err: - close(fd); - return -1; -} - -// List of signals which cause process termination -static int quit_signals[] = { SIGALRM, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 }; - -static void sighandler(int sig) { - (void)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; - int i; - - memset(&act, '\0', sizeof(act)); - act.sa_handler = SIG_DFL; - for (i = 0; quit_signals[i]; i++) { - if (sigaction(quit_signals[i], &act, NULL) < 0) { - PLOGE("Error removing signal handler"); - continue; - } - } -} - -/** - * Setup signal handlers trap signals which should result in program termination - * so that we can restore the terminal to its normal state and retrieve the - * return code. - */ -static void setup_sighandlers(void) { - struct sigaction act; - int i; - - // Install the termination handlers - // Note: we're assuming that none of these signal handlers are already trapped. - // If they are, we'll need to modify this code to save the previous handler and - // call it after we restore stdin to its previous state. - memset(&act, '\0', sizeof(act)); - act.sa_handler = &sighandler; - for (i = 0; quit_signals[i]; i++) { - if (sigaction(quit_signals[i], &act, NULL) < 0) { - PLOGE("Error installing signal handler"); - continue; - } - } -} - -int connect_daemon(int argc, char *argv[], int ppid) { - int uid = getuid(); - int ptmx = -1; - char pts_slave[PATH_MAX]; - char cwd[PATH_MAX]; - getcwd(cwd, sizeof(cwd)); - - struct sockaddr_un sun; - - // Open a socket to the daemon - int socketfd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (socketfd < 0) { - PLOGE("socket"); - exit(-1); - } - if (fcntl(socketfd, F_SETFD, FD_CLOEXEC)) { - PLOGE("fcntl FD_CLOEXEC"); - exit(-1); - } - - memset(&sun, 0, sizeof(sun)); - sun.sun_family = AF_LOCAL; - strcpy(sun.sun_path, REQUESTOR_DAEMON_PATH); - - if (0 != connect(socketfd, (struct sockaddr*)&sun, sizeof(sun))) { - PLOGE("connect"); - exit(-1); - } - - LOGD("connecting client %d", getpid()); - - int mount_storage = getenv("MOUNT_EMULATED_STORAGE") != NULL; - - // Determine which one of our streams are attached to a TTY - int atty = 0; - - // Send TTYs directly (instead of proxying with a PTY) if - // the SUPERUSER_SEND_TTY environment variable is set. - if (getenv("SUPERUSER_SEND_TTY") == NULL) { - 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)); - if (ptmx < 0) { - PLOGE("pts_open"); - exit(-1); - } - } else { - pts_slave[0] = '\0'; - } - - // Send some info to the daemon, starting with our PID - write_int(socketfd, getpid()); - // Send the slave path to the daemon - // (This is "" if we're not using PTYs) - write_string(socketfd, pts_slave); - // User ID - write_int(socketfd, uid); - // Parent PID - write_int(socketfd, ppid); - write_int(socketfd, mount_storage); - // CWD - write_string(socketfd, cwd); - - // Send stdin - if (atty & ATTY_IN) { - // Using PTY - send_fd(socketfd, -1); - } else { - send_fd(socketfd, STDIN_FILENO); - } - - // Send stdout - if (atty & ATTY_OUT) { - // Forward SIGWINCH - watch_sigwinch_async(STDOUT_FILENO, ptmx); - - // Using PTY - send_fd(socketfd, -1); - } else { - send_fd(socketfd, STDOUT_FILENO); - } - - // Send stderr - if (atty & ATTY_ERR) { - // Using PTY - send_fd(socketfd, -1); - } else { - send_fd(socketfd, STDERR_FILENO); - } - - // Number of command line arguments - write_int(socketfd, mount_storage ? argc - 1 : argc); - - // Command line arguments - int i; - for (i = 0; i < argc; i++) { - if (i == 1 && mount_storage) { - continue; - } - write_string(socketfd, argv[i]); - } - - // Wait for acknowledgement from daemon - read_int(socketfd); - - if (atty & ATTY_IN) { - setup_sighandlers(); - pump_stdin_async(ptmx); - } - if (atty & ATTY_OUT) { - pump_stdout_blocking(ptmx); - } - - // Get the exit code - int code = read_int(socketfd); - close(socketfd); - LOGD("client exited %d", code); - - return code; -} diff --git a/db.c b/db.c index e5d0a33e9..93c2dcf1a 100644 --- a/db.c +++ b/db.c @@ -1,17 +1,6 @@ /* ** Copyright 2013, Koushik Dutta (@koush) ** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. */ #include @@ -19,66 +8,68 @@ #include #include #include +#include +#include "magisk.h" #include "su.h" struct callback_data_t { - struct su_context *ctx; - policy_t policy; + struct su_context *ctx; + policy_t policy; }; static int database_callback(void *v, int argc, char **argv, char **azColName){ - struct callback_data_t *data = (struct callback_data_t *)v; - policy_t policy = INTERACTIVE; - int i; - time_t until = 0; - for(i = 0; i < argc; i++) { - if (strcmp(azColName[i], "policy") == 0) { - if (argv[i] != NULL) { - policy = atoi(argv[i]); - } - } - else if (strcmp(azColName[i], "until") == 0) { - if (argv[i] != NULL) { - until = atol(argv[i]); - } - } - } + struct callback_data_t *data = (struct callback_data_t *)v; + policy_t policy = INTERACTIVE; + int i; + time_t until = 0; + for(i = 0; i < argc; i++) { + if (strcmp(azColName[i], "policy") == 0) { + if (argv[i] != NULL) { + policy = atoi(argv[i]); + } + } + else if (strcmp(azColName[i], "until") == 0) { + if (argv[i] != NULL) { + until = atol(argv[i]); + } + } + } - if (policy == DENY) { - data->policy = DENY; - return -1; - } else if (policy == ALLOW && (until == 0 || until > time(NULL))) { - data->policy = ALLOW; - // even though we allow, continue, so we can see if there's another policy - // that denies... - } + if (policy == DENY) { + data->policy = DENY; + return -1; + } else if (policy == ALLOW && (until == 0 || until > time(NULL))) { + data->policy = ALLOW; + // even though we allow, continue, so we can see if there's another policy + // that denies... + } - return 0; + return 0; } policy_t database_check(struct su_context *ctx) { - sqlite3 *db = NULL; - - char query[512]; - snprintf(query, sizeof(query), "select policy, until from policies where uid=%d", ctx->from.uid); - int ret = sqlite3_open_v2(ctx->user.database_path, &db, SQLITE_OPEN_READONLY, NULL); - if (ret) { - LOGE("sqlite3 open failure: %d", ret); - sqlite3_close(db); - return INTERACTIVE; - } - - char *err = NULL; - struct callback_data_t data; - data.ctx = ctx; - data.policy = INTERACTIVE; - ret = sqlite3_exec(db, query, database_callback, &data, &err); - sqlite3_close(db); - if (err != NULL) { - LOGE("sqlite3_exec: %s", err); - return DENY; - } + sqlite3 *db = NULL; + + char query[512]; + snprintf(query, sizeof(query), "select policy, until from policies where uid=%d", ctx->from.uid); + int ret = sqlite3_open_v2(ctx->user.database_path, &db, SQLITE_OPEN_READONLY, NULL); + if (ret) { + LOGE("sqlite3 open failure: %d", ret); + sqlite3_close(db); + return INTERACTIVE; + } + + char *err = NULL; + struct callback_data_t data; + data.ctx = ctx; + data.policy = INTERACTIVE; + ret = sqlite3_exec(db, query, database_callback, &data, &err); + sqlite3_close(db); + if (err != NULL) { + LOGE("sqlite3_exec: %s", err); + return DENY; + } - return data.policy; + return data.policy; } diff --git a/misc.c b/misc.c new file mode 100644 index 000000000..33b49169f --- /dev/null +++ b/misc.c @@ -0,0 +1,54 @@ +/* misc.c - Miscellaneous stuffs for su + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "magisk.h" +#include "su.h" + +int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 }; + +void setup_sighandlers(void (*handler)(int)) { + struct sigaction act; + + // Install the termination handlers + // Note: we're assuming that none of these signal handlers are already trapped. + // If they are, we'll need to modify this code to save the previous handler and + // call it after we restore stdin to its previous state. + memset(&act, 0, sizeof(act)); + act.sa_handler = handler; + 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); + } +} + +char *get_command(const struct su_request *to) { + if (to->command) + return to->command; + if (to->shell) + return to->shell; + return DEFAULT_SHELL; +} + diff --git a/pts.c b/pts.c index f6228e022..625403bd7 100644 --- a/pts.c +++ b/pts.c @@ -1,17 +1,5 @@ /* * Copyright 2013, Tan Chee Eng (@tan-ce) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ /* @@ -21,6 +9,7 @@ * helper functions to handle raw input mode and terminal window resizing */ +#define _GNU_SOURCE #include #include #include @@ -29,6 +18,7 @@ #include #include +#include "magisk.h" #include "pts.h" /** @@ -36,16 +26,16 @@ */ // Ensures all the data is written out static int write_blocking(int fd, char *buf, ssize_t bufsz) { - ssize_t ret, written; + ssize_t ret, written; - written = 0; - do { - ret = write(fd, buf + written, bufsz - written); - if (ret == -1) return -1; - written += ret; - } while (written < bufsz); + written = 0; + do { + ret = write(fd, buf + written, bufsz - written); + if (ret == -1) return -1; + written += ret; + } while (written < bufsz); - return 0; + return 0; } /** @@ -53,13 +43,13 @@ static int write_blocking(int fd, char *buf, ssize_t bufsz) { * true, then close the output FD when we're done. */ static void pump_ex(int input, int output, int close_output) { - char buf[4096]; - int len; - while ((len = read(input, buf, 4096)) > 0) { - if (write_blocking(output, buf, len) == -1) break; - } - close(input); - if (close_output) close(output); + char buf[4096]; + int len; + while ((len = read(input, buf, 4096)) > 0) { + if (write_blocking(output, buf, len) == -1) break; + } + close(input); + if (close_output) close(output); } /** @@ -67,27 +57,27 @@ static void pump_ex(int input, int output, int close_output) { * output FD when done. */ static void pump(int input, int output) { - pump_ex(input, output, 1); + pump_ex(input, output, 1); } static void* pump_thread(void* data) { - int* files = (int*)data; - int input = files[0]; - int output = files[1]; - pump(input, output); - free(data); - return NULL; + int* files = (int*)data; + int input = files[0]; + int output = files[1]; + pump(input, output); + free(data); + return NULL; } static void pump_async(int input, int output) { - pthread_t writer; - int* files = (int*)malloc(sizeof(int) * 2); - if (files == NULL) { - exit(-1); - } - files[0] = input; - files[1] = output; - pthread_create(&writer, NULL, pump_thread, files); + pthread_t writer; + int* files = (int*)malloc(sizeof(int) * 2); + if (files == NULL) { + exit(-1); + } + files[0] = input; + files[1] = output; + pthread_create(&writer, NULL, pump_thread, files); } @@ -105,32 +95,32 @@ static void pump_async(int input, int output) { * on success, the file descriptor of the master device is returned. */ int pts_open(char *slave_name, size_t slave_name_size) { - int fdm; - char sn_tmp[256]; + int fdm; + char sn_tmp[256]; - // Open master ptmx device - fdm = open("/dev/ptmx", O_RDWR); - if (fdm == -1) return -1; + // Open master ptmx device + fdm = open("/dev/ptmx", O_RDWR); + if (fdm == -1) + goto error; - // Get the slave name - if (ptsname_r(fdm, slave_name, slave_name_size-1)) { - close(fdm); - return -2; - } + // Get the slave name + if (ptsname_r(fdm, slave_name, slave_name_size-1)) + goto error; - slave_name[slave_name_size - 1] = '\0'; + slave_name[slave_name_size - 1] = '\0'; - // Grant, then unlock - if (grantpt(fdm) == -1) { - close(fdm); - return -1; - } - if (unlockpt(fdm) == -1) { - close(fdm); - return -1; - } + // Grant, then unlock + if (grantpt(fdm) == -1) + goto error; - return fdm; + if (unlockpt(fdm) == -1) + goto error; + + return fdm; +error: + close(fdm); + PLOGE("pts_open"); + return -1; } // Stores the previous termios of stdin @@ -148,31 +138,31 @@ static int stdin_is_raw = 0; * on success 0 */ int set_stdin_raw(void) { - struct termios new_termios; + struct termios new_termios; - // Save the current stdin termios - if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) { - return -1; - } + // Save the current stdin termios + if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) { + return -1; + } - // Start from the current settings - new_termios = old_stdin; + // Start from the current settings + new_termios = old_stdin; - // Make the terminal like an SSH or telnet client - new_termios.c_iflag |= IGNPAR; - new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); - new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); - new_termios.c_oflag &= ~OPOST; - new_termios.c_cc[VMIN] = 1; - new_termios.c_cc[VTIME] = 0; + // Make the terminal like an SSH or telnet client + new_termios.c_iflag |= IGNPAR; + new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); + new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); + new_termios.c_oflag &= ~OPOST; + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) { - return -1; - } + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) { + return -1; + } - stdin_is_raw = 1; + stdin_is_raw = 1; - return 0; + return 0; } /** @@ -189,15 +179,15 @@ int set_stdin_raw(void) { * on success, 0 */ int restore_stdin(void) { - if (!stdin_is_raw) return 0; + if (!stdin_is_raw) return 0; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) { - return -1; - } + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) { + return -1; + } - stdin_is_raw = 0; + stdin_is_raw = 0; - return 0; + return 0; } // Flag indicating whether the sigwinch watcher should terminate. @@ -208,33 +198,33 @@ static volatile int closing_time = 0; * the terminal size. */ static void *watch_sigwinch(void *data) { - sigset_t winch; - int sig; - int master = ((int *)data)[0]; - int slave = ((int *)data)[1]; + sigset_t winch; + int sig; + int master = ((int *)data)[0]; + int slave = ((int *)data)[1]; - sigemptyset(&winch); - sigaddset(&winch, SIGWINCH); + sigemptyset(&winch); + sigaddset(&winch, SIGWINCH); - do { - // Wait for a SIGWINCH - sigwait(&winch, &sig); + do { + // Wait for a SIGWINCH + sigwait(&winch, &sig); - if (closing_time) break; + if (closing_time) break; - // Get the new terminal size - struct winsize w; - if (ioctl(master, TIOCGWINSZ, &w) == -1) { - continue; - } + // Get the new terminal size + struct winsize w; + if (ioctl(master, TIOCGWINSZ, &w) == -1) { + continue; + } - // Set the new terminal size - ioctl(slave, TIOCSWINSZ, &w); + // Set the new terminal size + ioctl(slave, TIOCSWINSZ, &w); - } while (1); + } while (1); - free(data); - return NULL; + free(data); + return NULL; } /** @@ -260,35 +250,35 @@ static void *watch_sigwinch(void *data) { * on success, 0 */ int watch_sigwinch_async(int master, int slave) { - pthread_t watcher; - int *files = (int *) malloc(sizeof(int) * 2); - if (files == NULL) { - return -1; - } + pthread_t watcher; + int *files = (int *) malloc(sizeof(int) * 2); + if (files == NULL) { + return -1; + } - // Block SIGWINCH so sigwait can later receive it - sigset_t winch; - sigemptyset(&winch); - sigaddset(&winch, SIGWINCH); - if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) { - free(files); - return -1; - } + // Block SIGWINCH so sigwait can later receive it + sigset_t winch; + sigemptyset(&winch); + sigaddset(&winch, SIGWINCH); + if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) { + free(files); + return -1; + } - // Initialize some variables, then start the thread - closing_time = 0; - files[0] = master; - files[1] = slave; - int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files); - if (ret != 0) { - free(files); - errno = ret; - return -1; - } + // Initialize some variables, then start the thread + closing_time = 0; + files[0] = master; + files[1] = slave; + int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files); + if (ret != 0) { + free(files); + errno = ret; + return -1; + } - // Set the initial terminal size - raise(SIGWINCH); - return 0; + // Set the initial terminal size + raise(SIGWINCH); + return 0; } /** @@ -297,8 +287,8 @@ int watch_sigwinch_async(int master, int slave) { * Cause the SIGWINCH watcher thread to terminate */ void watch_sigwinch_cleanup(void) { - closing_time = 1; - raise(SIGWINCH); + closing_time = 1; + raise(SIGWINCH); } /** @@ -308,11 +298,11 @@ void watch_sigwinch_cleanup(void) { * in a seperate thread */ void pump_stdin_async(int outfd) { - // Put stdin into raw mode - set_stdin_raw(); + // Put stdin into raw mode + set_stdin_raw(); - // Pump data from stdin to the PTY - pump_async(STDIN_FILENO, outfd); + // Pump data from stdin to the PTY + pump_async(STDIN_FILENO, outfd); } /** @@ -324,10 +314,10 @@ void pump_stdin_async(int outfd) { * Before returning, restores stdin settings. */ void pump_stdout_blocking(int infd) { - // Pump data from stdout to PTY - pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */); + // Pump data from stdout to PTY + pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */); - // Cleanup - restore_stdin(); - watch_sigwinch_cleanup(); + // Cleanup + restore_stdin(); + watch_sigwinch_cleanup(); } diff --git a/pts.h b/pts.h index c32364362..8f645562f 100644 --- a/pts.h +++ b/pts.h @@ -1,17 +1,5 @@ /* * Copyright 2013, Tan Chee Eng (@tan-ce) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ /* @@ -24,6 +12,8 @@ #ifndef _PTS_H_ #define _PTS_H_ +#include + /** * pts_open * diff --git a/su.c b/su.c index b97dbe606..b114eb29e 100644 --- a/su.c +++ b/su.c @@ -1,896 +1,431 @@ -// vim: set ts=4 expandtab sw=4 : /* -** Copyright 2017, John Wu (@topjohnwu) -** Copyright 2015, Pierre-Hugues Husson -** Copyright 2010, Adam Shanks (@ChainsDD) -** Copyright 2008, Zinx Verituse (@zinxv) -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ + * Copyright 2017, John Wu (@topjohnwu) + * Copyright 2015, Pierre-Hugues Husson + * Copyright 2010, Adam Shanks (@ChainsDD) + * Copyright 2008, Zinx Verituse (@zinxv) + */ + +/* su.c - The main function running in the daemon + */ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include #include -#include -#include +#include #include -#include -#include +#include +#include #include -#include -#include +#include #include -#include +#include -#include "su.h" +#include "magisk.h" #include "utils.h" +#include "resetprop.h" +#include "su.h" -extern int is_daemon; -extern int daemon_from_uid; -extern int daemon_from_pid; - -unsigned get_shell_uid() { - struct passwd* ppwd = getpwnam("shell"); - if (NULL == ppwd) { - return 2000; - } - - return ppwd->pw_uid; -} - -unsigned get_system_uid() { - struct passwd* ppwd = getpwnam("system"); - if (NULL == ppwd) { - return 1000; - } - - return ppwd->pw_uid; -} - -unsigned get_radio_uid() { - struct passwd* ppwd = getpwnam("radio"); - if (NULL == ppwd) { - return 1001; - } - - return ppwd->pw_uid; -} - -int fork_zero_fucks() { - int pid = fork(); - if (pid) { - int status; - waitpid(pid, &status, 0); - return pid; - } - else { - if ( (pid = fork()) != 0) - exit(0); - return 0; - } -} - -static int from_init(struct su_initiator *from) { - char path[PATH_MAX], exe[PATH_MAX]; - char args[4096], *argv0, *argv_rest; - int fd; - ssize_t len; - int i; - int err; - - from->uid = getuid(); - from->pid = getppid(); - - if (is_daemon) { - from->uid = daemon_from_uid; - from->pid = daemon_from_pid; - } - - /* Get the command line */ - snprintf(path, sizeof(path), "/proc/%u/cmdline", from->pid); - fd = open(path, O_RDONLY); - if (fd < 0) { - PLOGE("Opening command line"); - return -1; - } - len = read(fd, args, sizeof(args)); - err = errno; - close(fd); - if (len < 0 || len == sizeof(args)) { - PLOGEV("Reading command line", err); - return -1; - } - - argv0 = args; - argv_rest = NULL; - for (i = 0; i < len; i++) { - if (args[i] == '\0') { - if (!argv_rest) { - argv_rest = &args[i+1]; - } else { - args[i] = ' '; - } - } - } - args[len] = '\0'; - - if (argv_rest) { - strncpy(from->args, argv_rest, sizeof(from->args)); - from->args[sizeof(from->args)-1] = '\0'; - } else { - from->args[0] = '\0'; - } - - /* If this isn't app_process, use the real path instead of argv[0] */ - snprintf(path, sizeof(path), "/proc/%u/exe", from->pid); - len = readlink(path, exe, sizeof(exe)); - if (len < 0) { - PLOGE("Getting exe path"); - return -1; - } - exe[len] = '\0'; - if (strcmp(exe, "/system/bin/app_process")) { - argv0 = exe; - } - - strncpy(from->bin, argv0, sizeof(from->bin)); - from->bin[sizeof(from->bin)-1] = '\0'; - - return 0; -} - -static int get_multiuser_mode() { - char *data; - char sdk_ver[PROPERTY_VALUE_MAX]; - - data = read_file("/system/build.prop"); - get_property(data, sdk_ver, "ro.build.version.sdk", "0"); - free(data); - - int sdk = atoi(sdk_ver); - if (sdk < 17) - return MULTIUSER_MODE_NONE; - - int ret = MULTIUSER_MODE_OWNER_ONLY; - char mode[12]; - FILE *fp; - if ((fp = fopen(REQUESTOR_MULTIUSER_MODE, "r"))) { - fgets(mode, sizeof(mode), fp); - int last = strlen(mode) - 1; - if (mode[last] == '\n') - mode[last] = '\0'; - if (strcmp(mode, MULTIUSER_VALUE_USER) == 0) { - ret = MULTIUSER_MODE_USER; - } else if (strcmp(mode, MULTIUSER_VALUE_OWNER_MANAGED) == 0) { - ret = MULTIUSER_MODE_OWNER_MANAGED; - } - else { - ret = MULTIUSER_MODE_OWNER_ONLY; - } - fclose(fp); - } - return ret; -} - -static void read_options(struct su_context *ctx) { - ctx->user.multiuser_mode = get_multiuser_mode(); -} - -static void user_init(struct su_context *ctx) { - if (ctx->from.uid > 99999) { - ctx->user.android_user_id = ctx->from.uid / 100000; - if (ctx->user.multiuser_mode == MULTIUSER_MODE_USER) { - snprintf(ctx->user.database_path, PATH_MAX, "%s/%d/%s", REQUESTOR_USER_PATH, ctx->user.android_user_id, REQUESTOR_DATABASE_PATH); - snprintf(ctx->user.base_path, PATH_MAX, "%s/%d/%s", REQUESTOR_USER_PATH, ctx->user.android_user_id, REQUESTOR); - } - } -} - -static void populate_environment(const struct su_context *ctx) { - struct passwd *pw; - - if (ctx->to.keepenv) - return; - - pw = getpwuid(ctx->to.uid); - if (pw) { - setenv("HOME", pw->pw_dir, 1); - if (ctx->to.shell) - setenv("SHELL", ctx->to.shell, 1); - else - setenv("SHELL", DEFAULT_SHELL, 1); - if (ctx->to.login || ctx->to.uid) { - setenv("USER", pw->pw_name, 1); - setenv("LOGNAME", pw->pw_name, 1); - } - } -} - -void set_identity(unsigned int uid) { - /* - * Set effective uid back to root, otherwise setres[ug]id will fail - * if uid isn't root. - */ - if (seteuid(0)) { - PLOGE("seteuid (root)"); - exit(EXIT_FAILURE); - } - if (setresgid(uid, uid, uid)) { - PLOGE("setresgid (%u)", uid); - exit(EXIT_FAILURE); - } - if (setresuid(uid, uid, uid)) { - PLOGE("setresuid (%u)", uid); - exit(EXIT_FAILURE); - } -} - -static void socket_cleanup(struct su_context *ctx) { - if (ctx && ctx->sock_path[0]) { - if (unlink(ctx->sock_path)) - PLOGE("unlink (%s)", ctx->sock_path); - ctx->sock_path[0] = 0; - } -} - -/* - * For use in signal handlers/atexit-function - * NOTE: su_ctx points to main's local variable. - * It's OK due to the program uses exit(3), not return from main() - */ -static struct su_context *su_ctx = NULL; - -static void cleanup(void) { - socket_cleanup(su_ctx); -} - -static void cleanup_signal(int sig) { - socket_cleanup(su_ctx); - exit(128 + sig); -} - -static int socket_create_temp(char *path, size_t len) { - int fd; - struct sockaddr_un sun; - - fd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (fd < 0) { - PLOGE("socket"); - return -1; - } - if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { - PLOGE("fcntl FD_CLOEXEC"); - goto err; - } - - memset(&sun, 0, sizeof(sun)); - sun.sun_family = AF_LOCAL; - snprintf(path, len, "%s/.socket%d", REQUESTOR_CACHE_PATH, getpid()); - memset(sun.sun_path, 0, sizeof(sun.sun_path)); - snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", path); - - /* - * Delete the socket to protect from situations when - * something bad occured previously and the kernel reused pid from that process. - * Small probability, isn't it. - */ - unlink(sun.sun_path); - - if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) { - PLOGE("bind"); - goto err; - } - - if (listen(fd, 1) < 0) { - PLOGE("listen"); - goto err; - } - - return fd; -err: - close(fd); - return -1; -} - -static int socket_accept(int serv_fd) { - struct timeval tv; - fd_set fds; - int fd, rc; - - /* Wait 20 seconds for a connection, then give up. */ - tv.tv_sec = 20; - tv.tv_usec = 0; - FD_ZERO(&fds); - FD_SET(serv_fd, &fds); - do { - rc = select(serv_fd + 1, &fds, NULL, NULL, &tv); - } while (rc < 0 && errno == EINTR); - if (rc < 1) { - PLOGE("select"); - return -1; - } - - fd = accept(serv_fd, NULL, NULL); - if (fd < 0) { - PLOGE("accept"); - return -1; - } - - return fd; -} - -#define write_data(fd, data, data_len) \ -do { \ - uint32_t __len = htonl(data_len); \ - __len = write((fd), &__len, sizeof(__len)); \ - if (__len != sizeof(__len)) { \ - PLOGE("write(" #data ")"); \ - return -1; \ - } \ - __len = write((fd), data, data_len); \ - if (__len != data_len) { \ - PLOGE("write(" #data ")"); \ - return -1; \ - } \ -} while (0) - -#define write_string_data(fd, name, data) \ -do { \ - write_data(fd, name, strlen(name)); \ - write_data(fd, data, strlen(data)); \ -} while (0) - -// stringify everything. -#define write_token(fd, name, data) \ -do { \ - char buf[16]; \ - snprintf(buf, sizeof(buf), "%d", data); \ - write_string_data(fd, name, buf); \ -} while (0) - -static int socket_send_request(int fd, const struct su_context *ctx) { - write_string_data(fd, "version", VERSION); - write_token(fd, "versionCode", VERSION_CODE); - write_token(fd, "uid", ctx->from.uid); - write_token(fd, "eof", PROTO_VERSION); - return 0; -} - -static int socket_receive_result(int fd, char *result, ssize_t result_len) { - ssize_t len; - - LOGD("waiting for user"); - len = read(fd, result, result_len-1); - if (len < 0) { - PLOGE("read(result)"); - return -1; - } - result[len] = '\0'; - - return 0; -} +static struct su_context *su_ctx; static void usage(int status) { - FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr; + FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr; - fprintf(stream, - "Usage: su [options] [--] [-] [LOGIN] [--] [args...]\n\n" - "Options:\n" - " --daemon start the su daemon agent\n" - " -c, --command COMMAND pass COMMAND to the invoked shell\n" - " -h, --help display this help message and exit\n" - " -, -l, --login pretend the shell to be a login shell\n" - " -m, -p,\n" - " --preserve-environment do not change environment variables\n" - " -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n" - " -u display the multiuser mode and exit\n" - " -v, --version display version number and exit\n" - " -V display version code and exit,\n" - " this is used almost exclusively by Superuser.apk\n"); - exit(status); -} - -static __attribute__ ((noreturn)) void deny(struct su_context *ctx) { - char *cmd = get_command(&ctx->to); - - int send_to_app = 1; - - // no need to log if called by root - if (ctx->from.uid == AID_ROOT) - send_to_app = 0; - - // dumpstate (which logs to logcat/shell) will spam the crap out of the system with su calls - if (strcmp("/system/bin/dumpstate", ctx->from.bin) == 0) - send_to_app = 0; - - if (send_to_app) - send_result(ctx, DENY); - - LOGW("request rejected (%u->%u %s)", ctx->from.uid, ctx->to.uid, cmd); - fprintf(stderr, "%s\n", strerror(EACCES)); - exit(EXIT_FAILURE); -} - -static __attribute__ ((noreturn)) void allow(struct su_context *ctx) { - char *arg0; - int argc, err; - - umask(ctx->umask); - int send_to_app = 1; - - // no need to log if called by root - if (ctx->from.uid == AID_ROOT) - send_to_app = 0; - - // dumpstate (which logs to logcat/shell) will spam the crap out of the system with su calls - if (strcmp("/system/bin/dumpstate", ctx->from.bin) == 0) - send_to_app = 0; - - if (send_to_app) - send_result(ctx, ALLOW); - - char *binary; - argc = ctx->to.optind; - if (ctx->to.command) { - binary = ctx->to.shell; - ctx->to.argv[--argc] = ctx->to.command; - ctx->to.argv[--argc] = "-c"; - } - else if (ctx->to.shell) { - binary = ctx->to.shell; - } - else { - if (ctx->to.argv[argc]) { - binary = ctx->to.argv[argc++]; - } - else { - binary = DEFAULT_SHELL; - } - } - - arg0 = strrchr (binary, '/'); - arg0 = (arg0) ? arg0 + 1 : binary; - if (ctx->to.login) { - int s = strlen(arg0) + 2; - char *p = malloc(s); - - if (!p) - exit(EXIT_FAILURE); - - *p = '-'; - strcpy(p + 1, arg0); - arg0 = p; - } - - populate_environment(ctx); - set_identity(ctx->to.uid); - -#define PARG(arg) \ - (argc + (arg) < ctx->to.argc) ? " " : "", \ - (argc + (arg) < ctx->to.argc) ? ctx->to.argv[argc + (arg)] : "" - - LOGD("%u %s executing %u %s using binary %s : %s%s%s%s%s%s%s%s%s%s%s%s%s%s", - ctx->from.uid, ctx->from.bin, - ctx->to.uid, get_command(&ctx->to), binary, - arg0, PARG(0), PARG(1), PARG(2), PARG(3), PARG(4), PARG(5), - (ctx->to.optind + 6 < ctx->to.argc) ? " ..." : ""); - - setexeccon("u:r:su:s0"); - - ctx->to.argv[--argc] = arg0; - execvp(binary, ctx->to.argv + argc); - err = errno; - PLOGE("exec"); - fprintf(stderr, "Cannot execute %s: %s\n", binary, strerror(err)); - exit(EXIT_FAILURE); -} - -static int get_api_version() { - char sdk_ver[PROPERTY_VALUE_MAX]; - char *data = read_file("/system/build.prop"); - get_property(data, sdk_ver, "ro.build.version.sdk", "0"); - int ver = atoi(sdk_ver); - free(data); - return ver; -} - -static void fork_for_samsung(void) -{ - // Samsung CONFIG_SEC_RESTRICT_SETUID wants the parent process to have - // EUID 0, or else our setresuid() calls will be denied. So make sure - // all such syscalls are executed by a child process. - int rv; - - switch (fork()) { - case 0: - return; - case -1: - PLOGE("fork"); - exit(1); - default: - if (wait(&rv) < 0) { - exit(1); - } else { - exit(WEXITSTATUS(rv)); - } - } + fprintf(stream, + "Usage: su [options] [--] [-] [LOGIN] [--] [args...]\n\n" + "Options:\n" + " -c, --command COMMAND pass COMMAND to the invoked shell\n" + " -h, --help display this help message and exit\n" + " -, -l, --login pretend the shell to be a login shell\n" + " -m, -p,\n" + " --preserve-environment do not change environment variables\n" + " -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n" + " -u display the multiuser mode and exit\n" + " -v, --version display version number and exit\n" + " -V display version code and exit,\n" + " this is used almost exclusively by Superuser.apk\n"); + exit(status); } static char *concat_commands(int argc, char *argv[]) { - char command[ARG_MAX]; - int i; - command[0] = '\0'; - for (i = optind - 1; i < argc; ++i) { - if (strlen(command)) - sprintf(command, "%s %s", command, argv[i]); - else - sprintf(command, "%s", argv[i]); - } - return strdup(command); + char command[ARG_MAX]; + command[0] = '\0'; + for (int i = optind - 1; i < argc; ++i) { + if (strlen(command)) + sprintf(command, "%s %s", command, argv[i]); + else + sprintf(command, "%s", argv[i]); + } + return strdup(command); } -int su_main(int argc, char *argv[]) { - if (argc == 2 && strcmp(argv[1], "--daemon") == 0) { - //Everything we'll exec will be in su, not su_daemon - setexeccon("u:r:su:s0"); - return run_daemon(); - } - int ppid = getppid(); - if ((geteuid() != AID_ROOT && getuid() != AID_ROOT) || - (get_api_version() >= 18 && getuid() == AID_SHELL) || - get_api_version() >= 19) { - // attempt to connect to daemon... - LOGD("starting daemon client %d %d", getuid(), geteuid()); - return connect_daemon(argc, argv, ppid); - } else { - return su_main_nodaemon(argc, argv); - } - +static int get_multiuser_mode() { + // TODO: Multiuser support + return MULTIUSER_MODE_NONE; } -int su_main_nodaemon(int argc, char **argv) { - int ppid = getppid(); +static void populate_environment(const struct su_context *ctx) { + struct passwd *pw; - // Only fork when not running in daemon mode (already forked from daemon) - if (! is_daemon) - fork_for_samsung(); + if (ctx->to.keepenv) + return; - // Sanitize all secure environment variables (from linker_environ.c in AOSP linker). - /* The same list than GLibc at this point */ - static const char* const unsec_vars[] = { - "GCONV_PATH", - "GETCONF_DIR", - "HOSTALIASES", - "LD_AUDIT", - "LD_DEBUG", - "LD_DEBUG_OUTPUT", - "LD_DYNAMIC_WEAK", - "LD_LIBRARY_PATH", - "LD_ORIGIN_PATH", - "LD_PRELOAD", - "LD_PROFILE", - "LD_SHOW_AUXV", - "LD_USE_LOAD_BIAS", - "LOCALDOMAIN", - "LOCPATH", - "MALLOC_TRACE", - "MALLOC_CHECK_", - "NIS_PATH", - "NLSPATH", - "RESOLV_HOST_CONF", - "RES_OPTIONS", - "TMPDIR", - "TZDIR", - "LD_AOUT_LIBRARY_PATH", - "LD_AOUT_PRELOAD", - // not listed in linker, used due to system() call - "IFS", - }; - if(getauxval(AT_SECURE)) { - const char* const* cp = unsec_vars; - const char* const* endp = cp + sizeof(unsec_vars)/sizeof(unsec_vars[0]); - while (cp < endp) { - unsetenv(*cp); - cp++; - } - } - - LOGD("su invoked."); - - // Replace -cn with z, for CF compatibility - for (int i = 0; i < argc; ++i) { - if (strcmp(argv[i], "-cn") == 0) { - strcpy(argv[i], "-z"); - break; - } - } - - struct su_context ctx = { - .from = { - .pid = -1, - .uid = 0, - .bin = "", - .args = "", - }, - .to = { - .uid = AID_ROOT, - .login = 0, - .keepenv = 0, - .shell = NULL, - .command = NULL, - .argv = argv, - .argc = argc, - .optind = 0, - }, - .user = { - .android_user_id = 0, - .multiuser_mode = MULTIUSER_MODE_OWNER_ONLY, - .database_path = REQUESTOR_DATA_PATH REQUESTOR_DATABASE_PATH, - .base_path = REQUESTOR_DATA_PATH REQUESTOR - }, - }; - struct stat st; - int c, socket_serv_fd, fd; - char buf[64], *result; - policy_t dballow; - 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' }, - { NULL, 0, NULL, 0 }, - }; - - while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:", long_opts, NULL)) != -1) { - switch(c) { - case 'c': - ctx.to.shell = DEFAULT_SHELL; - ctx.to.command = concat_commands(argc, argv); - optind = argc; - break; - case 'h': - usage(EXIT_SUCCESS); - break; - case 'l': - ctx.to.login = 1; - break; - case 'm': - case 'p': - ctx.to.keepenv = 1; - break; - case 's': - ctx.to.shell = optarg; - break; - case 'V': - printf("%d\n", VERSION_CODE); - exit(EXIT_SUCCESS); - case 'v': - printf("%s\n", VERSION); - exit(EXIT_SUCCESS); - case 'u': - switch (get_multiuser_mode()) { - case MULTIUSER_MODE_USER: - printf("%s\n", MULTIUSER_VALUE_USER); - break; - case MULTIUSER_MODE_OWNER_MANAGED: - printf("%s\n", MULTIUSER_VALUE_OWNER_MANAGED); - break; - case MULTIUSER_MODE_OWNER_ONLY: - printf("%s\n", MULTIUSER_VALUE_OWNER_ONLY); - break; - case MULTIUSER_MODE_NONE: - printf("%s\n", MULTIUSER_VALUE_NONE); - break; - } - exit(EXIT_SUCCESS); - case 'z': - // Do nothing, placed here for legacy support :) - break; - default: - /* Bionic getopt_long doesn't terminate its error output by newline */ - fprintf(stderr, "\n"); - usage(2); - } - } - if (optind < argc && !strcmp(argv[optind], "-")) { - ctx.to.login = 1; - optind++; - } - /* username or uid */ - if (optind < argc && strcmp(argv[optind], "--")) { - struct passwd *pw; - pw = getpwnam(argv[optind]); - if (!pw) { - char *endptr; - - /* It seems we shouldn't do this at all */ - errno = 0; - ctx.to.uid = strtoul(argv[optind], &endptr, 10); - if (errno || *endptr) { - LOGE("Unknown id: %s\n", argv[optind]); - fprintf(stderr, "Unknown id: %s\n", argv[optind]); - exit(EXIT_FAILURE); - } - } else { - ctx.to.uid = pw->pw_uid; - } - optind++; - } - if (optind < argc && !strcmp(argv[optind], "--")) { - optind++; - } - ctx.to.optind = optind; - - su_ctx = &ctx; - if (from_init(&ctx.from) < 0) { - deny(&ctx); - } - - read_options(&ctx); - user_init(&ctx); - - // the latter two are necessary for stock ROMs like note 2 which do dumb things with su, or crash otherwise - if (ctx.from.uid == AID_ROOT) { - LOGD("Allowing root/system/radio."); - allow(&ctx); - } - - // verify superuser is installed - if (stat(ctx.user.base_path, &st) < 0) { - // send to market (disabled, because people are and think this is hijacking their su) - // if (0 == strcmp(JAVA_PACKAGE_NAME, REQUESTOR)) - // silent_run("am start -d http://www.clockworkmod.com/superuser/install.html -a android.intent.action.VIEW"); - PLOGE("stat %s", ctx.user.base_path); - deny(&ctx); - } - - // odd perms on superuser data dir - if (st.st_gid != st.st_uid) { - LOGE("Bad uid/gid %d/%d for Superuser Requestor application", - (int)st.st_uid, (int)st.st_gid); - deny(&ctx); - } - - // 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(&ctx); - } - - // 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(&ctx); - } - - // Add prop check - char value[PROP_VALUE_MAX]; - __system_property_get(ROOT_ACCESS_PROP, value); - if(strlen(value)) { - int prop_status = atoi(value); - switch(prop_status) { - case ROOT_ACCESS_DISABLED: - exit(EXIT_FAILURE); - case ROOT_ACCESS_APPS_ONLY: - if (ctx.from.uid == AID_SHELL) - exit(EXIT_FAILURE); - break; - case ROOT_ACCESS_ADB_ONLY: - if (ctx.from.uid != AID_SHELL) - exit(EXIT_FAILURE); - break; - case ROOT_ACCESS_APPS_AND_ADB: - default: - break; - } - } else { - exit(EXIT_FAILURE); - } - - ctx.umask = umask(027); - - mkdir(REQUESTOR_CACHE_PATH, 0770); - 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); - deny(&ctx); - } - - if (setgroups(0, NULL)) { - PLOGE("setgroups"); - deny(&ctx); - } - if (setegid(st.st_gid)) { - PLOGE("setegid (%u)", st.st_gid); - deny(&ctx); - } - if (seteuid(st.st_uid)) { - PLOGE("seteuid (%u)", st.st_uid); - deny(&ctx); - } - - dballow = database_check(&ctx); - switch (dballow) { - case INTERACTIVE: - break; - case ALLOW: - LOGD("db allowed"); - allow(&ctx); /* never returns */ - case DENY: - default: - LOGD("db denied"); - deny(&ctx); /* never returns too */ - } - - socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path)); - LOGD(ctx.sock_path); - if (socket_serv_fd < 0) { - deny(&ctx); - } - - signal(SIGHUP, cleanup_signal); - signal(SIGPIPE, cleanup_signal); - signal(SIGTERM, cleanup_signal); - signal(SIGQUIT, cleanup_signal); - signal(SIGINT, cleanup_signal); - signal(SIGABRT, cleanup_signal); - - if (send_request(&ctx) < 0) { - deny(&ctx); - } - - atexit(cleanup); - - fd = socket_accept(socket_serv_fd); - if (fd < 0) { - deny(&ctx); - } - if (socket_send_request(fd, &ctx)) { - deny(&ctx); - } - if (socket_receive_result(fd, buf, sizeof(buf))) { - deny(&ctx); - } - - close(fd); - close(socket_serv_fd); - socket_cleanup(&ctx); - - result = buf; - -#define SOCKET_RESPONSE "socket:" - if (strncmp(result, SOCKET_RESPONSE, sizeof(SOCKET_RESPONSE) - 1)) - LOGW("SECURITY RISK: Requestor still receives credentials in intent"); - else - result += sizeof(SOCKET_RESPONSE) - 1; - - if (!strcmp(result, "DENY")) { - deny(&ctx); - } else if (!strcmp(result, "ALLOW")) { - allow(&ctx); - } else { - LOGE("unknown response from Superuser Requestor: %s", result); - deny(&ctx); - } + pw = getpwuid(ctx->to.uid); + if (pw) { + setenv("HOME", pw->pw_dir, 1); + if (ctx->to.shell) + setenv("SHELL", ctx->to.shell, 1); + else + setenv("SHELL", DEFAULT_SHELL, 1); + if (ctx->to.login || ctx->to.uid) { + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + } + } } + +static __attribute__ ((noreturn)) void allow() { + char *arg0; + + umask(su_ctx->umask); + + // no need to log if called by root + if (su_ctx->from.uid != UID_ROOT) + app_send_result(su_ctx, ALLOW); + + char *binary = su_ctx->to.shell; + if (su_ctx->to.command) { + su_ctx->to.argv[--su_ctx->to.argc] = su_ctx->to.command; + su_ctx->to.argv[--su_ctx->to.argc] = "-c"; + } + + arg0 = strrchr(binary, '/'); + arg0 = arg0 ? (arg0 + 1) : binary; + if (su_ctx->to.login) { + int s = strlen(arg0) + 2; + char *p = xmalloc(s); + *p = '-'; + strcpy(p + 1, arg0); + arg0 = p; + } + + populate_environment(su_ctx); + set_identity(su_ctx->to.uid); + + setexeccon("u:r:su:s0"); + + su_ctx->to.argv[--su_ctx->to.argc] = arg0; + execvp(binary, su_ctx->to.argv + su_ctx->to.argc); + fprintf(stderr, "Cannot execute %s: %s\n", binary, strerror(errno)); + PLOGE("exec"); + exit(EXIT_FAILURE); +} + +static __attribute__ ((noreturn)) void deny() { + // no need to log if called by root + if (su_ctx->from.uid != UID_ROOT) + app_send_result(su_ctx, DENY); + + LOGW("su: request rejected (%u->%u)", su_ctx->from.uid, su_ctx->to.uid); + fprintf(stderr, "%s\n", strerror(EACCES)); + exit(EXIT_FAILURE); +} + +static void socket_cleanup() { + if (su_ctx && su_ctx->sock_path[0]) { + unlink(su_ctx->sock_path); + su_ctx->sock_path[0] = 0; + } +} + +static void cleanup_signal(int sig) { + socket_cleanup(); + exit(EXIT_FAILURE); +} + +int su_daemon_main(int argc, char **argv) { + // Sanitize all secure environment variables (from linker_environ.c in AOSP linker). + /* The same list than GLibc at this point */ + static const char* const unsec_vars[] = { + "GCONV_PATH", + "GETCONF_DIR", + "HOSTALIASES", + "LD_AUDIT", + "LD_DEBUG", + "LD_DEBUG_OUTPUT", + "LD_DYNAMIC_WEAK", + "LD_LIBRARY_PATH", + "LD_ORIGIN_PATH", + "LD_PRELOAD", + "LD_PROFILE", + "LD_SHOW_AUXV", + "LD_USE_LOAD_BIAS", + "LOCALDOMAIN", + "LOCPATH", + "MALLOC_TRACE", + "MALLOC_CHECK_", + "NIS_PATH", + "NLSPATH", + "RESOLV_HOST_CONF", + "RES_OPTIONS", + "TMPDIR", + "TZDIR", + "LD_AOUT_LIBRARY_PATH", + "LD_AOUT_PRELOAD", + // not listed in linker, used due to system() call + "IFS", + }; + if(getauxval(AT_SECURE)) { + const char* const* cp = unsec_vars; + const char* const* endp = cp + sizeof(unsec_vars)/sizeof(unsec_vars[0]); + while (cp < endp) { + unsetenv(*cp); + cp++; + } + } + + // Replace -cn with z, for CF compatibility + for (int i = 0; i < argc; ++i) { + if (strcmp(argv[i], "-cn") == 0) { + strcpy(argv[i], "-z"); + break; + } + } + + // Default values + struct su_context ctx = { + .from = { + .pid = from_pid, + .uid = from_uid, + }, + .to = { + .uid = UID_ROOT, + .login = 0, + .keepenv = 0, + .shell = DEFAULT_SHELL, + .command = NULL, + .argv = argv, + .argc = argc, + }, + .user = { + .android_user_id = 0, + .multiuser_mode = get_multiuser_mode(), + .database_path = APPLICATION_DATA_PATH REQUESTOR_DATABASE_PATH, + .base_path = APPLICATION_DATA_PATH REQUESTOR + }, + .umask = umask(027), + }; + su_ctx = &ctx; + + struct stat st; + int c, socket_serv_fd, fd; + char result[64]; + policy_t dballow; + 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' }, + { NULL, 0, NULL, 0 }, + }; + + while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:", long_opts, NULL)) != -1) { + switch (c) { + case 'c': + ctx.to.command = concat_commands(argc, argv); + optind = argc; + break; + case 'h': + usage(EXIT_SUCCESS); + break; + case 'l': + ctx.to.login = 1; + break; + case 'm': + case 'p': + ctx.to.keepenv = 1; + break; + case 's': + ctx.to.shell = optarg; + break; + case 'V': + printf("%d\n", VERSION_CODE); + exit(EXIT_SUCCESS); + case 'v': + printf("%s\n", SU_VERSION_STR); + exit(EXIT_SUCCESS); + case 'u': + switch (ctx.user.multiuser_mode) { + case MULTIUSER_MODE_USER: + printf("%s\n", MULTIUSER_VALUE_USER); + break; + case MULTIUSER_MODE_OWNER_MANAGED: + printf("%s\n", MULTIUSER_VALUE_OWNER_MANAGED); + break; + case MULTIUSER_MODE_OWNER_ONLY: + printf("%s\n", MULTIUSER_VALUE_OWNER_ONLY); + break; + case MULTIUSER_MODE_NONE: + printf("%s\n", MULTIUSER_VALUE_NONE); + break; + } + exit(EXIT_SUCCESS); + case 'z': + // Do nothing, placed here for legacy support :) + break; + default: + /* Bionic getopt_long doesn't terminate its error output by newline */ + fprintf(stderr, "\n"); + usage(2); + } + } + if (optind < argc && !strcmp(argv[optind], "-")) { + ctx.to.login = 1; + optind++; + } + /* username or uid */ + if (optind < argc && strcmp(argv[optind], "--")) { + struct passwd *pw; + pw = getpwnam(argv[optind]); + if (!pw) { + char *endptr; + + /* It seems we shouldn't do this at all */ + errno = 0; + ctx.to.uid = strtoul(argv[optind], &endptr, 10); + if (errno || *endptr) { + LOGE("Unknown id: %s\n", argv[optind]); + fprintf(stderr, "Unknown id: %s\n", argv[optind]); + exit(EXIT_FAILURE); + } + } else { + ctx.to.uid = pw->pw_uid; + } + optind++; + } + if (optind < argc && !strcmp(argv[optind], "--")) { + optind++; + } + + // Check property of root configuration + char *root_prop = getprop(ROOT_ACCESS_PROP); + if (root_prop) { + int prop_status = atoi(root_prop); + switch (prop_status) { + case ROOT_ACCESS_DISABLED: + exit(EXIT_FAILURE); + case ROOT_ACCESS_APPS_ONLY: + if (ctx.from.uid == UID_SHELL) + exit(EXIT_FAILURE); + break; + case ROOT_ACCESS_ADB_ONLY: + if (ctx.from.uid != UID_SHELL) + exit(EXIT_FAILURE); + break; + case ROOT_ACCESS_APPS_AND_ADB: + default: + break; + } + } else { + exit(EXIT_FAILURE); + } + free(root_prop); + + // The su_context setup is done, now every error leads to deny + err_handler = deny; + + // It's in multiuser mode + if (ctx.from.uid > 99999) { + ctx.user.android_user_id = ctx.from.uid / 100000; + if (ctx.user.multiuser_mode == MULTIUSER_MODE_USER) { + snprintf(ctx.user.database_path, PATH_MAX, "%s/%d/%s", + USER_DATA_PATH, ctx.user.android_user_id, REQUESTOR_DATABASE_PATH); + snprintf(ctx.user.base_path, PATH_MAX, "%s/%d/%s", + USER_DATA_PATH, ctx.user.android_user_id, REQUESTOR); + } + } + + // Allow root to start root + if (ctx.from.uid == UID_ROOT) { + allow(); + } + + // verify superuser is installed + xstat(ctx.user.base_path, &st); + + // odd perms on superuser data dir + if (st.st_gid != st.st_uid) { + LOGE("Bad uid/gid %d/%d for Superuser Requestor application", + (int)st.st_uid, (int)st.st_gid); + 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(); + } + + // 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); + 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); + } + + if (setgroups(0, NULL)) { + PLOGE("setgroups"); + } + if (setegid(st.st_gid)) { + PLOGE("setegid (%u)", st.st_gid); + } + if (seteuid(st.st_uid)) { + PLOGE("seteuid (%u)", st.st_uid); + } + + // If db exits, check directly instead query application + dballow = database_check(&ctx); + switch (dballow) { + case INTERACTIVE: + break; + case ALLOW: + allow(); + case DENY: + default: + deny(); + } + + // New request or no db exist, notify app for response + socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path)); + setup_sighandlers(cleanup_signal); + + // Start activity + app_send_request(su_ctx); + + atexit(socket_cleanup); + + fd = socket_accept(socket_serv_fd); + socket_send_request(fd, &ctx); + socket_receive_result(fd, result, sizeof(result)); + + close(fd); + close(socket_serv_fd); + socket_cleanup(); + + if (strcmp(result, "socket:DENY") == 0) { + deny(); + } else if (strcmp(result, "socket:ALLOW") == 0) { + allow(); + } else { + LOGE("unknown response from Superuser Requestor: %s", result); + deny(); + } +} + diff --git a/su.h b/su.h index d2ab7b61f..ef5d04f64 100644 --- a/su.h +++ b/su.h @@ -1,42 +1,16 @@ -/* -** Copyright 2010, Adam Shanks (@ChainsDD) -** Copyright 2008, Zinx Verituse (@zinxv) -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ +/* su.h - Store all general su info + */ -#ifndef SU_h -#define SU_h 1 +#ifndef _SU_H_ +#define _SU_H_ -#include "magisk.h" +#include +#include -#ifndef AID_SHELL -#define AID_SHELL (get_shell_uid()) -#endif +#define SU_VERSION_STR xstr(VERSION) ":MAGISKSU (topjohnwu)" -#ifndef AID_ROOT -#define AID_ROOT 0 -#endif - -#ifndef AID_SYSTEM -#define AID_SYSTEM (get_system_uid()) -#endif - -#ifndef AID_RADIO -#define AID_RADIO (get_radio_uid()) -#endif - -#define ROOT_ACCESS_PROP "persist.sys.root_access" +// Property check for root access +#define ROOT_ACCESS_PROP "persist.sys.root_access" #define ROOT_ACCESS_DISABLED 0 #define ROOT_ACCESS_APPS_ONLY 1 #define ROOT_ACCESS_ADB_ONLY 2 @@ -45,22 +19,19 @@ // DO NOT CHANGE LINE BELOW, java package name will always be the same #define JAVA_PACKAGE_NAME "com.topjohnwu.magisk" +#define APPLICATION_DATA_PATH "/data/data/" +#define USER_DATA_PATH "/data/user" + // If --rename-manifest-package is used in AAPT, this // must be changed to correspond to the new APK package name // See the two Android.mk files for more details. -#ifndef REQUESTOR #define REQUESTOR JAVA_PACKAGE_NAME -#endif // This is used if wrapping the fragment classes and activities -// with classes in another package. CM requirement. -#ifndef REQUESTOR_PREFIX +// with classes in another package. #define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser" -#endif -#define REQUESTOR_DATA_PATH "/data/data/" -#define REQUESTOR_FILES_PATH REQUESTOR_DATA_PATH REQUESTOR "/files" -#define REQUESTOR_USER_PATH "/data/user/" +#define REQUESTOR_FILES_PATH APPLICATION_DATA_PATH REQUESTOR "/files" #define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR -#define REQUESTOR_DAEMON_PATH "\0MAGISKSU" + // 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. // it is up to the Android application to symlink as appropriate. @@ -69,69 +40,55 @@ #define DEFAULT_SHELL "/system/bin/sh" -#define xstr(a) str(a) -#define str(a) #a - -#ifndef VERSION_CODE -#define VERSION_CODE 8 -#endif -#define VERSION xstr(VERSION_CODE) ":MAGISKSU (topjohnwu)" - -#define PROTO_VERSION 1 - struct su_initiator { - pid_t pid; - unsigned uid; - unsigned user; - char bin[PATH_MAX]; - char args[4096]; + pid_t pid; + unsigned uid; }; struct su_request { - unsigned uid; - int login; - int keepenv; - char *shell; - char *command; - char **argv; - int argc; - int optind; + unsigned uid; + int login; + int keepenv; + char *shell; + char *command; + char **argv; + int argc; }; struct su_user_info { - // the user in android userspace (multiuser) - // that invoked this action. - unsigned android_user_id; - // how su behaves with multiuser. see enum below. - int multiuser_mode; - // path to superuser directory. this is populated according - // to the multiuser mode. - // this is used to check uid/gid for protecting socket. - // this is used instead of database, as it is more likely - // to exist. db will not exist if su has never launched. - char base_path[PATH_MAX]; - // path to su database. this is populated according - // to the multiuser mode. - char database_path[PATH_MAX]; + // the user in android userspace (multiuser) + // that invoked this action. + unsigned android_user_id; + // how su behaves with multiuser. see enum below. + int multiuser_mode; + // path to superuser directory. this is populated according + // to the multiuser mode. + // this is used to check uid/gid for protecting socket. + // this is used instead of database, as it is more likely + // to exist. db will not exist if su has never launched. + char base_path[PATH_MAX]; + // path to su database. this is populated according + // to the multiuser mode. + char database_path[PATH_MAX]; }; struct su_context { - struct su_initiator from; - struct su_request to; - struct su_user_info user; - mode_t umask; - char sock_path[PATH_MAX]; + struct su_initiator from; + struct su_request to; + struct su_user_info user; + mode_t umask; + char sock_path[PATH_MAX]; }; // multiuser su behavior typedef enum { - // only owner can su - MULTIUSER_MODE_OWNER_ONLY = 0, - // owner gets a su prompt - MULTIUSER_MODE_OWNER_MANAGED = 1, - // user gets a su prompt - MULTIUSER_MODE_USER = 2, - MULTIUSER_MODE_NONE = 3, + // only owner can su + MULTIUSER_MODE_OWNER_ONLY = 0, + // owner gets a su prompt + 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" @@ -140,35 +97,38 @@ typedef enum { #define MULTIUSER_VALUE_NONE "none" typedef enum { - INTERACTIVE = 0, - DENY = 1, - ALLOW = 2, + INTERACTIVE = 0, + DENY = 1, + ALLOW = 2, } policy_t; -extern policy_t database_check(struct su_context *ctx); -extern void set_identity(unsigned int uid); -extern int send_request(struct su_context *ctx); -extern int send_result(struct su_context *ctx, policy_t policy); +extern int from_uid, from_pid; +extern int quit_signals[]; -static inline char *get_command(const struct su_request *to) -{ - if (to->command) - return to->command; - if (to->shell) - return to->shell; - char* ret = to->argv[to->optind]; - if (ret) - return ret; - return DEFAULT_SHELL; -} +// su.c -int run_daemon(); -int connect_daemon(int argc, char *argv[], int ppid); -int su_main(int argc, char *argv[]); -int su_main_nodaemon(int argc, char *argv[]); -// for when you give zero fucks about the state of the child process. -// this version of fork understands you don't care about the child. -// deadbeat dad fork. -int fork_zero_fucks(); +int su_daemon_main(int argc, char **argv); + +// su_client.c + +int socket_create_temp(char *path, size_t len); +int socket_accept(int serv_fd); +void socket_send_request(int fd, const struct su_context *ctx); +void socket_receive_result(int fd, char *result, ssize_t result_len); + +// activity.c + +void app_send_result(struct su_context *ctx, policy_t policy); +void app_send_request(struct su_context *ctx); + +// db.c + +policy_t database_check(struct su_context *ctx); + +// misc.c + +void setup_sighandlers(void (*handler)(int)); +void set_identity(unsigned uid); +char *get_command(const struct su_request *to); #endif diff --git a/su_client.c b/su_client.c new file mode 100644 index 000000000..da81c80e2 --- /dev/null +++ b/su_client.c @@ -0,0 +1,217 @@ +/* su_client.c - The entrypoint for su, connect to daemon and send correct info + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "magisk.h" +#include "daemon.h" +#include "utils.h" +#include "su.h" +#include "pts.h" + +int from_uid, from_pid; + +struct su_daemon_info { + int child; + int client; +}; + +static void *wait_result(void *args) { + struct su_daemon_info *info = args; + LOGD("su: wait_result waiting for %d\n", info->child); + err_handler = exit_thread; + int status, code; + + if (waitpid(info->child, &status, 0) > 0) + code = WEXITSTATUS(status); + else + code = -1; + + // Pass the return code back to the client + write_int(info->client, code); + LOGD("su: return code to client: %d\n", code); + close(info->client); + free(info); + return NULL; +} + +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); + } +} + +void su_daemon_receiver(int client) { + LOGD("su: get request\n"); + // Fork a new process, the child process will need to setsid, + // open a pseudo-terminal, and will eventually run exec + // The parent process (the root daemon) will open a new thread to + // send the return code back to our client + int child = fork(); + if (child < 0) { + PLOGE("fork"); + write(client, &child, sizeof(child)); + close(client); + return; + } else if (child != 0) { + pthread_t wait_thread; + + struct su_daemon_info *info = xmalloc(sizeof(*info)); + info->client = client; + info->child = child; + + // In parent, open a new thread to wait for the child to exit, + // and send the exit code across the wire. + xpthread_create(&wait_thread, NULL, wait_result, info); + return; + } + + LOGD("su: child process started\n"); + + // ack + write_int(client, 1); + + // Here we're in the child + // Set the error handler back to normal + err_handler = exit_proc; + // Become session leader + xsetsid(); + + // Check the credentials + struct ucred credentials; + socklen_t ucred_length = sizeof(struct ucred); + if(getsockopt(client, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) + PLOGE("getsockopt"); + + from_uid = credentials.uid; + from_pid = credentials.pid; + + // 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); + exit(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]); + } + + // Change directory to cwd + char *cwd = read_string(client); + LOGD("su: cwd=[%s]\n", cwd); + chdir(cwd); + free(cwd); + + // Get pts_slave + char *pts_slave = read_string(client); + LOGD("su: pts_slave=[%s]\n", pts_slave); + + // We no longer need the access to socket in the child, close it + close(client); + + //Check pts_slave file is owned by daemon_from_uid + int ptsfd; + struct stat stbuf; + int res = xstat(pts_slave, &stbuf); + + //If caller is not root, ensure the owner of pts_slave is the caller + if(stbuf.st_uid != credentials.uid && credentials.uid != 0) { + LOGE("Wrong permission of pts_slave"); + exit(1); + } + + // Opening the TTY has to occur after the + // fork() and setsid() so that it becomes + // our controlling TTY and not the daemon's + ptsfd = xopen(pts_slave, O_RDWR); + + free(pts_slave); + + // Swap out stdin, stdout, stderr + xdup2(ptsfd, STDIN_FILENO); + xdup2(ptsfd, STDOUT_FILENO); + xdup2(ptsfd, STDERR_FILENO); + + close(ptsfd); + + su_daemon_main(argc, argv); +} + +/* + * Connect daemon, send argc, argv, cwd, pts slave + */ +int su_client_main(int argc, char *argv[]) { + char buffer[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]); + } + + // CWD + getcwd(buffer, sizeof(buffer)); + write_string(socketfd, buffer); + + // We need a PTY. Get one. + ptmx = pts_open(buffer, sizeof(buffer)); + watch_sigwinch_async(STDOUT_FILENO, ptmx); + + // Send the slave path to the daemon + write_string(socketfd, buffer); + + // Wait for acknowledgement from daemon + read_int(socketfd); + + setup_sighandlers(sighandler); + pump_stdin_async(ptmx); + pump_stdout_blocking(ptmx); + + // Get the exit code + int code = read_int(socketfd); + close(socketfd); + + return code; +} diff --git a/su_socket.c b/su_socket.c new file mode 100644 index 000000000..aaf3951f0 --- /dev/null +++ b/su_socket.c @@ -0,0 +1,101 @@ +/* su_socket.c - Functions for communication to client + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "magisk.h" +#include "utils.h" +#include "su.h" + +int socket_create_temp(char *path, size_t len) { + int fd; + struct sockaddr_un sun; + + fd = xsocket(AF_LOCAL, SOCK_STREAM, 0); + if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { + PLOGE("fcntl FD_CLOEXEC"); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_LOCAL; + snprintf(path, len, "%s/.socket%d", REQUESTOR_CACHE_PATH, getpid()); + snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", path); + + /* + * Delete the socket to protect from situations when + * something bad occured previously and the kernel reused pid from that process. + * Small probability, isn't it. + */ + unlink(sun.sun_path); + + xbind(fd, (struct sockaddr*) &sun, sizeof(sun)); + xlisten(fd, 1); + + return fd; +} + +int socket_accept(int serv_fd) { + struct timeval tv; + fd_set fds; + int rc; + + /* Wait 20 seconds for a connection, then give up. */ + tv.tv_sec = 20; + tv.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(serv_fd, &fds); + do { + rc = select(serv_fd + 1, &fds, NULL, NULL, &tv); + } while (rc < 0 && errno == EINTR); + if (rc < 1) { + PLOGE("select"); + } + + return xaccept(serv_fd, NULL, NULL); +} + +#define write_data(fd, data, data_len) \ +do { \ + uint32_t __len = htonl(data_len); \ + __len = write((fd), &__len, sizeof(__len)); \ + if (__len != sizeof(__len)) { \ + PLOGE("write(" #data ")"); \ + } \ + __len = write((fd), data, data_len); \ + if (__len != data_len) { \ + PLOGE("write(" #data ")"); \ + } \ +} while (0) + +#define write_string_data(fd, name, data) \ +do { \ + write_data(fd, name, strlen(name)); \ + write_data(fd, data, strlen(data)); \ +} while (0) + +// stringify everything. +#define write_token(fd, name, data) \ +do { \ + char buf[16]; \ + snprintf(buf, sizeof(buf), "%d", data); \ + write_string_data(fd, name, buf); \ +} while (0) + +void socket_send_request(int fd, const struct su_context *ctx) { + write_token(fd, "uid", ctx->from.uid); + write_token(fd, "eof", 1); +} + +void socket_receive_result(int fd, char *result, ssize_t result_len) { + ssize_t len; + len = xread(fd, result, result_len - 1); + result[len] = '\0'; +} diff --git a/utils.c b/utils.c deleted file mode 100644 index 09b9b60fc..000000000 --- a/utils.c +++ /dev/null @@ -1,112 +0,0 @@ -/* -** Copyright 2012, The CyanogenMod Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "utils.h" - -/* reads a file, making sure it is terminated with \n \0 */ -char* read_file(const char *fn) -{ - struct stat st; - char *data = NULL; - - int fd = open(fn, O_RDONLY); - if (fd < 0) return data; - - if (fstat(fd, &st)) goto oops; - - data = malloc(st.st_size + 2); - if (!data) goto oops; - - if (read(fd, data, st.st_size) != st.st_size) goto oops; - close(fd); - data[st.st_size] = '\n'; - data[st.st_size + 1] = 0; - return data; - -oops: - close(fd); - if (data) free(data); - return NULL; -} - -int get_property(const char *data, char *found, const char *searchkey, const char *not_found) -{ - char *key, *value, *eol, *sol, *tmp; - if (data == NULL) goto defval; - int matched = 0; - sol = strdup(data); - while((eol = strchr(sol, '\n'))) { - key = sol; - *eol++ = 0; - sol = eol; - - value = strchr(key, '='); - if(value == 0) continue; - *value++ = 0; - - while(isspace(*key)) key++; - if(*key == '#') continue; - tmp = value - 2; - while((tmp > key) && isspace(*tmp)) *tmp-- = 0; - - while(isspace(*value)) value++; - tmp = eol - 2; - while((tmp > value) && isspace(*tmp)) *tmp-- = 0; - - if (strncmp(searchkey, key, strlen(searchkey)) == 0) { - matched = 1; - break; - } - } - int len; - if (matched) { - len = strlen(value); - if (len >= PROPERTY_VALUE_MAX) - return -1; - memcpy(found, value, len + 1); - } else goto defval; - return len; - -defval: - len = strlen(not_found); - memcpy(found, not_found, len + 1); - return len; -} - -/* - * Fast version of get_property which purpose is to check - * whether the property with given prefix exists. - * - * Assume nobody is stupid enough to put a propery with prefix ro.cm.version - * in his build.prop on a non-CM ROM and comment it out. - */ -int check_property(const char *data, const char *prefix) -{ - if (!data) - return 0; - return strstr(data, prefix) != NULL; -} diff --git a/utils.h b/utils.h deleted file mode 100644 index a9d4a8f55..000000000 --- a/utils.h +++ /dev/null @@ -1,30 +0,0 @@ -/* -** Copyright 2012, The CyanogenMod Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#ifndef _UTILS_H_ -#define _UTILS_H_ - -#ifndef PROPERTY_VALUE_MAX -#define PROPERTY_VALUE_MAX 92 -#endif - -/* reads a file, making sure it is terminated with \n \0 */ -extern char* read_file(const char *fn); - -extern int get_property(const char *data, char *found, const char *searchkey, - const char *not_found); -extern int check_property(const char *data, const char *prefix); -#endif