/* ** 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 #ifdef SUPERUSER_EMBEDDED #include #endif #include "binds.h" #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); struct ucred credentials; int 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; } int mount_storage = read_int(fd); // 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 return run_daemon_child(infd, outfd, errfd, argc, argv); } static int copy_file(const char* src, const char* dst, int mode) { int ifd = open(src, O_RDONLY); if(ifd<0) return 1; if(mode == 0) { struct stat stbuf; if(fstat(ifd, &stbuf)) return 1; mode = stbuf.st_mode & 0777; LOGE("File %s found mode %o", src, mode); } int ofd = open(dst, O_WRONLY|O_CREAT, mode); if(ofd<0) return 1; size_t s = lseek(ifd, 0, SEEK_END); if(s<0) return 1; lseek(ifd, 0, SEEK_SET); int ret = sendfile(ofd, ifd, NULL, s); if(ret<0) return 1; close(ofd); close(ifd); return 0; } static void prepare_su_bind() { int ret = 0; //Check if there is a use to mount bind if(access("/system/xbin/su", R_OK) != 0) return; ret = copy_file("/sbin/su", "/dev/su/su", 0755); if(ret) { PLOGE("Failed to copy su"); return; } chmod("/dev/su/su", 0755); ret = setfilecon("/dev/su/su", "u:object_r:system_file:s0"); if(ret) { LOGE("Failed to set file context"); return; } ret = mount("/dev/su/su", "/system/xbin/su", "", MS_BIND, NULL); if(ret) { LOGE("Failed to mount bind"); return; } } static void prepare_binds() { mkdir("/data/su", 0700); static int i = 0; auto void cb(void *arg, int uid, const char *src, const char *dst) { int ret = 0; char *tmpfile = NULL; asprintf(&tmpfile, "/dev/su/bind%d", i++); struct stat stbuf; ret = stat(src, &stbuf); if(ret) { free(tmpfile); LOGE("Failed to stat src %s file", src); return; } //Only shell uid is allowed to bind files not his own if(uid != 2000 && uid != stbuf.st_uid) { LOGE("File %s has wrong owner: %d vs %d", src, uid, stbuf.st_uid); return; } ret = copy_file(src, tmpfile, 0); if(ret) { free(tmpfile); PLOGE("Failed to copy su"); return; } chmod(tmpfile, stbuf.st_mode); ret = setfilecon(tmpfile, "u:object_r:system_file:s0"); if(ret) { LOGE("Failed to set file context"); return; } ret = mount(tmpfile, dst, "", MS_BIND, NULL); if(ret) { LOGE("Failed to mount bind"); return; } } bind_foreach(cb, NULL); } static void do_init() { auto void cb(void *arg, int uid, const char *path) { int ret = 0; int p = fork(); if(p) return; while(access("/system/bin/sh", R_OK)) sleep(1); ret = setexeccon("u:r:su:s0"); execl(path, path, NULL); LOGE("Failed to execute %s. Trying as shell script, ret = %d", path, ret); ret = setexeccon("u:r:su:s0"); execl("/system/bin/sh", "/system/bin/sh", path, NULL); LOGE("Failed to execute %s as shell script", path); _exit(1); } init_foreach(cb, NULL); } static void prepare() { setfscreatecon("u:object_r:su_daemon:s0"); mkdir("/dev/su", 0700); prepare_su_bind(); prepare_binds(); do_init(); setfscreatecon(NULL); } int run_daemon() { if (getuid() != 0 || getgid() != 0) { PLOGE("daemon requires root. uid/gid not root"); return -1; } prepare(); switch (fork()) { case 0: break; case -1: PLOGE("fork"); return 1; default: return 0; } if (setsid() < 0 || setcon("u:r:su_daemon:s0") < 0) return 1; 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; sprintf(sun.sun_path, "%s/server", REQUESTOR_DAEMON_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); unlink(REQUESTOR_DAEMON_PATH); int previous_umask = umask(027); mkdir(REQUESTOR_DAEMON_PATH, 0777); memset(sun.sun_path, 0, sizeof(sun.sun_path)); memcpy(sun.sun_path, "\0" "SUPERUSER", strlen("SUPERUSER") + 1); if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) { PLOGE("daemon bind"); goto err; } chmod(REQUESTOR_DAEMON_PATH, 0755); chmod(sun.sun_path, 0777); umask(previous_umask); 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]; 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; sprintf(sun.sun_path, "%s/server", REQUESTOR_DAEMON_PATH); memset(sun.sun_path, 0, sizeof(sun.sun_path)); memcpy(sun.sun_path, "\0" "SUPERUSER", strlen("SUPERUSER") + 1); 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); // 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; }