Restructure the native module

Consolidate all code into the src folder
This commit is contained in:
topjohnwu
2022-07-23 13:51:56 -07:00
parent c7c9fb9576
commit b9e89a1a2d
198 changed files with 52 additions and 45 deletions

View File

@@ -0,0 +1,12 @@
[package]
name = "magisk"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
path = "lib.rs"
[dependencies]
base = { path = "../base" }
cxx = "1.0.69"

View File

@@ -0,0 +1,10 @@
#include <sys/stat.h>
#include <magisk.hpp>
#include <base.hpp>
int main(int argc, char *argv[]) {
umask(0);
cmdline_logging();
return APPLET_STUB_MAIN(argc, argv);
}

View File

@@ -0,0 +1,52 @@
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <magisk.hpp>
#include <selinux.hpp>
#include <base.hpp>
using namespace std;
using main_fun = int (*)(int, char *[]);
constexpr const char *applets[] = { "su", "resetprop", "zygisk", nullptr };
static main_fun applet_mains[] = { su_client_main, resetprop_main, zygisk_main, nullptr };
static int call_applet(int argc, char *argv[]) {
// Applets
string_view base = basename(argv[0]);
for (int i = 0; applets[i]; ++i) {
if (base == applets[i]) {
return (*applet_mains[i])(argc, argv);
}
}
fprintf(stderr, "%s: applet not found\n", base.data());
return 1;
}
int main(int argc, char *argv[]) {
enable_selinux();
cmdline_logging();
init_argv0(argc, argv);
string_view base = basename(argv[0]);
// app_process is actually not an applet
if (str_starts(base, "app_process")) {
return app_process_main(argc, argv);
}
umask(0);
if (base == "magisk" || base == "magisk32" || base == "magisk64") {
if (argc > 1 && argv[1][0] != '-') {
// Calling applet via magisk [applet] args
--argc;
++argv;
} else {
return magisk_main(argc, argv);
}
}
return call_applet(argc, argv);
}

View File

@@ -0,0 +1,382 @@
#include <sys/mount.h>
#include <sys/wait.h>
#include <sys/sysmacros.h>
#include <linux/input.h>
#include <libgen.h>
#include <vector>
#include <string>
#include <magisk.hpp>
#include <db.hpp>
#include <base.hpp>
#include <daemon.hpp>
#include <resetprop.hpp>
#include <selinux.hpp>
#include "core.hpp"
using namespace std;
static bool safe_mode = false;
bool zygisk_enabled = false;
/*********
* Setup *
*********/
#define MNT_DIR_IS(dir) (me->mnt_dir == string_view(dir))
#define MNT_TYPE_IS(type) (me->mnt_type == string_view(type))
#define SETMIR(b, part) snprintf(b, sizeof(b), "%s/" MIRRDIR "/" #part, MAGISKTMP.data())
#define SETBLK(b, part) snprintf(b, sizeof(b), "%s/" BLOCKDIR "/" #part, MAGISKTMP.data())
#define do_mount_mirror(part) { \
SETMIR(buf1, part); \
SETBLK(buf2, part); \
unlink(buf2); \
mknod(buf2, S_IFBLK | 0600, st.st_dev); \
xmkdir(buf1, 0755); \
int flags = 0; \
auto opts = split_ro(me->mnt_opts, ",");\
for (string_view s : opts) { \
if (s == "ro") { \
flags |= MS_RDONLY; \
break; \
} \
} \
xmount(buf2, buf1, me->mnt_type, flags, nullptr); \
LOGI("mount: %s\n", buf1); \
}
#define mount_mirror(part) \
if (MNT_DIR_IS("/" #part) \
&& !MNT_TYPE_IS("tmpfs") \
&& !MNT_TYPE_IS("overlay") \
&& lstat(me->mnt_dir, &st) == 0) { \
do_mount_mirror(part); \
break; \
}
#define link_mirror(part) \
SETMIR(buf1, part); \
if (access("/system/" #part, F_OK) == 0 && access(buf1, F_OK) != 0) { \
xsymlink("./system/" #part, buf1); \
LOGI("link: %s\n", buf1); \
}
#define link_orig_dir(dir, part) \
if (MNT_DIR_IS(dir) && !MNT_TYPE_IS("tmpfs") && !MNT_TYPE_IS("overlay")) { \
SETMIR(buf1, part); \
rmdir(buf1); \
xsymlink(dir, buf1); \
LOGI("link: %s\n", buf1); \
break; \
}
#define link_orig(part) link_orig_dir("/" #part, part)
static void mount_mirrors() {
char buf1[4096];
char buf2[4096];
LOGI("* Mounting mirrors\n");
parse_mnt("/proc/mounts", [&](mntent *me) {
struct stat st{};
do {
mount_mirror(system)
mount_mirror(vendor)
mount_mirror(product)
mount_mirror(system_ext)
mount_mirror(data)
link_orig(cache)
link_orig(metadata)
link_orig(persist)
link_orig_dir("/mnt/vendor/persist", persist)
if (SDK_INT >= 24 && MNT_DIR_IS("/proc") && !strstr(me->mnt_opts, "hidepid=2")) {
xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009");
break;
}
} while (false);
return true;
});
SETMIR(buf1, system);
if (access(buf1, F_OK) != 0) {
xsymlink("./system_root/system", buf1);
LOGI("link: %s\n", buf1);
parse_mnt("/proc/mounts", [&](mntent *me) {
struct stat st;
if (MNT_DIR_IS("/") && me->mnt_type != "rootfs"sv && stat("/", &st) == 0) {
do_mount_mirror(system_root)
return false;
}
return true;
});
}
link_mirror(vendor)
link_mirror(product)
link_mirror(system_ext)
}
static bool magisk_env() {
char buf[4096];
LOGI("* Initializing Magisk environment\n");
preserve_stub_apk();
string pkg;
get_manager(0, &pkg);
sprintf(buf, "%s/0/%s/install", APP_DATA_DIR,
pkg.empty() ? "xxx" /* Ensure non-exist path */ : pkg.data());
// Alternative binaries paths
const char *alt_bin[] = { "/cache/data_adb/magisk", "/data/magisk", buf };
for (auto alt : alt_bin) {
struct stat st{};
if (lstat(alt, &st) == 0) {
if (S_ISLNK(st.st_mode)) {
unlink(alt);
continue;
}
rm_rf(DATABIN);
cp_afc(alt, DATABIN);
rm_rf(alt);
break;
}
}
rm_rf("/cache/data_adb");
// Directories in /data/adb
xmkdir(DATABIN, 0755);
xmkdir(MODULEROOT, 0755);
xmkdir(SECURE_DIR "/post-fs-data.d", 0755);
xmkdir(SECURE_DIR "/service.d", 0755);
restore_databincon();
if (access(DATABIN "/busybox", X_OK))
return false;
sprintf(buf, "%s/" BBPATH "/busybox", MAGISKTMP.data());
mkdir(dirname(buf), 0755);
cp_afc(DATABIN "/busybox", buf);
exec_command_async(buf, "--install", "-s", dirname(buf));
if (access(DATABIN "/magiskpolicy", X_OK) == 0) {
sprintf(buf, "%s/magiskpolicy", MAGISKTMP.data());
cp_afc(DATABIN "/magiskpolicy", buf);
}
return true;
}
void reboot() {
if (RECOVERY_MODE)
exec_command_sync("/system/bin/reboot", "recovery");
else
exec_command_sync("/system/bin/reboot");
}
static bool check_data() {
bool mnt = false;
file_readline("/proc/mounts", [&](string_view s) {
if (str_contains(s, " /data ") && !str_contains(s, "tmpfs")) {
mnt = true;
return false;
}
return true;
});
if (!mnt)
return false;
auto crypto = getprop("ro.crypto.state");
if (!crypto.empty()) {
if (crypto != "encrypted") {
// Unencrypted, we can directly access data
return true;
} else {
// Encrypted, check whether vold is started
return !getprop("init.svc.vold").empty();
}
}
// ro.crypto.state is not set, assume it's unencrypted
return true;
}
void unlock_blocks() {
int fd, dev, OFF = 0;
auto dir = xopen_dir("/dev/block");
if (!dir)
return;
dev = dirfd(dir.get());
for (dirent *entry; (entry = readdir(dir.get()));) {
if (entry->d_type == DT_BLK) {
if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0)
continue;
if (ioctl(fd, BLKROSET, &OFF) < 0)
PLOGE("unlock %s", entry->d_name);
close(fd);
}
}
}
#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))
static bool check_key_combo() {
uint8_t bitmask[(KEY_MAX + 1) / 8];
vector<int> events;
constexpr char name[] = "/dev/.ev";
// First collect candidate events that accepts volume down
for (int minor = 64; minor < 96; ++minor) {
if (xmknod(name, S_IFCHR | 0444, makedev(13, minor)))
continue;
int fd = open(name, O_RDONLY | O_CLOEXEC);
unlink(name);
if (fd < 0)
continue;
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask))
events.push_back(fd);
else
close(fd);
}
if (events.empty())
return false;
run_finally fin([&]{ std::for_each(events.begin(), events.end(), close); });
// Check if volume down key is held continuously for more than 3 seconds
for (int i = 0; i < 300; ++i) {
bool pressed = false;
for (const int &fd : events) {
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask)) {
pressed = true;
break;
}
}
if (!pressed)
return false;
// Check every 10ms
usleep(10000);
}
LOGD("KEY_VOLUMEDOWN detected: enter safe mode\n");
return true;
}
/***********************
* Boot Stage Handlers *
***********************/
static pthread_mutex_t stage_lock = PTHREAD_MUTEX_INITIALIZER;
extern int disable_deny();
void post_fs_data(int client) {
close(client);
mutex_guard lock(stage_lock);
if (getenv("REMOUNT_ROOT"))
xmount(nullptr, "/", nullptr, MS_REMOUNT | MS_RDONLY, nullptr);
if (!check_data())
goto unblock_init;
DAEMON_STATE = STATE_POST_FS_DATA;
setup_logfile(true);
LOGI("** post-fs-data mode running\n");
unlock_blocks();
mount_mirrors();
prune_su_access();
if (access(SECURE_DIR, F_OK) != 0) {
if (SDK_INT < 24) {
// There is no FBE pre 7.0, we can directly create the folder without issues
xmkdir(SECURE_DIR, 0700);
} else {
// If the folder is not automatically created by Android,
// do NOT proceed further. Manual creation of the folder
// will have no encryption flag, which will cause bootloops on FBE devices.
LOGE(SECURE_DIR " is not present, abort\n");
goto early_abort;
}
}
if (!magisk_env()) {
LOGE("* Magisk environment incomplete, abort\n");
goto early_abort;
}
if (getprop("persist.sys.safemode", true) == "1" || check_key_combo()) {
safe_mode = true;
// Disable all modules and denylist so next boot will be clean
disable_modules();
disable_deny();
} else {
exec_common_scripts("post-fs-data");
db_settings dbs;
get_db_settings(dbs, ZYGISK_CONFIG);
zygisk_enabled = dbs[ZYGISK_CONFIG];
initialize_denylist();
handle_modules();
}
early_abort:
// We still do magic mount because root itself might need it
magic_mount();
DAEMON_STATE = STATE_POST_FS_DATA_DONE;
unblock_init:
close(xopen(UNBLOCKFILE, O_RDONLY | O_CREAT, 0));
}
void late_start(int client) {
close(client);
mutex_guard lock(stage_lock);
run_finally fin([]{ DAEMON_STATE = STATE_LATE_START_DONE; });
setup_logfile(false);
LOGI("** late_start service mode running\n");
if (DAEMON_STATE < STATE_POST_FS_DATA_DONE || safe_mode)
return;
exec_common_scripts("service");
exec_module_scripts("service");
}
void boot_complete(int client) {
close(client);
mutex_guard lock(stage_lock);
DAEMON_STATE = STATE_BOOT_COMPLETE;
setup_logfile(false);
LOGI("** boot-complete triggered\n");
if (safe_mode)
return;
// At this point it's safe to create the folder
if (access(SECURE_DIR, F_OK) != 0)
xmkdir(SECURE_DIR, 0700);
// Ensure manager exists
check_pkg_refresh();
get_manager(0, nullptr, true);
}
void zygote_restart(int client) {
close(client);
LOGI("** zygote restarted\n");
pkg_xml_ino = 0;
prune_su_access();
}

208
native/src/core/cert.cpp Normal file
View File

