MagiskSU rewrite for unified binary

This commit is contained in:
topjohnwu 2017-04-15 03:21:31 +08:00
parent ed052e0b0b
commit a92c9fc226
12 changed files with 1184 additions and 2241 deletions

View File

@ -3,17 +3,6 @@
** Copyright 2010, Adam Shanks (@ChainsDD) ** Copyright 2010, Adam Shanks (@ChainsDD)
** Copyright 2008, Zinx Verituse (@zinxv) ** 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 <sys/types.h> #include <sys/types.h>
@ -25,6 +14,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include "magisk.h"
#include "su.h" #include "su.h"
/* intent actions */ /* intent actions */
@ -34,167 +24,160 @@
#define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am" #define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am"
// TODO: leverage this with exec_log? static void silent_run(char* const args[]) {
int silent_run(char* const args[]) { set_identity(0);
set_identity(0); pid_t pid;
pid_t pid; pid = fork();
pid = fork(); /* Parent */
/* Parent */ if (pid < 0) {
if (pid < 0) { PLOGE("fork");
PLOGE("fork"); }
return -1; else if (pid > 0) {
} return;
else if (pid > 0) { }
return 0; int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
} dup2(zero, 0);
int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC); int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
dup2(zero, 0); dup2(null, 1);
int null = open("/dev/null", O_WRONLY | O_CLOEXEC); dup2(null, 2);
dup2(null, 1); setenv("CLASSPATH", "/system/framework/am.jar", 1);
dup2(null, 2); execv(args[0], args);
setenv("CLASSPATH", "/system/framework/am.jar", 1); PLOGE("exec am");
execv(args[0], args); _exit(EXIT_FAILURE);
PLOGE("exec am");
_exit(EXIT_FAILURE);
return -1;
} }
int get_owner_login_user_args(struct su_context *ctx, char* user, int user_len) { static int get_owner_login_user_args(struct su_context *ctx, char* user, int user_len) {
int needs_owner_login_prompt = 0; int needs_owner_login_prompt = 0;
if (ctx->user.multiuser_mode == MULTIUSER_MODE_OWNER_MANAGED) { if (ctx->user.multiuser_mode == MULTIUSER_MODE_OWNER_MANAGED) {
if (0 != ctx->user.android_user_id) { if (0 != ctx->user.android_user_id) {
needs_owner_login_prompt = 1; needs_owner_login_prompt = 1;
} }
snprintf(user, user_len, "0"); snprintf(user, user_len, "0");
} }
else if (ctx->user.multiuser_mode == MULTIUSER_MODE_USER) { else if (ctx->user.multiuser_mode == MULTIUSER_MODE_USER) {
snprintf(user, user_len, "%d", ctx->user.android_user_id); snprintf(user, user_len, "%d", ctx->user.android_user_id);
} }
else if (ctx->user.multiuser_mode == MULTIUSER_MODE_NONE) { else if (ctx->user.multiuser_mode == MULTIUSER_MODE_NONE) {
user[0] = '\0'; user[0] = '\0';
} }
else { else {
snprintf(user, user_len, "0"); snprintf(user, user_len, "0");
} }
return needs_owner_login_prompt; return needs_owner_login_prompt;
} }
int send_result(struct su_context *ctx, policy_t policy) { void app_send_result(struct su_context *ctx, policy_t policy) {
char binary_version[256]; char binary_version[256];
sprintf(binary_version, "%d", VERSION_CODE); sprintf(binary_version, "%d", VERSION_CODE);
char uid[256]; char uid[256];
sprintf(uid, "%d", ctx->from.uid); sprintf(uid, "%d", ctx->from.uid);
char toUid[256]; char toUid[256];
sprintf(toUid, "%d", ctx->to.uid); sprintf(toUid, "%d", ctx->to.uid);
char pid[256]; char pid[256];
sprintf(pid, "%d", ctx->from.pid); sprintf(pid, "%d", ctx->from.pid);
char user[64]; char user[64];
get_owner_login_user_args(ctx, user, sizeof(user)); get_owner_login_user_args(ctx, user, sizeof(user));
if (0 != ctx->user.android_user_id) { if (0 != ctx->user.android_user_id) {
char android_user_id[256]; char android_user_id[256];
sprintf(android_user_id, "%d", ctx->user.android_user_id); sprintf(android_user_id, "%d", ctx->user.android_user_id);
char *user_result_command[] = { char *user_result_command[] = {
AM_PATH, AM_PATH,
ACTION_RESULT, ACTION_RESULT,
"--ei", "--ei",
"from.uid", "from.uid",
uid, uid,
"--ei", "--ei",
"to.uid", "to.uid",
toUid, toUid,
"--ei", "--ei",
"pid", "pid",
pid, pid,
"--es", "--es",
"command", "command",
get_command(&ctx->to), get_command(&ctx->to),
"--es", "--es",
"action", "action",
policy == ALLOW ? "allow" : "deny", policy == ALLOW ? "allow" : "deny",
user[0] ? "--user" : NULL, user[0] ? "--user" : NULL,
android_user_id, android_user_id,
NULL NULL
}; };
silent_run(user_result_command); silent_run(user_result_command);
} }
char *result_command[] = { char *result_command[] = {
AM_PATH, AM_PATH,
ACTION_RESULT, ACTION_RESULT,
"--ei", "--ei",
"from.uid", "from.uid",
uid, uid,
"--ei", "--ei",
"to.uid", "to.uid",
toUid, toUid,
"--ei", "--ei",
"pid", "pid",
pid, pid,
"--es", "--es",
"command", "command",
get_command(&ctx->to), get_command(&ctx->to),
"--es", "--es",
"action", "action",
policy == ALLOW ? "allow" : "deny", policy == ALLOW ? "allow" : "deny",
user[0] ? "--user" : NULL, user[0] ? "--user" : NULL,
user, user,
NULL NULL
}; };
return silent_run(result_command); silent_run(result_command);
} }
int send_request(struct su_context *ctx) { void app_send_request(struct su_context *ctx) {
// if su is operating in MULTIUSER_MODEL_OWNER, // if su is operating in MULTIUSER_MODEL_OWNER,
// and the user requestor is not the owner, // and the user requestor is not the owner,
// the owner needs to be notified of the request. // the owner needs to be notified of the request.
// so there will be two activities shown. // so there will be two activities shown.
char user[64]; char user[64];
int needs_owner_login_prompt = get_owner_login_user_args(ctx, user, sizeof(user)); int needs_owner_login_prompt = get_owner_login_user_args(ctx, user, sizeof(user));
int ret; if (needs_owner_login_prompt) {
if (needs_owner_login_prompt) { char uid[256];
char uid[256]; sprintf(uid, "%d", ctx->from.uid);
sprintf(uid, "%d", ctx->from.uid);
char android_user_id[256]; char android_user_id[256];
sprintf(android_user_id, "%d", ctx->user.android_user_id); sprintf(android_user_id, "%d", ctx->user.android_user_id);
// in multiuser mode, the owner gets the su prompt // in multiuser mode, the owner gets the su prompt
char *notify_command[] = { char *notify_command[] = {
AM_PATH, AM_PATH,
ACTION_NOTIFY, ACTION_NOTIFY,
"--ei", "--ei",
"caller_uid", "caller_uid",
uid, uid,
"--user", "--user",
android_user_id, android_user_id,
NULL NULL
}; };
int ret = silent_run(notify_command); silent_run(notify_command);
if (ret) { }
return ret;
}
}
char *request_command[] = { char *request_command[] = {
AM_PATH, AM_PATH,
ACTION_REQUEST, ACTION_REQUEST,
"--es", "--es",
"socket", "socket",
ctx->sock_path, ctx->sock_path,
user[0] ? "--user" : NULL, user[0] ? "--user" : NULL,
user, user,
NULL NULL
}; };
return silent_run(request_command); silent_run(request_command);
} }

