Magisk/native/jni/su/su_daemon.cpp

386 lines
8.8 KiB
C++
Raw Normal View History

2017-04-14 21:21:31 +02:00
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
2018-10-04 10:59:51 +02:00
#include <pwd.h>
2017-04-14 21:21:31 +02:00
#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"
2018-09-27 06:09:59 +02:00
#include "selinux.h"
2017-04-14 21:21:31 +02:00
#define TIMEOUT 3
#define LOCK_CACHE() pthread_mutex_lock(&cache_lock)
#define UNLOCK_CACHE() pthread_mutex_unlock(&cache_lock)
2017-07-16 09:31:40 +02:00
static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
2018-11-04 09:38:06 +01:00
static su_info *cache;
su_info::su_info(unsigned uid) :
uid(uid), access(DEFAULT_SU_ACCESS), _lock(PTHREAD_MUTEX_INITIALIZER),
count(0), ref(0), life(0), mgr_st({}) {}
su_info::~su_info() {
pthread_mutex_destroy(&_lock);
}
void su_info::lock() {
pthread_mutex_lock(&_lock);
}
void su_info::unlock() {
pthread_mutex_unlock(&_lock);
}
static void *info_collector(void *node) {
2018-11-04 09:38:06 +01:00
su_info *info = (su_info *) node;
while (1) {
sleep(1);
if (info->life) {
LOCK_CACHE();
if (--info->life == 0 && cache && info->uid == cache->uid)
2018-11-04 09:38:06 +01:00
cache = nullptr;
UNLOCK_CACHE();
}
if (!info->life && !info->ref) {
2018-11-04 09:38:06 +01:00
delete info;
return nullptr;
}
}
2017-05-07 21:08:34 +02:00
}
2017-04-15 20:28:12 +02:00
2018-11-04 09:38:06 +01:00
static void database_check(su_info *info) {
int uid = info->uid;
2018-11-20 07:50:31 +01:00
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;
}
if (uid > 0)
get_uid_policy(uid, &info->access);
// We need to check our manager
if (info->access.log || info->access.notify)
2018-11-05 00:24:08 +01:00
validate_manager(info->str[SU_MANAGER], uid / 100000, &info->mgr_st);
}
static struct su_info *get_su_info(unsigned uid) {
2018-11-04 09:38:06 +01:00
su_info *info;
bool cache_miss = false;
LOCK_CACHE();
if (cache && cache->uid == uid) {
info = cache;
} else {
2018-11-04 09:38:06 +01:00
cache_miss = true;
info = new su_info(uid);
cache = info;
}
// Update the cache status
info->life = TIMEOUT;
++info->ref;
// Start a thread to maintain the cache
if (cache_miss) {
pthread_t thread;
2018-11-04 09:38:06 +01:00
xpthread_create(&thread, nullptr, info_collector, info);
pthread_detach(thread);
}
UNLOCK_CACHE();
2017-07-16 09:31:40 +02:00
LOGD("su: request from uid=[%d] (#%d)\n", info->uid, ++info->count);
2017-07-16 09:31:40 +02:00
// Lock before the policy is determined
2018-11-04 09:38:06 +01:00
info->lock();
2017-07-16 09:31:40 +02:00
if (info->access.policy == QUERY) {
// Not cached, get data from database
database_check(info);
// Check su access settings
2018-11-05 00:24:08 +01:00
switch (info->cfg[ROOT_ACCESS]) {
case ROOT_ACCESS_DISABLED:
2018-10-04 20:41:48 +02:00
LOGW("Root access is disabled!\n");
info->access = NO_SU_ACCESS;
break;
case ROOT_ACCESS_ADB_ONLY:
if (info->uid != UID_SHELL) {
2018-10-04 20:41:48 +02:00
LOGW("Root access limited to ADB only!\n");
info->access = NO_SU_ACCESS;
}
break;
case ROOT_ACCESS_APPS_ONLY:
if (info->uid == UID_SHELL) {
2018-10-04 20:41:48 +02:00
LOGW("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
2018-10-04 07:49:52 +02:00
if ((info->uid % 100000) == (info->mgr_st.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
2018-11-05 00:24:08 +01:00
if (info->access.policy == QUERY && info->str[SU_MANAGER][0] == '\0')
info->access = NO_SU_ACCESS;
}
2018-10-04 10:59:51 +02:00
// If still not determined, ask manager
if (info->access.policy == QUERY) {
// Create random socket
struct sockaddr_un addr;
int sockfd = create_rand_socket(&addr);
// Connect manager
app_connect(addr.sun_path + 1, info);
int fd = socket_accept(sockfd, 60);
2018-10-04 21:06:13 +02:00
if (fd < 0) {
info->access.policy = DENY;
} else {
socket_send_request(fd, info);
int ret = read_int_be(fd);
2018-11-04 09:38:06 +01:00
info->access.policy = ret < 0 ? DENY : static_cast<policy_t>(ret);
2018-10-04 21:06:13 +02:00
close(fd);
}
2018-10-04 10:59:51 +02:00
close(sockfd);
}
// Unlock
2018-11-04 09:38:06 +01:00
info->unlock();
2018-10-04 10:59:51 +02:00
return info;
}
2018-10-04 10:59:51 +02:00
static void set_identity(unsigned uid) {
/*
* Set effective uid back to root, otherwise setres[ug]id will fail
* if uid isn't root.
*/
if (seteuid(0)) {
PLOGE("seteuid (root)");
}
if (setresgid(uid, uid, uid)) {
PLOGE("setresgid (%u)", uid);
}
if (setresuid(uid, uid, uid)) {
PLOGE("setresuid (%u)", uid);
}
}
void su_daemon_handler(int client, struct ucred *credential) {
2018-11-10 08:17:13 +01:00
LOGD("su: request from pid=[%d], client=[%d]\n", credential->pid, client);
2018-10-04 10:59:51 +02:00
2018-11-04 09:38:06 +01:00
su_info *info = get_su_info(credential->uid);
2018-10-04 10:59:51 +02:00
// Fail fast
2018-11-05 00:24:08 +01:00
if (info->access.policy == DENY && info->str[SU_MANAGER][0] == '\0') {
2018-10-20 22:12:08 +02:00
LOGD("su: fast deny\n");
2018-10-04 10:59:51 +02:00
write_int(client, DENY);
2018-10-20 22:12:08 +02:00
close(client);
2018-10-04 10:59:51 +02:00
return;
}
/* Fork a new process, the child process will need to setsid,
* open a pseudo-terminal if needed, and will eventually run exec
* The parent process will wait for the result and
* send the return code back to our client
*/
int child = xfork();
if (child) {
// Decrement reference count
--info->ref;
// Wait result
2018-11-10 08:17:13 +01:00
LOGD("su: waiting child pid=[%d]\n", child);
2018-10-04 10:59:51 +02:00
int status, code;
if (waitpid(child, &status, 0) > 0)
code = WEXITSTATUS(status);
else
code = -1;
2018-11-10 08:17:13 +01:00
LOGD("su: return code=[%d]\n", code);
2018-10-04 10:59:51 +02:00
write(client, &code, sizeof(code));
close(client);
return;
}
LOGD("su: fork handler\n");
2018-11-20 07:47:17 +01:00
// Abort upon any error occurred
log_cb.ex = exit;
2018-10-04 10:59:51 +02:00
struct su_context ctx = {
.info = info,
.pid = credential->pid
};
2017-04-14 21:21:31 +02:00
2018-10-20 22:12:08 +02:00
// ack
write_int(client, 0);
2017-04-14 21:21:31 +02:00
// Become session leader
xsetsid();
2018-10-04 10:59:51 +02:00
// Read su_request
2018-11-04 09:38:06 +01:00
xxread(client, &ctx.req, sizeof(su_req_base));
2018-10-04 10:59:51 +02:00
ctx.req.shell = read_string(client);
ctx.req.command = read_string(client);
2017-04-14 21:21:31 +02:00
// Get pts_slave
char *pts_slave = read_string(client);
// The FDs for each of the streams
2017-04-15 20:28:12 +02:00
int infd = recv_fd(client);
int outfd = recv_fd(client);
int errfd = recv_fd(client);
if (pts_slave[0]) {
2018-06-26 12:45:51 +02:00
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);
2017-04-15 20:28:12 +02:00
// If caller is not root, ensure the owner of pts_slave is the caller
2018-11-20 07:47:17 +01:00
if(st.st_uid != info->uid && info->uid != 0)
2017-04-15 20:28:12 +02:00
LOGE("su: Wrong permission of pts_slave");
// 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);
2017-04-15 20:28:12 +02:00
2018-10-04 10:59:51 +02:00
if (infd < 0)
infd = ptsfd;
if (outfd < 0)
2017-04-15 20:28:12 +02:00
outfd = ptsfd;
2018-10-04 10:59:51 +02:00
if (errfd < 0)
2017-04-15 20:28:12 +02:00
errfd = ptsfd;
2017-04-14 21:21:31 +02:00
}
free(pts_slave);
// Swap out stdin, stdout, stderr
2017-04-15 20:28:12 +02:00
xdup2(infd, STDIN_FILENO);
xdup2(outfd, STDOUT_FILENO);
xdup2(errfd, STDERR_FILENO);
2017-04-14 21:21:31 +02:00
// Unleash all streams from SELinux hell
setfilecon("/proc/self/fd/0", "u:object_r:" SEPOL_FILE_DOMAIN ":s0");
setfilecon("/proc/self/fd/1", "u:object_r:" SEPOL_FILE_DOMAIN ":s0");
setfilecon("/proc/self/fd/2", "u:object_r:" SEPOL_FILE_DOMAIN ":s0");
close(infd);
close(outfd);
close(errfd);
close(client);
2018-10-04 10:59:51 +02:00
// Handle namespaces
2018-10-04 20:41:48 +02:00
if (ctx.req.mount_master)
2018-11-05 00:24:08 +01:00
info->cfg[SU_MNT_NS] = NAMESPACE_MODE_GLOBAL;
switch (info->cfg[SU_MNT_NS]) {
2018-10-04 10:59:51 +02:00
case NAMESPACE_MODE_GLOBAL:
LOGD("su: use global namespace\n");
break;
case NAMESPACE_MODE_REQUESTER:
LOGD("su: use namespace of pid=[%d]\n", ctx.pid);
if (switch_mnt_ns(ctx.pid)) {
LOGD("su: setns failed, fallback to isolated\n");
xunshare(CLONE_NEWNS);
}
break;
case NAMESPACE_MODE_ISOLATE:
LOGD("su: use new isolated namespace\n");
xunshare(CLONE_NEWNS);
break;
2018-06-26 12:45:51 +02:00
}
2018-10-28 03:06:24 +01:00
if (info->access.log)
2018-10-04 20:41:48 +02:00
app_log(&ctx);
2018-10-28 03:06:24 +01:00
else if (info->access.notify)
app_notify(&ctx);
2018-10-04 20:41:48 +02:00
if (info->access.policy == ALLOW) {
2018-11-04 09:38:06 +01:00
const char *argv[] = { nullptr, nullptr, nullptr, nullptr };
2018-06-26 12:45:51 +02:00
2018-10-04 20:41:48 +02:00
argv[0] = ctx.req.login ? "-" : ctx.req.shell;
2018-06-26 12:45:51 +02:00
2018-10-04 10:59:51 +02:00
if (ctx.req.command[0]) {
argv[1] = "-c";
argv[2] = ctx.req.command;
}
2017-04-14 21:21:31 +02:00
2018-11-10 08:17:13 +01:00
// Setup environment
2018-10-04 10:59:51 +02:00
umask(022);
set_identity(ctx.req.uid);
2018-11-10 08:17:13 +01:00
char path[32], buf[4096];
snprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid);
xreadlink(path, buf, sizeof(buf));
chdir(buf);
snprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid);
memset(buf, 0, sizeof(buf));
int fd = open(path, O_RDONLY);
read(fd, buf, sizeof(buf));
close(fd);
clearenv();
for (size_t pos = 0; buf[pos];) {
putenv(buf + pos);
pos += strlen(buf + pos) + 1;
}
if (!ctx.req.keepenv) {
struct passwd *pw;
pw = getpwuid(ctx.req.uid);
if (pw) {
setenv("HOME", pw->pw_dir, 1);
if (ctx.req.login || ctx.req.uid) {
setenv("USER", pw->pw_name, 1);
setenv("LOGNAME", pw->pw_name, 1);
}
setenv("SHELL", ctx.req.shell, 1);
}
}
2017-04-14 21:21:31 +02:00
2018-11-04 09:38:06 +01:00
execvp(ctx.req.shell, (char **) argv);
2018-10-04 10:59:51 +02:00
fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell, strerror(errno));
PLOGE("exec");
exit(EXIT_FAILURE);
2017-04-15 20:28:12 +02:00
} else {
2018-10-04 10:59:51 +02:00
LOGW("su: request rejected (%u->%u)", info->uid, ctx.req.uid);
2018-06-13 12:14:23 +02:00
fprintf(stderr, "%s\n", strerror(EACCES));
2018-10-04 10:59:51 +02:00
exit(EXIT_FAILURE);
2018-06-13 12:14:23 +02:00
}
2017-04-14 21:21:31 +02:00
}