@@ -0,0 +1,208 @@
#include <base.hpp>
using namespace std;
#define APK_SIGNING_BLOCK_MAGIC "APK Sig Block 42"
#define SIGNATURE_SCHEME_V2_MAGIC 0x7109871a
#define EOCD_MAGIC 0x6054b50
// Top-level block container
struct signing_block {
uint64_t block_sz;
struct id_value_pair {
uint64_t len;
struct /* v2_signature */ {
uint32_t id;
uint8_t value[0]; // size = (len - 4)
};
} id_value_pair_sequence[0];
uint64_t block_sz_; // *MUST* be same as block_sz
char magic[16]; // "APK Sig Block 42"
};
struct len_prefixed {
uint32_t len;
};
// Generic length prefixed raw data
struct len_prefixed_value : public len_prefixed {
uint8_t value[0];
};
// V2 Signature Block
struct v2_signature {
uint32_t id; // 0x7109871a
uint32_t signer_sequence_len;
struct signer : public len_prefixed {
struct signed_data : public len_prefixed {
uint32_t digest_sequence_len;
struct : public len_prefixed {
uint32_t algorithm;
len_prefixed_value digest;
} digest_sequence[0];
uint32_t certificate_sequence_len;
len_prefixed_value certificate_sequence[0];
uint32_t attribute_sequence_len;
struct attribute : public len_prefixed {
uint32_t id;
uint8_t value[0]; // size = (len - 4)
} attribute_sequence[0];
} signed_data;
uint32_t signature_sequence_len;
struct : public len_prefixed {
uint32_t id;
len_prefixed_value signature;
} signature_sequence[0];
len_prefixed_value public_key;
} signer_sequence[0];
};
// End of central directory record
struct EOCD {
uint32_t magic; // 0x6054b50
uint8_t pad[8]; // 8 bytes of irrelevant data
uint32_t central_dir_sz; // size of central directory
uint32_t central_dir_off; // offset of central directory
uint16_t comment_sz; // size of comment
char comment[0];
} __attribute__((packed));
/*
* A v2/v3 signed APK has the format as following
*
* +---------------+
* | zip content |
* +---------------+
* | signing block |
* +---------------+
* | central dir |
* +---------------+
* | EOCD |
* +---------------+
*
* Scan from end of file to find EOCD, and figure our way back to the
* offset of the signing block. Next, directly extract the certificate
* from the v2 signature block.
*
* All structures above are mostly just for documentation purpose.
*
* This method extracts the first certificate of the first signer
* within the APK v2 signature block.
*/
string read_certificate(int fd, int version) {
uint32_t u32;
uint64_t u64;
// Find EOCD
for (int i = 0;; i++) {
// i is the absolute offset to end of file
uint16_t comment_sz = 0;
xlseek(fd, -static_cast<off_t>(sizeof(comment_sz)) - i, SEEK_END);
xxread(fd, &comment_sz, sizeof(comment_sz));
if (comment_sz == i) {
// Double check if we actually found the structure
xlseek(fd, -static_cast<off_t>(sizeof(EOCD)), SEEK_CUR);
uint32_t magic = 0;
xxread(fd, &magic, sizeof(magic));
if (magic == EOCD_MAGIC) {
break;
}
}
if (i == 0xffff) {
// Comments cannot be longer than 0xffff (overflow), abort
LOGE("cert: invalid APK format\n");
return {};
}
}
// We are now at EOCD + sizeof(magic)
// Seek and read central_dir_off to find start of central directory
uint32_t central_dir_off = 0;
{
constexpr off_t off = offsetof(EOCD, central_dir_off) - sizeof(EOCD::magic);
xlseek(fd, off, SEEK_CUR);
}
xxread(fd, &central_dir_off, sizeof(central_dir_off));
// Parse APK comment to get version code
if (version >= 0) {
xlseek(fd, sizeof(EOCD::comment_sz), SEEK_CUR);
FILE *fp = fdopen(fd, "r"); // DO NOT close this file pointer
int apk_ver = -1;
parse_prop_file(fp, [&](string_view key, string_view value) -> bool {
if (key == "versionCode") {
apk_ver = parse_int(value);
return false;
}
return true;
});
if (version > apk_ver) {
// Enforce the magisk app to always be newer than magiskd
LOGE("cert: APK version too low\n");
return {};
}
}
// Next, find the start of the APK signing block
{
constexpr int off = sizeof(signing_block::block_sz_) + sizeof(signing_block::magic);
xlseek(fd, (off_t) (central_dir_off - off), SEEK_SET);
}
xxread(fd, &u64, sizeof(u64)); // u64 = block_sz_
char magic[sizeof(signing_block::magic)] = {0};
xxread(fd, magic, sizeof(magic));
if (memcmp(magic, APK_SIGNING_BLOCK_MAGIC, sizeof(magic)) != 0) {
// Invalid signing block magic, abort
LOGE("cert: invalid signing block magic\n");
return {};
}
uint64_t signing_blk_sz = 0;
xlseek(fd, -static_cast<off_t>(u64 + sizeof(signing_blk_sz)), SEEK_CUR);
xxread(fd, &signing_blk_sz, sizeof(signing_blk_sz));
if (signing_blk_sz != u64) {
// block_sz != block_sz_, invalid signing block format, abort
LOGE("cert: invalid signing block format\n");
return {};
}
// Finally, we are now at the beginning of the id-value pair sequence
for (;;) {
xxread(fd, &u64, sizeof(u64)); // id-value pair length
if (u64 == signing_blk_sz) {
// Outside of the id-value pair sequence; actually reading block_sz_
break;
}
uint32_t id;
xxread(fd, &id, sizeof(id));
if (id == SIGNATURE_SCHEME_V2_MAGIC) {
// Skip [signer sequence length] + [1st signer length] + [signed data length]
xlseek(fd, sizeof(uint32_t) * 3, SEEK_CUR);
xxread(fd, &u32, sizeof(u32)); // digest sequence length
xlseek(fd, u32, SEEK_CUR); // skip all digests
xlseek(fd, sizeof(uint32_t), SEEK_CUR); // cert sequence length
xxread(fd, &u32, sizeof(u32)); // 1st cert length
string cert;
cert.resize(u32);
xxread(fd, cert.data(), u32);
return cert;
} else {
// Skip this id-value pair
xlseek(fd, u64 - sizeof(id), SEEK_CUR);
}
}
LOGE("cert: cannot find certificate\n");
return {};
}

39
native/src/core/core.hpp Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <string>
#include <vector>
extern bool RECOVERY_MODE;
extern int DAEMON_STATE;
extern std::atomic<ino_t> pkg_xml_ino;
// Daemon state
enum : int {
STATE_NONE,
STATE_POST_FS_DATA,
STATE_POST_FS_DATA_DONE,
STATE_LATE_START_DONE,
STATE_BOOT_COMPLETE
};
void unlock_blocks();
void reboot();
void start_log_daemon();
void setup_logfile(bool reset);
std::string read_certificate(int fd, int version = -1);
// Module stuffs
void handle_modules();
void magic_mount();
void disable_modules();
void remove_modules();
void exec_module_scripts(const char *stage);
// Scripting
void exec_script(const char *script);
void exec_common_scripts(const char *stage);
void exec_module_scripts(const char *stage, const std::vector<std::string_view> &modules);
void install_apk(const char *apk);
void uninstall_pkg(const char *pkg);
void clear_pkg(const char *pkg, int user_id);
[[noreturn]] void install_module(const char *file);

461
native/src/core/daemon.cpp Normal file
View File

@@ -0,0 +1,461 @@
#include <csignal>
#include <libgen.h>
#include <sys/un.h>
#include <sys/mount.h>
#include <magisk.hpp>
#include <base.hpp>
#include <daemon.hpp>
#include <selinux.hpp>
#include <db.hpp>
#include <resetprop.hpp>
#include <flags.h>
#include <core-rs.cpp>
#include "core.hpp"
using namespace std;
int SDK_INT = -1;
string MAGISKTMP;
bool RECOVERY_MODE = false;
int DAEMON_STATE = STATE_NONE;
static struct stat self_st;
static map<int, poll_callback> *poll_map;
static vector<pollfd> *poll_fds;
static int poll_ctrl;
enum {
POLL_CTRL_NEW,
POLL_CTRL_RM,
};
void register_poll(const pollfd *pfd, poll_callback callback) {
if (gettid() == getpid()) {
// On main thread, directly modify
poll_map->try_emplace(pfd->fd, callback);
poll_fds->emplace_back(*pfd);
} else {
// Send it to poll_ctrl
write_int(poll_ctrl, POLL_CTRL_NEW);
xwrite(poll_ctrl, pfd, sizeof(*pfd));
xwrite(poll_ctrl, &callback, sizeof(callback));
}
}
void unregister_poll(int fd, bool auto_close) {
if (fd < 0)
return;
if (gettid() == getpid()) {
// On main thread, directly modify
poll_map->erase(fd);
for (auto &poll_fd : *poll_fds) {
if (poll_fd.fd == fd) {
if (auto_close) {
close(poll_fd.fd);
}
// Cannot modify while iterating, invalidate it instead
// It will be removed in the next poll loop
poll_fd.fd = -1;
break;
}
}
} else {
// Send it to poll_ctrl
write_int(poll_ctrl, POLL_CTRL_RM);
write_int(poll_ctrl, fd);
write_int(poll_ctrl, auto_close);
}
}
void clear_poll() {
if (poll_fds) {
for (auto &poll_fd : *poll_fds) {
close(poll_fd.fd);
}
}
delete poll_fds;
delete poll_map;
poll_fds = nullptr;
poll_map = nullptr;
}
static void poll_ctrl_handler(pollfd *pfd) {
int code = read_int(pfd->fd);
switch (code) {
case POLL_CTRL_NEW: {
pollfd new_fd;
poll_callback cb;
xxread(pfd->fd, &new_fd, sizeof(new_fd));
xxread(pfd->fd, &cb, sizeof(cb));
register_poll(&new_fd, cb);
break;
}
case POLL_CTRL_RM: {
int fd = read_int(pfd->fd);
bool auto_close = read_int(pfd->fd);
unregister_poll(fd, auto_close);
break;
}
}
}
[[noreturn]] static void poll_loop() {
// Register poll_ctrl
int pipefd[2];
xpipe2(pipefd, O_CLOEXEC);
poll_ctrl = pipefd[1];
pollfd poll_ctrl_pfd = { pipefd[0], POLLIN, 0 };
register_poll(&poll_ctrl_pfd, poll_ctrl_handler);
for (;;) {
if (poll(poll_fds->data(), poll_fds->size(), -1) <= 0)
continue;
// MUST iterate with index because any poll_callback could add new elements to poll_fds
for (int i = 0; i < poll_fds->size();) {
auto &pfd = (*poll_fds)[i];
if (pfd.revents) {
if (pfd.revents & POLLERR || pfd.revents & POLLNVAL) {
poll_map->erase(pfd.fd);
poll_fds->erase(poll_fds->begin() + i);
continue;
}
if (auto it = poll_map->find(pfd.fd); it != poll_map->end()) {
it->second(&pfd);
}
}
++i;
}
}
}
static void handle_request_async(int client, int code, const sock_cred &cred) {
switch (code) {
case MainRequest::DENYLIST:
denylist_handler(client, &cred);
break;
case MainRequest::SUPERUSER:
su_daemon_handler(client, &cred);
break;
case MainRequest::POST_FS_DATA:
post_fs_data(client);
break;
case MainRequest::LATE_START:
late_start(client);
break;
case MainRequest::BOOT_COMPLETE:
boot_complete(client);
break;
case MainRequest::ZYGOTE_RESTART:
zygote_restart(client);
break;
case MainRequest::SQLITE_CMD:
exec_sql(client);
break;
case MainRequest::REMOVE_MODULES:
remove_modules();
write_int(client, 0);
close(client);
reboot();
break;
case MainRequest::ZYGISK:
case MainRequest::ZYGISK_PASSTHROUGH:
zygisk_handler(client, &cred);
break;
default:
__builtin_unreachable();
}
}
static void handle_request_sync(int client, int code) {
switch (code) {
case MainRequest::CHECK_VERSION:
#if MAGISK_DEBUG
write_string(client, MAGISK_VERSION ":MAGISK:D");
#else
write_string(client, MAGISK_VERSION ":MAGISK:R");
#endif
break;
case MainRequest::CHECK_VERSION_CODE:
write_int(client, MAGISK_VER_CODE);
break;
case MainRequest::GET_PATH:
write_string(client, MAGISKTMP.data());
break;
case MainRequest::START_DAEMON:
setup_logfile(true);
break;
case MainRequest::STOP_DAEMON:
denylist_handler(-1, nullptr);
write_int(client, 0);
// Terminate the daemon!
exit(0);
default:
__builtin_unreachable();
}
}
static bool is_client(pid_t pid) {
// Verify caller is the same as server
char path[32];
sprintf(path, "/proc/%d/exe", pid);
struct stat st{};
return !(stat(path, &st) || st.st_dev != self_st.st_dev || st.st_ino != self_st.st_ino);
}
static void handle_request(pollfd *pfd) {
int client = xaccept4(pfd->fd, nullptr, nullptr, SOCK_CLOEXEC);
// Verify client credentials
sock_cred cred;
bool is_root;
bool is_zygote;
int code;
if (!get_client_cred(client, &cred)) {
// Client died
goto done;
}
is_root = cred.uid == AID_ROOT;
is_zygote = cred.context == "u:r:zygote:s0";
if (!is_root && !is_zygote && !is_client(cred.pid)) {
// Unsupported client state
write_int(client, MainResponse::ACCESS_DENIED);
goto done;
}
code = read_int(client);
if (code < 0 || code >= MainRequest::END || code == MainRequest::_SYNC_BARRIER_) {
// Unknown request code
goto done;
}
// Check client permissions
switch (code) {
case MainRequest::POST_FS_DATA:
case MainRequest::LATE_START:
case MainRequest::BOOT_COMPLETE:
case MainRequest::ZYGOTE_RESTART:
case MainRequest::SQLITE_CMD:
case MainRequest::GET_PATH:
case MainRequest::DENYLIST:
case MainRequest::STOP_DAEMON:
if (!is_root) {
write_int(client, MainResponse::ROOT_REQUIRED);
goto done;
}
break;
case MainRequest::REMOVE_MODULES:
if (!is_root && cred.uid != AID_SHELL) {
write_int(client, MainResponse::ACCESS_DENIED);
goto done;
}
break;
case MainRequest::ZYGISK:
if (!is_zygote && selinux_enabled()) {
// Invalid client context
write_int(client, MainResponse::ACCESS_DENIED);
goto done;
}
break;
default:
break;
}
write_int(client, MainResponse::OK);
if (code < MainRequest::_SYNC_BARRIER_) {
handle_request_sync(client, code);
goto done;
}
// Handle async requests in another thread
exec_task([=] { handle_request_async(client, code, cred); });
return;
done:
close(client);
}
static void switch_cgroup(const char *cgroup, int pid) {
char buf[32];
snprintf(buf, sizeof(buf), "%s/cgroup.procs", cgroup);
if (access(buf, F_OK) != 0)
return;
int fd = xopen(buf, O_WRONLY | O_APPEND | O_CLOEXEC);
if (fd == -1)
return;
snprintf(buf, sizeof(buf), "%d\n", pid);
xwrite(fd, buf, strlen(buf));
close(fd);
}
static void daemon_entry() {
magisk_logging();
// Block all signals
sigset_t block_set;
sigfillset(&block_set);
pthread_sigmask(SIG_SETMASK, &block_set, nullptr);
// Change process name
set_nice_name("magiskd");
int fd = xopen("/dev/null", O_WRONLY);
xdup2(fd, STDOUT_FILENO);
xdup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
close(fd);
fd = xopen("/dev/zero", O_RDONLY);
xdup2(fd, STDIN_FILENO);
if (fd > STDERR_FILENO)
close(fd);
setsid();
setcon("u:r:" SEPOL_PROC_DOMAIN ":s0");
start_log_daemon();
LOGI(NAME_WITH_VER(Magisk) " daemon started\n");
// Escape from cgroup
int pid = getpid();
switch_cgroup("/acct", pid);
switch_cgroup("/dev/cg2_bpf", pid);
switch_cgroup("/sys/fs/cgroup", pid);
if (getprop("ro.config.per_app_memcg") != "false") {
switch_cgroup("/dev/memcg/apps", pid);
}
// Get self stat
char buf[64];
xreadlink("/proc/self/exe", buf, sizeof(buf));
MAGISKTMP = dirname(buf);
xstat("/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") {
SDK_INT = parse_int(val);
return false;
}
return true;
});
if (SDK_INT < 0) {
// In case some devices do not store this info in build.prop, fallback to getprop
auto sdk = getprop("ro.build.version.sdk");
if (!sdk.empty()) {
SDK_INT = parse_int(sdk);
}
}
LOGI("* Device API level: %d\n", SDK_INT);
restore_tmpcon();
// SAR cleanups
auto mount_list = MAGISKTMP + "/" ROOTMNT;
if (access(mount_list.data(), F_OK) == 0) {
file_readline(true, mount_list.data(), [](string_view line) -> bool {
umount2(line.data(), MNT_DETACH);
return true;
});
}
rm_rf((MAGISKTMP + "/" ROOTOVL).data());
// Load config status
auto config = MAGISKTMP + "/" INTLROOT "/config";
parse_prop_file(config.data(), [](auto key, auto val) -> bool {
if (key == "RECOVERYMODE" && val == "true")
RECOVERY_MODE = true;
return true;
});
// Use isolated devpts if kernel support
if (access("/dev/pts/ptmx", F_OK) == 0) {
auto pts = MAGISKTMP + "/" SHELLPTS;
if (access(pts.data(), F_OK)) {
xmkdirs(pts.data(), 0755);
xmount("devpts", pts.data(), "devpts",
MS_NOSUID | MS_NOEXEC, "newinstance");
auto ptmx = pts + "/ptmx";
if (access(ptmx.data(), F_OK)) {
xumount(pts.data());
rmdir(pts.data());
}
}
}
sockaddr_un sun{};
socklen_t len = setup_sockaddr(&sun, MAIN_SOCKET);
fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (xbind(fd, (sockaddr *) &sun, len))
exit(1);
xlisten(fd, 10);
default_new(poll_map);
default_new(poll_fds);
default_new(module_list);
// Register handler for main socket
pollfd main_socket_pfd = { fd, POLLIN, 0 };
register_poll(&main_socket_pfd, handle_request);
// Loop forever to listen for requests
poll_loop();
}
int connect_daemon(int req, bool create) {
sockaddr_un sun{};
socklen_t len = setup_sockaddr(&sun, MAIN_SOCKET);
int fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (connect(fd, (sockaddr *) &sun, len)) {
if (!create || getuid() != AID_ROOT) {
LOGE("No daemon is currently running!\n");
close(fd);
return -1;
}
char buf[64];
xreadlink("/proc/self/exe", buf, sizeof(buf));
if (str_starts(buf, "/system/bin/")) {
LOGE("Start daemon on /dev or /sbin\n");
close(fd);
return -1;
}
if (fork_dont_care() == 0) {
close(fd);
daemon_entry();
}
while (connect(fd, (struct sockaddr *) &sun, len))
usleep(10000);
}
write_int(fd, req);
int res = read_int(fd);
if (res < MainResponse::ERROR || res >= MainResponse::END)
res = MainResponse::ERROR;
switch (res) {
case MainResponse::OK:
break;
case MainResponse::ERROR:
LOGE("Daemon error\n");
exit(-1);
case MainResponse::ROOT_REQUIRED:
LOGE("Root is required for this operation\n");
exit(-1);
case MainResponse::ACCESS_DENIED:
LOGE("Access denied\n");
exit(-1);
default:
__builtin_unreachable();
}
return fd;
}