736
daemon.c
View File

@ -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 <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/ioctl.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 <sys/sendfile.h>
#include <stdarg.h>
#include <sys/types.h>
#include <pthread.h>
#include <sched.h>
#include <termios.h>
#include <signal.h>
#include <string.h>
#include <dirent.h>
#include <selinux/selinux.h>
#ifdef SUPERUSER_EMBEDDED
#include <cutils/multiuser.h>
#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/<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) {
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;
}

111
db.c
View File

@ -1,17 +1,6 @@
/* /*
** Copyright 2013, Koushik Dutta (@koush) ** 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 <stdlib.h> #include <stdlib.h>
@ -19,66 +8,68 @@
#include <limits.h> #include <limits.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <time.h> #include <time.h>
#include <string.h>
#include "magisk.h"
#include "su.h" #include "su.h"
struct callback_data_t { struct callback_data_t {
struct su_context *ctx; struct su_context *ctx;
policy_t policy; policy_t policy;
}; };
static int database_callback(void *v, int argc, char **argv, char **azColName){ static int database_callback(void *v, int argc, char **argv, char **azColName){
struct callback_data_t *data = (struct callback_data_t *)v; struct callback_data_t *data = (struct callback_data_t *)v;
policy_t policy = INTERACTIVE; policy_t policy = INTERACTIVE;
int i; int i;
time_t until = 0; time_t until = 0;
for(i = 0; i < argc; i++) { for(i = 0; i < argc; i++) {
if (strcmp(azColName[i], "policy") == 0) { if (strcmp(azColName[i], "policy") == 0) {
if (argv[i] != NULL) { if (argv[i] != NULL) {
policy = atoi(argv[i]); policy = atoi(argv[i]);
} }
} }
else if (strcmp(azColName[i], "until") == 0) { else if (strcmp(azColName[i], "until") == 0) {
if (argv[i] != NULL) { if (argv[i] != NULL) {
until = atol(argv[i]); until = atol(argv[i]);
} }
} }
} }
if (policy == DENY) { if (policy == DENY) {
data->policy = DENY; data->policy = DENY;
return -1; return -1;
} else if (policy == ALLOW && (until == 0 || until > time(NULL))) { } else if (policy == ALLOW && (until == 0 || until > time(NULL))) {
data->policy = ALLOW; data->policy = ALLOW;
// even though we allow, continue, so we can see if there's another policy // even though we allow, continue, so we can see if there's another policy
// that denies... // that denies...
} }
return 0; return 0;
} }
policy_t database_check(struct su_context *ctx) { policy_t database_check(struct su_context *ctx) {
sqlite3 *db = NULL; sqlite3 *db = NULL;
char query[512]; char query[512];
snprintf(query, sizeof(query), "select policy, until from policies where uid=%d", ctx->from.uid); 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); int ret = sqlite3_open_v2(ctx->user.database_path, &db, SQLITE_OPEN_READONLY, NULL);
if (ret) { if (ret) {
LOGE("sqlite3 open failure: %d", ret); LOGE("sqlite3 open failure: %d", ret);
sqlite3_close(db); sqlite3_close(db);
return INTERACTIVE; return INTERACTIVE;
} }
char *err = NULL; char *err = NULL;
struct callback_data_t data; struct callback_data_t data;
data.ctx = ctx; data.ctx = ctx;
data.policy = INTERACTIVE; data.policy = INTERACTIVE;
ret = sqlite3_exec(db, query, database_callback, &data, &err); ret = sqlite3_exec(db, query, database_callback, &data, &err);
sqlite3_close(db); sqlite3_close(db);
if (err != NULL) { if (err != NULL) {
LOGE("sqlite3_exec: %s", err); LOGE("sqlite3_exec: %s", err);
return DENY; return DENY;
} }
return data.policy; return data.policy;
} }

54
misc.c Normal file
View File

@ -0,0 +1,54 @@
/* misc.c - Miscellaneous stuffs for su
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#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;
}

280
pts.c
View File

@ -1,17 +1,5 @@
/* /*
* Copyright 2013, Tan Chee Eng (@tan-ce) * 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 * helper functions to handle raw input mode and terminal window resizing
*/ */
#define _GNU_SOURCE
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
@ -29,6 +18,7 @@
#include <errno.h> #include <errno.h>
#include <pthread.h> #include <pthread.h>
#include "magisk.h"
#include "pts.h" #include "pts.h"
/** /**
@ -36,16 +26,16 @@
*/ */
// Ensures all the data is written out // Ensures all the data is written out
static int write_blocking(int fd, char *buf, ssize_t bufsz) { static int write_blocking(int fd, char *buf, ssize_t bufsz) {
ssize_t ret, written; ssize_t ret, written;
written = 0; written = 0;
do { do {
ret = write(fd, buf + written, bufsz - written); ret = write(fd, buf + written, bufsz - written);
if (ret == -1) return -1; if (ret == -1) return -1;
written += ret; written += ret;
} while (written < bufsz); } 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. * true, then close the output FD when we're done.
*/ */
static void pump_ex(int input, int output, int close_output) { static void pump_ex(int input, int output, int close_output) {
char buf[4096]; char buf[4096];
int len; int len;
while ((len = read(input, buf, 4096)) > 0) { while ((len = read(input, buf, 4096)) > 0) {
if (write_blocking(output, buf, len) == -1) break; if (write_blocking(output, buf, len) == -1) break;
} }
close(input); close(input);
if (close_output) close(output); if (close_output) close(output);
} }
/** /**
@ -67,27 +57,27 @@ static void pump_ex(int input, int output, int close_output) {
* output FD when done. * output FD when done.
*/ */
static void pump(int input, int output) { static void pump(int input, int output) {
pump_ex(input, output, 1); pump_ex(input, output, 1);
} }
static void* pump_thread(void* data) { static void* pump_thread(void* data) {
int* files = (int*)data; int* files = (int*)data;
int input = files[0]; int input = files[0];
int output = files[1]; int output = files[1];
pump(input, output); pump(input, output);
free(data); free(data);
return NULL; return NULL;
} }
static void pump_async(int input, int output) { static void pump_async(int input, int output) {
pthread_t writer; pthread_t writer;
int* files = (int*)malloc(sizeof(int) * 2); int* files = (int*)malloc(sizeof(int) * 2);
if (files == NULL) { if (files == NULL) {
exit(-1); exit(-1);
} }
files[0] = input; files[0] = input;
files[1] = output; files[1] = output;
pthread_create(&writer, NULL, pump_thread, files); 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. * on success, the file descriptor of the master device is returned.
*/ */
int pts_open(char *slave_name, size_t slave_name_size) { int pts_open(char *slave_name, size_t slave_name_size) {
int fdm; int fdm;
char sn_tmp[256]; char sn_tmp[256];
// Open master ptmx device // Open master ptmx device
fdm = open("/dev/ptmx", O_RDWR); fdm = open("/dev/ptmx", O_RDWR);
if (fdm == -1) return -1; if (fdm == -1)
goto error;
// Get the slave name // Get the slave name
if (ptsname_r(fdm, slave_name, slave_name_size-1)) { if (ptsname_r(fdm, slave_name, slave_name_size-1))
close(fdm); goto error;
return -2;
}
slave_name[slave_name_size - 1] = '\0'; slave_name[slave_name_size - 1] = '\0';
// Grant, then unlock // Grant, then unlock
if (grantpt(fdm) == -1) { if (grantpt(fdm) == -1)
close(fdm); goto error;
return -1;
}
if (unlockpt(fdm) == -1) {
close(fdm);
return -1;
}
return fdm; if (unlockpt(fdm) == -1)
goto error;
return fdm;
error:
close(fdm);
PLOGE("pts_open");
return -1;
} }
// Stores the previous termios of stdin // Stores the previous termios of stdin
@ -148,31 +138,31 @@ static int stdin_is_raw = 0;
* on success 0 * on success 0
*/ */
int set_stdin_raw(void) { int set_stdin_raw(void) {
struct termios new_termios; struct termios new_termios;
// Save the current stdin termios // Save the current stdin termios
if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) { if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
return -1; return -1;
} }
// Start from the current settings // Start from the current settings
new_termios = old_stdin; new_termios = old_stdin;
// Make the terminal like an SSH or telnet client // Make the terminal like an SSH or telnet client
new_termios.c_iflag |= IGNPAR; new_termios.c_iflag |= IGNPAR;
new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
new_termios.c_oflag &= ~OPOST; new_termios.c_oflag &= ~OPOST;
new_termios.c_cc[VMIN] = 1; new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0; new_termios.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) { if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
return -1; 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 * on success, 0
*/ */
int restore_stdin(void) { int restore_stdin(void) {
if (!stdin_is_raw) return 0; if (!stdin_is_raw) return 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) { if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
return -1; return -1;
} }
stdin_is_raw = 0; stdin_is_raw = 0;
return 0; return 0;
} }
// Flag indicating whether the sigwinch watcher should terminate. // Flag indicating whether the sigwinch watcher should terminate.
@ -208,33 +198,33 @@ static volatile int closing_time = 0;
* the terminal size. * the terminal size.
*/ */
static void *watch_sigwinch(void *data) { static void *watch_sigwinch(void *data) {
sigset_t winch; sigset_t winch;
int sig; int sig;
int master = ((int *)data)[0]; int master = ((int *)data)[0];
int slave = ((int *)data)[1]; int slave = ((int *)data)[1];
sigemptyset(&winch); sigemptyset(&winch);
sigaddset(&winch, SIGWINCH); sigaddset(&winch, SIGWINCH);
do { do {
// Wait for a SIGWINCH // Wait for a SIGWINCH
sigwait(&winch, &sig); sigwait(&winch, &sig);
if (closing_time) break; if (closing_time) break;
// Get the new terminal size // Get the new terminal size
struct winsize w; struct winsize w;
if (ioctl(master, TIOCGWINSZ, &w) == -1) { if (ioctl(master, TIOCGWINSZ, &w) == -1) {
continue; continue;
} }
// Set the new terminal size // Set the new terminal size
ioctl(slave, TIOCSWINSZ, &w); ioctl(slave, TIOCSWINSZ, &w);
} while (1); } while (1);
free(data); free(data);
return NULL; return NULL;
} }
/** /**
@ -260,35 +250,35 @@ static void *watch_sigwinch(void *data) {
* on success, 0 * on success, 0
*/ */
int watch_sigwinch_async(int master, int slave) { int watch_sigwinch_async(int master, int slave) {
pthread_t watcher; pthread_t watcher;
int *files = (int *) malloc(sizeof(int) * 2); int *files = (int *) malloc(sizeof(int) * 2);
if (files == NULL) { if (files == NULL) {
return -1; return -1;
} }
// Block SIGWINCH so sigwait can later receive it // Block SIGWINCH so sigwait can later receive it
sigset_t winch; sigset_t winch;
sigemptyset(&winch); sigemptyset(&winch);
sigaddset(&winch, SIGWINCH); sigaddset(&winch, SIGWINCH);
if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) { if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) {
free(files); free(files);
return -1; return -1;
} }
// Initialize some variables, then start the thread // Initialize some variables, then start the thread
closing_time = 0; closing_time = 0;
files[0] = master; files[0] = master;
files[1] = slave; files[1] = slave;
int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files); int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files);
if (ret != 0) { if (ret != 0) {
free(files); free(files);
errno = ret; errno = ret;
return -1; return -1;
} }
// Set the initial terminal size // Set the initial terminal size
raise(SIGWINCH); raise(SIGWINCH);
return 0; return 0;
} }
/** /**
@ -297,8 +287,8 @@ int watch_sigwinch_async(int master, int slave) {
* Cause the SIGWINCH watcher thread to terminate * Cause the SIGWINCH watcher thread to terminate
*/ */
void watch_sigwinch_cleanup(void) { void watch_sigwinch_cleanup(void) {
closing_time = 1; closing_time = 1;
raise(SIGWINCH); raise(SIGWINCH);
} }
/** /**
@ -308,11 +298,11 @@ void watch_sigwinch_cleanup(void) {
* in a seperate thread * in a seperate thread
*/ */
void pump_stdin_async(int outfd) { void pump_stdin_async(int outfd) {
// Put stdin into raw mode // Put stdin into raw mode
set_stdin_raw(); set_stdin_raw();
// Pump data from stdin to the PTY // Pump data from stdin to the PTY
pump_async(STDIN_FILENO, outfd); pump_async(STDIN_FILENO, outfd);
} }
/** /**
@ -324,10 +314,10 @@ void pump_stdin_async(int outfd) {
* Before returning, restores stdin settings. * Before returning, restores stdin settings.
*/ */
void pump_stdout_blocking(int infd) { void pump_stdout_blocking(int infd) {
// Pump data from stdout to PTY // Pump data from stdout to PTY
pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */); pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */);
// Cleanup // Cleanup
restore_stdin(); restore_stdin();
watch_sigwinch_cleanup(); watch_sigwinch_cleanup();
} }

