Files
Magisk/native/src/core/su/su.cpp

412 lines
13 KiB
C++
Raw Normal View History

/*
2023-05-23 20:51:23 -07:00
* Copyright 2017 - 2023, John Wu (@topjohnwu)
2017-04-15 03:21:31 +08:00
* Copyright 2015, Pierre-Hugues Husson <phh@phh.me>
* Copyright 2010, Adam Shanks (@ChainsDD)
* Copyright 2008, Zinx Verituse (@zinxv)
*/
#include <unistd.h>
#include <getopt.h>
2017-04-15 03:21:31 +08:00
#include <fcntl.h>
#include <pwd.h>
2017-07-08 01:12:47 +08:00
#include <sched.h>
#include <sys/types.h>
2017-04-15 03:21:31 +08:00
#include <sys/stat.h>
2025-02-02 04:30:16 +08:00
#include <algorithm>
2023-11-08 01:46:02 -08:00
#include <consts.hpp>
2022-05-12 02:03:42 -07:00
#include <base.hpp>
#include <flags.h>
2025-02-02 04:30:16 +08:00
#include <core.hpp>
2019-02-10 03:57:51 -05:00
2025-02-02 04:30:16 +08:00
using namespace std;
#define DEFAULT_SHELL "/system/bin/sh"
// Constants for atty
#define ATTY_IN (1 << 0)
#define ATTY_OUT (1 << 1)
#define ATTY_ERR (1 << 2)
2018-11-20 04:40:42 -05:00
int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };
2018-10-12 21:46:09 -04:00
2023-05-16 19:26:44 +08:00
[[noreturn]] static void usage(int status) {
FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;
fprintf(stream,
"MagiskSU\n\n"
2023-05-22 16:56:43 +08:00
"Usage: su [options] [-] [user [argument...]]\n\n"
"Options:\n"
2023-05-23 20:51:23 -07:00
" -c, --command COMMAND Pass COMMAND to the invoked shell\n"
2023-05-22 16:56:43 +08:00
" -g, --group GROUP Specify the primary group\n"
2023-05-23 20:51:23 -07:00
" -G, --supp-group GROUP Specify a supplementary group.\n"
" The first specified supplementary group is also used\n"
" as a primary group if the option -g is not specified.\n"
" -Z, --context CONTEXT Change SELinux context\n"
2023-05-18 02:08:25 +08:00
" -t, --target PID PID to take mount namespace from\n"
2023-05-23 20:51:23 -07:00
" -h, --help Display this help message and exit\n"
" -, -l, --login Pretend the shell to be a login shell\n"
" -m, -p,\n"
2023-05-23 20:51:23 -07:00
" --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"
2023-05-23 20:51:23 -07:00
" --mount-master Force run in the global mount namespace\n\n");
exit(status);
}
2018-10-04 04:59:51 -04:00
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, nullptr);
}
}
2018-10-12 21:46:09 -04:00
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);
}
2018-10-12 21:46:09 -04:00
}
2018-10-04 04:59:51 -04:00
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' },
2023-05-23 20:51:23 -07:00
{ "context", required_argument, nullptr, 'Z' },
{ "mount-master", no_argument, nullptr, 'M' },
2023-05-18 02:08:25 +08:00
{ "target", required_argument, nullptr, 't' },
2023-05-22 16:56:43 +08:00
{ "group", required_argument, nullptr, 'g' },
{ "supp-group", required_argument, nullptr, 'G' },
{ nullptr, 0, nullptr, 0 },
};
2025-02-02 04:30:16 +08:00
auto req = SuRequest::New();
for (int i = 0; i < argc; i++) {
2023-05-23 20:51:23 -07:00
// Replace -cn and -z with -Z for backwards compatibility
if (strcmp(argv[i], "-cn") == 0 || strcmp(argv[i], "-z") == 0)
strcpy(argv[i], "-Z");
// Replace -mm with -M for supporting getopt_long
else if (strcmp(argv[i], "-mm") == 0)
strcpy(argv[i], "-M");
}
2023-05-23 20:51:23 -07:00
while ((c = getopt_long(argc, argv, "c:hlmps:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) {
switch (c) {
2025-02-02 04:30:16 +08:00
case 'c': {
string command;
2021-01-12 00:07:48 -08:00
for (int i = optind - 1; i < argc; ++i) {
2025-02-02 04:30:16 +08:00
if (!command.empty())
command += ' ';
command += argv[i];
2021-01-12 00:07:48 -08:00
}
2025-02-02 04:30:16 +08:00
req.command = command;
optind = argc;
break;
2025-02-02 04:30:16 +08:00
}
case 'h':
usage(EXIT_SUCCESS);
case 'l':
2025-02-02 04:30:16 +08:00
req.login = true;
break;
case 'm':
case 'p':
2025-02-02 04:30:16 +08:00
req.keep_env = true;
break;
case 's':
2025-02-02 04:30:16 +08:00
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);
2023-05-23 20:51:23 -07:00
case 'Z':
2025-02-02 04:30:16 +08:00
req.context = optarg;
break;
case 'M':
2023-05-18 02:08:25 +08:00
case 't':
2025-02-02 04:30:16 +08:00
if (req.target_pid != -1) {
2023-05-18 02:08:25 +08:00
fprintf(stderr, "Can't use -M and -t at the same time\n");
usage(EXIT_FAILURE);
}
if (optarg == nullptr) {
2025-02-02 04:30:16 +08:00
req.target_pid = 0;
2023-05-18 02:08:25 +08:00
} else {
2025-02-02 04:30:16 +08:00
req.target_pid = parse_int(optarg);
if (*optarg == '-' || req.target_pid == -1) {
2023-05-18 02:08:25 +08:00
fprintf(stderr, "Invalid PID: %s\n", optarg);
usage(EXIT_FAILURE);
}
}
break;
2023-05-22 16:56:43 +08:00
case 'g':
2025-02-02 04:30:16 +08:00
case 'G': {
vector<gid_t> gids;
2023-05-22 16:56:43 +08:00
if (int gid = parse_int(optarg); gid >= 0) {
2025-02-02 04:30:16 +08:00
gids.insert(c == 'g' ? gids.begin() : gids.end(), gid);
2023-05-22 16:56:43 +08:00
} else {
fprintf(stderr, "Invalid GID: %s\n", optarg);
usage(EXIT_FAILURE);
}
2025-02-02 04:30:16 +08:00
std::copy(gids.begin(), gids.end(), std::back_inserter(req.gids));
2023-05-22 16:56:43 +08:00
break;
2025-02-02 04:30:16 +08:00
}
default:
/* Bionic getopt_long doesn't terminate its error output by newline */
fprintf(stderr, "\n");
usage(2);
}
}
if (optind < argc && strcmp(argv[optind], "-") == 0) {
2025-02-02 04:30:16 +08:00
req.login = true;
optind++;
}
/* username or uid */
if (optind < argc) {
struct passwd *pw;
2023-05-22 16:56:43 +08:00
pw = getpwnam(argv[optind]);
if (pw)
2025-02-02 04:30:16 +08:00
req.target_uid = pw->pw_uid;
2023-05-22 16:56:43 +08:00
else
2025-02-02 04:30:16 +08:00
req.target_uid = parse_int(argv[optind]);
optind++;
}
int ptmx, fd;
// Connect to client
2023-11-17 13:35:50 -08:00
fd = connect_daemon(+RequestCode::SUPERUSER);
2025-02-02 04:30:16 +08:00
// Send request
req.write_to_fd(fd);
// 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;
// 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) {
// We need a PTY. Get one.
write_int(fd, 1);
ptmx = recv_fd(fd);
} else {
write_int(fd, 0);
}
if (atty) {
setup_sighandlers(sighandler);
2025-04-09 22:21:17 +08:00
// if stdin is not a tty, if we pump to ptmx, our process may intercept the input to ptmx and
// output to stdout, which cause the target process lost input.
pump_tty(ptmx, (atty & ATTY_IN) ? ptmx : -1);
}
// Get the exit code
int code = read_int(fd);
close(fd);
return code;
2018-11-04 03:38:06 -05:00
}
2025-02-02 04:30:16 +08:00
// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root
static void set_identity(int uid, const rust::Vec<gid_t> &groups) {
if (seteuid(0)) {
PLOGE("seteuid (root)");
}
gid_t gid;
if (!groups.empty()) {
if (setgroups(groups.size(), groups.data())) {
PLOGE("setgroups");
}
gid = groups[0];
} else {
gid = uid;
}
if (setresgid(gid, gid, gid)) {
PLOGE("setresgid (%u)", uid);
}
if (setresuid(uid, uid, uid)) {
PLOGE("setresuid (%u)", uid);
}
}
void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode) {
// Become session leader
xsetsid();
// The FDs for each of the streams
int infd = recv_fd(client);
int outfd = recv_fd(client);
int errfd = recv_fd(client);
// App need a PTY
if (read_int(client)) {
string pts;
string ptmx;
auto magiskpts = get_magisk_tmp() + "/"s SHELLPTS;
if (access(magiskpts.data(), F_OK)) {
pts = "/dev/pts";
ptmx = "/dev/ptmx";
} else {
pts = magiskpts;
ptmx = magiskpts + "/ptmx";
}
int ptmx_fd = xopen(ptmx.data(), O_RDWR);
grantpt(ptmx_fd);
unlockpt(ptmx_fd);
int pty_num = get_pty_num(ptmx_fd);
if (pty_num < 0) {
// Kernel issue? Fallback to /dev/pts
close(ptmx_fd);
pts = "/dev/pts";
ptmx_fd = xopen("/dev/ptmx", O_RDWR);
grantpt(ptmx_fd);
unlockpt(ptmx_fd);
pty_num = get_pty_num(ptmx_fd);
}
send_fd(client, ptmx_fd);
close(ptmx_fd);
string pts_slave = pts + "/" + to_string(pty_num);
LOGD("su: pts_slave=[%s]\n", pts_slave.data());
// 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.data(), O_RDWR);
if (infd < 0)
infd = ptsfd;
if (outfd < 0)
outfd = ptsfd;
if (errfd < 0)
errfd = ptsfd;
}
// Swap out stdin, stdout, stderr
xdup2(infd, STDIN_FILENO);
xdup2(outfd, STDOUT_FILENO);
xdup2(errfd, STDERR_FILENO);
close(infd);
close(outfd);
close(errfd);
close(client);
// Handle namespaces
if (req.target_pid == -1)
req.target_pid = pid;
else if (req.target_pid == 0)
mode = MntNsMode::Global;
else if (mode == MntNsMode::Global)
mode = MntNsMode::Requester;
switch (mode) {
case MntNsMode::Global:
LOGD("su: use global namespace\n");
break;
case MntNsMode::Requester:
LOGD("su: use namespace of pid=[%d]\n", req.target_pid);
switch_mnt_ns(req.target_pid);
break;
case MntNsMode::Isolate:
LOGD("su: use new isolated namespace\n");
switch_mnt_ns(req.target_pid);
xunshare(CLONE_NEWNS);
xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr);
break;
}
const char *argv[4] = { nullptr };
argv[0] = req.login ? "-" : req.shell.c_str();
if (!req.command.empty()) {
argv[1] = "-c";
argv[2] = req.command.c_str();
}
// Setup environment
umask(022);
char path[32];
ssprintf(path, sizeof(path), "/proc/%d/cwd", pid);
char cwd[4096];
if (realpath(path, cwd, sizeof(cwd)) > 0)
chdir(cwd);
ssprintf(path, sizeof(path), "/proc/%d/environ", pid);
auto env = full_read(path);
clearenv();
for (size_t pos = 0; pos < env.size(); ++pos) {
putenv(env.data() + pos);
pos = env.find_first_of('\0', pos);
if (pos == std::string::npos)
break;
}
if (!req.keep_env) {
struct passwd *pw;
pw = getpwuid(req.target_uid);
if (pw) {
setenv("HOME", pw->pw_dir, 1);
setenv("USER", pw->pw_name, 1);
setenv("LOGNAME", pw->pw_name, 1);
setenv("SHELL", req.shell.c_str(), 1);
}
}
// Unblock all signals
sigset_t block_set;
sigemptyset(&block_set);
sigprocmask(SIG_SETMASK, &block_set, nullptr);
if (!req.context.empty()) {
auto f = xopen_file("/proc/self/attr/exec", "we");
if (f) fprintf(f.get(), "%s", req.context.c_str());
}
set_identity(req.target_uid, req.gids);
execvp(req.shell.c_str(), (char **) argv);
fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno));
PLOGE("exec");
}