397
native/src/core/db.cpp Normal file
View File

@@ -0,0 +1,397 @@
#include <unistd.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <magisk.hpp>
#include <db.hpp>
#include <socket.hpp>
#include <base.hpp>
#define DB_VERSION 12
using namespace std;
struct sqlite3;
static sqlite3 *mDB = nullptr;
#define DBLOGV(...)
//#define DBLOGV(...) LOGD("magiskdb: " __VA_ARGS__)
// SQLite APIs
#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */
#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */
#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */
static int (*sqlite3_open_v2)(
const char *filename,
sqlite3 **ppDb,
int flags,
const char *zVfs);
static const char *(*sqlite3_errmsg)(sqlite3 *db);
static int (*sqlite3_close)(sqlite3 *db);
static void (*sqlite3_free)(void *v);
static int (*sqlite3_exec)(
sqlite3 *db,
const char *sql,
int (*callback)(void*, int, char**, char**),
void *v,
char **errmsg);
// Internal Android linker APIs
static void (*android_get_LD_LIBRARY_PATH)(char *buffer, size_t buffer_size);
static void (*android_update_LD_LIBRARY_PATH)(const char *ld_library_path);
#define DLERR(ptr) if (!(ptr)) { \
LOGE("db: %s\n", dlerror()); \
return false; \
}
#define DLOAD(handle, arg) {\
auto f = dlsym(handle, #arg); \
DLERR(f) \
*(void **) &(arg) = f; \
}
#ifdef __LP64__
constexpr char apex_path[] = "/apex/com.android.runtime/lib64:/apex/com.android.art/lib64:/apex/com.android.i18n/lib64:";
#else
constexpr char apex_path[] = "/apex/com.android.runtime/lib:/apex/com.android.art/lib:/apex/com.android.i18n/lib:";
#endif
static int dl_init = 0;
static bool dload_sqlite() {
if (dl_init)
return dl_init > 0;
dl_init = -1;
auto sqlite = dlopen("libsqlite.so", RTLD_LAZY);
if (!sqlite) {
// Should only happen on Android 10+
auto dl = dlopen("libdl_android.so", RTLD_LAZY);
DLERR(dl);
DLOAD(dl, android_get_LD_LIBRARY_PATH);
DLOAD(dl, android_update_LD_LIBRARY_PATH);
// Inject APEX into LD_LIBRARY_PATH
char ld_path[4096];
memcpy(ld_path, apex_path, sizeof(apex_path));
constexpr int len = sizeof(apex_path) - 1;
android_get_LD_LIBRARY_PATH(ld_path + len, sizeof(ld_path) - len);
android_update_LD_LIBRARY_PATH(ld_path);
sqlite = dlopen("libsqlite.so", RTLD_LAZY);
// Revert LD_LIBRARY_PATH just in case
android_update_LD_LIBRARY_PATH(ld_path + len);
}
DLERR(sqlite);
DLOAD(sqlite, sqlite3_open_v2);
DLOAD(sqlite, sqlite3_errmsg);
DLOAD(sqlite, sqlite3_close);
DLOAD(sqlite, sqlite3_exec);
DLOAD(sqlite, sqlite3_free);
dl_init = 1;
return true;
}
int db_strings::get_idx(string_view key) const {
int idx = 0;
for (const char *k : DB_STRING_KEYS) {
if (key == k)
break;
++idx;
}
return idx;
}
db_settings::db_settings() {
// Default settings
data[ROOT_ACCESS] = ROOT_ACCESS_APPS_AND_ADB;
data[SU_MULTIUSER_MODE] = MULTIUSER_MODE_OWNER_ONLY;
data[SU_MNT_NS] = NAMESPACE_MODE_REQUESTER;
data[DENYLIST_CONFIG] = false;
data[ZYGISK_CONFIG] = false;
}
int db_settings::get_idx(string_view key) const {
int idx = 0;
for (const char *k : DB_SETTING_KEYS) {
if (key == k)
break;
++idx;
}
return idx;
}
static int ver_cb(void *ver, int, char **data, char **) {
*((int *) ver) = parse_int(data[0]);
return 0;
}
#define err_ret(e) if (e) return e;
static char *open_and_init_db(sqlite3 *&db) {
if (!dload_sqlite())
return strdup("Cannot load libsqlite.so");
int ret = sqlite3_open_v2(MAGISKDB, &db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr);
if (ret)
return strdup(sqlite3_errmsg(db));
int ver = 0;
bool upgrade = false;
char *err = nullptr;
sqlite3_exec(db, "PRAGMA user_version", ver_cb, &ver, &err);
err_ret(err);
if (ver > DB_VERSION) {
// Don't support downgrading database
sqlite3_close(db);
return strdup("Downgrading database is not supported");
}
auto create_policy = [&] {
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS policies "
"(uid INT, policy INT, until INT, logging INT, "
"notification INT, PRIMARY KEY(uid))",
nullptr, nullptr, &err);
};
auto create_settings = [&] {
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS settings "
"(key TEXT, value INT, PRIMARY KEY(key))",
nullptr, nullptr, &err);
};
auto create_strings = [&] {
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS strings "
"(key TEXT, value TEXT, PRIMARY KEY(key))",
nullptr, nullptr, &err);
};
auto create_denylist = [&] {
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS denylist "
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process))",
nullptr, nullptr, &err);
};
// Database changelog:
//
// 0 - 6: DB stored in app private data. There are no longer any code in the project to
// migrate these data, so no need to take any of these versions into consideration.
// 7 : create table `hidelist` (process TEXT, PRIMARY KEY(process))
// 8 : add new column (package_name TEXT) to table `hidelist`
// 9 : rebuild table `hidelist` to change primary key (PRIMARY KEY(package_name, process))
// 10: remove table `logs`
// 11: remove table `hidelist` and create table `denylist` (same data structure)
// 12: rebuild table `policies` to drop column `package_name`
if (/* 0, 1, 2, 3, 4, 5, 6 */ ver <= 6) {
create_policy();
err_ret(err);
create_settings();
err_ret(err);
create_strings();
err_ret(err);
create_denylist();
err_ret(err);
// Directly jump to latest
ver = DB_VERSION;
upgrade = true;
}
if (ver == 7) {
sqlite3_exec(db,
"BEGIN TRANSACTION;"
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
"CREATE TABLE IF NOT EXISTS hidelist "
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
"INSERT INTO hidelist SELECT process as package_name, process FROM hidelist_tmp;"
"DROP TABLE hidelist_tmp;"
"COMMIT;",
nullptr, nullptr, &err);
err_ret(err);
// Directly jump to version 9
ver = 9;
upgrade = true;
}
if (ver == 8) {
sqlite3_exec(db,
"BEGIN TRANSACTION;"
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
"CREATE TABLE IF NOT EXISTS hidelist "
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
"INSERT INTO hidelist SELECT * FROM hidelist_tmp;"
"DROP TABLE hidelist_tmp;"
"COMMIT;",
nullptr, nullptr, &err);
err_ret(err);
ver = 9;
upgrade = true;
}
if (ver == 9) {
sqlite3_exec(db, "DROP TABLE IF EXISTS logs", nullptr, nullptr, &err);
err_ret(err);
ver = 10;
upgrade = true;
}
if (ver == 10) {
sqlite3_exec(db,
"DROP TABLE IF EXISTS hidelist;"
"DELETE FROM settings WHERE key='magiskhide';",
nullptr, nullptr, &err);
err_ret(err);
create_denylist();
err_ret(err);
ver = 11;
upgrade = true;
}
if (ver == 11) {
sqlite3_exec(db,
"BEGIN TRANSACTION;"
"ALTER TABLE policies RENAME TO policies_tmp;"
"CREATE TABLE IF NOT EXISTS policies "
"(uid INT, policy INT, until INT, logging INT, "
"notification INT, PRIMARY KEY(uid));"
"INSERT INTO policies "
"SELECT uid, policy, until, logging, notification FROM policies_tmp;"
"DROP TABLE policies_tmp;"
"COMMIT;",
nullptr, nullptr, &err);
err_ret(err);
ver = 12;
upgrade = true;
}
if (upgrade) {
// Set version
char query[32];
sprintf(query, "PRAGMA user_version=%d", ver);
sqlite3_exec(db, query, nullptr, nullptr, &err);
err_ret(err);
}
return nullptr;
}
char *db_exec(const char *sql) {
char *err = nullptr;
if (mDB == nullptr) {
err = open_and_init_db(mDB);
db_err_cmd(err,
// Open fails, remove and reconstruct
unlink(MAGISKDB);
err = open_and_init_db(mDB);
err_ret(err);
);
}
if (mDB) {
sqlite3_exec(mDB, sql, nullptr, nullptr, &err);
return err;
}
return nullptr;
}
static int sqlite_db_row_callback(void *cb, int col_num, char **data, char **col_name) {
auto &func = *static_cast<const db_row_cb*>(cb);
db_row row;
for (int i = 0; i < col_num; ++i)
row[col_name[i]] = data[i];
return func(row) ? 0 : 1;
}
char *db_exec(const char *sql, const db_row_cb &fn) {
char *err = nullptr;
if (mDB == nullptr) {
err = open_and_init_db(mDB);
db_err_cmd(err,
// Open fails, remove and reconstruct
unlink(MAGISKDB);
err = open_and_init_db(mDB);
err_ret(err);
);
}
if (mDB) {
sqlite3_exec(mDB, sql, sqlite_db_row_callback, (void *) &fn, &err);
return err;
}
return nullptr;
}
int get_db_settings(db_settings &cfg, int key) {
char *err = nullptr;
auto settings_cb = [&](db_row &row) -> bool {
cfg[row["key"]] = parse_int(row["value"]);
DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data());
return true;
};
if (key >= 0) {
char query[128];
snprintf(query, sizeof(query), "SELECT * FROM settings WHERE key='%s'", DB_SETTING_KEYS[key]);
err = db_exec(query, settings_cb);
} else {
err = db_exec("SELECT * FROM settings", settings_cb);
}
db_err_cmd(err, return 1);
return 0;
}
int get_db_strings(db_strings &str, int key) {
char *err = nullptr;
auto string_cb = [&](db_row &row) -> bool {
str[row["key"]] = row["value"];
DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data());
return true;
};
if (key >= 0) {
char query[128];
snprintf(query, sizeof(query), "SELECT * FROM strings WHERE key='%s'", DB_STRING_KEYS[key]);
err = db_exec(query, string_cb);
} else {
err = db_exec("SELECT * FROM strings", string_cb);
}
db_err_cmd(err, return 1);
return 0;
}
void rm_db_strings(int key) {
char *err;
char query[128];
snprintf(query, sizeof(query), "DELETE FROM strings WHERE key == '%s'", DB_STRING_KEYS[key]);
err = db_exec(query);
db_err_cmd(err, return);
}
void exec_sql(int client) {
run_finally f([=]{ close(client); });
string sql = read_string(client);
char *err = db_exec(sql.data(), [client](db_row &row) -> bool {
string out;
bool first = true;
for (auto it : row) {
if (first) first = false;
else out += '|';
out += it.first;
out += '=';
out += it.second;
}
write_string(client, out);
return true;
});
write_int(client, 0);
db_err_cmd(err, return; );
}
bool db_err(char *e) {
if (e) {
LOGE("sqlite3_exec: %s\n", e);
sqlite3_free(e);
return true;
}
return false;
}