14
pts.h
View File

@ -1,17 +1,5 @@
/* /*
* Copyright 2013, Tan Chee Eng (@tan-ce) * 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_ #ifndef _PTS_H_
#define _PTS_H_ #define _PTS_H_
#include <sys/types.h>
/** /**
* pts_open * pts_open
* *

1279
su.c

File diff suppressed because it is too large Load Diff

198
su.h
View File

@ -1,42 +1,16 @@
/* /* su.h - Store all general su info
** 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.
*/
#ifndef SU_h #ifndef _SU_H_
#define SU_h 1 #define _SU_H_
#include "magisk.h" #include <limits.h>
#include <sys/types.h>
#ifndef AID_SHELL #define SU_VERSION_STR xstr(VERSION) ":MAGISKSU (topjohnwu)"
#define AID_SHELL (get_shell_uid())
#endif
#ifndef AID_ROOT // Property check for root access
#define AID_ROOT 0 #define ROOT_ACCESS_PROP "persist.sys.root_access"
#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"
#define ROOT_ACCESS_DISABLED 0 #define ROOT_ACCESS_DISABLED 0
#define ROOT_ACCESS_APPS_ONLY 1 #define ROOT_ACCESS_APPS_ONLY 1
#define ROOT_ACCESS_ADB_ONLY 2 #define ROOT_ACCESS_ADB_ONLY 2
@ -45,22 +19,19 @@
// DO NOT CHANGE LINE BELOW, java package name will always be the same // DO NOT CHANGE LINE BELOW, java package name will always be the same
#define JAVA_PACKAGE_NAME "com.topjohnwu.magisk" #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 // If --rename-manifest-package is used in AAPT, this
// must be changed to correspond to the new APK package name // must be changed to correspond to the new APK package name
// See the two Android.mk files for more details. // See the two Android.mk files for more details.
#ifndef REQUESTOR
#define REQUESTOR JAVA_PACKAGE_NAME #define REQUESTOR JAVA_PACKAGE_NAME
#endif
// This is used if wrapping the fragment classes and activities // This is used if wrapping the fragment classes and activities
// with classes in another package. CM requirement. // with classes in another package.
#ifndef REQUESTOR_PREFIX
#define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser" #define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser"
#endif #define REQUESTOR_FILES_PATH APPLICATION_DATA_PATH REQUESTOR "/files"
#define REQUESTOR_DATA_PATH "/data/data/"
#define REQUESTOR_FILES_PATH REQUESTOR_DATA_PATH REQUESTOR "/files"
#define REQUESTOR_USER_PATH "/data/user/"
#define REQUESTOR_CACHE_PATH "/dev/" REQUESTOR #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 // 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. // SQLiteOpenHelper, etc. Though that is the behavior as of current.
// it is up to the Android application to symlink as appropriate. // it is up to the Android application to symlink as appropriate.
@ -69,69 +40,55 @@
#define DEFAULT_SHELL "/system/bin/sh" #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 { struct su_initiator {
pid_t pid; pid_t pid;
unsigned uid; unsigned uid;
unsigned user;
char bin[PATH_MAX];
char args[4096];
}; };
struct su_request { struct su_request {
unsigned uid; unsigned uid;
int login; int login;
int keepenv; int keepenv;
char *shell; char *shell;
char *command; char *command;
char **argv; char **argv;
int argc; int argc;
int optind;
}; };
struct su_user_info { struct su_user_info {
// the user in android userspace (multiuser) // the user in android userspace (multiuser)
// that invoked this action. // that invoked this action.
unsigned android_user_id; unsigned android_user_id;
// how su behaves with multiuser. see enum below. // how su behaves with multiuser. see enum below.
int multiuser_mode; int multiuser_mode;
// path to superuser directory. this is populated according // path to superuser directory. this is populated according
// to the multiuser mode. // to the multiuser mode.
// this is used to check uid/gid for protecting socket. // this is used to check uid/gid for protecting socket.
// this is used instead of database, as it is more likely // this is used instead of database, as it is more likely
// to exist. db will not exist if su has never launched. // to exist. db will not exist if su has never launched.
char base_path[PATH_MAX]; char base_path[PATH_MAX];
// path to su database. this is populated according // path to su database. this is populated according
// to the multiuser mode. // to the multiuser mode.
char database_path[PATH_MAX]; char database_path[PATH_MAX];
}; };
struct su_context { struct su_context {
struct su_initiator from; struct su_initiator from;
struct su_request to; struct su_request to;
struct su_user_info user; struct su_user_info user;
mode_t umask; mode_t umask;
char sock_path[PATH_MAX]; char sock_path[PATH_MAX];
}; };
// multiuser su behavior // multiuser su behavior
typedef enum { typedef enum {
// only owner can su // only owner can su
MULTIUSER_MODE_OWNER_ONLY = 0, MULTIUSER_MODE_OWNER_ONLY = 0,
// owner gets a su prompt // owner gets a su prompt
MULTIUSER_MODE_OWNER_MANAGED = 1, MULTIUSER_MODE_OWNER_MANAGED = 1,
// user gets a su prompt // user gets a su prompt
MULTIUSER_MODE_USER = 2, MULTIUSER_MODE_USER = 2,
MULTIUSER_MODE_NONE = 3, MULTIUSER_MODE_NONE = 3,
} multiuser_mode_t; } multiuser_mode_t;
#define MULTIUSER_VALUE_OWNER_ONLY "owner" #define MULTIUSER_VALUE_OWNER_ONLY "owner"
@ -140,35 +97,38 @@ typedef enum {
#define MULTIUSER_VALUE_NONE "none" #define MULTIUSER_VALUE_NONE "none"
typedef enum { typedef enum {
INTERACTIVE = 0, INTERACTIVE = 0,
DENY = 1, DENY = 1,
ALLOW = 2, ALLOW = 2,
} policy_t; } policy_t;
extern policy_t database_check(struct su_context *ctx); extern int from_uid, from_pid;
extern void set_identity(unsigned int uid); extern int quit_signals[];
extern int send_request(struct su_context *ctx);
extern int send_result(struct su_context *ctx, policy_t policy);
static inline char *get_command(const struct su_request *to) // su.c
{
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;
}
int run_daemon(); int su_daemon_main(int argc, char **argv);
int connect_daemon(int argc, char *argv[], int ppid);
int su_main(int argc, char *argv[]); // su_client.c
int su_main_nodaemon(int argc, char *argv[]);
// for when you give zero fucks about the state of the child process. int socket_create_temp(char *path, size_t len);
// this version of fork understands you don't care about the child. int socket_accept(int serv_fd);
// deadbeat dad fork. void socket_send_request(int fd, const struct su_context *ctx);
int fork_zero_fucks(); 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 #endif

217
su_client.c Normal file
View File

@ -0,0 +1,217 @@
/* su_client.c - The entrypoint for su, connect to daemon and send correct info
*/
#include <limits.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#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;
}

101
su_socket.c Normal file
View File

@ -0,0 +1,101 @@
/* su_socket.c - Functions for communication to client
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#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';
}

112
utils.c
View File

@ -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 <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

30
utils.h
View File

@ -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