mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-25 09:57:39 +00:00
Rewrite su daemon and client
This commit is contained in:
parent
09ef19f7ec
commit
d119dd9a0c
@ -67,7 +67,7 @@ static void *request_handler(void *args) {
|
||||
ls_hide_list(client);
|
||||
break;
|
||||
case SUPERUSER:
|
||||
su_daemon_receiver(client, &credential);
|
||||
su_daemon_handler(client, &credential);
|
||||
break;
|
||||
case CHECK_VERSION:
|
||||
write_string(client, xstr(MAGISK_VERSION) ":MAGISK");
|
||||
|
@ -39,7 +39,7 @@ int create_rand_socket(struct sockaddr_un *sun) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
int socket_accept(int serv_fd, int timeout) {
|
||||
int socket_accept(int sockfd, int timeout) {
|
||||
struct timeval tv;
|
||||
fd_set fds;
|
||||
int rc;
|
||||
@ -47,16 +47,16 @@ int socket_accept(int serv_fd, int timeout) {
|
||||
tv.tv_sec = timeout;
|
||||
tv.tv_usec = 0;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(serv_fd, &fds);
|
||||
FD_SET(sockfd, &fds);
|
||||
do {
|
||||
rc = select(serv_fd + 1, &fds, NULL, NULL, &tv);
|
||||
rc = select(sockfd + 1, &fds, NULL, NULL, &tv);
|
||||
} while (rc < 0 && errno == EINTR);
|
||||
if (rc < 1) {
|
||||
PLOGE("select");
|
||||
exit(-1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return xaccept4(serv_fd, NULL, NULL, SOCK_CLOEXEC);
|
||||
return xaccept4(sockfd, NULL, NULL, SOCK_CLOEXEC);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -164,13 +164,15 @@ void send_fd(int sockfd, int fd) {
|
||||
|
||||
int read_int(int fd) {
|
||||
int val;
|
||||
xxread(fd, &val, sizeof(int));
|
||||
if (xxread(fd, &val, sizeof(int)) != sizeof(int))
|
||||
return -1;
|
||||
return val;
|
||||
}
|
||||
|
||||
int read_int_be(int fd) {
|
||||
uint32_t val;
|
||||
xxread(fd, &val, sizeof(val));
|
||||
if (xxread(fd, &val, sizeof(val)) != sizeof(int))
|
||||
return -1;
|
||||
return ntohl(val);
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ int check_and_start_logger();
|
||||
|
||||
socklen_t setup_sockaddr(struct sockaddr_un *sun, daemon_t d);
|
||||
int create_rand_socket(struct sockaddr_un *sun);
|
||||
int socket_accept(int serv_fd, int timeout);
|
||||
int socket_accept(int sockfd, int timeout);
|
||||
int recv_fd(int sockfd);
|
||||
void send_fd(int sockfd, int fd);
|
||||
int read_int(int fd);
|
||||
@ -98,6 +98,6 @@ void ls_hide_list(int client);
|
||||
* Superuser *
|
||||
*************/
|
||||
|
||||
void su_daemon_receiver(int client, struct ucred *credential);
|
||||
void su_daemon_handler(int client, struct ucred *credential);
|
||||
|
||||
#endif
|
||||
|
@ -14,86 +14,86 @@
|
||||
|
||||
#include "magisk.h"
|
||||
#include "daemon.h"
|
||||
#include "utils.h"
|
||||
#include "su.h"
|
||||
|
||||
#define AM_PATH "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am"
|
||||
|
||||
static char *get_command(const struct su_request *to) {
|
||||
if (to->command)
|
||||
if (to->command[0])
|
||||
return to->command;
|
||||
if (to->shell)
|
||||
if (to->shell[0])
|
||||
return to->shell;
|
||||
return DEFAULT_SHELL;
|
||||
}
|
||||
|
||||
static void silent_run(char * const args[]) {
|
||||
set_identity(0);
|
||||
if (fork())
|
||||
if (fork_dont_care())
|
||||
return;
|
||||
int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
|
||||
dup2(zero, 0);
|
||||
xdup2(zero, 0);
|
||||
int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
|
||||
dup2(null, 1);
|
||||
dup2(null, 2);
|
||||
xdup2(null, 1);
|
||||
xdup2(null, 2);
|
||||
setenv("CLASSPATH", "/system/framework/am.jar", 1);
|
||||
execv(args[0], args);
|
||||
PLOGE("exec am");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void setup_user(char *user) {
|
||||
switch (DB_SET(su_ctx->info, SU_MULTIUSER_MODE)) {
|
||||
static void setup_user(char *user, struct su_info *info) {
|
||||
switch (DB_SET(info, SU_MULTIUSER_MODE)) {
|
||||
case MULTIUSER_MODE_OWNER_ONLY:
|
||||
case MULTIUSER_MODE_OWNER_MANAGED:
|
||||
sprintf(user, "%d", 0);
|
||||
break;
|
||||
case MULTIUSER_MODE_USER:
|
||||
sprintf(user, "%d", su_ctx->info->uid / 100000);
|
||||
sprintf(user, "%d", info->uid / 100000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_log() {
|
||||
void app_log(struct su_context *ctx) {
|
||||
char user[8];
|
||||
setup_user(user);
|
||||
setup_user(user, ctx->info);
|
||||
|
||||
char fromUid[8];
|
||||
sprintf(fromUid, "%d",
|
||||
DB_SET(su_ctx->info, SU_MULTIUSER_MODE) == MULTIUSER_MODE_OWNER_MANAGED ?
|
||||
su_ctx->info->uid % 100000 : su_ctx->info->uid);
|
||||
DB_SET(ctx->info, SU_MULTIUSER_MODE) == MULTIUSER_MODE_OWNER_MANAGED ?
|
||||
ctx->info->uid % 100000 : ctx->info->uid);
|
||||
|
||||
char toUid[8];
|
||||
sprintf(toUid, "%d", su_ctx->to.uid);
|
||||
sprintf(toUid, "%d", ctx->req.uid);
|
||||
|
||||
char pid[8];
|
||||
sprintf(pid, "%d", su_ctx->pid);
|
||||
sprintf(pid, "%d", ctx->pid);
|
||||
|
||||
char policy[2];
|
||||
sprintf(policy, "%d", su_ctx->info->access.policy);
|
||||
sprintf(policy, "%d", ctx->info->access.policy);
|
||||
|
||||
char *cmd[] = {
|
||||
AM_PATH, "broadcast",
|
||||
"-a", "android.intent.action.BOOT_COMPLETED",
|
||||
"-p", DB_STR(su_ctx->info, SU_MANAGER),
|
||||
"-p", DB_STR(ctx->info, SU_MANAGER),
|
||||
"--user", user,
|
||||
"--es", "action", "log",
|
||||
"--ei", "from.uid", fromUid,
|
||||
"--ei", "to.uid", toUid,
|
||||
"--ei", "pid", pid,
|
||||
"--ei", "policy", policy,
|
||||
"--es", "command", get_command(&su_ctx->to),
|
||||
"--es", "command", get_command(&ctx->req),
|
||||
NULL
|
||||
};
|
||||
silent_run(cmd);
|
||||
}
|
||||
|
||||
void app_connect(const char *socket) {
|
||||
void app_connect(const char *socket, struct su_info *info) {
|
||||
char user[8];
|
||||
setup_user(user);
|
||||
setup_user(user, info);
|
||||
char *cmd[] = {
|
||||
AM_PATH, "broadcast",
|
||||
"-a", "android.intent.action.BOOT_COMPLETED",
|
||||
"-p", DB_STR(su_ctx->info, SU_MANAGER),
|
||||
"-p", DB_STR(info, SU_MANAGER),
|
||||
"--user", user,
|
||||
"--es", "action", "request",
|
||||
"--es", "socket", (char *) socket,
|
||||
@ -102,8 +102,8 @@ void app_connect(const char *socket) {
|
||||
silent_run(cmd);
|
||||
}
|
||||
|
||||
void socket_send_request(int fd) {
|
||||
write_key_token(fd, "uid", su_ctx->info->uid);
|
||||
void socket_send_request(int fd, struct su_info *info) {
|
||||
write_key_token(fd, "uid", info->uid);
|
||||
write_string_be(fd, "eof");
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
@ -27,11 +26,9 @@
|
||||
#include "daemon.h"
|
||||
#include "utils.h"
|
||||
#include "su.h"
|
||||
#include "selinux.h"
|
||||
#include "pts.h"
|
||||
#include "flags.h"
|
||||
|
||||
struct su_context *su_ctx;
|
||||
|
||||
static void usage(int status) {
|
||||
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
|
||||
|
||||
@ -49,7 +46,7 @@ static void usage(int status) {
|
||||
" -V display version code and exit\n"
|
||||
" -mm, -M,\n"
|
||||
" --mount-master force run in the global mount namespace\n");
|
||||
exit2(status);
|
||||
exit(status);
|
||||
}
|
||||
|
||||
static char *concat_commands(int argc, char *argv[]) {
|
||||
@ -64,142 +61,103 @@ static char *concat_commands(int argc, char *argv[]) {
|
||||
return strdup(command);
|
||||
}
|
||||
|
||||
static void populate_environment() {
|
||||
struct passwd *pw;
|
||||
static void sighandler(int sig) {
|
||||
restore_stdin();
|
||||
|
||||
if (su_ctx->to.keepenv)
|
||||
return;
|
||||
// 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);
|
||||
|
||||
pw = getpwuid(su_ctx->to.uid);
|
||||
if (pw) {
|
||||
setenv("HOME", pw->pw_dir, 1);
|
||||
if (su_ctx->to.shell)
|
||||
setenv("SHELL", su_ctx->to.shell, 1);
|
||||
else
|
||||
setenv("SHELL", DEFAULT_SHELL, 1);
|
||||
if (su_ctx->to.login || su_ctx->to.uid) {
|
||||
setenv("USER", pw->pw_name, 1);
|
||||
setenv("LOGNAME", pw->pw_name, 1);
|
||||
}
|
||||
// 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 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);
|
||||
}
|
||||
}
|
||||
|
||||
static __attribute__ ((noreturn)) void allow() {
|
||||
char* argv[] = { NULL, NULL, NULL, NULL };
|
||||
|
||||
if (su_ctx->to.login)
|
||||
argv[0] = "-";
|
||||
else
|
||||
argv[0] = basename(su_ctx->to.shell);
|
||||
|
||||
if (su_ctx->to.command) {
|
||||
argv[1] = "-c";
|
||||
argv[2] = su_ctx->to.command;
|
||||
}
|
||||
|
||||
// Setup shell
|
||||
umask(022);
|
||||
populate_environment();
|
||||
set_identity(su_ctx->to.uid);
|
||||
|
||||
if (su_ctx->info->access.notify || su_ctx->info->access.log)
|
||||
app_log();
|
||||
|
||||
execvp(su_ctx->to.shell, argv);
|
||||
fprintf(stderr, "Cannot execute %s: %s\n", su_ctx->to.shell, strerror(errno));
|
||||
PLOGE("exec");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static __attribute__ ((noreturn)) void deny() {
|
||||
if (su_ctx->info->access.notify || su_ctx->info->access.log)
|
||||
app_log();
|
||||
|
||||
LOGW("su: request rejected (%u->%u)", su_ctx->info->uid, su_ctx->to.uid);
|
||||
fprintf(stderr, "%s\n", strerror(EACCES));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
__attribute__ ((noreturn)) void exit2(int status) {
|
||||
// Handle the pipe, or the daemon will get stuck
|
||||
if (su_ctx->pipefd[0] >= 0) {
|
||||
xwrite(su_ctx->pipefd[1], &su_ctx->info->access.policy, sizeof(policy_t));
|
||||
close(su_ctx->pipefd[0]);
|
||||
close(su_ctx->pipefd[1]);
|
||||
}
|
||||
exit(status);
|
||||
}
|
||||
|
||||
int su_daemon_main(int argc, char **argv) {
|
||||
/*
|
||||
* Connect daemon, send argc, argv, cwd, pts slave
|
||||
*/
|
||||
int su_client_main(int argc, char *argv[]) {
|
||||
int c;
|
||||
struct option long_opts[] = {
|
||||
{ "command", required_argument, NULL, 'c' },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "login", no_argument, NULL, 'l' },
|
||||
{ "preserve-environment", no_argument, NULL, 'p' },
|
||||
{ "shell", required_argument, NULL, 's' },
|
||||
{ "version", no_argument, NULL, 'v' },
|
||||
{ "context", required_argument, NULL, 'z' },
|
||||
{ "mount-master", no_argument, NULL, 'M' },
|
||||
{ NULL, 0, NULL, 0 },
|
||||
{ "command", required_argument, NULL, 'c' },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "login", no_argument, NULL, 'l' },
|
||||
{ "preserve-environment", no_argument, NULL, 'p' },
|
||||
{ "shell", required_argument, NULL, 's' },
|
||||
{ "version", no_argument, NULL, 'v' },
|
||||
{ "context", required_argument, NULL, 'z' },
|
||||
{ "mount-master", no_argument, NULL, 'M' },
|
||||
{ NULL, 0, NULL, 0 },
|
||||
};
|
||||
|
||||
struct su_request su_req = {
|
||||
.uid = UID_ROOT,
|
||||
.login = 0,
|
||||
.keepenv = 0,
|
||||
.shell = DEFAULT_SHELL,
|
||||
.command = "",
|
||||
};
|
||||
|
||||
|
||||
for (int i = 0; i < argc; i++) {
|
||||
// Replace -cn with -z, -mm with -M for supporting getopt_long
|
||||
if (strcmp(argv[i], "-cn") == 0)
|
||||
strcpy(argv[i], "-z");
|
||||
else if (strcmp(argv[i], "-mm") == 0)
|
||||
strcpy(argv[i], "-M");
|
||||
}
|
||||
|
||||
while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:M", long_opts, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
su_ctx->to.command = concat_commands(argc, argv);
|
||||
optind = argc;
|
||||
break;
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
break;
|
||||
case 'l':
|
||||
su_ctx->to.login = 1;
|
||||
break;
|
||||
case 'm':
|
||||
case 'p':
|
||||
su_ctx->to.keepenv = 1;
|
||||
break;
|
||||
case 's':
|
||||
su_ctx->to.shell = optarg;
|
||||
break;
|
||||
case 'V':
|
||||
printf("%d\n", MAGISK_VER_CODE);
|
||||
exit2(EXIT_SUCCESS);
|
||||
case 'v':
|
||||
printf("%s\n", xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)");
|
||||
exit2(EXIT_SUCCESS);
|
||||
case 'z':
|
||||
// Do nothing, placed here for legacy support :)
|
||||
break;
|
||||
case 'M':
|
||||
DB_SET(su_ctx->info, SU_MNT_NS) = NAMESPACE_MODE_GLOBAL;
|
||||
break;
|
||||
default:
|
||||
/* Bionic getopt_long doesn't terminate its error output by newline */
|
||||
fprintf(stderr, "\n");
|
||||
usage(2);
|
||||
case 'c':
|
||||
su_req.command = concat_commands(argc, argv);
|
||||
optind = argc;
|
||||
break;
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
break;
|
||||
case 'l':
|
||||
su_req.login = 1;
|
||||
break;
|
||||
case 'm':
|
||||
case 'p':
|
||||
su_req.keepenv = 1;
|
||||
break;
|
||||
case 's':
|
||||
su_req.shell = optarg;
|
||||
break;
|
||||
case 'V':
|
||||
printf("%d\n", MAGISK_VER_CODE);
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'v':
|
||||
printf("%s\n", xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)");
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'z':
|
||||
// Do nothing, placed here for legacy support :)
|
||||
break;
|
||||
case 'M':
|
||||
/* TODO */
|
||||
break;
|
||||
default:
|
||||
/* Bionic getopt_long doesn't terminate its error output by newline */
|
||||
fprintf(stderr, "\n");
|
||||
usage(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc && strcmp(argv[optind], "-") == 0) {
|
||||
su_ctx->to.login = 1;
|
||||
su_req.login = 1;
|
||||
optind++;
|
||||
}
|
||||
/* username or uid */
|
||||
@ -207,54 +165,69 @@ int su_daemon_main(int argc, char **argv) {
|
||||
struct passwd *pw;
|
||||
pw = getpwnam(argv[optind]);
|
||||
if (pw)
|
||||
su_ctx->to.uid = pw->pw_uid;
|
||||
su_req.uid = pw->pw_uid;
|
||||
else
|
||||
su_ctx->to.uid = atoi(argv[optind]);
|
||||
su_req.uid = atoi(argv[optind]);
|
||||
optind++;
|
||||
}
|
||||
|
||||
// Handle namespaces
|
||||
switch (DB_SET(su_ctx->info, SU_MNT_NS)) {
|
||||
case NAMESPACE_MODE_GLOBAL:
|
||||
LOGD("su: use global namespace\n");
|
||||
break;
|
||||
case NAMESPACE_MODE_REQUESTER:
|
||||
LOGD("su: use namespace of pid=[%d]\n", su_ctx->pid);
|
||||
if (switch_mnt_ns(su_ctx->pid)) {
|
||||
LOGD("su: setns failed, fallback to isolated\n");
|
||||
xunshare(CLONE_NEWNS);
|
||||
}
|
||||
break;
|
||||
case NAMESPACE_MODE_ISOLATE:
|
||||
LOGD("su: use new isolated namespace\n");
|
||||
xunshare(CLONE_NEWNS);
|
||||
break;
|
||||
char pts_slave[PATH_MAX];
|
||||
int ptmx, fd;
|
||||
|
||||
// Connect to client
|
||||
fd = connect_daemon();
|
||||
|
||||
// Tell the daemon we are su
|
||||
write_int(fd, SUPERUSER);
|
||||
|
||||
// Send su_request
|
||||
xwrite(fd, &su_req, 3 * sizeof(unsigned));
|
||||
write_string(fd, su_req.shell);
|
||||
write_string(fd, su_req.command);
|
||||
|
||||
// Determine which one of our streams are attached to a TTY
|
||||
int atty = 0;
|
||||
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));
|
||||
} else {
|
||||
pts_slave[0] = '\0';
|
||||
}
|
||||
|
||||
// Change directory to cwd
|
||||
chdir(su_ctx->cwd);
|
||||
// Send pts_slave
|
||||
write_string(fd, pts_slave);
|
||||
|
||||
if (su_ctx->pipefd[0] >= 0) {
|
||||
// Create random socket
|
||||
struct sockaddr_un addr;
|
||||
int sockfd = create_rand_socket(&addr);
|
||||
// Send stdin
|
||||
send_fd(fd, (atty & ATTY_IN) ? -1 : STDIN_FILENO);
|
||||
// Send stdout
|
||||
send_fd(fd, (atty & ATTY_OUT) ? -1 : STDOUT_FILENO);
|
||||
// Send stderr
|
||||
send_fd(fd, (atty & ATTY_ERR) ? -1 : STDERR_FILENO);
|
||||
|
||||
// Connect Magisk Manager
|
||||
app_connect(addr.sun_path + 1);
|
||||
int fd = socket_accept(sockfd, 60);
|
||||
|
||||
socket_send_request(fd);
|
||||
su_ctx->info->access.policy = read_int_be(fd);
|
||||
|
||||
close(fd);
|
||||
close(sockfd);
|
||||
|
||||
// Report the policy to main daemon
|
||||
xwrite(su_ctx->pipefd[1], &su_ctx->info->access.policy, sizeof(policy_t));
|
||||
close(su_ctx->pipefd[0]);
|
||||
close(su_ctx->pipefd[1]);
|
||||
// Wait for ack from daemon
|
||||
if (read_int(fd)) {
|
||||
// Fast fail
|
||||
fprintf(stderr, "%s\n", strerror(EACCES));
|
||||
return DENY;
|
||||
}
|
||||
|
||||
su_ctx->info->access.policy == ALLOW ? allow() : deny();
|
||||
}
|
||||
if (atty & ATTY_IN) {
|
||||
setup_sighandlers(sighandler);
|
||||
pump_stdin_async(ptmx);
|
||||
}
|
||||
if (atty & ATTY_OUT) {
|
||||
// Forward SIGWINCH
|
||||
watch_sigwinch_async(STDOUT_FILENO, ptmx);
|
||||
pump_stdout_blocking(ptmx);
|
||||
}
|
||||
|
||||
// Get the exit code
|
||||
int code = read_int(fd);
|
||||
close(fd);
|
||||
|
||||
return code;
|
||||
}
|
@ -12,6 +12,11 @@
|
||||
|
||||
#define DEFAULT_SHELL "/system/bin/sh"
|
||||
|
||||
// Constants for atty
|
||||
#define ATTY_IN 1
|
||||
#define ATTY_OUT 2
|
||||
#define ATTY_ERR 4
|
||||
|
||||
struct su_info {
|
||||
unsigned uid; /* Unique key to find su_info */
|
||||
pthread_mutex_t lock; /* Internal lock */
|
||||
@ -33,32 +38,22 @@ struct su_info {
|
||||
|
||||
struct su_request {
|
||||
unsigned uid;
|
||||
int login;
|
||||
int keepenv;
|
||||
unsigned login;
|
||||
unsigned keepenv;
|
||||
char *shell;
|
||||
char *command;
|
||||
};
|
||||
|
||||
struct su_context {
|
||||
struct su_info *info;
|
||||
struct su_request to;
|
||||
struct su_request req;
|
||||
pid_t pid;
|
||||
char cwd[PATH_MAX];
|
||||
int pipefd[2];
|
||||
};
|
||||
|
||||
extern struct su_context *su_ctx;
|
||||
|
||||
// su.c
|
||||
|
||||
int su_daemon_main(int argc, char **argv);
|
||||
__attribute__ ((noreturn)) void exit2(int status);
|
||||
void set_identity(unsigned uid);
|
||||
|
||||
// connect.c
|
||||
|
||||
void app_log();
|
||||
void app_connect(const char *socket);
|
||||
void socket_send_request(int fd);
|
||||
void app_log(struct su_context *ctx);
|
||||
void app_connect(const char *socket, struct su_info *info);
|
||||
void socket_send_request(int fd, struct su_info *info);
|
||||
|
||||
#endif
|
||||
|
@ -2,13 +2,14 @@
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <limits.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@ -21,44 +22,16 @@
|
||||
#include "pts.h"
|
||||
#include "selinux.h"
|
||||
|
||||
// Constants for the atty bitfield
|
||||
#define ATTY_IN 1
|
||||
#define ATTY_OUT 2
|
||||
#define ATTY_ERR 4
|
||||
|
||||
#define TIMEOUT 3
|
||||
|
||||
#define LOCK_CACHE() pthread_mutex_lock(&cache_lock)
|
||||
#define LOCK_INFO() pthread_mutex_lock(&info->lock)
|
||||
#define UNLOCK_CACHE() pthread_mutex_unlock(&cache_lock)
|
||||
#define UNLOCK_INFO() pthread_mutex_unlock(&ctx.info->lock)
|
||||
#define UNLOCK_INFO() pthread_mutex_unlock(&info->lock)
|
||||
|
||||
static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static struct su_info *cache;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static void *info_collector(void *node) {
|
||||
struct su_info *info = node;
|
||||
while (1) {
|
||||
@ -185,48 +158,133 @@ static struct su_info *get_su_info(unsigned uid) {
|
||||
if (info->access.policy == QUERY && DB_STR(info, SU_MANAGER)[0] == '\0')
|
||||
info->access = NO_SU_ACCESS;
|
||||
}
|
||||
|
||||
// If still not determined, ask manager
|
||||
if (info->access.policy == QUERY) {
|
||||
// Create random socket
|
||||
struct sockaddr_un addr;
|
||||
int sockfd = create_rand_socket(&addr);
|
||||
|
||||
// Connect manager
|
||||
app_connect(addr.sun_path + 1, info);
|
||||
int fd = socket_accept(sockfd, 60);
|
||||
|
||||
socket_send_request(fd, info);
|
||||
int ret = read_int_be(fd);
|
||||
info->access.policy = ret < 0 ? DENY : ret;
|
||||
|
||||
close(fd);
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
// Unlock
|
||||
UNLOCK_INFO();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static void su_executor(int client) {
|
||||
LOGD("su: executor started\n");
|
||||
static void populate_environment(struct su_request *req) {
|
||||
struct passwd *pw;
|
||||
|
||||
if (req->keepenv)
|
||||
return;
|
||||
|
||||
pw = getpwuid(req->uid);
|
||||
if (pw) {
|
||||
setenv("HOME", pw->pw_dir, 1);
|
||||
if (req->shell)
|
||||
setenv("SHELL", req->shell, 1);
|
||||
else
|
||||
setenv("SHELL", DEFAULT_SHELL, 1);
|
||||
if (req->login || req->uid) {
|
||||
setenv("USER", pw->pw_name, 1);
|
||||
setenv("LOGNAME", pw->pw_name, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static 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);
|
||||
}
|
||||
}
|
||||
|
||||
void su_daemon_handler(int client, struct ucred *credential) {
|
||||
LOGD("su: request from client: %d\n", client);
|
||||
|
||||
struct su_info *info = get_su_info(credential->uid);
|
||||
|
||||
// Fail fast
|
||||
if (info->access.policy == DENY && !info->access.log && !info->access.notify) {
|
||||
write_int(client, DENY);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Fork a new process, the child process will need to setsid,
|
||||
* open a pseudo-terminal if needed, and will eventually run exec
|
||||
* The parent process will wait for the result and
|
||||
* send the return code back to our client
|
||||
*/
|
||||
int child = xfork();
|
||||
if (child) {
|
||||
// Decrement reference count
|
||||
--info->ref;
|
||||
|
||||
// Wait result
|
||||
LOGD("su: waiting child: [%d]\n", child);
|
||||
int status, code;
|
||||
|
||||
if (waitpid(child, &status, 0) > 0)
|
||||
code = WEXITSTATUS(status);
|
||||
else
|
||||
code = -1;
|
||||
|
||||
LOGD("su: return code: [%d]\n", code);
|
||||
write(client, &code, sizeof(code));
|
||||
close(client);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("su: fork handler\n");
|
||||
|
||||
struct su_context ctx = {
|
||||
.info = info,
|
||||
.pid = credential->pid
|
||||
};
|
||||
|
||||
// Become session leader
|
||||
xsetsid();
|
||||
|
||||
// Migrate environment from client
|
||||
char path[32], buf[4096];
|
||||
snprintf(path, sizeof(path), "/proc/%d/cwd", su_ctx->pid);
|
||||
xreadlink(path, su_ctx->cwd, sizeof(su_ctx->cwd));
|
||||
snprintf(path, sizeof(path), "/proc/%d/environ", su_ctx->pid);
|
||||
snprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid);
|
||||
xreadlink(path, buf, sizeof(buf));
|
||||
chdir(buf);
|
||||
snprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid);
|
||||
memset(buf, 0, sizeof(buf));
|
||||
int fd = open(path, O_RDONLY);
|
||||
read(fd, buf, sizeof(buf));
|
||||
close(fd);
|
||||
clearenv();
|
||||
for (size_t pos = 0; buf[pos];) {
|
||||
putenv(buf + pos);
|
||||
pos += strlen(buf + pos) + 1;
|
||||
}
|
||||
|
||||
// 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);
|
||||
exit2(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]);
|
||||
// Replace -cn with -z, -mm with -M for supporting getopt_long
|
||||
if (strcmp(argv[i], "-cn") == 0)
|
||||
strcpy(argv[i], "-z");
|
||||
else if (strcmp(argv[i], "-mm") == 0)
|
||||
strcpy(argv[i], "-M");
|
||||
}
|
||||
// Read su_request
|
||||
xxread(client, &ctx.req, 3 * sizeof(unsigned));
|
||||
ctx.req.shell = read_string(client);
|
||||
ctx.req.command = read_string(client);
|
||||
|
||||
// Get pts_slave
|
||||
char *pts_slave = read_string(client);
|
||||
@ -244,10 +302,10 @@ static void su_executor(int client) {
|
||||
xstat(pts_slave, &st);
|
||||
|
||||
// If caller is not root, ensure the owner of pts_slave is the caller
|
||||
if(st.st_uid != su_ctx->info->uid && su_ctx->info->uid != 0) {
|
||||
if(st.st_uid != info->uid && info->uid != 0) {
|
||||
LOGE("su: Wrong permission of pts_slave");
|
||||
su_ctx->info->access.policy = DENY;
|
||||
exit2(1);
|
||||
info->access.policy = DENY;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Set our pts_slave to devpts, same restriction as adb shell
|
||||
@ -258,18 +316,12 @@ static void su_executor(int client) {
|
||||
// our controlling TTY and not the daemon's
|
||||
ptsfd = xopen(pts_slave, O_RDWR);
|
||||
|
||||
if (infd < 0) {
|
||||
LOGD("su: stdin using PTY");
|
||||
infd = ptsfd;
|
||||
}
|
||||
if (outfd < 0) {
|
||||
LOGD("su: stdout using PTY");
|
||||
if (infd < 0)
|
||||
infd = ptsfd;
|
||||
if (outfd < 0)
|
||||
outfd = ptsfd;
|
||||
}
|
||||
if (errfd < 0) {
|
||||
LOGD("su: stderr using PTY");
|
||||
if (errfd < 0)
|
||||
errfd = ptsfd;
|
||||
}
|
||||
}
|
||||
|
||||
free(pts_slave);
|
||||
@ -285,142 +337,56 @@ static void su_executor(int client) {
|
||||
write_int(client, 0);
|
||||
close(client);
|
||||
|
||||
// Run the actual main
|
||||
su_daemon_main(argc, argv);
|
||||
}
|
||||
|
||||
void su_daemon_receiver(int client, struct ucred *credential) {
|
||||
LOGD("su: request from client: %d\n", client);
|
||||
|
||||
// Default values
|
||||
struct su_context ctx = {
|
||||
.info = get_su_info(credential->uid),
|
||||
.to = {
|
||||
.uid = UID_ROOT,
|
||||
.login = 0,
|
||||
.keepenv = 0,
|
||||
.shell = DEFAULT_SHELL,
|
||||
.command = NULL,
|
||||
},
|
||||
.pid = credential->pid,
|
||||
.pipefd = { -1, -1 }
|
||||
};
|
||||
|
||||
// Fail fast
|
||||
if (ctx.info->access.policy == DENY && !ctx.info->access.log && !ctx.info->access.notify) {
|
||||
UNLOCK_INFO();
|
||||
write_int(client, DENY);
|
||||
return;
|
||||
// Handle namespaces
|
||||
switch (DB_SET(info, SU_MNT_NS)) {
|
||||
case NAMESPACE_MODE_GLOBAL:
|
||||
LOGD("su: use global namespace\n");
|
||||
break;
|
||||
case NAMESPACE_MODE_REQUESTER:
|
||||
LOGD("su: use namespace of pid=[%d]\n", ctx.pid);
|
||||
if (switch_mnt_ns(ctx.pid)) {
|
||||
LOGD("su: setns failed, fallback to isolated\n");
|
||||
xunshare(CLONE_NEWNS);
|
||||
}
|
||||
break;
|
||||
case NAMESPACE_MODE_ISOLATE:
|
||||
LOGD("su: use new isolated namespace\n");
|
||||
xunshare(CLONE_NEWNS);
|
||||
break;
|
||||
}
|
||||
|
||||
// If still not determined, open a pipe and wait for results
|
||||
if (ctx.info->access.policy == QUERY)
|
||||
xpipe2(ctx.pipefd, O_CLOEXEC);
|
||||
if (info->access.policy) {
|
||||
char* argv[] = { NULL, NULL, NULL, NULL };
|
||||
|
||||
/* Fork a new process, the child process will need to setsid,
|
||||
* open a pseudo-terminal if needed, and will eventually run exec
|
||||
* The parent process will wait for the result and
|
||||
* send the return code back to our client
|
||||
*/
|
||||
int child = xfork();
|
||||
if (child == 0) {
|
||||
su_ctx = &ctx;
|
||||
su_executor(client);
|
||||
}
|
||||
if (ctx.req.login)
|
||||
argv[0] = "-";
|
||||
else
|
||||
argv[0] = ctx.req.shell;
|
||||
|
||||
// Wait for results
|
||||
if (ctx.pipefd[0] >= 0) {
|
||||
xxread(ctx.pipefd[0], &ctx.info->access.policy, sizeof(policy_t));
|
||||
close(ctx.pipefd[0]);
|
||||
close(ctx.pipefd[1]);
|
||||
}
|
||||
if (ctx.req.command[0]) {
|
||||
argv[1] = "-c";
|
||||
argv[2] = ctx.req.command;
|
||||
}
|
||||
|
||||
// The policy is determined, unlock
|
||||
UNLOCK_INFO();
|
||||
// Setup shell
|
||||
umask(022);
|
||||
populate_environment(&ctx.req);
|
||||
set_identity(ctx.req.uid);
|
||||
|
||||
// Info is now useless to us, decrement reference count
|
||||
--ctx.info->ref;
|
||||
if (info->access.notify || info->access.log)
|
||||
app_log(&ctx);
|
||||
|
||||
// Wait result
|
||||
LOGD("su: waiting child: [%d]\n", child);
|
||||
int status, code;
|
||||
|
||||
if (waitpid(child, &status, 0) > 0)
|
||||
code = WEXITSTATUS(status);
|
||||
else
|
||||
code = -1;
|
||||
|
||||
LOGD("su: return code: [%d]\n", code);
|
||||
write(client, &code, sizeof(code));
|
||||
close(client);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connect daemon, send argc, argv, cwd, pts slave
|
||||
*/
|
||||
int su_client_main(int argc, char *argv[]) {
|
||||
char pts_slave[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]);
|
||||
}
|
||||
|
||||
// Determine which one of our streams are attached to a TTY
|
||||
int atty = 0;
|
||||
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));
|
||||
execvp(ctx.req.shell, argv);
|
||||
fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell, strerror(errno));
|
||||
PLOGE("exec");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
pts_slave[0] = '\0';
|
||||
}
|
||||
if (info->access.notify || info->access.log)
|
||||
app_log(&ctx);
|
||||
|
||||
// Send the pts_slave path to the daemon
|
||||
write_string(socketfd, pts_slave);
|
||||
|
||||
// Send stdin
|
||||
send_fd(socketfd, (atty & ATTY_IN) ? -1 : STDIN_FILENO);
|
||||
// Send stdout
|
||||
send_fd(socketfd, (atty & ATTY_OUT) ? -1 : STDOUT_FILENO);
|
||||
// Send stderr
|
||||
send_fd(socketfd, (atty & ATTY_ERR) ? -1 : STDERR_FILENO);
|
||||
|
||||
// Wait for acknowledgement from daemon
|
||||
if (read_int(socketfd)) {
|
||||
// Fast fail
|
||||
LOGW("su: request rejected (%u->%u)", info->uid, ctx.req.uid);
|
||||
fprintf(stderr, "%s\n", strerror(EACCES));
|
||||
return DENY;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (atty & ATTY_IN) {
|
||||
setup_sighandlers(sighandler);
|
||||
pump_stdin_async(ptmx);
|
||||
}
|
||||
if (atty & ATTY_OUT) {
|
||||
// Forward SIGWINCH
|
||||
watch_sigwinch_async(STDOUT_FILENO, ptmx);
|
||||
pump_stdout_blocking(ptmx);
|
||||
}
|
||||
|
||||
// Get the exit code
|
||||
int code = read_int(socketfd);
|
||||
close(socketfd);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user