16
native/src/core/lib.rs Normal file
View File

@@ -0,0 +1,16 @@
pub use base;
pub use logging::*;
mod logging;
#[cxx::bridge]
pub mod ffi {
extern "Rust" {
fn rust_test_entry();
fn android_logging();
fn magisk_logging();
fn zygisk_logging();
}
}
fn rust_test_entry() {}

159
native/src/core/logging.cpp Normal file
View File

@@ -0,0 +1,159 @@
#include <sys/uio.h>
#include <android/log.h>
#include <magisk.hpp>
#include <base.hpp>
#include <daemon.hpp>
#include <stream.hpp>
#include "core.hpp"
using namespace std;
struct log_meta {
int prio;
int len;
int pid;
int tid;
};
atomic<int> logd_fd = -1;
void setup_logfile(bool reset) {
if (logd_fd < 0)
return;
iovec iov{};
log_meta meta = {
.prio = -1,
.len = reset
};
iov.iov_base = &meta;
iov.iov_len = sizeof(meta);
writev(logd_fd, &iov, 1);
}
// Maximum message length for pipes to transfer atomically
#define MAX_MSG_LEN (int) (PIPE_BUF - sizeof(log_meta))
static void *logfile_writer(void *arg) {
int pipefd = (long) arg;
run_finally close_pipes([=] {
// Close up all logging pipes when thread dies
close(pipefd);
close(logd_fd.exchange(-1));
});
struct {
void *data;
size_t len;
} tmp{};
stream_ptr strm = make_unique<byte_stream>(tmp.data, tmp.len);
bool switched = false;
log_meta meta{};
char buf[MAX_MSG_LEN];
char aux[64];
iovec iov[2];
iov[0].iov_base = aux;
iov[1].iov_base = buf;
for (;;) {
// Read meta data
if (read(pipefd, &meta, sizeof(meta)) != sizeof(meta))
return nullptr;
if (meta.prio < 0) {
if (!switched) {
run_finally free_tmp([&] {
free(tmp.data);
tmp.data = nullptr;
tmp.len = 0;
});
rename(LOGFILE, LOGFILE ".bak");
int fd = open(LOGFILE, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0644);
if (fd < 0)
return nullptr;
if (tmp.data)
write(fd, tmp.data, tmp.len);
strm = make_unique<fd_stream>(fd);
switched = true;
}
continue;
}
// Read message
if (read(pipefd, buf, meta.len) != meta.len)
return nullptr;
timeval tv;
tm tm;
gettimeofday(&tv, nullptr);
localtime_r(&tv.tv_sec, &tm);
// Format detailed info
char type;
switch (meta.prio) {
case ANDROID_LOG_DEBUG:
type = 'D';
break;
case ANDROID_LOG_INFO:
type = 'I';
break;
case ANDROID_LOG_WARN:
type = 'W';
break;
default:
type = 'E';
break;
}
long ms = tv.tv_usec / 1000;
size_t off = strftime(aux, sizeof(aux), "%m-%d %T", &tm);
off += snprintf(aux + off, sizeof(aux) - off,
".%03ld %5d %5d %c : ", ms, meta.pid, meta.tid, type);
iov[0].iov_len = off;
iov[1].iov_len = meta.len;
strm->writev(iov, 2);
}
}
void magisk_log_write(int prio, const char *msg, int len) {
if (logd_fd >= 0) {
// Truncate
len = std::min(MAX_MSG_LEN, len);
log_meta meta = {
.prio = prio,
.len = len,
.pid = getpid(),
.tid = gettid()
};
iovec iov[2];
iov[0].iov_base = &meta;
iov[0].iov_len = sizeof(meta);
iov[1].iov_base = (void *) msg;
iov[1].iov_len = len;
if (writev(logd_fd, iov, 2) < 0) {
// Stop trying to write to file
close(logd_fd.exchange(-1));
}
}
}
void start_log_daemon() {
int fds[2];
if (pipe2(fds, O_CLOEXEC) == 0) {
logd_fd = fds[1];
long fd = fds[0];
new_daemon_thread(logfile_writer, (void *) fd);
}
}

112
native/src/core/logging.rs Normal file
View File

@@ -0,0 +1,112 @@
use base::ffi::LogLevel;
use base::*;
use std::fmt::Arguments;
#[allow(dead_code, non_camel_case_types)]
#[repr(i32)]
enum ALogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT,
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT,
}
extern "C" {
fn __android_log_write(prio: i32, tag: *const u8, msg: *const u8);
fn magisk_log_write(prio: i32, msg: *const u8, len: i32);
fn zygisk_log_write(prio: i32, msg: *const u8, len: i32);
}
fn level_to_prio(level: LogLevel) -> i32 {
match level {
LogLevel::Error => ALogPriority::ANDROID_LOG_ERROR as i32,
LogLevel::Warn => ALogPriority::ANDROID_LOG_WARN as i32,
LogLevel::Info => ALogPriority::ANDROID_LOG_INFO as i32,
LogLevel::Debug => ALogPriority::ANDROID_LOG_DEBUG as i32,
_ => 0,
}
}
pub fn android_logging() {
fn android_log_fmt(level: LogLevel, args: Arguments) {
let mut buf: [u8; 4096] = [0; 4096];
fmt_to_buf(&mut buf, args);
unsafe {
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), buf.as_ptr());
}
}
fn android_log_write(level: LogLevel, msg: &[u8]) {
unsafe {
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), msg.as_ptr());
}
}
let logger = Logger {
fmt: android_log_fmt,
write: android_log_write,
flags: 0,
};
exit_on_error(false);
unsafe {
LOGGER = logger;
}
}
pub fn magisk_logging() {
fn magisk_fmt(level: LogLevel, args: Arguments) {
let mut buf: [u8; 4096] = [0; 4096];
let len = fmt_to_buf(&mut buf, args);
unsafe {
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), buf.as_ptr());
magisk_log_write(level_to_prio(level), buf.as_ptr(), len as i32);
}
}
fn magisk_write(level: LogLevel, msg: &[u8]) {
unsafe {
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), msg.as_ptr());
magisk_log_write(level_to_prio(level), msg.as_ptr(), msg.len() as i32);
}
}
let logger = Logger {
fmt: magisk_fmt,
write: magisk_write,
flags: 0,
};
exit_on_error(false);
unsafe {
LOGGER = logger;
}
}
pub fn zygisk_logging() {
fn zygisk_fmt(level: LogLevel, args: Arguments) {
let mut buf: [u8; 4096] = [0; 4096];
let len = fmt_to_buf(&mut buf, args);
unsafe {
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), buf.as_ptr());
zygisk_log_write(level_to_prio(level), buf.as_ptr(), len as i32);
}
}
fn zygisk_write(level: LogLevel, msg: &[u8]) {
unsafe {
__android_log_write(level_to_prio(level), b"Magisk\0".as_ptr(), msg.as_ptr());
zygisk_log_write(level_to_prio(level), msg.as_ptr(), msg.len() as i32);
}
}
let logger = Logger {
fmt: zygisk_fmt,
write: zygisk_write,
flags: 0,
};
exit_on_error(false);
unsafe {
LOGGER = logger;
}
}

