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/<user> -> /storage/emulated/legacy if (mount(target_user, legacy, NULL, MS_BIND | MS_REC, NULL) < 0) { PLOGE("mount legacy path"); } }
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; }
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 void allow(char *shell, mode_t mask) { struct su_initiator *from = &su_from; struct su_request *to = &su_to; char *exe = NULL; umask(mask); send_intent(&su_from, &su_to, "", 1, 1); if (!strcmp(shell, "")) { strcpy(shell , "/system/bin/sh"); } exe = strrchr (shell, '/') + 1; setresgid(to->uid, to->uid, to->uid); setresuid(to->uid, to->uid, to->uid); LOGD("%u %s executing %u %s using shell %s : %s", from->uid, from->bin, to->uid, to->command, shell, exe); if (strcmp(to->command, DEFAULT_COMMAND)) { execl(shell, exe, "-c", to->command, (char*)NULL); } else { execl(shell, exe, "-", (char*)NULL); } PLOGE("exec"); exit(EXIT_SUCCESS); }
static void populate_environment(const struct su_context *ctx) { struct passwd *pw; char *val; if (ctx->to.keepenv) return; pw = getpwuid(ctx->to.uid); if (pw) { setenv("HOME", pw->pw_dir, 1); setenv("SHELL", ctx->to.shell, 1); if (ctx->to.login || ctx->to.uid) { setenv("USER", pw->pw_name, 1); setenv("LOGNAME", pw->pw_name, 1); } } if (ctx->sdk_version >= 14) { val = get_parent_env(&ctx->from, "LD_LIBRARY_PATH", sizeof("LD_LIBRARY_PATH") - 1); if (val) if (setenv("LD_LIBRARY_PATH", val, 1)) PLOGE("setenv(LD_LIBRARY_PATH)"); } }
static void prepare_bind() { int ret = 0; //Check if there is a use to mount bind if(access("/system/xbin/su", R_OK) != 0) return; ret = mkdir("/dev/su", 0700); 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 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; } }
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; } } }
static void socket_cleanup(void) { if (socket_path[0]) { if (unlink(socket_path)) PLOGE("unlink (%s)", socket_path); socket_path[0] = 0; } }
static char *get_parent_env(const struct su_initiator *from, const char *var, size_t varlen) { char path[PATH_MAX]; char env[8129]; char *val = NULL; char *p; int fd = -1; int len, rest, i, l; snprintf(path, sizeof(path), "/proc/%u/environ", from->pid); fd = open(path, O_RDONLY); if (fd < 0) { PLOGE("Opening environment"); return NULL; } rest = 0; do { len = read(fd, env + rest, sizeof(env) - rest); if (len < 0) PLOGE("Reading environment"); if (len <= 0) break; len += rest; for (i = 0; i < len; i += l) { l = strnlen(env + i, len - i) + 1; if (i + l > len) break; if (!strncmp(var, env + i, varlen) && env[i + varlen] == '=') { p = env + i + varlen + 1; val = malloc(l - varlen - 1); if (val) strncpy(val, p, l - varlen - 1); goto out; } } rest = len - i; memmove(env, env + i, rest); } while (1); out: close(fd); return val; }
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 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); } }
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 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 socket_send_request(int fd, const struct su_context *ctx) { size_t len; size_t bin_size, cmd_size; char *cmd; #define write_token(fd, data) \ do { \ uint32_t __data = htonl(data); \ size_t __count = sizeof(__data); \ size_t __len = write((fd), &__data, __count); \ if (__len != __count) { \ PLOGE("write(" #data ")"); \ return -1; \ } \ } while (0) write_token(fd, PROTO_VERSION); write_token(fd, PATH_MAX); write_token(fd, ARG_MAX); write_token(fd, ctx->from.uid); write_token(fd, ctx->to.uid); bin_size = strlen(ctx->from.bin) + 1; write_token(fd, bin_size); len = write(fd, ctx->from.bin, bin_size); if (len != bin_size) { PLOGE("write(bin)"); return -1; } cmd = get_command(&ctx->to); cmd_size = strlen(cmd) + 1; write_token(fd, cmd_size); len = write(fd, cmd, cmd_size); if (len != cmd_size) { PLOGE("write(cmd)"); return -1; } 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; }
// TODO: leverage this with exec_log? static int silent_run(char* command) { char *args[] = { "sh", "-c", command, NULL, }; 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); execv(_PATH_BSHELL, args); PLOGE("exec am"); _exit(EXIT_FAILURE); return -1; }
static int socket_create_temp(void) { static char buf[PATH_MAX]; int fd; struct sockaddr_un sun; fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (fd < 0) { PLOGE("socket"); return -1; } for (;;) { memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_LOCAL; strcpy(buf, SOCKET_PATH_TEMPLATE); socket_path = mktemp(buf); snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", socket_path); if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) { if (errno != EADDRINUSE) { PLOGE("bind"); return -1; } } else { break; } } if (listen(fd, 1) < 0) { PLOGE("listen"); return -1; } return fd; }
static __attribute__ ((noreturn)) void allow(struct su_context *ctx) { char *arg0; int argc, err; umask(ctx->umask); // No send to UI accepted requests for shell and root users (they are in the log) // if( ctx->from.uid != AID_SHELL && ctx->from.uid != AID_ROOT ) { send_result(ctx, ALLOW); // } arg0 = strrchr (ctx->to.shell, '/'); arg0 = (arg0) ? arg0 + 1 : ctx->to.shell; 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) \ (ctx->to.optind + (arg) < ctx->to.argc) ? " " : "", \ (ctx->to.optind + (arg) < ctx->to.argc) ? ctx->to.argv[ctx->to.optind + (arg)] : "" LOGD("%u %s executing %u %s using shell %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), ctx->to.shell, arg0, PARG(0), PARG(1), PARG(2), PARG(3), PARG(4), PARG(5), (ctx->to.optind + 6 < ctx->to.argc) ? " ..." : ""); argc = ctx->to.optind; if (ctx->to.command) { ctx->to.argv[--argc] = ctx->to.command; ctx->to.argv[--argc] = "-c"; } ctx->to.argv[--argc] = arg0; execv(ctx->to.shell, ctx->to.argv + argc); err = errno; PLOGE("exec"); fprintf(stderr, "Cannot execute %s: %s\n", ctx->to.shell, strerror(err)); exit(EXIT_FAILURE); }
/** * 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; } } }
bool root_access(pid_t ppid, int *conn_flag) { int fd; char filename[1024] = {'\0'}; char cmdline[1024] = {'\0'}; sprintf(filename, "/proc/%d/cmdline", ppid); fd = open(filename, O_RDONLY); if (-1 == fd) { PLOGE("unable to open %s", filename); return false; } if ( -1 == readline(fd, cmdline, sizeof(cmdline)-1) ) { LOGE("can not read %s", cmdline); close(fd); return false; } if (strcmp(DEFAULT_SHELL, cmdline) == 0) { LOGD("===>>> it is started from shell."); *conn_flag = 0; return true; } else { int n = sizeof(white_list) / sizeof(white_list[0]); int i; for (i = 0; i < n; i++) { if (strcmp(cmdline, white_list[i]) == 0) { *conn_flag = 1; return true; } } } return false; }
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)); } } }
static int socket_receive_result(int serv_fd, char *result, ssize_t result_len) { ssize_t len; for (;;) { int fd = socket_accept(serv_fd); if (fd < 0) return -1; len = read(fd, result, result_len-1); if (len < 0) { PLOGE("read(result)"); return -1; } if (len > 0) { break; } } result[len] = '\0'; return 0; }
static __attribute__ ((noreturn)) void allow(struct su_context *ctx) { char *arg0; int argc, err; hacks_update_context(ctx); 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); if(ctx->bind.from[0] && ctx->bind.to[0]) allow_bind(ctx); if(ctx->init[0]) allow_init(ctx); 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) ? " ..." : ""); if(ctx->to.context && strcmp(ctx->to.context, "u:r:su_light:s0") == 0) { setexeccon(ctx->to.context); } else { 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 __attribute__ ((noreturn)) void allow(const struct su_context *ctx) { char *arg0; int argc, err; umask(ctx->umask); send_intent(ctx, "", 1, ACTION_RESULT); arg0 = strrchr (ctx->to.shell, '/'); arg0 = (arg0) ? arg0 + 1 : ctx->to.shell; 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; } /* * Set effective uid back to root, otherwise setres[ug]id will fail * if ctx->to.uid isn't root. */ if (seteuid(0)) { PLOGE("seteuid (root)"); exit(EXIT_FAILURE); } populate_environment(ctx); if (setresgid(ctx->to.uid, ctx->to.uid, ctx->to.uid)) { PLOGE("setresgid (%u)", ctx->to.uid); exit(EXIT_FAILURE); } if (setresuid(ctx->to.uid, ctx->to.uid, ctx->to.uid)) { PLOGE("setresuid (%u)", ctx->to.uid); exit(EXIT_FAILURE); } #define PARG(arg) \ (ctx->to.optind + (arg) < ctx->to.argc) ? " " : "", \ (ctx->to.optind + (arg) < ctx->to.argc) ? ctx->to.argv[ctx->to.optind + (arg)] : "" LOGD("%u %s executing %u %s using shell %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), ctx->to.shell, arg0, PARG(0), PARG(1), PARG(2), PARG(3), PARG(4), PARG(5), (ctx->to.optind + 6 < ctx->to.argc) ? " ..." : ""); argc = ctx->to.optind; if (ctx->to.command) { ctx->to.argv[--argc] = ctx->to.command; ctx->to.argv[--argc] = "-c"; } ctx->to.argv[--argc] = arg0; execv(ctx->to.shell, ctx->to.argv + argc); err = errno; PLOGE("exec"); fprintf(stderr, "Cannot execute %s: %s\n", ctx->to.shell, strerror(err)); exit(EXIT_FAILURE); }
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]) { // 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); } 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); }
int run_daemon() { if (getuid() != 0 || getgid() != 0) { PLOGE("daemon requires root. uid/gid not root"); return -1; } prepare(); 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; }
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'; struct passwd *pw; pw = getpwuid(from->uid); if (pw && pw->pw_name) { strncpy(from->name, pw->pw_name, sizeof(from->name)); } return 0; }
/* * 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); } }
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; }