mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-27 20:15:29 +00:00
Merge MagiskSU into Magisk main repo
This commit is contained in:
commit
492d6dfcf0
141
native/jni/su/activity.c
Normal file
141
native/jni/su/activity.c
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
** Copyright 2017, John Wu (@topjohnwu)
|
||||||
|
** Copyright 2010, Adam Shanks (@ChainsDD)
|
||||||
|
** Copyright 2008, Zinx Verituse (@zinxv)
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
|
#include "su.h"
|
||||||
|
|
||||||
|
/* intent actions */
|
||||||
|
#define ACTION_REQUEST "%s/" REQUESTOR_PREFIX ".RequestActivity"
|
||||||
|
#define ACTION_RESULT "%s/" REQUESTOR_PREFIX ".SuReceiver"
|
||||||
|
|
||||||
|
#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)
|
||||||
|
return to->command;
|
||||||
|
if (to->shell)
|
||||||
|
return to->shell;
|
||||||
|
return DEFAULT_SHELL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void silent_run(char* const args[]) {
|
||||||
|
set_identity(0);
|
||||||
|
if (fork())
|
||||||
|
return;
|
||||||
|
int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
|
||||||
|
dup2(zero, 0);
|
||||||
|
int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
|
||||||
|
dup2(null, 1);
|
||||||
|
dup2(null, 2);
|
||||||
|
setenv("CLASSPATH", "/system/framework/am.jar", 1);
|
||||||
|
execv(args[0], args);
|
||||||
|
PLOGE("exec am");
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int setup_user(struct su_context *ctx, char* user) {
|
||||||
|
switch (ctx->info->dbs.v[SU_MULTIUSER_MODE]) {
|
||||||
|
case MULTIUSER_MODE_OWNER_ONLY: /* Should already be denied if not owner */
|
||||||
|
case MULTIUSER_MODE_OWNER_MANAGED:
|
||||||
|
sprintf(user, "%d", 0);
|
||||||
|
return ctx->info->uid / 100000;
|
||||||
|
case MULTIUSER_MODE_USER:
|
||||||
|
sprintf(user, "%d", ctx->info->uid / 100000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_send_result(struct su_context *ctx, policy_t policy) {
|
||||||
|
char fromUid[16];
|
||||||
|
if (ctx->info->dbs.v[SU_MULTIUSER_MODE] == MULTIUSER_MODE_OWNER_MANAGED)
|
||||||
|
sprintf(fromUid, "%d", ctx->info->uid % 100000);
|
||||||
|
else
|
||||||
|
sprintf(fromUid, "%d", ctx->info->uid);
|
||||||
|
|
||||||
|
char toUid[16];
|
||||||
|
sprintf(toUid, "%d", ctx->to.uid);
|
||||||
|
|
||||||
|
char pid[16];
|
||||||
|
sprintf(pid, "%d", ctx->pid);
|
||||||
|
|
||||||
|
char user[16];
|
||||||
|
int notify = setup_user(ctx, user);
|
||||||
|
|
||||||
|
char activity[128];
|
||||||
|
sprintf(activity, ACTION_RESULT, ctx->info->str.s[SU_MANAGER]);
|
||||||
|
|
||||||
|
// Send notice to manager, enable logging
|
||||||
|
char *result_command[] = {
|
||||||
|
AM_PATH, "broadcast", "-n",
|
||||||
|
activity,
|
||||||
|
"--user", user,
|
||||||
|
"--ei", "mode", "0",
|
||||||
|
"--ei", "from.uid", fromUid,
|
||||||
|
"--ei", "to.uid", toUid,
|
||||||
|
"--ei", "pid", pid,
|
||||||
|
"--es", "command", get_command(&ctx->to),
|
||||||
|
"--es", "action", policy == ALLOW ? "allow" : "deny",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
silent_run(result_command);
|
||||||
|
|
||||||
|
// Send notice to user (if needed) to create toasts
|
||||||
|
if (notify) {
|
||||||
|
sprintf(fromUid, "%d", ctx->info->uid);
|
||||||
|
sprintf(user, "%d", notify);
|
||||||
|
char *notify_command[] = {
|
||||||
|
AM_PATH, "broadcast", "-n",
|
||||||
|
activity,
|
||||||
|
"--user", user,
|
||||||
|
"--ei", "mode", "1",
|
||||||
|
"--ei", "from.uid", fromUid,
|
||||||
|
"--es", "action", policy == ALLOW ? "allow" : "deny",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
silent_run(notify_command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_send_request(struct su_context *ctx) {
|
||||||
|
char user[16];
|
||||||
|
int notify = setup_user(ctx, user);
|
||||||
|
|
||||||
|
char activity[128];
|
||||||
|
sprintf(activity, ACTION_REQUEST, ctx->info->str.s[SU_MANAGER]);
|
||||||
|
|
||||||
|
char *request_command[] = {
|
||||||
|
AM_PATH, "start", "-n",
|
||||||
|
activity,
|
||||||
|
"--user", user,
|
||||||
|
"--es", "socket", ctx->sock_path,
|
||||||
|
"--ez", "timeout", notify ? "false" : "true",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
silent_run(request_command);
|
||||||
|
|
||||||
|
// Send notice to user to tell them root is managed by owner
|
||||||
|
if (notify) {
|
||||||
|
sprintf(user, "%d", notify);
|
||||||
|
sprintf(activity, ACTION_RESULT, ctx->info->str.s[SU_MANAGER]);
|
||||||
|
char *notify_command[] = {
|
||||||
|
AM_PATH, "broadcast", "-n",
|
||||||
|
activity,
|
||||||
|
"--user", user,
|
||||||
|
"--ei", "mode", "2",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
silent_run(notify_command);
|
||||||
|
}
|
||||||
|
}
|
318
native/jni/su/pts.c
Normal file
318
native/jni/su/pts.c
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013, Tan Chee Eng (@tan-ce)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* pts.c
|
||||||
|
*
|
||||||
|
* Manages the pseudo-terminal driver on Linux/Android and provides some
|
||||||
|
* helper functions to handle raw input mode and terminal window resizing
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
|
#include "pts.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions
|
||||||
|
*/
|
||||||
|
// Ensures all the data is written out
|
||||||
|
static int write_blocking(int fd, char *buf, ssize_t bufsz) {
|
||||||
|
ssize_t ret, written;
|
||||||
|
|
||||||
|
written = 0;
|
||||||
|
do {
|
||||||
|
ret = write(fd, buf + written, bufsz - written);
|
||||||
|
if (ret == -1) return -1;
|
||||||
|
written += ret;
|
||||||
|
} while (written < bufsz);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pump data from input FD to output FD. If close_output is
|
||||||
|
* true, then close the output FD when we're done.
|
||||||
|
*/
|
||||||
|
static void pump_ex(int input, int output, int close_output) {
|
||||||
|
char buf[4096];
|
||||||
|
int len;
|
||||||
|
while ((len = read(input, buf, 4096)) > 0) {
|
||||||
|
if (write_blocking(output, buf, len) == -1) break;
|
||||||
|
}
|
||||||
|
close(input);
|
||||||
|
if (close_output) close(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pump data from input FD to output FD. Will close the
|
||||||
|
* output FD when done.
|
||||||
|
*/
|
||||||
|
static void pump(int input, int output) {
|
||||||
|
pump_ex(input, output, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* pump_thread(void* data) {
|
||||||
|
int* files = (int*)data;
|
||||||
|
int input = files[0];
|
||||||
|
int output = files[1];
|
||||||
|
pump(input, output);
|
||||||
|
free(data);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pump_async(int input, int output) {
|
||||||
|
pthread_t writer;
|
||||||
|
int* files = (int*)malloc(sizeof(int) * 2);
|
||||||
|
if (files == NULL) {
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
files[0] = input;
|
||||||
|
files[1] = output;
|
||||||
|
pthread_create(&writer, NULL, pump_thread, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pts_open
|
||||||
|
*
|
||||||
|
* Opens a pts device and returns the name of the slave tty device.
|
||||||
|
*
|
||||||
|
* Arguments
|
||||||
|
* slave_name the name of the slave device
|
||||||
|
* slave_name_size the size of the buffer passed via slave_name
|
||||||
|
*
|
||||||
|
* Return Values
|
||||||
|
* on failure either -2 or -1 (errno set) 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 fdm;
|
||||||
|
char sn_tmp[256];
|
||||||
|
|
||||||
|
// Open master ptmx device
|
||||||
|
fdm = open("/dev/ptmx", O_RDWR);
|
||||||
|
if (fdm == -1)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
// Get the slave name
|
||||||
|
if (ptsname_r(fdm, slave_name, slave_name_size-1))
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
slave_name[slave_name_size - 1] = '\0';
|
||||||
|
|
||||||
|
// Grant, then unlock
|
||||||
|
if (grantpt(fdm) == -1)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
if (unlockpt(fdm) == -1)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
return fdm;
|
||||||
|
error:
|
||||||
|
close(fdm);
|
||||||
|
PLOGE("pts_open");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stores the previous termios of stdin
|
||||||
|
static struct termios old_stdin;
|
||||||
|
static int stdin_is_raw = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set_stdin_raw
|
||||||
|
*
|
||||||
|
* Changes stdin to raw unbuffered mode, disables echo,
|
||||||
|
* auto carriage return, etc.
|
||||||
|
*
|
||||||
|
* Return Value
|
||||||
|
* on failure -1, and errno is set
|
||||||
|
* on success 0
|
||||||
|
*/
|
||||||
|
int set_stdin_raw(void) {
|
||||||
|
struct termios new_termios;
|
||||||
|
|
||||||
|
// Save the current stdin termios
|
||||||
|
if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start from the current settings
|
||||||
|
new_termios = old_stdin;
|
||||||
|
|
||||||
|
// Make the terminal like an SSH or telnet client
|
||||||
|
new_termios.c_iflag |= IGNPAR;
|
||||||
|
new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
|
||||||
|
new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
|
||||||
|
new_termios.c_oflag &= ~OPOST;
|
||||||
|
new_termios.c_cc[VMIN] = 1;
|
||||||
|
new_termios.c_cc[VTIME] = 0;
|
||||||
|
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdin_is_raw = 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* restore_stdin
|
||||||
|
*
|
||||||
|
* Restore termios on stdin to the state it was before
|
||||||
|
* set_stdin_raw() was called. If set_stdin_raw() was
|
||||||
|
* never called, does nothing and doesn't return an error.
|
||||||
|
*
|
||||||
|
* This function is async-safe.
|
||||||
|
*
|
||||||
|
* Return Value
|
||||||
|
* on failure, -1 and errno is set
|
||||||
|
* on success, 0
|
||||||
|
*/
|
||||||
|
int restore_stdin(void) {
|
||||||
|
if (!stdin_is_raw) return 0;
|
||||||
|
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdin_is_raw = 0;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag indicating whether the sigwinch watcher should terminate.
|
||||||
|
static volatile int closing_time = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread process. Wait for a SIGWINCH to be received, then update
|
||||||
|
* the terminal size.
|
||||||
|
*/
|
||||||
|
static void *watch_sigwinch(void *data) {
|
||||||
|
sigset_t winch;
|
||||||
|
int sig;
|
||||||
|
int master = ((int *)data)[0];
|
||||||
|
int slave = ((int *)data)[1];
|
||||||
|
|
||||||
|
sigemptyset(&winch);
|
||||||
|
sigaddset(&winch, SIGWINCH);
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (closing_time) break;
|
||||||
|
|
||||||
|
// Get the new terminal size
|
||||||
|
struct winsize w;
|
||||||
|
if (ioctl(master, TIOCGWINSZ, &w) == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new terminal size
|
||||||
|
ioctl(slave, TIOCSWINSZ, &w);
|
||||||
|
|
||||||
|
} while (sigwait(&winch, &sig) == 0);
|
||||||
|
|
||||||
|
free(data);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* watch_sigwinch_async
|
||||||
|
*
|
||||||
|
* After calling this function, if the application receives
|
||||||
|
* SIGWINCH, the terminal window size will be read from
|
||||||
|
* "input" and set on "output".
|
||||||
|
*
|
||||||
|
* NOTE: This function blocks SIGWINCH and spawns a thread.
|
||||||
|
* NOTE 2: This function must be called before any of the
|
||||||
|
* pump functions.
|
||||||
|
*
|
||||||
|
* Arguments
|
||||||
|
* master A file descriptor of the TTY window size to follow
|
||||||
|
* slave A file descriptor of the TTY window size which is
|
||||||
|
* to be set on SIGWINCH
|
||||||
|
*
|
||||||
|
* Return Value
|
||||||
|
* on failure, -1 and errno will be set. In this case, no
|
||||||
|
* thread has been spawned and SIGWINCH will not be
|
||||||
|
* blocked.
|
||||||
|
* on success, 0
|
||||||
|
*/
|
||||||
|
int watch_sigwinch_async(int master, int slave) {
|
||||||
|
pthread_t watcher;
|
||||||
|
int *files = (int *) malloc(sizeof(int) * 2);
|
||||||
|
if (files == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block SIGWINCH so sigwait can later receive it
|
||||||
|
sigset_t winch;
|
||||||
|
sigemptyset(&winch);
|
||||||
|
sigaddset(&winch, SIGWINCH);
|
||||||
|
if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) {
|
||||||
|
free(files);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize some variables, then start the thread
|
||||||
|
closing_time = 0;
|
||||||
|
files[0] = master;
|
||||||
|
files[1] = slave;
|
||||||
|
int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files);
|
||||||
|
if (ret != 0) {
|
||||||
|
free(files);
|
||||||
|
errno = ret;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* watch_sigwinch_cleanup
|
||||||
|
*
|
||||||
|
* Cause the SIGWINCH watcher thread to terminate
|
||||||
|
*/
|
||||||
|
void watch_sigwinch_cleanup(void) {
|
||||||
|
closing_time = 1;
|
||||||
|
raise(SIGWINCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pump_stdin_async
|
||||||
|
*
|
||||||
|
* Forward data from STDIN to the given FD
|
||||||
|
* in a seperate thread
|
||||||
|
*/
|
||||||
|
void pump_stdin_async(int outfd) {
|
||||||
|
// Put stdin into raw mode
|
||||||
|
set_stdin_raw();
|
||||||
|
|
||||||
|
// Pump data from stdin to the PTY
|
||||||
|
pump_async(STDIN_FILENO, outfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pump_stdout_blocking
|
||||||
|
*
|
||||||
|
* Forward data from the FD to STDOUT.
|
||||||
|
* Returns when the remote end of the FD closes.
|
||||||
|
*
|
||||||
|
* Before returning, restores stdin settings.
|
||||||
|
*/
|
||||||
|
void pump_stdout_blocking(int infd) {
|
||||||
|
// Pump data from stdout to PTY
|
||||||
|
pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
restore_stdin();
|
||||||
|
watch_sigwinch_cleanup();
|
||||||
|
}
|
106
native/jni/su/pts.h
Normal file
106
native/jni/su/pts.h
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013, Tan Chee Eng (@tan-ce)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* pts.h
|
||||||
|
*
|
||||||
|
* Manages the pseudo-terminal driver on Linux/Android and provides some
|
||||||
|
* helper functions to handle raw input mode and terminal window resizing
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PTS_H_
|
||||||
|
#define _PTS_H_
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pts_open
|
||||||
|
*
|
||||||
|
* Opens a pts device and returns the name of the slave tty device.
|
||||||
|
*
|
||||||
|
* Arguments
|
||||||
|
* slave_name the name of the slave device
|
||||||
|
* slave_name_size the size of the buffer passed via slave_name
|
||||||
|
*
|
||||||
|
* Return Values
|
||||||
|
* on failure either -2 or -1 (errno set) is returned.
|
||||||
|
* on success, the file descriptor of the master device is returned.
|
||||||
|
*/
|
||||||
|
int pts_open(char *slave_name, size_t slave_name_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set_stdin_raw
|
||||||
|
*
|
||||||
|
* Changes stdin to raw unbuffered mode, disables echo,
|
||||||
|
* auto carriage return, etc.
|
||||||
|
*
|
||||||
|
* Return Value
|
||||||
|
* on failure -1, and errno is set
|
||||||
|
* on success 0
|
||||||
|
*/
|
||||||
|
int set_stdin_raw(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* restore_stdin
|
||||||
|
*
|
||||||
|
* Restore termios on stdin to the state it was before
|
||||||
|
* set_stdin_raw() was called. If set_stdin_raw() was
|
||||||
|
* never called, does nothing and doesn't return an error.
|
||||||
|
*
|
||||||
|
* This function is async-safe.
|
||||||
|
*
|
||||||
|
* Return Value
|
||||||
|
* on failure, -1 and errno is set
|
||||||
|
* on success, 0
|
||||||
|
*/
|
||||||
|
int restore_stdin(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* watch_sigwinch_async
|
||||||
|
*
|
||||||
|
* After calling this function, if the application receives
|
||||||
|
* SIGWINCH, the terminal window size will be read from
|
||||||
|
* "input" and set on "output".
|
||||||
|
*
|
||||||
|
* NOTE: This function blocks SIGWINCH and spawns a thread.
|
||||||
|
*
|
||||||
|
* Arguments
|
||||||
|
* master A file descriptor of the TTY window size to follow
|
||||||
|
* slave A file descriptor of the TTY window size which is
|
||||||
|
* to be set on SIGWINCH
|
||||||
|
*
|
||||||
|
* Return Value
|
||||||
|
* on failure, -1 and errno will be set. In this case, no
|
||||||
|
* thread has been spawned and SIGWINCH will not be
|
||||||
|
* blocked.
|
||||||
|
* on success, 0
|
||||||
|
*/
|
||||||
|
int watch_sigwinch_async(int master, int slave);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* watch_sigwinch_cleanup
|
||||||
|
*
|
||||||
|
* Cause the SIGWINCH watcher thread to terminate
|
||||||
|
*/
|
||||||
|
void watch_sigwinch_cleanup(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pump_stdin_async
|
||||||
|
*
|
||||||
|
* Forward data from STDIN to the given FD
|
||||||
|
* in a seperate thread
|
||||||
|
*/
|
||||||
|
void pump_stdin_async(int outfd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pump_stdout_blocking
|
||||||
|
*
|
||||||
|
* Forward data from the FD to STDOUT.
|
||||||
|
* Returns when the remote end of the FD closes.
|
||||||
|
*
|
||||||
|
* Before returning, restores stdin settings.
|
||||||
|
*/
|
||||||
|
void pump_stdout_blocking(int infd);
|
||||||
|
|
||||||
|
#endif
|
284
native/jni/su/su.c
Normal file
284
native/jni/su/su.c
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017, John Wu (@topjohnwu)
|
||||||
|
* Copyright 2015, Pierre-Hugues Husson <phh@phh.me>
|
||||||
|
* Copyright 2010, Adam Shanks (@ChainsDD)
|
||||||
|
* Copyright 2008, Zinx Verituse (@zinxv)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* su.c - The main function running in the daemon
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <selinux/selinux.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "su.h"
|
||||||
|
|
||||||
|
struct su_context *su_ctx;
|
||||||
|
|
||||||
|
static void usage(int status) {
|
||||||
|
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
|
||||||
|
|
||||||
|
fprintf(stream,
|
||||||
|
"MagiskSU v" xstr(MAGISK_VERSION) "(" xstr(MAGISK_VER_CODE) ")\n\n"
|
||||||
|
"Usage: su [options] [-] [user [argument...]]\n\n"
|
||||||
|
"Options:\n"
|
||||||
|
" -c, --command COMMAND pass COMMAND to the invoked shell\n"
|
||||||
|
" -h, --help display this help message and exit\n"
|
||||||
|
" -, -l, --login pretend the shell to be a login shell\n"
|
||||||
|
" -m, -p,\n"
|
||||||
|
" --preserve-environment preserve the entire environment\n"
|
||||||
|
" -s, --shell SHELL use SHELL instead of the default " DEFAULT_SHELL "\n"
|
||||||
|
" -v, --version display version number and exit\n"
|
||||||
|
" -V display version code and exit,\n"
|
||||||
|
" this is used almost exclusively by Superuser.apk\n"
|
||||||
|
" -mm, -M,\n"
|
||||||
|
" --mount-master run in the global mount namespace,\n"
|
||||||
|
" use if you need to publicly apply mounts\n");
|
||||||
|
exit2(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *concat_commands(int argc, char *argv[]) {
|
||||||
|
char command[ARG_MAX];
|
||||||
|
command[0] = '\0';
|
||||||
|
for (int i = optind - 1; i < argc; ++i) {
|
||||||
|
if (command[0])
|
||||||
|
sprintf(command, "%s %s", command, argv[i]);
|
||||||
|
else
|
||||||
|
strcpy(command, argv[i]);
|
||||||
|
}
|
||||||
|
return strdup(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populate_environment(const struct su_context *ctx) {
|
||||||
|
struct passwd *pw;
|
||||||
|
|
||||||
|
if (ctx->to.keepenv)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pw = getpwuid(ctx->to.uid);
|
||||||
|
if (pw) {
|
||||||
|
setenv("HOME", pw->pw_dir, 1);
|
||||||
|
if (ctx->to.shell)
|
||||||
|
setenv("SHELL", ctx->to.shell, 1);
|
||||||
|
else
|
||||||
|
setenv("SHELL", DEFAULT_SHELL, 1);
|
||||||
|
if (ctx->to.login || ctx->to.uid) {
|
||||||
|
setenv("USER", pw->pw_name, 1);
|
||||||
|
setenv("LOGNAME", pw->pw_name, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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->info->access.notify || su_ctx->info->access.log)
|
||||||
|
app_send_result(su_ctx, ALLOW);
|
||||||
|
|
||||||
|
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(su_ctx);
|
||||||
|
set_identity(su_ctx->to.uid);
|
||||||
|
|
||||||
|
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_send_result(su_ctx, DENY);
|
||||||
|
|
||||||
|
LOGW("su: request rejected (%u->%u)", su_ctx->info->uid, su_ctx->to.uid);
|
||||||
|
fprintf(stderr, "%s\n", strerror(EACCES));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void socket_cleanup() {
|
||||||
|
if (su_ctx && su_ctx->sock_path[0]) {
|
||||||
|
unlink(su_ctx->sock_path);
|
||||||
|
su_ctx->sock_path[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanup_signal(int sig) {
|
||||||
|
socket_cleanup();
|
||||||
|
exit2(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) {
|
||||||
|
int c, socket_serv_fd, fd;
|
||||||
|
char result[64];
|
||||||
|
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 },
|
||||||
|
};
|
||||||
|
|
||||||
|
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", MAGISKSU_VER_STR);
|
||||||
|
exit2(EXIT_SUCCESS);
|
||||||
|
case 'z':
|
||||||
|
// Do nothing, placed here for legacy support :)
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
su_ctx->info->dbs.v[SU_MNT_NS] = NAMESPACE_MODE_GLOBAL;
|
||||||
|
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;
|
||||||
|
optind++;
|
||||||
|
}
|
||||||
|
/* username or uid */
|
||||||
|
if (optind < argc) {
|
||||||
|
struct passwd *pw;
|
||||||
|
pw = getpwnam(argv[optind]);
|
||||||
|
if (pw)
|
||||||
|
su_ctx->to.uid = pw->pw_uid;
|
||||||
|
else
|
||||||
|
su_ctx->to.uid = atoi(argv[optind]);
|
||||||
|
optind++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle namespaces
|
||||||
|
switch (su_ctx->info->dbs.v[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change directory to cwd
|
||||||
|
chdir(su_ctx->cwd);
|
||||||
|
|
||||||
|
// New request or no db exist, notify user for response
|
||||||
|
if (su_ctx->pipefd[0] >= 0) {
|
||||||
|
socket_serv_fd = socket_create_temp(su_ctx->sock_path, sizeof(su_ctx->sock_path));
|
||||||
|
setup_sighandlers(cleanup_signal);
|
||||||
|
|
||||||
|
// Start activity
|
||||||
|
app_send_request(su_ctx);
|
||||||
|
|
||||||
|
atexit(socket_cleanup);
|
||||||
|
|
||||||
|
fd = socket_accept(socket_serv_fd);
|
||||||
|
socket_send_request(fd, su_ctx);
|
||||||
|
socket_receive_result(fd, result, sizeof(result));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
close(socket_serv_fd);
|
||||||
|
socket_cleanup();
|
||||||
|
|
||||||
|
if (strcmp(result, "socket:ALLOW") == 0)
|
||||||
|
su_ctx->info->access.policy = ALLOW;
|
||||||
|
else
|
||||||
|
su_ctx->info->access.policy = DENY;
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (su_ctx->info->access.policy == ALLOW)
|
||||||
|
allow();
|
||||||
|
else
|
||||||
|
deny();
|
||||||
|
}
|
||||||
|
|
76
native/jni/su/su.h
Normal file
76
native/jni/su/su.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/* su.h - Store all general su info
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SU_H_
|
||||||
|
#define _SU_H_
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "db.h"
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
#define MAGISKSU_VER_STR xstr(MAGISK_VERSION) ":MAGISKSU (topjohnwu)"
|
||||||
|
|
||||||
|
// This is used if wrapping the fragment classes and activities
|
||||||
|
// with classes in another package.
|
||||||
|
#define REQUESTOR_PREFIX JAVA_PACKAGE_NAME ".superuser"
|
||||||
|
|
||||||
|
#define DEFAULT_SHELL "/system/bin/sh"
|
||||||
|
|
||||||
|
struct su_info {
|
||||||
|
unsigned uid; /* Unique key to find su_info */
|
||||||
|
pthread_mutex_t lock; /* Internal lock */
|
||||||
|
int count; /* Just a count for debugging purpose */
|
||||||
|
|
||||||
|
/* These values should be guarded with internal lock */
|
||||||
|
struct db_settings dbs;
|
||||||
|
struct db_strings str;
|
||||||
|
struct su_access access;
|
||||||
|
struct stat manager_stat;
|
||||||
|
|
||||||
|
/* These should be guarded with global list lock */
|
||||||
|
struct list_head pos;
|
||||||
|
int ref;
|
||||||
|
int clock;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct su_request {
|
||||||
|
unsigned uid;
|
||||||
|
int login;
|
||||||
|
int keepenv;
|
||||||
|
char *shell;
|
||||||
|
char *command;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct su_context {
|
||||||
|
struct su_info *info;
|
||||||
|
struct su_request to;
|
||||||
|
pid_t pid;
|
||||||
|
char cwd[PATH_MAX];
|
||||||
|
char sock_path[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);
|
||||||
|
|
||||||
|
// su_client.c
|
||||||
|
|
||||||
|
int socket_create_temp(char *path, size_t len);
|
||||||
|
int socket_accept(int serv_fd);
|
||||||
|
void socket_send_request(int fd, const struct su_context *ctx);
|
||||||
|
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);
|
||||||
|
|
||||||
|
#endif
|
452
native/jni/su/su_daemon.c
Normal file
452
native/jni/su/su_daemon.c
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
/* su_daemon.c - The entrypoint for su, connect to daemon and send correct info
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 <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"
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
// Constants for the atty bitfield
|
||||||
|
#define ATTY_IN 1
|
||||||
|
#define ATTY_OUT 2
|
||||||
|
#define ATTY_ERR 4
|
||||||
|
|
||||||
|
#define TIMEOUT 3
|
||||||
|
|
||||||
|
#define LOCK_LIST() pthread_mutex_lock(&list_lock)
|
||||||
|
#define LOCK_UID() pthread_mutex_lock(&info->lock)
|
||||||
|
#define UNLOCK_LIST() pthread_mutex_unlock(&list_lock)
|
||||||
|
#define UNLOCK_UID() pthread_mutex_unlock(&ctx.info->lock)
|
||||||
|
|
||||||
|
static struct list_head info_cache = { .prev = &info_cache, .next = &info_cache };
|
||||||
|
static pthread_mutex_t list_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
sleep(1);
|
||||||
|
if (info->clock && --info->clock == 0) {
|
||||||
|
LOCK_LIST();
|
||||||
|
list_pop(&info->pos);
|
||||||
|
UNLOCK_LIST();
|
||||||
|
}
|
||||||
|
if (!info->clock && !info->ref) {
|
||||||
|
pthread_mutex_destroy(&info->lock);
|
||||||
|
free(info);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void database_check(struct su_info *info) {
|
||||||
|
int uid = info->uid;
|
||||||
|
sqlite3 *db = get_magiskdb();
|
||||||
|
if (db) {
|
||||||
|
get_db_settings(db, -1, &info->dbs);
|
||||||
|
get_db_strings(db, -1, &info->str);
|
||||||
|
|
||||||
|
// Check multiuser settings
|
||||||
|
switch (info->dbs.v[SU_MULTIUSER_MODE]) {
|
||||||
|
case MULTIUSER_MODE_OWNER_ONLY:
|
||||||
|
if (info->uid / 100000) {
|
||||||
|
uid = -1;
|
||||||
|
info->access = NO_SU_ACCESS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MULTIUSER_MODE_OWNER_MANAGED:
|
||||||
|
uid = info->uid % 100000;
|
||||||
|
break;
|
||||||
|
case MULTIUSER_MODE_USER:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid > 0)
|
||||||
|
get_uid_policy(db, uid, &info->access);
|
||||||
|
sqlite3_close(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to check our manager
|
||||||
|
if (info->access.log || info->access.notify)
|
||||||
|
validate_manager(info->str.s[SU_MANAGER], uid / 100000, &info->manager_stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct su_info *get_su_info(unsigned uid) {
|
||||||
|
struct su_info *info = NULL, *node;
|
||||||
|
|
||||||
|
LOCK_LIST();
|
||||||
|
|
||||||
|
// Search for existing info in cache
|
||||||
|
list_for_each(node, &info_cache, struct su_info, pos) {
|
||||||
|
if (node->uid == uid) {
|
||||||
|
info = node;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int cache_miss = info == NULL;
|
||||||
|
|
||||||
|
if (cache_miss) {
|
||||||
|
// If cache miss, create a new one and push to cache
|
||||||
|
info = malloc(sizeof(*info));
|
||||||
|
info->uid = uid;
|
||||||
|
info->dbs = DEFAULT_DB_SETTINGS;
|
||||||
|
info->access = DEFAULT_SU_ACCESS;
|
||||||
|
INIT_DB_STRINGS(&info->str);
|
||||||
|
info->ref = 0;
|
||||||
|
info->count = 0;
|
||||||
|
pthread_mutex_init(&info->lock, NULL);
|
||||||
|
list_insert_end(&info_cache, &info->pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the cache status
|
||||||
|
info->clock = TIMEOUT;
|
||||||
|
++info->ref;
|
||||||
|
|
||||||
|
// Start a thread to maintain the info cache
|
||||||
|
if (cache_miss) {
|
||||||
|
pthread_t thread;
|
||||||
|
xpthread_create(&thread, NULL, info_collector, info);
|
||||||
|
pthread_detach(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNLOCK_LIST();
|
||||||
|
|
||||||
|
LOGD("su: request from uid=[%d] (#%d)\n", info->uid, ++info->count);
|
||||||
|
|
||||||
|
// Lock before the policy is determined
|
||||||
|
LOCK_UID();
|
||||||
|
|
||||||
|
if (info->access.policy == QUERY) {
|
||||||
|
// Not cached, get data from database
|
||||||
|
database_check(info);
|
||||||
|
|
||||||
|
// Check su access settings
|
||||||
|
switch (info->dbs.v[ROOT_ACCESS]) {
|
||||||
|
case ROOT_ACCESS_DISABLED:
|
||||||
|
LOGE("Root access is disabled!\n");
|
||||||
|
info->access = NO_SU_ACCESS;
|
||||||
|
break;
|
||||||
|
case ROOT_ACCESS_ADB_ONLY:
|
||||||
|
if (info->uid != UID_SHELL) {
|
||||||
|
LOGE("Root access limited to ADB only!\n");
|
||||||
|
info->access = NO_SU_ACCESS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ROOT_ACCESS_APPS_ONLY:
|
||||||
|
if (info->uid == UID_SHELL) {
|
||||||
|
LOGE("Root access is disabled for ADB!\n");
|
||||||
|
info->access = NO_SU_ACCESS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ROOT_ACCESS_APPS_AND_ADB:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's the manager, allow it silently
|
||||||
|
if ((info->uid % 100000) == (info->manager_stat.st_uid % 100000))
|
||||||
|
info->access = SILENT_SU_ACCESS;
|
||||||
|
|
||||||
|
// Allow if it's root
|
||||||
|
if (info->uid == UID_ROOT)
|
||||||
|
info->access = SILENT_SU_ACCESS;
|
||||||
|
|
||||||
|
// If still not determined, check if manager exists
|
||||||
|
if (info->access.policy == QUERY && info->str.s[SU_MANAGER][0] == '\0')
|
||||||
|
info->access = NO_SU_ACCESS;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void su_executor(int client) {
|
||||||
|
LOGD("su: executor started\n");
|
||||||
|
|
||||||
|
// ack
|
||||||
|
write_int(client, 0);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
int fd = open(path, O_RDONLY);
|
||||||
|
read(fd, buf, sizeof(buf));
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get pts_slave
|
||||||
|
char *pts_slave = read_string(client);
|
||||||
|
|
||||||
|
// The FDs for each of the streams
|
||||||
|
int infd = recv_fd(client);
|
||||||
|
int outfd = recv_fd(client);
|
||||||
|
int errfd = recv_fd(client);
|
||||||
|
int ptsfd = -1;
|
||||||
|
|
||||||
|
// We no longer need the access to socket in the child, close it
|
||||||
|
close(client);
|
||||||
|
|
||||||
|
if (pts_slave[0]) {
|
||||||
|
LOGD("su: pts_slave=[%s]\n", pts_slave);
|
||||||
|
// Check pts_slave file is owned by daemon_from_uid
|
||||||
|
struct stat st;
|
||||||
|
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) {
|
||||||
|
LOGE("su: Wrong permission of pts_slave");
|
||||||
|
su_ctx->info->access.policy = DENY;
|
||||||
|
exit2(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);
|
||||||
|
|
||||||
|
if (infd < 0) {
|
||||||
|
LOGD("su: stdin using PTY");
|
||||||
|
infd = ptsfd;
|
||||||
|
}
|
||||||
|
if (outfd < 0) {
|
||||||
|
LOGD("su: stdout using PTY");
|
||||||
|
outfd = ptsfd;
|
||||||
|
}
|
||||||
|
if (errfd < 0) {
|
||||||
|
LOGD("su: stderr using PTY");
|
||||||
|
errfd = ptsfd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(pts_slave);
|
||||||
|
|
||||||
|
// Swap out stdin, stdout, stderr
|
||||||
|
xdup2(infd, STDIN_FILENO);
|
||||||
|
xdup2(outfd, STDOUT_FILENO);
|
||||||
|
xdup2(errfd, STDERR_FILENO);
|
||||||
|
|
||||||
|
close(ptsfd);
|
||||||
|
|
||||||
|
// 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_UID();
|
||||||
|
write_int(client, DENY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still not determined, open a pipe and wait for results
|
||||||
|
if (ctx.info->access.policy == QUERY)
|
||||||
|
xpipe2(ctx.pipefd, O_CLOEXEC);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The policy is determined, unlock
|
||||||
|
UNLOCK_UID();
|
||||||
|
|
||||||
|
// Info is now useless to us, decrement reference count
|
||||||
|
--ctx.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(buffer, sizeof(buffer));
|
||||||
|
} else {
|
||||||
|
buffer[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the pts_slave path to the daemon
|
||||||
|
write_string(socketfd, buffer);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for acknowledgement from daemon
|
||||||
|
if (read_int(socketfd)) {
|
||||||
|
// Fast fail
|
||||||
|
fprintf(stderr, "%s\n", strerror(EACCES));
|
||||||
|
return DENY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atty & ATTY_IN) {
|
||||||
|
setup_sighandlers(sighandler);
|
||||||
|
pump_stdin_async(ptmx);
|
||||||
|
}
|
||||||
|
if (atty & ATTY_OUT) {
|
||||||
|
pump_stdout_blocking(ptmx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the exit code
|
||||||
|
int code = read_int(socketfd);
|
||||||
|
close(socketfd);
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
108
native/jni/su/su_socket.c
Normal file
108
native/jni/su/su_socket.c
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/* 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 <endian.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <selinux/selinux.h>
|
||||||
|
|
||||||
|
#include "magisk.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "su.h"
|
||||||
|
#include "magiskpolicy.h"
|
||||||
|
|
||||||
|
int socket_create_temp(char *path, size_t len) {
|
||||||
|
int fd;
|
||||||
|
struct sockaddr_un sun;
|
||||||
|
|
||||||
|
fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 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, "/dev/.socket%d", getpid());
|
||||||
|
strcpy(sun.sun_path, 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(path);
|
||||||
|
|
||||||
|
xbind(fd, (struct sockaddr*) &sun, sizeof(sun));
|
||||||
|
xlisten(fd, 1);
|
||||||
|
|
||||||
|
// Set attributes so requester can access it
|
||||||
|
setfilecon(path, "u:object_r:"SEPOL_FILE_DOMAIN":s0");
|
||||||
|
chown(path, su_ctx->info->manager_stat.st_uid, su_ctx->info->manager_stat.st_gid);
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int socket_accept(int serv_fd) {
|
||||||
|
struct timeval tv;
|
||||||
|
fd_set fds;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
/* Wait 60 seconds for a connection, then give up. */
|
||||||
|
tv.tv_sec = 60;
|
||||||
|
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 xaccept4(serv_fd, NULL, NULL, SOCK_CLOEXEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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->info->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';
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user