135
native/src/core/magisk.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include <sys/mount.h>
#include <libgen.h>
#include <base.hpp>
#include <magisk.hpp>
#include <daemon.hpp>
#include <selinux.hpp>
#include <flags.h>
#include "core.hpp"
using namespace std;
[[noreturn]] static void usage() {
fprintf(stderr,
R"EOF(Magisk - Multi-purpose Utility
Usage: magisk [applet [arguments]...]
or: magisk [options]...
Options:
-c print current binary version
-v print running daemon version
-V print running daemon version code
--list list all available applets
--remove-modules remove all modules and reboot
--install-module ZIP install a module zip file
Advanced Options (Internal APIs):
--daemon manually start magisk daemon
--stop remove all magisk changes and stop daemon
--[init trigger] callback on init triggers. Valid triggers:
post-fs-data, service, boot-complete, zygote-restart
--unlock-blocks set BLKROSET flag to OFF for all block devices
--restorecon restore selinux context on Magisk files
--clone-attr SRC DEST clone permission, owner, and selinux context
--clone SRC DEST clone SRC to DEST
--sqlite SQL exec SQL commands to Magisk database
--path print Magisk tmpfs mount path
--denylist ARGS denylist config CLI
Available applets:
)EOF");
for (int i = 0; applet_names[i]; ++i)
fprintf(stderr, i ? ", %s" : " %s", applet_names[i]);
fprintf(stderr, "\n\n");
exit(1);
}
int magisk_main(int argc, char *argv[]) {
if (argc < 2)
usage();
if (argv[1] == "-c"sv) {
#if MAGISK_DEBUG
printf(MAGISK_VERSION ":MAGISK:D (" str(MAGISK_VER_CODE) ")\n");
#else
printf(MAGISK_VERSION ":MAGISK:R (" str(MAGISK_VER_CODE) ")\n");
#endif
return 0;
} else if (argv[1] == "-v"sv) {
int fd = connect_daemon(MainRequest::CHECK_VERSION);
string v = read_string(fd);
printf("%s\n", v.data());
return 0;
} else if (argv[1] == "-V"sv) {
int fd = connect_daemon(MainRequest::CHECK_VERSION_CODE);
printf("%d\n", read_int(fd));
return 0;
} else if (argv[1] == "--list"sv) {
for (int i = 0; applet_names[i]; ++i)
printf("%s\n", applet_names[i]);
return 0;
} else if (argv[1] == "--unlock-blocks"sv) {
unlock_blocks();
return 0;
} else if (argv[1] == "--restorecon"sv) {
restorecon();
return 0;
} else if (argc >= 4 && argv[1] == "--clone-attr"sv) {
clone_attr(argv[2], argv[3]);
return 0;
} else if (argc >= 4 && argv[1] == "--clone"sv) {
cp_afc(argv[2], argv[3]);
return 0;
} else if (argv[1] == "--daemon"sv) {
close(connect_daemon(MainRequest::START_DAEMON, true));
return 0;
} else if (argv[1] == "--stop"sv) {
int fd = connect_daemon(MainRequest::STOP_DAEMON);
return read_int(fd);
} else if (argv[1] == "--post-fs-data"sv) {
close(connect_daemon(MainRequest::POST_FS_DATA, true));
return 0;
} else if (argv[1] == "--service"sv) {
close(connect_daemon(MainRequest::LATE_START, true));
return 0;
} else if (argv[1] == "--boot-complete"sv) {
close(connect_daemon(MainRequest::BOOT_COMPLETE));
return 0;
} else if (argv[1] == "--zygote-restart"sv) {
close(connect_daemon(MainRequest::ZYGOTE_RESTART));
return 0;
} else if (argv[1] == "--denylist"sv) {
return denylist_cli(argc - 1, argv + 1);
} else if (argc >= 3 && argv[1] == "--sqlite"sv) {
int fd = connect_daemon(MainRequest::SQLITE_CMD);
write_string(fd, argv[2]);
string res;
for (;;) {
read_string(fd, res);
if (res.empty())
return 0;
printf("%s\n", res.data());
}
} else if (argv[1] == "--remove-modules"sv) {
int fd = connect_daemon(MainRequest::REMOVE_MODULES);
return read_int(fd);
} else if (argv[1] == "--path"sv) {
int fd = connect_daemon(MainRequest::GET_PATH);
string path = read_string(fd);
printf("%s\n", path.data());
return 0;
} else if (argc >= 3 && argv[1] == "--install-module"sv) {
install_module(argv[2]);
}
#if 0
/* Entry point for testing stuffs */
else if (argv[1] == "--test"sv) {
rust_test_entry();
return 0;
}
#endif
usage();
}

808
native/src/core/module.cpp Normal file
View File

