Magisk/native/jni/core/daemon.cpp
topjohnwu 25c557248c Use ContentProvider call method for communication
Previously, we use either BroadcastReceivers or Activities to receive
messages from our native daemon, but both have their own downsides.
Some OEMs blocks broadcasts if the app is not running in the background,
regardless of who the caller is. Activities on the other hand, despite
working 100% of the time, will steal the focus of the current foreground
app, even though we are just doing some logging and showing a toast.
In addition, since stubs for hiding Magisk Manager is introduced, our
only communication method is left with the broadcast option, as
only broadcasting allows targeting a specific package name, not a
component name (which will be obfuscated in the case of stubs).

To make sure root requests will work on all devices, Magisk had to do
some experiments every boot to test whether broadcast is deliverable or
not. This makes the whole thing even more complicated then ever.

So lets take a look at another kind of component in Android apps:
ContentProviders. It is a vital part of Android's ecosystem, and as far
as I know no OEMs will block requests to ContentProviders (or else
tons of functionality will break catastrophically). Starting at API 11,
the system supports calling a specific method in ContentProviders,
optionally sending extra data along with the method call. This is
perfect for the native daemon to start a communication with Magisk
Manager. Another cool thing is that we no longer need to know the
component name of the reciever, as ContentProviders identify themselves
with an "authority" name, which in Magisk Manager's case is tied to the
package name. We already have a mechanism to keep track of our current
manager package name, so this works out of the box.

So yay! No more flaky broadcast tests, no more stupid OEMs blocking
broadcasts for some bizzare reasons. This method should in theory
work on almost all devices and situations.
2019-11-04 14:32:28 -05:00

196 lines
4.3 KiB
C++

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <magisk.h>
#include <utils.h>
#include <daemon.h>
#include <selinux.h>
#include <db.h>
#include <resetprop.h>
#include <flags.h>
int SDK_INT = -1;
bool RECOVERY_MODE = false;
static struct stat self_st;
static void verify_client(int client, pid_t pid) {
// Verify caller is the same as server
char path[32];
sprintf(path, "/proc/%d/exe", pid);
struct stat st;
if (stat(path, &st) || st.st_dev != self_st.st_dev || st.st_ino != self_st.st_ino) {
close(client);
pthread_exit(nullptr);
}
}
static void *request_handler(void *args) {
int client = reinterpret_cast<intptr_t>(args);
struct ucred credential;
get_client_cred(client, &credential);
if (credential.uid != 0)
verify_client(client, credential.pid);
int req = read_int(client);
switch (req) {
case MAGISKHIDE:
case POST_FS_DATA:
case LATE_START:
case BOOT_COMPLETE:
case SQLITE_CMD:
if (credential.uid != 0) {
write_int(client, ROOT_REQUIRED);
close(client);
return nullptr;
}
default:
break;
}
switch (req) {
case MAGISKHIDE:
magiskhide_handler(client);
break;
case SUPERUSER:
su_daemon_handler(client, &credential);
break;
case CHECK_VERSION:
write_string(client, MAGISK_VERSION ":MAGISK");
close(client);
break;
case CHECK_VERSION_CODE:
write_int(client, MAGISK_VER_CODE);
close(client);
break;
case POST_FS_DATA:
post_fs_data(client);
break;
case LATE_START:
late_start(client);
break;
case BOOT_COMPLETE:
boot_complete(client);
break;
case SQLITE_CMD:
exec_sql(client);
break;
case REMOVE_MODULES:
if (credential.uid == UID_SHELL || credential.uid == UID_ROOT) {
remove_modules();
write_int(client, 0);
} else {
write_int(client, 1);
}
close(client);
break;
default:
close(client);
break;
}
return nullptr;
}
static void main_daemon() {
android_logging();
setsid();
setcon("u:r:" SEPOL_PROC_DOMAIN ":s0");
restore_rootcon();
// Unmount pre-init patches
if (access(ROOTMNT, F_OK) == 0) {
file_readline(ROOTMNT, [](auto line) -> bool {
umount2(line.data(), MNT_DETACH);
return true;
}, true);
}
int fd = xopen("/dev/null", O_RDWR | O_CLOEXEC);
xdup2(fd, STDOUT_FILENO);
xdup2(fd, STDERR_FILENO);
close(fd);
fd = xopen("/dev/zero", O_RDWR | O_CLOEXEC);
xdup2(fd, STDIN_FILENO);
close(fd);
LOGI(SHOW_VER(Magisk) " daemon started\n");
// Get server stat
stat("/proc/self/exe", &self_st);
// Get API level
parse_prop_file("/system/build.prop", [](auto key, auto val) -> bool {
if (key == "ro.build.version.sdk") {
LOGI("* Device API level: %s\n", val.data());
SDK_INT = parse_int(val);
return false;
}
return true;
});
// Load config status
parse_prop_file(MAGISKTMP "/config", [](auto key, auto val) -> bool {
if (key == "RECOVERYMODE" && val == "true")
RECOVERY_MODE = true;
return true;
});
struct sockaddr_un sun;
socklen_t len = setup_sockaddr(&sun, MAIN_SOCKET);
fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (xbind(fd, (struct sockaddr*) &sun, len))
exit(1);
xlisten(fd, 10);
// Change process name
set_nice_name("magiskd");
// Block all signals
sigset_t block_set;
sigfillset(&block_set);
pthread_sigmask(SIG_SETMASK, &block_set, nullptr);
// Loop forever to listen for requests
for (;;) {
int client = xaccept4(fd, nullptr, nullptr, SOCK_CLOEXEC);
new_daemon_thread(request_handler, reinterpret_cast<void*>(client));
}
}
int connect_daemon(bool create) {
struct sockaddr_un sun;
socklen_t len = setup_sockaddr(&sun, MAIN_SOCKET);
int fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (connect(fd, (struct sockaddr*) &sun, len)) {
if (!create || getuid() != UID_ROOT || getgid() != UID_ROOT) {
LOGE("No daemon is currently running!\n");
exit(1);
}
int ppid = getpid();
LOGD("client: launching new main daemon process\n");
if (fork_dont_care() == 0) {
close(fd);
// Make sure ppid is not in acct
char src[64], dest[64];
sprintf(src, "/acct/uid_0/pid_%d", ppid);
sprintf(dest, "/acct/uid_0/pid_%d", getpid());
rename(src, dest);
main_daemon();
}
while (connect(fd, (struct sockaddr*) &sun, len))
usleep(10000);
}
return fd;
}