mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-02 18:12:07 +00:00
Convert indentation to spaces
The tab war is lost
This commit is contained in:
@@ -10,9 +10,9 @@
|
||||
using namespace std;
|
||||
|
||||
enum {
|
||||
NAMED_ACTIVITY,
|
||||
PKG_ACTIVITY,
|
||||
CONTENT_PROVIDER
|
||||
NAMED_ACTIVITY,
|
||||
PKG_ACTIVITY,
|
||||
CONTENT_PROVIDER
|
||||
};
|
||||
|
||||
#define CALL_PROVIDER \
|
||||
@@ -34,179 +34,179 @@ enum {
|
||||
(to.command[0] ? to.command : to.shell[0] ? to.shell : DEFAULT_SHELL)
|
||||
|
||||
class Extra {
|
||||
const char *key;
|
||||
enum {
|
||||
INT,
|
||||
BOOL,
|
||||
STRING
|
||||
} type;
|
||||
union {
|
||||
int int_val;
|
||||
bool bool_val;
|
||||
const char * str_val;
|
||||
};
|
||||
char buf[32];
|
||||
const char *key;
|
||||
enum {
|
||||
INT,
|
||||
BOOL,
|
||||
STRING
|
||||
} type;
|
||||
union {
|
||||
int int_val;
|
||||
bool bool_val;
|
||||
const char * str_val;
|
||||
};
|
||||
char buf[32];
|
||||
public:
|
||||
Extra(const char *k, int v): key(k), type(INT), int_val(v) {}
|
||||
Extra(const char *k, bool v): key(k), type(BOOL), bool_val(v) {}
|
||||
Extra(const char *k, const char *v): key(k), type(STRING), str_val(v) {}
|
||||
Extra(const char *k, int v): key(k), type(INT), int_val(v) {}
|
||||
Extra(const char *k, bool v): key(k), type(BOOL), bool_val(v) {}
|
||||
Extra(const char *k, const char *v): key(k), type(STRING), str_val(v) {}
|
||||
|
||||
void add_intent(vector<const char *> &vec) {
|
||||
const char *val;
|
||||
switch (type) {
|
||||
case INT:
|
||||
vec.push_back("--ei");
|
||||
sprintf(buf, "%d", int_val);
|
||||
val = buf;
|
||||
break;
|
||||
case BOOL:
|
||||
vec.push_back("--ez");
|
||||
val = bool_val ? "true" : "false";
|
||||
break;
|
||||
case STRING:
|
||||
vec.push_back("--es");
|
||||
val = str_val;
|
||||
break;
|
||||
}
|
||||
vec.push_back(key);
|
||||
vec.push_back(val);
|
||||
}
|
||||
void add_intent(vector<const char *> &vec) {
|
||||
const char *val;
|
||||
switch (type) {
|
||||
case INT:
|
||||
vec.push_back("--ei");
|
||||
sprintf(buf, "%d", int_val);
|
||||
val = buf;
|
||||
break;
|
||||
case BOOL:
|
||||
vec.push_back("--ez");
|
||||
val = bool_val ? "true" : "false";
|
||||
break;
|
||||
case STRING:
|
||||
vec.push_back("--es");
|
||||
val = str_val;
|
||||
break;
|
||||
}
|
||||
vec.push_back(key);
|
||||
vec.push_back(val);
|
||||
}
|
||||
|
||||
void add_bind(vector<const char *> &vec) {
|
||||
switch (type) {
|
||||
case INT:
|
||||
sprintf(buf, "%s:i:%d", key, int_val);
|
||||
break;
|
||||
case BOOL:
|
||||
sprintf(buf, "%s:b:%s", key, bool_val ? "true" : "false");
|
||||
break;
|
||||
case STRING:
|
||||
sprintf(buf, "%s:s:%s", key, str_val);
|
||||
break;
|
||||
}
|
||||
vec.push_back("--extra");
|
||||
vec.push_back(buf);
|
||||
}
|
||||
void add_bind(vector<const char *> &vec) {
|
||||
switch (type) {
|
||||
case INT:
|
||||
sprintf(buf, "%s:i:%d", key, int_val);
|
||||
break;
|
||||
case BOOL:
|
||||
sprintf(buf, "%s:b:%s", key, bool_val ? "true" : "false");
|
||||
break;
|
||||
case STRING:
|
||||
sprintf(buf, "%s:s:%s", key, str_val);
|
||||
break;
|
||||
}
|
||||
vec.push_back("--extra");
|
||||
vec.push_back(buf);
|
||||
}
|
||||
};
|
||||
|
||||
static bool check_no_error(int fd) {
|
||||
char buf[1024];
|
||||
auto out = xopen_file(fd, "r");
|
||||
while (fgets(buf, sizeof(buf), out.get())) {
|
||||
if (strncmp(buf, "Error", 5) == 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
char buf[1024];
|
||||
auto out = xopen_file(fd, "r");
|
||||
while (fgets(buf, sizeof(buf), out.get())) {
|
||||
if (strncmp(buf, "Error", 5) == 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void exec_cmd(const char *action, vector<Extra> &data,
|
||||
const shared_ptr<su_info> &info, int mode = CONTENT_PROVIDER) {
|
||||
char target[128];
|
||||
char user[4];
|
||||
sprintf(user, "%d", get_user(info));
|
||||
const shared_ptr<su_info> &info, int mode = CONTENT_PROVIDER) {
|
||||
char target[128];
|
||||
char user[4];
|
||||
sprintf(user, "%d", get_user(info));
|
||||
|
||||
// First try content provider call method
|
||||
if (mode >= CONTENT_PROVIDER) {
|
||||
sprintf(target, "content://%s.provider", info->str[SU_MANAGER].data());
|
||||
vector<const char *> args{ CALL_PROVIDER };
|
||||
for (auto &e : data) {
|
||||
e.add_bind(args);
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
exec_t exec {
|
||||
.err = true,
|
||||
.fd = -1,
|
||||
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/content.jar", 1); },
|
||||
.argv = args.data()
|
||||
};
|
||||
exec_command_sync(exec);
|
||||
if (check_no_error(exec.fd))
|
||||
return;
|
||||
}
|
||||
// First try content provider call method
|
||||
if (mode >= CONTENT_PROVIDER) {
|
||||
sprintf(target, "content://%s.provider", info->str[SU_MANAGER].data());
|
||||
vector<const char *> args{ CALL_PROVIDER };
|
||||
for (auto &e : data) {
|
||||
e.add_bind(args);
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
exec_t exec {
|
||||
.err = true,
|
||||
.fd = -1,
|
||||
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/content.jar", 1); },
|
||||
.argv = args.data()
|
||||
};
|
||||
exec_command_sync(exec);
|
||||
if (check_no_error(exec.fd))
|
||||
return;
|
||||
}
|
||||
|
||||
vector<const char *> args{ START_ACTIVITY };
|
||||
for (auto &e : data) {
|
||||
e.add_intent(args);
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
exec_t exec {
|
||||
.err = true,
|
||||
.fd = -1,
|
||||
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/am.jar", 1); },
|
||||
.argv = args.data()
|
||||
};
|
||||
vector<const char *> args{ START_ACTIVITY };
|
||||
for (auto &e : data) {
|
||||
e.add_intent(args);
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
exec_t exec {
|
||||
.err = true,
|
||||
.fd = -1,
|
||||
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/am.jar", 1); },
|
||||
.argv = args.data()
|
||||
};
|
||||
|
||||
if (mode >= PKG_ACTIVITY) {
|
||||
// Then try start activity without component name
|
||||
strcpy(target, info->str[SU_MANAGER].data());
|
||||
exec_command_sync(exec);
|
||||
if (check_no_error(exec.fd))
|
||||
return;
|
||||
}
|
||||
if (mode >= PKG_ACTIVITY) {
|
||||
// Then try start activity without component name
|
||||
strcpy(target, info->str[SU_MANAGER].data());
|
||||
exec_command_sync(exec);
|
||||
if (check_no_error(exec.fd))
|
||||
return;
|
||||
}
|
||||
|
||||
// Finally, fallback to start activity with component name
|
||||
args[4] = "-n";
|
||||
sprintf(target, "%s/a.m", info->str[SU_MANAGER].data());
|
||||
exec.fd = -2;
|
||||
exec.fork = fork_dont_care;
|
||||
exec_command(exec);
|
||||
// Finally, fallback to start activity with component name
|
||||
args[4] = "-n";
|
||||
sprintf(target, "%s/a.m", info->str[SU_MANAGER].data());
|
||||
exec.fd = -2;
|
||||
exec.fork = fork_dont_care;
|
||||
exec_command(exec);
|
||||
}
|
||||
|
||||
void app_log(const su_context &ctx) {
|
||||
if (fork_dont_care() == 0) {
|
||||
vector<Extra> extras;
|
||||
extras.reserve(6);
|
||||
extras.emplace_back("from.uid", ctx.info->uid);
|
||||
extras.emplace_back("to.uid", ctx.req.uid);
|
||||
extras.emplace_back("pid", ctx.pid);
|
||||
extras.emplace_back("policy", ctx.info->access.policy);
|
||||
extras.emplace_back("command", get_cmd(ctx.req));
|
||||
extras.emplace_back("notify", (bool) ctx.info->access.notify);
|
||||
if (fork_dont_care() == 0) {
|
||||
vector<Extra> extras;
|
||||
extras.reserve(6);
|
||||
extras.emplace_back("from.uid", ctx.info->uid);
|
||||
extras.emplace_back("to.uid", ctx.req.uid);
|
||||
extras.emplace_back("pid", ctx.pid);
|
||||
extras.emplace_back("policy", ctx.info->access.policy);
|
||||
extras.emplace_back("command", get_cmd(ctx.req));
|
||||
extras.emplace_back("notify", (bool) ctx.info->access.notify);
|
||||
|
||||
exec_cmd("log", extras, ctx.info);
|
||||
exit(0);
|
||||
}
|
||||
exec_cmd("log", extras, ctx.info);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
void app_notify(const su_context &ctx) {
|
||||
if (fork_dont_care() == 0) {
|
||||
vector<Extra> extras;
|
||||
extras.reserve(2);
|
||||
extras.emplace_back("from.uid", ctx.info->uid);
|
||||
extras.emplace_back("policy", ctx.info->access.policy);
|
||||
if (fork_dont_care() == 0) {
|
||||
vector<Extra> extras;
|
||||
extras.reserve(2);
|
||||
extras.emplace_back("from.uid", ctx.info->uid);
|
||||
extras.emplace_back("policy", ctx.info->access.policy);
|
||||
|
||||
exec_cmd("notify", extras, ctx.info);
|
||||
exit(0);
|
||||
}
|
||||
exec_cmd("notify", extras, ctx.info);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
int app_request(const shared_ptr<su_info> &info) {
|
||||
// Create FIFO
|
||||
char fifo[64];
|
||||
strcpy(fifo, "/dev/socket/");
|
||||
gen_rand_str(fifo + 12, 32, true);
|
||||
mkfifo(fifo, 0600);
|
||||
chown(fifo, info->mgr_st.st_uid, info->mgr_st.st_gid);
|
||||
setfilecon(fifo, "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||
// Create FIFO
|
||||
char fifo[64];
|
||||
strcpy(fifo, "/dev/socket/");
|
||||
gen_rand_str(fifo + 12, 32, true);
|
||||
mkfifo(fifo, 0600);
|
||||
chown(fifo, info->mgr_st.st_uid, info->mgr_st.st_gid);
|
||||
setfilecon(fifo, "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||
|
||||
// Send request
|
||||
vector<Extra> extras;
|
||||
extras.reserve(2);
|
||||
extras.emplace_back("fifo", fifo);
|
||||
extras.emplace_back("uid", info->uid);
|
||||
exec_cmd("request", extras, info, PKG_ACTIVITY);
|
||||
// Send request
|
||||
vector<Extra> extras;
|
||||
extras.reserve(2);
|
||||
extras.emplace_back("fifo", fifo);
|
||||
extras.emplace_back("uid", info->uid);
|
||||
exec_cmd("request", extras, info, PKG_ACTIVITY);
|
||||
|
||||
// Wait for data input for at most 70 seconds
|
||||
int fd = xopen(fifo, O_RDONLY | O_CLOEXEC);
|
||||
struct pollfd pfd = {
|
||||
.fd = fd,
|
||||
.events = POLL_IN
|
||||
};
|
||||
if (xpoll(&pfd, 1, 70 * 1000) <= 0) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
// Wait for data input for at most 70 seconds
|
||||
int fd = xopen(fifo, O_RDONLY | O_CLOEXEC);
|
||||
struct pollfd pfd = {
|
||||
.fd = fd,
|
||||
.events = POLL_IN
|
||||
};
|
||||
if (xpoll(&pfd, 1, 70 * 1000) <= 0) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
unlink(fifo);
|
||||
return fd;
|
||||
unlink(fifo);
|
||||
return fd;
|
||||
}
|
||||
|
||||
@@ -26,16 +26,16 @@
|
||||
*/
|
||||
// Ensures all the data is written out
|
||||
static int write_blocking(int fd, char *buf, ssize_t bufsz) {
|
||||
ssize_t ret, written;
|
||||
ssize_t ret, written;
|
||||
|
||||
written = 0;
|
||||
do {
|
||||
ret = write(fd, buf + written, bufsz - written);
|
||||
if (ret == -1) return -1;
|
||||
written += ret;
|
||||
} while (written < bufsz);
|
||||
written = 0;
|
||||
do {
|
||||
ret = write(fd, buf + written, bufsz - written);
|
||||
if (ret == -1) return -1;
|
||||
written += ret;
|
||||
} while (written < bufsz);
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,28 +43,28 @@ static int write_blocking(int fd, char *buf, ssize_t bufsz) {
|
||||
* true, then close the output FD when we're done.
|
||||
*/
|
||||
static void pump(int input, int output, bool close_output = true) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
static void* pump_thread(void* data) {
|
||||
int *fds = (int*) data;
|
||||
pump(fds[0], fds[1]);
|
||||
delete[] fds;
|
||||
return nullptr;
|
||||
int *fds = (int*) data;
|
||||
pump(fds[0], fds[1]);
|
||||
delete[] fds;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void pump_async(int input, int output) {
|
||||
pthread_t writer;
|
||||
int *fds = new int[2];
|
||||
fds[0] = input;
|
||||
fds[1] = output;
|
||||
pthread_create(&writer, nullptr, pump_thread, fds);
|
||||
pthread_t writer;
|
||||
int *fds = new int[2];
|
||||
fds[0] = input;
|
||||
fds[1] = output;
|
||||
pthread_create(&writer, nullptr, pump_thread, fds);
|
||||
}
|
||||
|
||||
|
||||
@@ -82,31 +82,31 @@ static void pump_async(int input, int output) {
|
||||
* on success, the file descriptor of the master device is returned.
|
||||
*/
|
||||
int pts_open(char *slave_name, size_t slave_name_size) {
|
||||
int fdm;
|
||||
int fdm;
|
||||
|
||||
// Open master ptmx device
|
||||
fdm = open("/dev/ptmx", O_RDWR);
|
||||
if (fdm == -1)
|
||||
goto error;
|
||||
// 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;
|
||||
// Get the slave name
|
||||
if (ptsname_r(fdm, slave_name, slave_name_size-1))
|
||||
goto error;
|
||||
|
||||
slave_name[slave_name_size - 1] = '\0';
|
||||
slave_name[slave_name_size - 1] = '\0';
|
||||
|
||||
// Grant, then unlock
|
||||
if (grantpt(fdm) == -1)
|
||||
goto error;
|
||||
// Grant, then unlock
|
||||
if (grantpt(fdm) == -1)
|
||||
goto error;
|
||||
|
||||
if (unlockpt(fdm) == -1)
|
||||
goto error;
|
||||
if (unlockpt(fdm) == -1)
|
||||
goto error;
|
||||
|
||||
return fdm;
|
||||
return fdm;
|
||||
error:
|
||||
close(fdm);
|
||||
PLOGE("pts_open");
|
||||
return -1;
|
||||
close(fdm);
|
||||
PLOGE("pts_open");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Stores the previous termios of stdin
|
||||
@@ -124,31 +124,31 @@ static int stdin_is_raw = 0;
|
||||
* on success 0
|
||||
*/
|
||||
int set_stdin_raw(void) {
|
||||
struct termios new_termios;
|
||||
struct termios new_termios;
|
||||
|
||||
// Save the current stdin termios
|
||||
if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
|
||||
return -1;
|
||||
}
|
||||
// Save the current stdin termios
|
||||
if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Start from the current settings
|
||||
new_termios = old_stdin;
|
||||
// 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;
|
||||
// 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;
|
||||
}
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
stdin_is_raw = 1;
|
||||
stdin_is_raw = 1;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,15 +165,15 @@ int set_stdin_raw(void) {
|
||||
* on success, 0
|
||||
*/
|
||||
int restore_stdin(void) {
|
||||
if (!stdin_is_raw) return 0;
|
||||
if (!stdin_is_raw) return 0;
|
||||
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
stdin_is_raw = 0;
|
||||
stdin_is_raw = 0;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Flag indicating whether the sigwinch watcher should terminate.
|
||||
@@ -184,30 +184,30 @@ static volatile bool close_sigwinch_watcher = false;
|
||||
* the terminal size.
|
||||
*/
|
||||
static void *watch_sigwinch(void *data) {
|
||||
sigset_t winch;
|
||||
int *fds = (int *)data;
|
||||
int sig;
|
||||
sigset_t winch;
|
||||
int *fds = (int *)data;
|
||||
int sig;
|
||||
|
||||
sigemptyset(&winch);
|
||||
sigaddset(&winch, SIGWINCH);
|
||||
pthread_sigmask(SIG_UNBLOCK, &winch, nullptr);
|
||||
sigemptyset(&winch);
|
||||
sigaddset(&winch, SIGWINCH);
|
||||
pthread_sigmask(SIG_UNBLOCK, &winch, nullptr);
|
||||
|
||||
do {
|
||||
if (close_sigwinch_watcher)
|
||||
break;
|
||||
do {
|
||||
if (close_sigwinch_watcher)
|
||||
break;
|
||||
|
||||
// Get the new terminal size
|
||||
struct winsize w;
|
||||
if (ioctl(fds[0], TIOCGWINSZ, &w) == -1)
|
||||
continue;
|
||||
// Get the new terminal size
|
||||
struct winsize w;
|
||||
if (ioctl(fds[0], TIOCGWINSZ, &w) == -1)
|
||||
continue;
|
||||
|
||||
// Set the new terminal size
|
||||
ioctl(fds[1], TIOCSWINSZ, &w);
|
||||
// Set the new terminal size
|
||||
ioctl(fds[1], TIOCSWINSZ, &w);
|
||||
|
||||
} while (sigwait(&winch, &sig) == 0);
|
||||
delete[] fds;
|
||||
} while (sigwait(&winch, &sig) == 0);
|
||||
delete[] fds;
|
||||
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,30 +233,30 @@ static void *watch_sigwinch(void *data) {
|
||||
* on success, 0
|
||||
*/
|
||||
int watch_sigwinch_async(int master, int slave) {
|
||||
pthread_t watcher;
|
||||
int *fds = new int[2];
|
||||
pthread_t watcher;
|
||||
int *fds = new int[2];
|
||||
|
||||
// Block SIGWINCH so sigwait can later receive it
|
||||
sigset_t winch;
|
||||
sigemptyset(&winch);
|
||||
sigaddset(&winch, SIGWINCH);
|
||||
if (pthread_sigmask(SIG_BLOCK, &winch, nullptr) == -1) {
|
||||
delete[] fds;
|
||||
return -1;
|
||||
}
|
||||
// Block SIGWINCH so sigwait can later receive it
|
||||
sigset_t winch;
|
||||
sigemptyset(&winch);
|
||||
sigaddset(&winch, SIGWINCH);
|
||||
if (pthread_sigmask(SIG_BLOCK, &winch, nullptr) == -1) {
|
||||
delete[] fds;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Initialize some variables, then start the thread
|
||||
close_sigwinch_watcher = 0;
|
||||
fds[0] = master;
|
||||
fds[1] = slave;
|
||||
int ret = pthread_create(&watcher, nullptr, &watch_sigwinch, fds);
|
||||
if (ret != 0) {
|
||||
delete[] fds;
|
||||
errno = ret;
|
||||
return -1;
|
||||
}
|
||||
// Initialize some variables, then start the thread
|
||||
close_sigwinch_watcher = 0;
|
||||
fds[0] = master;
|
||||
fds[1] = slave;
|
||||
int ret = pthread_create(&watcher, nullptr, &watch_sigwinch, fds);
|
||||
if (ret != 0) {
|
||||
delete[] fds;
|
||||
errno = ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,11 +266,11 @@ int watch_sigwinch_async(int master, int slave) {
|
||||
* in a seperate thread
|
||||
*/
|
||||
void pump_stdin_async(int outfd) {
|
||||
// Put stdin into raw mode
|
||||
set_stdin_raw();
|
||||
// Put stdin into raw mode
|
||||
set_stdin_raw();
|
||||
|
||||
// Pump data from stdin to the PTY
|
||||
pump_async(STDIN_FILENO, outfd);
|
||||
// Pump data from stdin to the PTY
|
||||
pump_async(STDIN_FILENO, outfd);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,11 +282,11 @@ void pump_stdin_async(int outfd) {
|
||||
* Before returning, restores stdin settings.
|
||||
*/
|
||||
void pump_stdout_blocking(int infd) {
|
||||
// Pump data from stdout to PTY
|
||||
pump(infd, STDOUT_FILENO, false /* Don't close output when done */);
|
||||
// Pump data from stdout to PTY
|
||||
pump(infd, STDOUT_FILENO, false /* Don't close output when done */);
|
||||
|
||||
// Cleanup
|
||||
restore_stdin();
|
||||
close_sigwinch_watcher = true;
|
||||
raise(SIGWINCH);
|
||||
// Cleanup
|
||||
restore_stdin();
|
||||
close_sigwinch_watcher = true;
|
||||
raise(SIGWINCH);
|
||||
}
|
||||
|
||||
@@ -31,200 +31,200 @@
|
||||
int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };
|
||||
|
||||
static void usage(int status) {
|
||||
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
|
||||
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
|
||||
|
||||
fprintf(stream,
|
||||
"MagiskSU\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"
|
||||
" -mm, -M,\n"
|
||||
" --mount-master force run in the global mount namespace\n");
|
||||
exit(status);
|
||||
fprintf(stream,
|
||||
"MagiskSU\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"
|
||||
" -mm, -M,\n"
|
||||
" --mount-master force run in the global mount namespace\n");
|
||||
exit(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);
|
||||
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 sighandler(int sig) {
|
||||
restore_stdin();
|
||||
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);
|
||||
// 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;
|
||||
// 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, nullptr);
|
||||
}
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_handler = SIG_DFL;
|
||||
for (int i = 0; quit_signals[i]; ++i) {
|
||||
sigaction(quit_signals[i], &act, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void setup_sighandlers(void (*handler)(int)) {
|
||||
struct sigaction act;
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_handler = handler;
|
||||
for (int i = 0; quit_signals[i]; ++i) {
|
||||
sigaction(quit_signals[i], &act, nullptr);
|
||||
}
|
||||
struct sigaction act;
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_handler = handler;
|
||||
for (int i = 0; quit_signals[i]; ++i) {
|
||||
sigaction(quit_signals[i], &act, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
int su_client_main(int argc, char *argv[]) {
|
||||
int c;
|
||||
struct option long_opts[] = {
|
||||
{ "command", required_argument, nullptr, 'c' },
|
||||
{ "help", no_argument, nullptr, 'h' },
|
||||
{ "login", no_argument, nullptr, 'l' },
|
||||
{ "preserve-environment", no_argument, nullptr, 'p' },
|
||||
{ "shell", required_argument, nullptr, 's' },
|
||||
{ "version", no_argument, nullptr, 'v' },
|
||||
{ "context", required_argument, nullptr, 'z' },
|
||||
{ "mount-master", no_argument, nullptr, 'M' },
|
||||
{ nullptr, 0, nullptr, 0 },
|
||||
};
|
||||
int c;
|
||||
struct option long_opts[] = {
|
||||
{ "command", required_argument, nullptr, 'c' },
|
||||
{ "help", no_argument, nullptr, 'h' },
|
||||
{ "login", no_argument, nullptr, 'l' },
|
||||
{ "preserve-environment", no_argument, nullptr, 'p' },
|
||||
{ "shell", required_argument, nullptr, 's' },
|
||||
{ "version", no_argument, nullptr, 'v' },
|
||||
{ "context", required_argument, nullptr, 'z' },
|
||||
{ "mount-master", no_argument, nullptr, 'M' },
|
||||
{ nullptr, 0, nullptr, 0 },
|
||||
};
|
||||
|
||||
su_request su_req;
|
||||
su_request su_req;
|
||||
|
||||
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");
|
||||
}
|
||||
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, nullptr)) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
su_req.command = concat_commands(argc, argv);
|
||||
optind = argc;
|
||||
break;
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
break;
|
||||
case 'l':
|
||||
su_req.login = true;
|
||||
break;
|
||||
case 'm':
|
||||
case 'p':
|
||||
su_req.keepenv = true;
|
||||
break;
|
||||
case 's':
|
||||
su_req.shell = optarg;
|
||||
break;
|
||||
case 'V':
|
||||
printf("%d\n", MAGISK_VER_CODE);
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'v':
|
||||
printf("%s\n", MAGISK_VERSION ":MAGISKSU");
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'z':
|
||||
// Do nothing, placed here for legacy support :)
|
||||
break;
|
||||
case 'M':
|
||||
su_req.mount_master = true;
|
||||
break;
|
||||
default:
|
||||
/* Bionic getopt_long doesn't terminate its error output by newline */
|
||||
fprintf(stderr, "\n");
|
||||
usage(2);
|
||||
}
|
||||
}
|
||||
while ((c = getopt_long(argc, argv, "c:hlmps:Vvuz:M", long_opts, nullptr)) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
su_req.command = concat_commands(argc, argv);
|
||||
optind = argc;
|
||||
break;
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
break;
|
||||
case 'l':
|
||||
su_req.login = true;
|
||||
break;
|
||||
case 'm':
|
||||
case 'p':
|
||||
su_req.keepenv = true;
|
||||
break;
|
||||
case 's':
|
||||
su_req.shell = optarg;
|
||||
break;
|
||||
case 'V':
|
||||
printf("%d\n", MAGISK_VER_CODE);
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'v':
|
||||
printf("%s\n", MAGISK_VERSION ":MAGISKSU");
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'z':
|
||||
// Do nothing, placed here for legacy support :)
|
||||
break;
|
||||
case 'M':
|
||||
su_req.mount_master = true;
|
||||
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_req.login = true;
|
||||
optind++;
|
||||
}
|
||||
/* username or uid */
|
||||
if (optind < argc) {
|
||||
struct passwd *pw;
|
||||
pw = getpwnam(argv[optind]);
|
||||
if (pw)
|
||||
su_req.uid = pw->pw_uid;
|
||||
else
|
||||
su_req.uid = parse_int(argv[optind]);
|
||||
optind++;
|
||||
}
|
||||
if (optind < argc && strcmp(argv[optind], "-") == 0) {
|
||||
su_req.login = true;
|
||||
optind++;
|
||||
}
|
||||
/* username or uid */
|
||||
if (optind < argc) {
|
||||
struct passwd *pw;
|
||||
pw = getpwnam(argv[optind]);
|
||||
if (pw)
|
||||
su_req.uid = pw->pw_uid;
|
||||
else
|
||||
su_req.uid = parse_int(argv[optind]);
|
||||
optind++;
|
||||
}
|
||||
|
||||
char pts_slave[PATH_MAX];
|
||||
int ptmx, fd;
|
||||
char pts_slave[PATH_MAX];
|
||||
int ptmx, fd;
|
||||
|
||||
// Connect to client
|
||||
fd = connect_daemon();
|
||||
// Connect to client
|
||||
fd = connect_daemon();
|
||||
|
||||
// Tell the daemon we are su
|
||||
write_int(fd, SUPERUSER);
|
||||
// Tell the daemon we are su
|
||||
write_int(fd, SUPERUSER);
|
||||
|
||||
// Send su_request
|
||||
xwrite(fd, &su_req, sizeof(su_req_base));
|
||||
write_string(fd, su_req.shell);
|
||||
write_string(fd, su_req.command);
|
||||
// Send su_request
|
||||
xwrite(fd, &su_req, sizeof(su_req_base));
|
||||
write_string(fd, su_req.shell);
|
||||
write_string(fd, su_req.command);
|
||||
|
||||
// Wait for ack from daemon
|
||||
if (read_int(fd)) {
|
||||
// Fast fail
|
||||
fprintf(stderr, "%s\n", strerror(EACCES));
|
||||
return EACCES;
|
||||
}
|
||||
// Wait for ack from daemon
|
||||
if (read_int(fd)) {
|
||||
// Fast fail
|
||||
fprintf(stderr, "%s\n", strerror(EACCES));
|
||||
return EACCES;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// 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';
|
||||
}
|
||||
if (atty) {
|
||||
// We need a PTY. Get one.
|
||||
ptmx = pts_open(pts_slave, sizeof(pts_slave));
|
||||
} else {
|
||||
pts_slave[0] = '\0';
|
||||
}
|
||||
|
||||
// Send pts_slave
|
||||
write_string(fd, pts_slave);
|
||||
// Send pts_slave
|
||||
write_string(fd, pts_slave);
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
|
||||
if (atty) {
|
||||
setup_sighandlers(sighandler);
|
||||
watch_sigwinch_async(STDOUT_FILENO, ptmx);
|
||||
pump_stdin_async(ptmx);
|
||||
pump_stdout_blocking(ptmx);
|
||||
}
|
||||
if (atty) {
|
||||
setup_sighandlers(sighandler);
|
||||
watch_sigwinch_async(STDOUT_FILENO, ptmx);
|
||||
pump_stdin_async(ptmx);
|
||||
pump_stdout_blocking(ptmx);
|
||||
}
|
||||
|
||||
// Get the exit code
|
||||
int code = read_int(fd);
|
||||
close(fd);
|
||||
// Get the exit code
|
||||
int code = read_int(fd);
|
||||
close(fd);
|
||||
|
||||
return code;
|
||||
return code;
|
||||
}
|
||||
|
||||
@@ -16,54 +16,54 @@
|
||||
|
||||
class su_info {
|
||||
public:
|
||||
/* Unique key */
|
||||
const int uid;
|
||||
/* Unique key */
|
||||
const int uid;
|
||||
|
||||
/* These should be guarded with internal lock */
|
||||
db_settings cfg;
|
||||
db_strings str;
|
||||
su_access access;
|
||||
struct stat mgr_st;
|
||||
/* These should be guarded with internal lock */
|
||||
db_settings cfg;
|
||||
db_strings str;
|
||||
su_access access;
|
||||
struct stat mgr_st;
|
||||
|
||||
/* This should be guarded with global cache lock */
|
||||
long timestamp;
|
||||
/* This should be guarded with global cache lock */
|
||||
long timestamp;
|
||||
|
||||
su_info(unsigned uid = 0);
|
||||
~su_info();
|
||||
mutex_guard lock();
|
||||
bool is_fresh();
|
||||
void refresh();
|
||||
su_info(unsigned uid = 0);
|
||||
~su_info();
|
||||
mutex_guard lock();
|
||||
bool is_fresh();
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
pthread_mutex_t _lock; /* Internal lock */
|
||||
pthread_mutex_t _lock; /* Internal lock */
|
||||
};
|
||||
|
||||
struct su_req_base {
|
||||
int uid = UID_ROOT;
|
||||
bool login = false;
|
||||
bool keepenv = false;
|
||||
bool mount_master = false;
|
||||
int uid = UID_ROOT;
|
||||
bool login = false;
|
||||
bool keepenv = false;
|
||||
bool mount_master = false;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct su_request : public su_req_base {
|
||||
const char *shell = DEFAULT_SHELL;
|
||||
const char *command = "";
|
||||
su_request(bool dyn = false) : dyn(dyn) {}
|
||||
~su_request() {
|
||||
if (dyn) {
|
||||
free(const_cast<char*>(shell));
|
||||
free(const_cast<char*>(command));
|
||||
}
|
||||
}
|
||||
const char *shell = DEFAULT_SHELL;
|
||||
const char *command = "";
|
||||
su_request(bool dyn = false) : dyn(dyn) {}
|
||||
~su_request() {
|
||||
if (dyn) {
|
||||
free(const_cast<char*>(shell));
|
||||
free(const_cast<char*>(command));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool dyn;
|
||||
bool dyn;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct su_context {
|
||||
std::shared_ptr<su_info> info;
|
||||
su_request req;
|
||||
int pid;
|
||||
std::shared_ptr<su_info> info;
|
||||
su_request req;
|
||||
int pid;
|
||||
};
|
||||
|
||||
void app_log(const su_context &ctx);
|
||||
|
||||
@@ -25,319 +25,319 @@ static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static shared_ptr<su_info> cached;
|
||||
|
||||
su_info::su_info(unsigned uid) :
|
||||
uid(uid), access(DEFAULT_SU_ACCESS), mgr_st({}),
|
||||
timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {}
|
||||
uid(uid), access(DEFAULT_SU_ACCESS), mgr_st({}),
|
||||
timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {}
|
||||
|
||||
su_info::~su_info() {
|
||||
pthread_mutex_destroy(&_lock);
|
||||
pthread_mutex_destroy(&_lock);
|
||||
}
|
||||
|
||||
mutex_guard su_info::lock() {
|
||||
return mutex_guard(_lock);
|
||||
return mutex_guard(_lock);
|
||||
}
|
||||
|
||||
bool su_info::is_fresh() {
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
long current = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
|
||||
return current - timestamp < 3000; /* 3 seconds */
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
long current = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
|
||||
return current - timestamp < 3000; /* 3 seconds */
|
||||
}
|
||||
|
||||
void su_info::refresh() {
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
timestamp = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
timestamp = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
|
||||
}
|
||||
|
||||
static void database_check(const shared_ptr<su_info> &info) {
|
||||
int uid = info->uid;
|
||||
get_db_settings(info->cfg);
|
||||
get_db_strings(info->str);
|
||||
int uid = info->uid;
|
||||
get_db_settings(info->cfg);
|
||||
get_db_strings(info->str);
|
||||
|
||||
// Check multiuser settings
|
||||
switch (info->cfg[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;
|
||||
}
|
||||
// Check multiuser settings
|
||||
switch (info->cfg[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(info->access, uid);
|
||||
if (uid > 0)
|
||||
get_uid_policy(info->access, uid);
|
||||
|
||||
// We need to check our manager
|
||||
if (info->access.log || info->access.notify)
|
||||
validate_manager(info->str[SU_MANAGER], uid / 100000, &info->mgr_st);
|
||||
// We need to check our manager
|
||||
if (info->access.log || info->access.notify)
|
||||
validate_manager(info->str[SU_MANAGER], uid / 100000, &info->mgr_st);
|
||||
}
|
||||
|
||||
static shared_ptr<su_info> get_su_info(unsigned uid) {
|
||||
LOGD("su: request from uid=[%d]\n", uid);
|
||||
LOGD("su: request from uid=[%d]\n", uid);
|
||||
|
||||
shared_ptr<su_info> info;
|
||||
shared_ptr<su_info> info;
|
||||
|
||||
{
|
||||
mutex_guard lock(cache_lock);
|
||||
if (!cached || cached->uid != uid || !cached->is_fresh())
|
||||
cached = make_shared<su_info>(uid);
|
||||
cached->refresh();
|
||||
info = cached;
|
||||
}
|
||||
{
|
||||
mutex_guard lock(cache_lock);
|
||||
if (!cached || cached->uid != uid || !cached->is_fresh())
|
||||
cached = make_shared<su_info>(uid);
|
||||
cached->refresh();
|
||||
info = cached;
|
||||
}
|
||||
|
||||
mutex_guard lock = info->lock();
|
||||
mutex_guard lock = info->lock();
|
||||
|
||||
if (info->access.policy == QUERY) {
|
||||
// Not cached, get data from database
|
||||
database_check(info);
|
||||
if (info->access.policy == QUERY) {
|
||||
// Not cached, get data from database
|
||||
database_check(info);
|
||||
|
||||
// If it's root or the manager, allow it silently
|
||||
if (info->uid == UID_ROOT || (info->uid % 100000) == (info->mgr_st.st_uid % 100000)) {
|
||||
info->access = SILENT_SU_ACCESS;
|
||||
return info;
|
||||
}
|
||||
// If it's root or the manager, allow it silently
|
||||
if (info->uid == UID_ROOT || (info->uid % 100000) == (info->mgr_st.st_uid % 100000)) {
|
||||
info->access = SILENT_SU_ACCESS;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check su access settings
|
||||
switch (info->cfg[ROOT_ACCESS]) {
|
||||
case ROOT_ACCESS_DISABLED:
|
||||
LOGW("Root access is disabled!\n");
|
||||
info->access = NO_SU_ACCESS;
|
||||
break;
|
||||
case ROOT_ACCESS_ADB_ONLY:
|
||||
if (info->uid != UID_SHELL) {
|
||||
LOGW("Root access limited to ADB only!\n");
|
||||
info->access = NO_SU_ACCESS;
|
||||
}
|
||||
break;
|
||||
case ROOT_ACCESS_APPS_ONLY:
|
||||
if (info->uid == UID_SHELL) {
|
||||
LOGW("Root access is disabled for ADB!\n");
|
||||
info->access = NO_SU_ACCESS;
|
||||
}
|
||||
break;
|
||||
case ROOT_ACCESS_APPS_AND_ADB:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Check su access settings
|
||||
switch (info->cfg[ROOT_ACCESS]) {
|
||||
case ROOT_ACCESS_DISABLED:
|
||||
LOGW("Root access is disabled!\n");
|
||||
info->access = NO_SU_ACCESS;
|
||||
break;
|
||||
case ROOT_ACCESS_ADB_ONLY:
|
||||
if (info->uid != UID_SHELL) {
|
||||
LOGW("Root access limited to ADB only!\n");
|
||||
info->access = NO_SU_ACCESS;
|
||||
}
|
||||
break;
|
||||
case ROOT_ACCESS_APPS_ONLY:
|
||||
if (info->uid == UID_SHELL) {
|
||||
LOGW("Root access is disabled for ADB!\n");
|
||||
info->access = NO_SU_ACCESS;
|
||||
}
|
||||
break;
|
||||
case ROOT_ACCESS_APPS_AND_ADB:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (info->access.policy != QUERY)
|
||||
return info;
|
||||
if (info->access.policy != QUERY)
|
||||
return info;
|
||||
|
||||
// If still not determined, check if manager exists
|
||||
if (info->str[SU_MANAGER].empty()) {
|
||||
info->access = NO_SU_ACCESS;
|
||||
return info;
|
||||
}
|
||||
} else {
|
||||
return info;
|
||||
}
|
||||
// If still not determined, check if manager exists
|
||||
if (info->str[SU_MANAGER].empty()) {
|
||||
info->access = NO_SU_ACCESS;
|
||||
return info;
|
||||
}
|
||||
} else {
|
||||
return info;
|
||||
}
|
||||
|
||||
// If still not determined, ask manager
|
||||
int fd = app_request(info);
|
||||
if (fd < 0) {
|
||||
info->access.policy = DENY;
|
||||
} else {
|
||||
int ret = read_int_be(fd);
|
||||
info->access.policy = ret < 0 ? DENY : static_cast<policy_t>(ret);
|
||||
close(fd);
|
||||
}
|
||||
// If still not determined, ask manager
|
||||
int fd = app_request(info);
|
||||
if (fd < 0) {
|
||||
info->access.policy = DENY;
|
||||
} else {
|
||||
int ret = read_int_be(fd);
|
||||
info->access.policy = ret < 0 ? DENY : static_cast<policy_t>(ret);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return info;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root
|
||||
static void set_identity(unsigned uid) {
|
||||
if (seteuid(0)) {
|
||||
PLOGE("seteuid (root)");
|
||||
}
|
||||
if (setresgid(uid, uid, uid)) {
|
||||
PLOGE("setresgid (%u)", uid);
|
||||
}
|
||||
if (setresuid(uid, uid, uid)) {
|
||||
PLOGE("setresuid (%u)", uid);
|
||||
}
|
||||
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 pid=[%d], client=[%d]\n", credential->pid, client);
|
||||
LOGD("su: request from pid=[%d], client=[%d]\n", credential->pid, client);
|
||||
|
||||
su_context ctx = {
|
||||
.info = get_su_info(credential->uid),
|
||||
.req = su_request(true),
|
||||
.pid = credential->pid
|
||||
};
|
||||
su_context ctx = {
|
||||
.info = get_su_info(credential->uid),
|
||||
.req = su_request(true),
|
||||
.pid = credential->pid
|
||||
};
|
||||
|
||||
// Read su_request
|
||||
xxread(client, &ctx.req, sizeof(su_req_base));
|
||||
ctx.req.shell = read_string(client);
|
||||
ctx.req.command = read_string(client);
|
||||
// Read su_request
|
||||
xxread(client, &ctx.req, sizeof(su_req_base));
|
||||
ctx.req.shell = read_string(client);
|
||||
ctx.req.command = read_string(client);
|
||||
|
||||
if (ctx.info->access.log)
|
||||
app_log(ctx);
|
||||
else if (ctx.info->access.notify)
|
||||
app_notify(ctx);
|
||||
if (ctx.info->access.log)
|
||||
app_log(ctx);
|
||||
else if (ctx.info->access.notify)
|
||||
app_notify(ctx);
|
||||
|
||||
// Fail fast
|
||||
if (ctx.info->access.policy == DENY) {
|
||||
LOGW("su: request rejected (%u)\n", ctx.info->uid);
|
||||
ctx.info.reset();
|
||||
write_int(client, DENY);
|
||||
close(client);
|
||||
return;
|
||||
}
|
||||
// Fail fast
|
||||
if (ctx.info->access.policy == DENY) {
|
||||
LOGW("su: request rejected (%u)\n", ctx.info->uid);
|
||||
ctx.info.reset();
|
||||
write_int(client, DENY);
|
||||
close(client);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fork a child root process
|
||||
//
|
||||
// The child process will need to setsid, open a pseudo-terminal
|
||||
// if needed, and eventually exec shell.
|
||||
// The parent process will wait for the result and
|
||||
// send the return code back to our client.
|
||||
// Fork a child root process
|
||||
//
|
||||
// The child process will need to setsid, open a pseudo-terminal
|
||||
// if needed, and eventually exec shell.
|
||||
// The parent process will wait for the result and
|
||||
// send the return code back to our client.
|
||||
|
||||
if (int child = xfork(); child) {
|
||||
ctx.info.reset();
|
||||
if (int child = xfork(); child) {
|
||||
ctx.info.reset();
|
||||
|
||||
// Wait result
|
||||
LOGD("su: waiting child pid=[%d]\n", child);
|
||||
int status, code;
|
||||
// Wait result
|
||||
LOGD("su: waiting child pid=[%d]\n", child);
|
||||
int status, code;
|
||||
|
||||
if (waitpid(child, &status, 0) > 0)
|
||||
code = WEXITSTATUS(status);
|
||||
else
|
||||
code = -1;
|
||||
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: return code=[%d]\n", code);
|
||||
write(client, &code, sizeof(code));
|
||||
close(client);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("su: fork handler\n");
|
||||
LOGD("su: fork handler\n");
|
||||
|
||||
// Abort upon any error occurred
|
||||
log_cb.ex = exit;
|
||||
// Abort upon any error occurred
|
||||
log_cb.ex = exit;
|
||||
|
||||
// ack
|
||||
write_int(client, 0);
|
||||
// ack
|
||||
write_int(client, 0);
|
||||
|
||||
// Become session leader
|
||||
xsetsid();
|
||||
// Become session leader
|
||||
xsetsid();
|
||||
|
||||
// Get pts_slave
|
||||
char *pts_slave = read_string(client);
|
||||
// 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);
|
||||
// The FDs for each of the streams
|
||||
int infd = recv_fd(client);
|
||||
int outfd = recv_fd(client);
|
||||
int errfd = recv_fd(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 (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 != ctx.info->uid && ctx.info->uid != 0)
|
||||
LOGE("su: Wrong permission of pts_slave\n");
|
||||
// If caller is not root, ensure the owner of pts_slave is the caller
|
||||
if(st.st_uid != ctx.info->uid && ctx.info->uid != 0)
|
||||
LOGE("su: Wrong permission of pts_slave\n");
|
||||
|
||||
// Opening the TTY has to occur after the
|
||||
// fork() and setsid() so that it becomes
|
||||
// our controlling TTY and not the daemon's
|
||||
int ptsfd = xopen(pts_slave, O_RDWR);
|
||||
// Opening the TTY has to occur after the
|
||||
// fork() and setsid() so that it becomes
|
||||
// our controlling TTY and not the daemon's
|
||||
int ptsfd = xopen(pts_slave, O_RDWR);
|
||||
|
||||
if (infd < 0)
|
||||
infd = ptsfd;
|
||||
if (outfd < 0)
|
||||
outfd = ptsfd;
|
||||
if (errfd < 0)
|
||||
errfd = ptsfd;
|
||||
}
|
||||
if (infd < 0)
|
||||
infd = ptsfd;
|
||||
if (outfd < 0)
|
||||
outfd = ptsfd;
|
||||
if (errfd < 0)
|
||||
errfd = ptsfd;
|
||||
}
|
||||
|
||||
free(pts_slave);
|
||||
free(pts_slave);
|
||||
|
||||
// Swap out stdin, stdout, stderr
|
||||
xdup2(infd, STDIN_FILENO);
|
||||
xdup2(outfd, STDOUT_FILENO);
|
||||
xdup2(errfd, STDERR_FILENO);
|
||||
// Swap out stdin, stdout, stderr
|
||||
xdup2(infd, STDIN_FILENO);
|
||||
xdup2(outfd, STDOUT_FILENO);
|
||||
xdup2(errfd, STDERR_FILENO);
|
||||
|
||||
// Unleash all streams from SELinux hell
|
||||
setfilecon("/proc/self/fd/0", "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||
setfilecon("/proc/self/fd/1", "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||
setfilecon("/proc/self/fd/2", "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||
// Unleash all streams from SELinux hell
|
||||
setfilecon("/proc/self/fd/0", "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||
setfilecon("/proc/self/fd/1", "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||
setfilecon("/proc/self/fd/2", "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||
|
||||
close(infd);
|
||||
close(outfd);
|
||||
close(errfd);
|
||||
close(client);
|
||||
close(infd);
|
||||
close(outfd);
|
||||
close(errfd);
|
||||
close(client);
|
||||
|
||||
// Handle namespaces
|
||||
if (ctx.req.mount_master)
|
||||
ctx.info->cfg[SU_MNT_NS] = NAMESPACE_MODE_GLOBAL;
|
||||
switch (ctx.info->cfg[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 global\n");
|
||||
break;
|
||||
case NAMESPACE_MODE_ISOLATE:
|
||||
LOGD("su: use new isolated namespace\n");
|
||||
switch_mnt_ns(ctx.pid);
|
||||
xunshare(CLONE_NEWNS);
|
||||
xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr);
|
||||
break;
|
||||
}
|
||||
// Handle namespaces
|
||||
if (ctx.req.mount_master)
|
||||
ctx.info->cfg[SU_MNT_NS] = NAMESPACE_MODE_GLOBAL;
|
||||
switch (ctx.info->cfg[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 global\n");
|
||||
break;
|
||||
case NAMESPACE_MODE_ISOLATE:
|
||||
LOGD("su: use new isolated namespace\n");
|
||||
switch_mnt_ns(ctx.pid);
|
||||
xunshare(CLONE_NEWNS);
|
||||
xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr);
|
||||
break;
|
||||
}
|
||||
|
||||
const char *argv[] = { nullptr, nullptr, nullptr, nullptr };
|
||||
const char *argv[] = { nullptr, nullptr, nullptr, nullptr };
|
||||
|
||||
argv[0] = ctx.req.login ? "-" : ctx.req.shell;
|
||||
argv[0] = ctx.req.login ? "-" : ctx.req.shell;
|
||||
|
||||
if (ctx.req.command[0]) {
|
||||
argv[1] = "-c";
|
||||
argv[2] = ctx.req.command;
|
||||
}
|
||||
if (ctx.req.command[0]) {
|
||||
argv[1] = "-c";
|
||||
argv[2] = ctx.req.command;
|
||||
}
|
||||
|
||||
// Setup environment
|
||||
umask(022);
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid);
|
||||
chdir(path);
|
||||
snprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid);
|
||||
char buf[4096] = { 0 };
|
||||
int fd = xopen(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;
|
||||
}
|
||||
if (!ctx.req.keepenv) {
|
||||
struct passwd *pw;
|
||||
pw = getpwuid(ctx.req.uid);
|
||||
if (pw) {
|
||||
setenv("HOME", pw->pw_dir, 1);
|
||||
setenv("USER", pw->pw_name, 1);
|
||||
setenv("LOGNAME", pw->pw_name, 1);
|
||||
setenv("SHELL", ctx.req.shell, 1);
|
||||
}
|
||||
}
|
||||
// Setup environment
|
||||
umask(022);
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid);
|
||||
chdir(path);
|
||||
snprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid);
|
||||
char buf[4096] = { 0 };
|
||||
int fd = xopen(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;
|
||||
}
|
||||
if (!ctx.req.keepenv) {
|
||||
struct passwd *pw;
|
||||
pw = getpwuid(ctx.req.uid);
|
||||
if (pw) {
|
||||
setenv("HOME", pw->pw_dir, 1);
|
||||
setenv("USER", pw->pw_name, 1);
|
||||
setenv("LOGNAME", pw->pw_name, 1);
|
||||
setenv("SHELL", ctx.req.shell, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Unblock all signals
|
||||
sigset_t block_set;
|
||||
sigemptyset(&block_set);
|
||||
sigprocmask(SIG_SETMASK, &block_set, nullptr);
|
||||
set_identity(ctx.req.uid);
|
||||
execvp(ctx.req.shell, (char **) argv);
|
||||
fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell, strerror(errno));
|
||||
PLOGE("exec");
|
||||
exit(EXIT_FAILURE);
|
||||
// Unblock all signals
|
||||
sigset_t block_set;
|
||||
sigemptyset(&block_set);
|
||||
sigprocmask(SIG_SETMASK, &block_set, nullptr);
|
||||
set_identity(ctx.req.uid);
|
||||
execvp(ctx.req.shell, (char **) argv);
|
||||
fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell, strerror(errno));
|
||||
PLOGE("exec");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user