@@ -0,0 +1,808 @@
#include <sys/mount.h>
#include <map>
#include <utility>
#include <base.hpp>
#include <magisk.hpp>
#include <daemon.hpp>
#include <selinux.hpp>
#include <resetprop.hpp>
#include "core.hpp"
using namespace std;
#define VLOGD(tag, from, to) LOGD("%-8s: %s <- %s\n", tag, to, from)
#define TYPE_MIRROR (1 << 0) /* mount from mirror */
#define TYPE_INTER (1 << 1) /* intermediate node */
#define TYPE_TMPFS (1 << 2) /* replace with tmpfs */
#define TYPE_MODULE (1 << 3) /* mount from module */
#define TYPE_ROOT (1 << 4) /* partition root */
#define TYPE_CUSTOM (1 << 5) /* custom node type overrides all */
#define TYPE_DIR (TYPE_INTER|TYPE_TMPFS|TYPE_ROOT)
class node_entry;
class dir_node;
class inter_node;
class mirror_node;
class tmpfs_node;
class module_node;
class root_node;
template<class T> static bool isa(node_entry *node);
static int bind_mount(const char *from, const char *to) {
int ret = xmount(from, to, nullptr, MS_BIND, nullptr);
if (ret == 0)
VLOGD("bind_mnt", from, to);
return ret;
}
template<class T> uint8_t type_id() { return TYPE_CUSTOM; }
template<> uint8_t type_id<dir_node>() { return TYPE_DIR; }
template<> uint8_t type_id<inter_node>() { return TYPE_INTER; }
template<> uint8_t type_id<mirror_node>() { return TYPE_MIRROR; }
template<> uint8_t type_id<tmpfs_node>() { return TYPE_TMPFS; }
template<> uint8_t type_id<module_node>() { return TYPE_MODULE; }
template<> uint8_t type_id<root_node>() { return TYPE_ROOT; }
class node_entry {
public:
virtual ~node_entry() = default;
// Node info
bool is_dir() { return file_type() == DT_DIR; }
bool is_lnk() { return file_type() == DT_LNK; }
bool is_reg() { return file_type() == DT_REG; }
uint8_t type() { return node_type; }
const string &name() { return _name; }
// Don't call the following two functions before prepare
const string &node_path();
string mirror_path() { return mirror_dir + node_path(); }
// Tree methods
dir_node *parent() { return _parent; }
void merge(node_entry *other);
virtual void mount() = 0;
static string module_mnt;
static string mirror_dir;
protected:
template<class T>
node_entry(const char *name, uint8_t file_type, T*)
: _name(name), _file_type(file_type), node_type(type_id<T>()) {}
template<class T>
explicit node_entry(T*) : _file_type(0), node_type(type_id<T>()) {}
void create_and_mount(const string &src);
// Use top bit of _file_type for node exist status
bool exist() { return static_cast<bool>(_file_type & (1 << 7)); }
void set_exist(bool b) { if (b) _file_type |= (1 << 7); else _file_type &= ~(1 << 7); }
uint8_t file_type() { return static_cast<uint8_t>(_file_type & ~(1 << 7)); }
private:
friend class dir_node;
static bool should_be_tmpfs(node_entry *child);
// Node properties
string _name;
uint8_t _file_type;
uint8_t node_type;
dir_node *_parent = nullptr;
// Cache, it should only be used within prepare
string _node_path;
};
class dir_node : public node_entry {
public:
friend void node_entry::merge(node_entry *other);
using map_type = map<string_view, node_entry *>;
using iterator = map_type::iterator;
~dir_node() override {
for (auto &it : children)
delete it.second;
children.clear();
}
// Return false to indicate need to upgrade to module
bool collect_files(const char *module, int dfd);
// Return false to indicate need to upgrade to skeleton
bool prepare();
// Default directory mount logic
void mount() override {
for (auto &pair : children)
pair.second->mount();
}
/***************
* Tree Methods
***************/
bool is_empty() { return children.empty(); }
template<class T>
T *child(string_view name) { return iterator_to_node<T>(children.find(name)); }
// Lazy val
root_node *root() {
if (!_root)
_root = _parent->root();
return _root;
}
// Return child with name or nullptr
node_entry *extract(string_view name);
// Return false if rejected
bool insert(node_entry *node) {
auto fn = [=](auto) { return node; };
return node && iterator_to_node(insert(node->_name, node->node_type, fn));
}
// Return inserted node or null if rejected
template<class T, class ...Args>
T *emplace(string_view name, Args &&...args) {
auto fn = [&](auto) { return new T(std::forward<Args>(args)...); };
return iterator_to_node<T>(insert(name, type_id<T>(), fn));
}
// Return inserted node, existing node with same rank, or null if rejected
template<class T, class ...Args>
T *emplace_or_get(string_view name, Args &&...args) {
auto fn = [&](auto) { return new T(std::forward<Args>(args)...); };
return iterator_to_node<T>(insert(name, type_id<T>(), fn, true));
}
// Return upgraded node or null if rejected
template<class T, class ...Args>
T *upgrade(string_view name, Args &...args) {
return iterator_to_node<T>(upgrade<T>(children.find(name), args...));
}
protected:
template<class T>
dir_node(const char *name, uint8_t file_type, T *self) : node_entry(name, file_type, self) {
if constexpr (std::is_same_v<T, root_node>)
_root = self;
}
template<class T>
dir_node(node_entry *node, T *self) : node_entry(self) {
merge(node);
if constexpr (std::is_same_v<T, root_node>)
_root = self;
}
template<class T>
dir_node(const char *name, T *self) : dir_node(name, DT_DIR, self) {}
template<class T = node_entry>
T *iterator_to_node(iterator it) {
return static_cast<T*>(it == children.end() ? nullptr : it->second);
}
// Emplace insert a new node, or upgrade if the requested type has a higher rank.
// Return iterator to new node or end() if insertion is rejected.
// If get_same is true and a node with the same rank exists, it will return that node instead.
// fn is the node construction callback. Signature: (node_ent *&) -> node_ent *
// fn gets a reference to the existing node pointer and returns a new node object.
// Input is null when there is no existing node. If returns null, the insertion is rejected.
// If fn consumes the input, it should set the reference to null.
template<typename Func>
iterator insert(iterator it, uint8_t type, const Func &fn, bool get_same);
template<typename Func>
iterator insert(string_view name, uint8_t type, const Func &fn, bool get_same = false) {
return insert(children.find(name), type, fn, get_same);
}
template<class To, class From = node_entry, class ...Args>
iterator upgrade(iterator it, Args &&...args);
// dir nodes host children
map_type children;
// Root node lookup cache
root_node *_root = nullptr;
};
class root_node : public dir_node {
public:
explicit root_node(const char *name) : dir_node(name, this), prefix("") {}
explicit root_node(node_entry *node) : dir_node(node, this), prefix("/system") {}
const char * const prefix;
};
class inter_node : public dir_node {
public:
inter_node(const char *name, const char *module) : dir_node(name, this), module(module) {}
private:
const char *module;
friend class module_node;
};
class module_node : public node_entry {
public:
module_node(const char *module, dirent *entry)
: node_entry(entry->d_name, entry->d_type, this), module(module) {}
module_node(node_entry *node, const char *module) : node_entry(this), module(module) {
merge(node);
}
explicit module_node(inter_node *node) : module_node(node, node->module) {}
void mount() override;
private:
const char *module;
};
// Don't create the following two nodes before prepare
class mirror_node : public node_entry {
public:
explicit mirror_node(dirent *entry) : node_entry(entry->d_name, entry->d_type, this) {}
void mount() override {
create_and_mount(mirror_path());
}
};
class tmpfs_node : public dir_node {
public:
explicit tmpfs_node(node_entry *node);
void mount() override;
};
// Poor man's dynamic cast without RTTI
template<class T>
static bool isa(node_entry *node) {
return node && (node->type() & type_id<T>());
}
template<class T>
static T *dyn_cast(node_entry *node) {
return isa<T>(node) ? static_cast<T*>(node) : nullptr;
}
string node_entry::module_mnt;
string node_entry::mirror_dir;
// other will be deleted
void node_entry::merge(node_entry *other) {
_name.swap(other->_name);
_file_type = other->_file_type;
_parent = other->_parent;
// Merge children if both is dir
if (auto a = dyn_cast<dir_node>(this)) {
if (auto b = dyn_cast<dir_node>(other)) {
a->children.merge(b->children);
for (auto &pair : a->children)
pair.second->_parent = a;
}
}
delete other;
}
const string &node_entry::node_path() {
if (_parent && _node_path.empty())
_node_path = _parent->node_path() + '/' + _name;
return _node_path;
}
/*************************
* Node Tree Construction
*************************/
template<typename Func>
dir_node::iterator dir_node::insert(iterator it, uint8_t type, const Func &fn, bool get_same) {
node_entry *node = nullptr;
if (it != children.end()) {
// Upgrade existing node only if higher rank
if (it->second->node_type < type) {
node = fn(it->second);
if (!node)
return children.end();
if (it->second)
node->merge(it->second);
it = children.erase(it);
// Minor optimization to make insert O(1) by using hint
if (it == children.begin())
it = children.emplace(node->_name, node).first;
else
it = children.emplace_hint(--it, node->_name, node);
} else {
if (get_same && it->second->node_type != type)
return children.end();
return it;
}
} else {
node = fn(node);
if (!node)
return children.end();
node->_parent = this;
it = children.emplace(node->_name, node).first;
}
return it;
}
template<class To, class From, class... Args>
dir_node::iterator dir_node::upgrade(iterator it, Args &&... args) {
return insert(it, type_id<To>(), [&](node_entry *&ex) -> node_entry * {
if (!ex)
return nullptr;
if constexpr (!std::is_same_v<From, node_entry>) {
// Type check if type is specified
if (!isa<From>(ex))
return nullptr;
}
auto node = new To(static_cast<From *>(ex), std::forward<Args>(args)...);
ex = nullptr;
return node;
}, false);
}
node_entry* dir_node::extract(string_view name) {
auto it = children.find(name);
if (it != children.end()) {
auto ret = it->second;
children.erase(it);
return ret;
}
return nullptr;
}
tmpfs_node::tmpfs_node(node_entry *node) : dir_node(node, this) {
string mirror = mirror_path();
if (auto dir = open_dir(mirror.data())) {
set_exist(true);
for (dirent *entry; (entry = xreaddir(dir.get()));) {
// Insert mirror nodes
emplace<mirror_node>(entry->d_name, entry);
}
} else {
// It is actually possible that mirror does not exist (nested mount points)
// Set self to non exist so this node will be ignored at mount
// Keep it the same as `node`
return;
}
for (auto it = children.begin(); it != children.end(); ++it) {
// Need to upgrade all inter_node children to tmpfs_node
if (isa<inter_node>(it->second))
it = upgrade<tmpfs_node>(it);
}
}
// We need to upgrade to tmpfs node if any child:
// - Target does not exist
// - Source or target is a symlink
bool node_entry::should_be_tmpfs(node_entry *child) {
struct stat st;
if (lstat(child->node_path().data(), &st) != 0) {
return true;
} else {
child->set_exist(true);
if (child->is_lnk() || S_ISLNK(st.st_mode))
return true;
}
return false;
}
bool dir_node::prepare() {
bool to_tmpfs = false;
for (auto it = children.begin(); it != children.end();) {
if (should_be_tmpfs(it->second)) {
if (node_type > type_id<tmpfs_node>()) {
// Upgrade will fail, remove the unsupported child node
LOGW("Unable to add: %s, skipped\n", it->second->node_path().data());
delete it->second;
it = children.erase(it);
continue;
}
// Tell parent to upgrade self to tmpfs
to_tmpfs = true;
// If child is inter_node and it does not (need to) exist, upgrade to module
if (auto dn = dyn_cast<inter_node>(it->second); dn) {
if (!dn->exist()) {
if (auto nit = upgrade<module_node, inter_node>(it); nit != children.end()) {
it = nit;
goto next_node;
}
}
}
}
if (auto dn = dyn_cast<dir_node>(it->second); dn && dn->is_dir() && !dn->prepare()) {
// Upgrade child to tmpfs
it = upgrade<tmpfs_node>(it);
}
next_node:
++it;
}
return !to_tmpfs;
}
bool dir_node::collect_files(const char *module, int dfd) {
auto dir = xopen_dir(xopenat(dfd, _name.data(), O_RDONLY | O_CLOEXEC));
if (!dir)
return true;
for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (entry->d_name == ".replace"sv) {
// Stop traversing and tell parent to upgrade self to module
return false;
}
if (entry->d_type == DT_DIR) {
dir_node *dn;
if (auto it = children.find(entry->d_name); it == children.end()) {
dn = emplace<inter_node>(entry->d_name, entry->d_name, module);
} else {
dn = dyn_cast<inter_node>(it->second);
// it has been accessed by at least two modules, it must be guarantee to exist
// set it so that it won't be upgrade to module_node but tmpfs_node
if (dn) dn->set_exist(true);
}
if (dn && !dn->collect_files(module, dirfd(dir.get()))) {
upgrade<module_node>(dn->name(), module);
}
} else {
emplace<module_node>(entry->d_name, module, entry);
}
}
return true;
}
/************************
* Mount Implementations
************************/
void node_entry::create_and_mount(const string &src) {
const string &dest = node_path();
if (is_lnk()) {
VLOGD("cp_link", src.data(), dest.data());
cp_afc(src.data(), dest.data());
} else {
if (is_dir())
xmkdir(dest.data(), 0);
else if (is_reg())
close(xopen(dest.data(), O_RDONLY | O_CREAT | O_CLOEXEC, 0));
else
return;
bind_mount(src.data(), dest.data());
}
}
void module_node::mount() {
string src = module_mnt + module + parent()->root()->prefix + node_path();
if (exist())
clone_attr(mirror_path().data(), src.data());
if (isa<tmpfs_node>(parent()))
create_and_mount(src);
else if (is_dir() || is_reg())
bind_mount(src.data(), node_path().data());
}
void tmpfs_node::mount() {
if (!exist())
return;
string src = mirror_path();
const string &dest = node_path();
file_attr a{};
if (access(src.data(), F_OK) == 0)
getattr(src.data(), &a);
else
getattr(parent()->node_path().data(), &a);
mkdir(dest.data(), 0);
if (!isa<tmpfs_node>(parent())) {
// We don't need another layer of tmpfs if parent is skel
xmount("tmpfs", dest.data(), "tmpfs", 0, nullptr);
VLOGD("mnt_tmp", "tmpfs", dest.data());
}
setattr(dest.data(), &a);
dir_node::mount();
}
/****************
* Magisk Stuffs
****************/
class magisk_node : public node_entry {
public:
explicit magisk_node(const char *name) : node_entry(name, DT_REG, this) {}
void mount() override {
const string src = MAGISKTMP + "/" + name();
if (access(src.data(), F_OK))
return;
const string &dir_name = parent()->node_path();
if (name() == "magisk") {
for (int i = 0; applet_names[i]; ++i) {
string dest = dir_name + "/" + applet_names[i];
VLOGD("create", "./magisk", dest.data());
xsymlink("./magisk", dest.data());
}
} else {
string dest = dir_name + "/supolicy";
VLOGD("create", "./magiskpolicy", dest.data());
xsymlink("./magiskpolicy", dest.data());
}
create_and_mount(src);
}
};
static void inject_magisk_bins(root_node *system) {
auto bin = system->child<inter_node>("bin");
if (!bin) {
bin = new inter_node("bin", "");
system->insert(bin);
}
// Insert binaries
bin->insert(new magisk_node("magisk"));
bin->insert(new magisk_node("magiskpolicy"));
// Also delete all applets to make sure no modules can override it
for (int i = 0; applet_names[i]; ++i)
delete bin->extract(applet_names[i]);
delete bin->extract("supolicy");
}
vector<module_info> *module_list;
int app_process_32 = -1;
int app_process_64 = -1;
#define mount_zygisk(bit) \
if (access("/system/bin/app_process" #bit, F_OK) == 0) { \
app_process_##bit = xopen("/system/bin/app_process" #bit, O_RDONLY | O_CLOEXEC); \
string zbin = zygisk_bin + "/app_process" #bit; \
string dbin = zygisk_bin + "/magisk" #bit; \
string mbin = MAGISKTMP + "/magisk" #bit; \
int src = xopen(mbin.data(), O_RDONLY | O_CLOEXEC); \
int out = xopen(zbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0); \
xsendfile(out, src, nullptr, INT_MAX); \
close(out); \
out = xopen(dbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0); \
lseek(src, 0, SEEK_SET); \
xsendfile(out, src, nullptr, INT_MAX); \
close(out); \
close(src); \
clone_attr("/system/bin/app_process" #bit, zbin.data()); \
clone_attr("/system/bin/app_process" #bit, dbin.data()); \
bind_mount(zbin.data(), "/system/bin/app_process" #bit); \
}
void magic_mount() {
node_entry::mirror_dir = MAGISKTMP + "/" MIRRDIR;
node_entry::module_mnt = MAGISKTMP + "/" MODULEMNT "/";
auto root = make_unique<root_node>("");
auto system = new root_node("system");
root->insert(system);
char buf[4096];
LOGI("* Loading modules\n");
for (const auto &m : *module_list) {
const char *module = m.name.data();
char *b = buf + sprintf(buf, "%s/" MODULEMNT "/%s/", MAGISKTMP.data(), module);
// Read props
strcpy(b, "system.prop");
if (access(buf, F_OK) == 0) {
LOGI("%s: loading [system.prop]\n", module);
load_prop_file(buf, false);
}
// Check whether skip mounting
strcpy(b, "skip_mount");
if (access(buf, F_OK) == 0)
continue;
// Double check whether the system folder exists
strcpy(b, "system");
if (access(buf, F_OK) != 0)
continue;
LOGI("%s: loading mount files\n", module);
b[-1] = '\0';
int fd = xopen(buf, O_RDONLY | O_CLOEXEC);
system->collect_files(module, fd);
close(fd);
}
if (MAGISKTMP != "/sbin") {
// Need to inject our binaries into /system/bin
inject_magisk_bins(system);
}
if (!system->is_empty()) {
// Handle special read-only partitions
for (const char *part : { "/vendor", "/product", "/system_ext" }) {
struct stat st{};
if (lstat(part, &st) == 0 && S_ISDIR(st.st_mode)) {
if (auto old = system->extract(part + 1)) {
auto new_node = new root_node(old);
root->insert(new_node);
}
}
}
root->prepare();
root->mount();
}
// Mount on top of modules to enable zygisk
if (zygisk_enabled) {
string zygisk_bin = MAGISKTMP + "/" ZYGISKBIN;
mkdir(zygisk_bin.data(), 0);
mount_zygisk(32)
mount_zygisk(64)
}
}
static void prepare_modules() {
// Upgrade modules
if (auto dir = open_dir(MODULEUPGRADE); dir) {
int ufd = dirfd(dir.get());
int mfd = xopen(MODULEROOT, O_RDONLY | O_CLOEXEC);
for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (entry->d_type == DT_DIR) {
// Cleanup old module if exists
if (faccessat(mfd, entry->d_name, F_OK, 0) == 0) {
int modfd = xopenat(mfd, entry->d_name, O_RDONLY | O_CLOEXEC);
if (faccessat(modfd, "disable", F_OK, 0) == 0) {
auto disable = entry->d_name + "/disable"s;
close(xopenat(ufd, disable.data(), O_RDONLY | O_CREAT | O_CLOEXEC, 0));
}
frm_rf(modfd);
unlinkat(mfd, entry->d_name, AT_REMOVEDIR);
}
LOGI("Upgrade / New module: %s\n", entry->d_name);
renameat(ufd, entry->d_name, mfd, entry->d_name);
}
}
close(mfd);
rm_rf(MODULEUPGRADE);
}
// Setup module mount (workaround nosuid selabel issue)
auto src = MAGISKTMP + "/" MIRRDIR MODULEROOT;
auto dest = MAGISKTMP + "/" MODULEMNT;
xmkdir(dest.data(), 0755);
bind_mount(src.data(), dest.data());
restorecon();
chmod(SECURE_DIR, 0700);
}
template<typename Func>
static void foreach_module(Func fn) {
auto dir = open_dir(MODULEROOT);
if (!dir)
return;
int dfd = dirfd(dir.get());
for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (entry->d_type == DT_DIR && entry->d_name != ".core"sv) {
int modfd = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
fn(dfd, entry, modfd);
close(modfd);
}
}
}
static void collect_modules(bool open_zygisk) {
foreach_module([=](int dfd, dirent *entry, int modfd) {
if (faccessat(modfd, "remove", F_OK, 0) == 0) {
LOGI("%s: remove\n", entry->d_name);
auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh";
if (access(uninstaller.data(), F_OK) == 0)
exec_script(uninstaller.data());
frm_rf(xdup(modfd));
unlinkat(dfd, entry->d_name, AT_REMOVEDIR);
return;
}
unlinkat(modfd, "update", 0);
if (faccessat(modfd, "disable", F_OK, 0) == 0)
return;
module_info info;
if (zygisk_enabled) {
// Riru and its modules are not compatible with zygisk
if (entry->d_name == "riru-core"sv || faccessat(modfd, "riru", F_OK, 0) == 0) {
LOGI("%s: ignore\n", entry->d_name);
return;
}
if (open_zygisk) {
#if defined(__arm__)
info.z32 = openat(modfd, "zygisk/armeabi-v7a.so", O_RDONLY | O_CLOEXEC);
#elif defined(__aarch64__)
info.z32 = openat(modfd, "zygisk/armeabi-v7a.so", O_RDONLY | O_CLOEXEC);
info.z64 = openat(modfd, "zygisk/arm64-v8a.so", O_RDONLY | O_CLOEXEC);
#elif defined(__i386__)
info.z32 = openat(modfd, "zygisk/x86.so", O_RDONLY | O_CLOEXEC);
#elif defined(__x86_64__)
info.z32 = openat(modfd, "zygisk/x86.so", O_RDONLY | O_CLOEXEC);
info.z64 = openat(modfd, "zygisk/x86_64.so", O_RDONLY | O_CLOEXEC);
#else
#error Unsupported ABI
#endif
unlinkat(modfd, "zygisk/unloaded", 0);
}
} else {
// Ignore zygisk modules when zygisk is not enabled
if (faccessat(modfd, "zygisk", F_OK, 0) == 0) {
LOGI("%s: ignore\n", entry->d_name);
return;
}
}
info.name = entry->d_name;
module_list->push_back(info);
});
if (zygisk_enabled) {
bool use_memfd = true;
auto convert_to_memfd = [&](int fd) -> int {
if (fd < 0)
return -1;
if (use_memfd) {
int memfd = syscall(__NR_memfd_create, "jit-cache", MFD_CLOEXEC);
if (memfd >= 0) {
xsendfile(memfd, fd, nullptr, INT_MAX);
close(fd);
return memfd;
} else {
// memfd_create failed, just use what we had
use_memfd = false;
}
}
return fd;
};
std::for_each(module_list->begin(), module_list->end(), [&](module_info &info) {
info.z32 = convert_to_memfd(info.z32);
#if defined(__LP64__)
info.z64 = convert_to_memfd(info.z64);
#endif
});
}
}
void handle_modules() {
prepare_modules();
collect_modules(false);
exec_module_scripts("post-fs-data");
// Recollect modules (module scripts could remove itself)
module_list->clear();
collect_modules(true);
}
void disable_modules() {
foreach_module([](int, auto, int modfd) {
close(xopenat(modfd, "disable", O_RDONLY | O_CREAT | O_CLOEXEC, 0));
});
}
void remove_modules() {
foreach_module([](int, dirent *entry, int) {
auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh";
if (access(uninstaller.data(), F_OK) == 0)
exec_script(uninstaller.data());
});
rm_rf(MODULEROOT);
}
void exec_module_scripts(const char *stage) {
vector<string_view> module_names;
std::transform(module_list->begin(), module_list->end(), std::back_inserter(module_names),
[](const module_info &info) -> string_view { return info.name; });
exec_module_scripts(stage, module_names);
}

279
native/src/core/package.cpp Normal file
View File

@@ -0,0 +1,279 @@
#include <base.hpp>
#include <magisk.hpp>
#include <daemon.hpp>
#include <db.hpp>
#include <flags.h>
#include "core.hpp"
using namespace std;
#define ENFORCE_SIGNATURE (!MAGISK_DEBUG)
// These functions will be called on every single zygote process specialization and su request,
// so performance is absolutely critical. Most operations should either have its result cached
// or simply skipped unless necessary.
atomic<ino_t> pkg_xml_ino = 0;
static atomic_flag skip_mgr_check;
static pthread_mutex_t pkg_lock = PTHREAD_MUTEX_INITIALIZER;
// pkg_lock protects all following variables
static int mgr_app_id = -1;
static string *mgr_pkg;
static string *mgr_cert;
static int stub_apk_fd = -1;
static const string *default_cert;
void check_pkg_refresh() {
struct stat st{};
if (stat("/data/system/packages.xml", &st) == 0 &&
pkg_xml_ino.exchange(st.st_ino) != st.st_ino) {
skip_mgr_check.clear();
skip_pkg_rescan.clear();
}
}
// app_id = app_no + AID_APP_START
// app_no range: [0, 9999]
vector<bool> get_app_no_list() {
vector<bool> list;
auto data_dir = xopen_dir(APP_DATA_DIR);
if (!data_dir)
return list;
dirent *entry;
while ((entry = xreaddir(data_dir.get()))) {
// For each user
int dfd = xopenat(dirfd(data_dir.get()), entry->d_name, O_RDONLY);
if (auto dir = xopen_dir(dfd)) {
while ((entry = xreaddir(dir.get()))) {
// For each package
struct stat st{};
xfstatat(dfd, entry->d_name, &st, 0);
int app_id = to_app_id(st.st_uid);
if (app_id >= AID_APP_START && app_id <= AID_APP_END) {
int app_no = app_id - AID_APP_START;
if (list.size() <= app_no) {
list.resize(app_no + 1);
}
list[app_no] = true;
}
}
} else {
close(dfd);
}
}
return list;
}
void preserve_stub_apk() {
mutex_guard g(pkg_lock);
string stub_path = MAGISKTMP + "/stub.apk";
stub_apk_fd = xopen(stub_path.data(), O_RDONLY | O_CLOEXEC);
unlink(stub_path.data());
default_cert = new string(read_certificate(stub_apk_fd));
lseek(stub_apk_fd, 0, SEEK_SET);
}
static void install_stub() {
if (stub_apk_fd < 0)
return;
struct stat st{};
fstat(stub_apk_fd, &st);
char apk[] = "/data/stub.apk";
int dfd = xopen(apk, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
xsendfile(dfd, stub_apk_fd, nullptr, st.st_size);
lseek(stub_apk_fd, 0, SEEK_SET);
close(dfd);
install_apk(apk);
}
int get_manager(int user_id, string *pkg, bool install) {
mutex_guard g(pkg_lock);
char app_path[128];
struct stat st{};
if (mgr_pkg == nullptr)
default_new(mgr_pkg);
if (mgr_cert == nullptr)
default_new(mgr_cert);
auto check_dyn = [&](int u) -> bool {
#if ENFORCE_SIGNATURE
snprintf(app_path, sizeof(app_path),
"%s/%d/%s/dyn/current.apk", APP_DATA_DIR, u, mgr_pkg->data());
int dyn = open(app_path, O_RDONLY | O_CLOEXEC);
if (dyn < 0) {
LOGW("pkg: no dyn APK, ignore\n");
return false;
}
bool mismatch = default_cert && read_certificate(dyn, MAGISK_VER_CODE) != *default_cert;
close(dyn);
if (mismatch) {
LOGE("pkg: dyn APK signature mismatch: %s\n", app_path);
clear_pkg(mgr_pkg->data(), u);
return false;
}
#endif
return true;
};
if (skip_mgr_check.test_and_set()) {
if (mgr_app_id < 0) {
goto not_found;
}
// Just need to check whether the app is installed in the user
const char *name = mgr_pkg->empty() ? JAVA_PACKAGE_NAME : mgr_pkg->data();
snprintf(app_path, sizeof(app_path), "%s/%d/%s", APP_DATA_DIR, user_id, name);
if (access(app_path, F_OK) == 0) {
// Always check dyn signature for repackaged app
if (!mgr_pkg->empty() && !check_dyn(user_id))
goto not_found;
if (pkg) *pkg = name;
return user_id * AID_USER_OFFSET + mgr_app_id;
} else {
goto not_found;
}
} else {
// Here, we want to actually find the manager app and cache the results.
// This means that we check all users, not just the requested user.
// Certificates are also verified to prevent manipulation.
db_strings str;
get_db_strings(str, SU_MANAGER);
vector<int> users;
bool collected = false;
auto collect_users = [&] {
if (collected)
return;
collected = true;
auto data_dir = xopen_dir(APP_DATA_DIR);
if (!data_dir)
return;
dirent *entry;
while ((entry = xreaddir(data_dir.get()))) {
// Only collect users not requested as we've already checked it
if (int u = parse_int(entry->d_name); u >= 0 && u != user_id)
users.push_back(parse_int(entry->d_name));
}
};
if (!str[SU_MANAGER].empty()) {
// Check the repackaged package name
bool invalid = false;
auto check_pkg = [&](int u) -> bool {
snprintf(app_path, sizeof(app_path),
"%s/%d/%s", APP_DATA_DIR, u, str[SU_MANAGER].data());
if (stat(app_path, &st) == 0) {
int app_id = to_app_id(st.st_uid);
string apk = find_apk_path(str[SU_MANAGER].data());
int fd = xopen(apk.data(), O_RDONLY | O_CLOEXEC);
string cert = read_certificate(fd);
close(fd);
// Verify validity
if (str[SU_MANAGER] == *mgr_pkg) {
if (app_id != mgr_app_id || cert != *mgr_cert) {
// app ID or cert should never change
LOGE("pkg: repackaged APK signature invalid: %s\n", apk.data());
uninstall_pkg(mgr_pkg->data());
invalid = true;
install = true;
return false;
}
}
mgr_pkg->swap(str[SU_MANAGER]);
mgr_app_id = app_id;
mgr_cert->swap(cert);
return true;
}
return false;
};
if (check_pkg(user_id)) {
if (!check_dyn(user_id))
goto not_found;
if (pkg) *pkg = *mgr_pkg;
return st.st_uid;
}
if (!invalid) {
collect_users();
for (int u : users) {
if (check_pkg(u)) {
// Found repackaged app, but not installed in the requested user
goto not_found;
}
if (invalid)
break;
}
}
// Repackaged app not found, remove package from db
rm_db_strings(SU_MANAGER);
// Fallthrough
}
// Check the original package name
bool invalid = false;
auto check_pkg = [&](int u) -> bool {
snprintf(app_path, sizeof(app_path), "%s/%d/" JAVA_PACKAGE_NAME, APP_DATA_DIR, u);
if (stat(app_path, &st) == 0) {
#if ENFORCE_SIGNATURE
string apk = find_apk_path(JAVA_PACKAGE_NAME);
int fd = xopen(apk.data(), O_RDONLY | O_CLOEXEC);
string cert = read_certificate(fd, MAGISK_VER_CODE);
close(fd);
if (default_cert && cert != *default_cert) {
// Found APK with invalid signature, force replace with stub
LOGE("pkg: APK signature mismatch: %s\n", apk.data());
uninstall_pkg(JAVA_PACKAGE_NAME);
invalid = true;
install = true;
return false;
}
#endif
mgr_pkg->clear();
mgr_cert->clear();
mgr_app_id = to_app_id(st.st_uid);
return true;
}
return false;
};
if (check_pkg(user_id)) {
if (pkg) *pkg = JAVA_PACKAGE_NAME;
return st.st_uid;
}
if (!invalid) {
collect_users();
for (int u : users) {
if (check_pkg(u)) {
// Found app, but not installed in the requested user
goto not_found;
}
if (invalid)
break;
}
}
}
// No manager app is found, clear all cached value
mgr_app_id = -1;
mgr_pkg->clear();
mgr_cert->clear();
if (install)
install_stub();
not_found:
const char *name = mgr_pkg->empty() ? JAVA_PACKAGE_NAME : mgr_pkg->data();
LOGW("pkg: cannot find %s for user=[%d]\n", name, user_id);
if (pkg) pkg->clear();
return -1;
}

View File

@@ -0,0 +1,101 @@
#include <string_view>
#include <magisk.hpp>
#include <selinux.hpp>
#include <base.hpp>
using namespace std;
#define UNLABEL_CON "u:object_r:unlabeled:s0"
#define SYSTEM_CON "u:object_r:system_file:s0"
#define ADB_CON "u:object_r:adb_data_file:s0"
#define ROOT_CON "u:object_r:rootfs:s0"
#define MAGISK_CON "u:object_r:" SEPOL_FILE_TYPE ":s0"
#define EXEC_CON "u:object_r:" SEPOL_EXEC_TYPE ":s0"
static void restore_syscon(int dirfd) {
struct dirent *entry;
char *con;
if (fgetfilecon(dirfd, &con) >= 0) {
if (strlen(con) == 0 || strcmp(con, UNLABEL_CON) == 0 || strcmp(con, ADB_CON) == 0)
fsetfilecon(dirfd, SYSTEM_CON);
freecon(con);
}
auto dir = xopen_dir(dirfd);
while ((entry = xreaddir(dir.get()))) {
int fd = openat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
if (entry->d_type == DT_DIR) {
restore_syscon(fd);
continue;
} else if (entry->d_type == DT_REG) {
if (fgetfilecon(fd, &con) >= 0) {
if (con[0] == '\0' || strcmp(con, UNLABEL_CON) == 0 || strcmp(con, ADB_CON) == 0)
fsetfilecon(fd, SYSTEM_CON);
freecon(con);
}
} else if (entry->d_type == DT_LNK) {
getfilecon_at(dirfd, entry->d_name, &con);
if (con[0] == '\0' || strcmp(con, UNLABEL_CON) == 0 || strcmp(con, ADB_CON) == 0)
setfilecon_at(dirfd, entry->d_name, con);
freecon(con);
}
close(fd);
}
}
static void restore_magiskcon(int dirfd) {
struct dirent *entry;
fsetfilecon(dirfd, MAGISK_CON);
fchown(dirfd, 0, 0);
auto dir = xopen_dir(dirfd);
while ((entry = xreaddir(dir.get()))) {
int fd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
if (entry->d_type == DT_DIR) {
restore_magiskcon(fd);
continue;
} else if (entry->d_type) {
fsetfilecon(fd, MAGISK_CON);
fchown(fd, 0, 0);
}
close(fd);
}
}
void restorecon() {
if (!selinux_enabled())
return;
int fd = xopen(SELINUX_CONTEXT, O_WRONLY | O_CLOEXEC);
if (write(fd, ADB_CON, sizeof(ADB_CON)) >= 0)
lsetfilecon(SECURE_DIR, ADB_CON);
close(fd);
lsetfilecon(MODULEROOT, SYSTEM_CON);
restore_syscon(xopen(MODULEROOT, O_RDONLY | O_CLOEXEC));
}
void restore_databincon() {
restore_magiskcon(xopen(DATABIN, O_RDONLY | O_CLOEXEC));
}
void restore_tmpcon() {
if (!selinux_enabled())
return;
if (MAGISKTMP == "/sbin")
setfilecon(MAGISKTMP.data(), ROOT_CON);
else
chmod(MAGISKTMP.data(), 0700);
auto dir = xopen_dir(MAGISKTMP.data());
int dfd = dirfd(dir.get());
for (dirent *entry; (entry = xreaddir(dir.get()));)
setfilecon_at(dfd, entry->d_name, SYSTEM_CON);
if (SDK_INT >= 26) {
string magisk = MAGISKTMP + "/magisk";
setfilecon(magisk.data(), EXEC_CON);
}
}

View File

@@ -0,0 +1,241 @@
#include <string>
#include <vector>
#include <sys/wait.h>
#include <magisk.hpp>
#include <base.hpp>
#include <selinux.hpp>
#include <daemon.hpp>
#include "core.hpp"
using namespace std;
#define BBEXEC_CMD bbpath(), "sh"
static const char *bbpath() {
static string path;
if (path.empty())
path = MAGISKTMP + "/" BBPATH "/busybox";
return path.data();
}
static void set_script_env() {
setenv("ASH_STANDALONE", "1", 1);
char new_path[4096];
sprintf(new_path, "%s:%s", getenv("PATH"), MAGISKTMP.data());
setenv("PATH", new_path, 1);
if (zygisk_enabled)
setenv("ZYGISK_ENABLED", "1", 1);
};
void exec_script(const char *script) {
exec_t exec {
.pre_exec = set_script_env,
.fork = fork_no_orphan
};
exec_command_sync(exec, BBEXEC_CMD, script);
}
static timespec pfs_timeout;
#define PFS_SETUP() \
if (pfs) { \
if (int pid = xfork()) { \
if (pid < 0) \
return; \
/* In parent process, simply wait for child to finish */ \
waitpid(pid, nullptr, 0); \
return; \
} \
timer_pid = xfork(); \
if (timer_pid == 0) { \
/* In timer process, count down */ \
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &pfs_timeout, nullptr); \
exit(0); \
} \
}
#define PFS_WAIT() \
if (pfs) { \
/* If we ran out of time, don't block */ \
if (timer_pid < 0) \
continue; \
if (int pid = waitpid(-1, nullptr, 0); pid == timer_pid) { \
LOGW("* post-fs-data scripts blocking phase timeout\n"); \
timer_pid = -1; \
} \
}
#define PFS_DONE() \
if (pfs) { \
if (timer_pid > 0) \
kill(timer_pid, SIGKILL); \
exit(0); \
}
void exec_common_scripts(const char *stage) {
LOGI("* Running %s.d scripts\n", stage);
char path[4096];
char *name = path + sprintf(path, SECURE_DIR "/%s.d", stage);
auto dir = xopen_dir(path);
if (!dir) return;
bool pfs = stage == "post-fs-data"sv;
int timer_pid = -1;
if (pfs) {
// Setup timer
clock_gettime(CLOCK_MONOTONIC, &pfs_timeout);
pfs_timeout.tv_sec += POST_FS_DATA_SCRIPT_MAX_TIME;
}
PFS_SETUP()
*(name++) = '/';
int dfd = dirfd(dir.get());
for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (entry->d_type == DT_REG) {
if (faccessat(dfd, entry->d_name, X_OK, 0) != 0)
continue;
LOGI("%s.d: exec [%s]\n", stage, entry->d_name);
strcpy(name, entry->d_name);
exec_t exec {
.pre_exec = set_script_env,
.fork = pfs ? xfork : fork_dont_care
};
exec_command(exec, BBEXEC_CMD, path);
PFS_WAIT()
}
}
PFS_DONE()
}
static bool operator>(const timespec &a, const timespec &b) {
if (a.tv_sec != b.tv_sec)
return a.tv_sec > b.tv_sec;
return a.tv_nsec > b.tv_nsec;
}
void exec_module_scripts(const char *stage, const vector<string_view> &modules) {
LOGI("* Running module %s scripts\n", stage);
if (modules.empty())
return;
bool pfs = stage == "post-fs-data"sv;
if (pfs) {
timespec now{};
clock_gettime(CLOCK_MONOTONIC, &now);
// If we had already timed out, treat it as service mode
if (now > pfs_timeout)
pfs = false;
}
int timer_pid = -1;
PFS_SETUP()
char path[4096];
for (auto &m : modules) {
const char *module = m.data();
sprintf(path, MODULEROOT "/%s/%s.sh", module, stage);
if (access(path, F_OK) == -1)
continue;
LOGI("%s: exec [%s.sh]\n", module, stage);
exec_t exec {
.pre_exec = set_script_env,
.fork = pfs ? xfork : fork_dont_care
};
exec_command(exec, BBEXEC_CMD, path);
PFS_WAIT()
}
PFS_DONE()
}
constexpr char install_script[] = R"EOF(
APK=%s
log -t Magisk "pm_install: $APK"
log -t Magisk "pm_install: $(pm install -r $APK 2>&1)"
rm -f $APK
)EOF";
void install_apk(const char *apk) {
setfilecon(apk, "u:object_r:" SEPOL_FILE_TYPE ":s0");
exec_t exec {
.fork = fork_no_orphan
};
char cmds[sizeof(install_script) + 4096];
snprintf(cmds, sizeof(cmds), install_script, apk);
exec_command_sync(exec, "/system/bin/sh", "-c", cmds);
}
constexpr char uninstall_script[] = R"EOF(
PKG=%s
log -t Magisk "pm_uninstall: $PKG"
log -t Magisk "pm_uninstall: $(pm uninstall $PKG 2>&1)"
)EOF";
void uninstall_pkg(const char *pkg) {
exec_t exec {
.fork = fork_no_orphan
};
char cmds[sizeof(uninstall_script) + 256];
snprintf(cmds, sizeof(cmds), uninstall_script, pkg);
exec_command_sync(exec, "/system/bin/sh", "-c", cmds);
}
constexpr char clear_script[] = R"EOF(
PKG=%s
USER=%d
log -t Magisk "pm_clear: $PKG (user=$USER)"
log -t Magisk "pm_clear: $(pm clear --user $USER $PKG 2>&1)"
)EOF";
void clear_pkg(const char *pkg, int user_id) {
exec_t exec {
.fork = fork_no_orphan
};
char cmds[sizeof(clear_script) + 288];
snprintf(cmds, sizeof(cmds), clear_script, pkg, user_id);
exec_command_sync(exec, "/system/bin/sh", "-c", cmds);
}
[[noreturn]] __printflike(2, 3)
static void abort(FILE *fp, const char *fmt, ...) {
va_list valist;
va_start(valist, fmt);
vfprintf(fp, fmt, valist);
fprintf(fp, "\n\n");
va_end(valist);
exit(1);
}
constexpr char install_module_script[] = R"EOF(
exec $(magisk --path)/.magisk/busybox/busybox sh -c '
. /data/adb/magisk/util_functions.sh
install_module
exit 0'
)EOF";
void install_module(const char *file) {
if (getuid() != 0)
abort(stderr, "Run this command with root");
if (access(DATABIN, F_OK) ||
access(DATABIN "/busybox", X_OK) ||
access(DATABIN "/util_functions.sh", F_OK))
abort(stderr, "Incomplete Magisk install");
if (access(file, F_OK))
abort(stderr, "'%s' does not exist", file);
char *zip = realpath(file, nullptr);
setenv("OUTFD", "1", 1);
setenv("ZIPFILE", zip, 1);
setenv("ASH_STANDALONE", "1", 1);
free(zip);
int fd = xopen("/dev/null", O_RDONLY);
xdup2(fd, STDERR_FILENO);
close(fd);
const char *argv[] = { "/system/bin/sh", "-c", install_module_script, nullptr };
execve(argv[0], (char **) argv, environ);
abort(stdout, "Failed to execute BusyBox shell");
}

180
native/src/core/socket.cpp Normal file
View File

@@ -0,0 +1,180 @@
#include <fcntl.h>
#include <endian.h>
#include <socket.hpp>
#include <base.hpp>
using namespace std;
static size_t socket_len(sockaddr_un *sun) {
if (sun->sun_path[0])
return sizeof(sa_family_t) + strlen(sun->sun_path) + 1;
else
return sizeof(sa_family_t) + strlen(sun->sun_path + 1) + 1;
}
socklen_t setup_sockaddr(sockaddr_un *sun, const char *name) {
memset(sun, 0, sizeof(*sun));
sun->sun_family = AF_UNIX;
strcpy(sun->sun_path + 1, name);
return socket_len(sun);
}
bool get_client_cred(int fd, sock_cred *cred) {
socklen_t len = sizeof(ucred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len) != 0)
return false;
char buf[4096];
len = sizeof(buf);
if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &len) != 0)
len = 0;
buf[len] = '\0';
cred->context = buf;
return true;
}
static int send_fds(int sockfd, void *cmsgbuf, size_t bufsz, const int *fds, int cnt) {
iovec iov = {
.iov_base = &cnt,
.iov_len = sizeof(cnt),
};
msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
};
if (cnt) {
msg.msg_control = cmsgbuf;
msg.msg_controllen = bufsz;
cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * cnt);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * cnt);
}
return xsendmsg(sockfd, &msg, 0);
}
int send_fds(int sockfd, const int *fds, int cnt) {
if (cnt == 0) {
return send_fds(sockfd, nullptr, 0, nullptr, 0);
}
vector<char> cmsgbuf;
cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt));
return send_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), fds, cnt);
}
int send_fd(int sockfd, int fd) {
if (fd < 0) {
return send_fds(sockfd, nullptr, 0, nullptr, 0);
}
char cmsgbuf[CMSG_SPACE(sizeof(int))];
return send_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), &fd, 1);
}
static void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) {
iovec iov = {
.iov_base = &cnt,
.iov_len = sizeof(cnt),
};
msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsgbuf,
.msg_controllen = bufsz
};
xrecvmsg(sockfd, &msg, MSG_WAITALL);
cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
if (msg.msg_controllen != bufsz ||
cmsg == nullptr ||
cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) ||
cmsg->cmsg_level != SOL_SOCKET ||
cmsg->cmsg_type != SCM_RIGHTS) {
return nullptr;
}
return CMSG_DATA(cmsg);
}
vector<int> recv_fds(int sockfd) {
vector<int> results;
// Peek fd count to allocate proper buffer
int cnt;
recv(sockfd, &cnt, sizeof(cnt), MSG_PEEK);
if (cnt == 0)
return results;
vector<char> cmsgbuf;
cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt));
void *data = recv_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), cnt);
if (data == nullptr)
return results;
results.resize(cnt);
memcpy(results.data(), data, sizeof(int) * cnt);
return results;
}
int recv_fd(int sockfd) {
char cmsgbuf[CMSG_SPACE(sizeof(int))];
void *data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1);
if (data == nullptr)
return -1;
int result;
memcpy(&result, data, sizeof(int));
return result;
}
int read_int(int fd) {
int val;
if (xxread(fd, &val, sizeof(val)) != sizeof(val))
return -1;
return val;
}
int read_int_be(int fd) {
uint32_t val;
if (xxread(fd, &val, sizeof(val)) != sizeof(val))
return -1;
return ntohl(val);
}
void write_int(int fd, int val) {
if (fd < 0) return;
xwrite(fd, &val, sizeof(val));
}
void write_int_be(int fd, int val) {
uint32_t nl = htonl(val);
xwrite(fd, &nl, sizeof(nl));
}
bool read_string(int fd, std::string &str) {
int len = read_int(fd);
str.clear();
if (len < 0)
return false;
str.resize(len);
return xxread(fd, str.data(), len) == len;
}
string read_string(int fd) {
string str;
read_string(fd, str);
return str;
}
void write_string(int fd, string_view str) {
if (fd < 0) return;
write_int(fd, str.size());
xwrite(fd, str.data(), str.size());
}

View File

@@ -0,0 +1,97 @@
// Cached thread pool implementation
#include <base.hpp>
#include <daemon.hpp>
using namespace std;
#define THREAD_IDLE_MAX_SEC 60
#define CORE_POOL_SIZE 3
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t send_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP;
static pthread_cond_t recv_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP;
// The following variables should be guarded by lock
static int idle_threads = 0;
static int total_threads = 0;
static function<void()> pending_task;
static void operator+=(timespec &a, const timespec &b) {
a.tv_sec += b.tv_sec;
a.tv_nsec += b.tv_nsec;
if (a.tv_nsec >= 1000000000L) {
a.tv_sec++;
a.tv_nsec -= 1000000000L;
}
}
static void reset_pool() {
clear_poll();
pthread_mutex_unlock(&lock);
pthread_mutex_destroy(&lock);
pthread_mutex_init(&lock, nullptr);
pthread_cond_destroy(&send_task);
send_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP;
pthread_cond_destroy(&recv_task);
recv_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP;
idle_threads = 0;
total_threads = 0;
pending_task = nullptr;
}
static void *thread_pool_loop(void * const is_core_pool) {
pthread_atfork(nullptr, nullptr, &reset_pool);
// Block all signals
sigset_t mask;
sigfillset(&mask);
for (;;) {
// Restore sigmask
pthread_sigmask(SIG_SETMASK, &mask, nullptr);
function<void()> local_task;
{
mutex_guard g(lock);
++idle_threads;
if (!pending_task) {
if (is_core_pool) {
pthread_cond_wait(&send_task, &lock);
} else {
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
ts += { THREAD_IDLE_MAX_SEC, 0 };
if (pthread_cond_timedwait(&send_task, &lock, &ts) == ETIMEDOUT) {
// Terminate thread after max idle time
--idle_threads;
--total_threads;
return nullptr;
}
}
}
if (pending_task) {
local_task.swap(pending_task);
pthread_cond_signal(&recv_task);
}
--idle_threads;
}
if (local_task)
local_task();
if (getpid() == gettid())
exit(0);
}
}
void exec_task(function<void()> &&task) {
mutex_guard g(lock);
pending_task.swap(task);
if (idle_threads == 0) {
++total_threads;
long is_core_pool = total_threads <= CORE_POOL_SIZE;
new_daemon_thread(thread_pool_loop, (void *) is_core_pool);
} else {
pthread_cond_signal(&send_task);
}
pthread_cond_wait(&recv_task, &lock);
}