mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-13 13:51:49 +00:00
Restructure the native module
Consolidate all code into the src folder
This commit is contained in:
12
native/src/core/Cargo.toml
Normal file
12
native/src/core/Cargo.toml
Normal 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"
|
||||
10
native/src/core/applet_stub.cpp
Normal file
10
native/src/core/applet_stub.cpp
Normal 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);
|
||||
}
|
||||
52
native/src/core/applets.cpp
Normal file
52
native/src/core/applets.cpp
Normal 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);
|
||||
}
|
||||
382
native/src/core/bootstages.cpp
Normal file
382
native/src/core/bootstages.cpp
Normal 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
208
native/src/core/cert.cpp
Normal 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, ¢ral_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
39
native/src/core/core.hpp
Normal 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
461
native/src/core/daemon.cpp
Normal 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
397
native/src/core/db.cpp
Normal 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
16
native/src/core/lib.rs
Normal 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
159
native/src/core/logging.cpp
Normal 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
112
native/src/core/logging.rs
Normal 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
135
native/src/core/magisk.cpp
Normal 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
808
native/src/core/module.cpp
Normal 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
279
native/src/core/package.cpp
Normal 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;
|
||||
}
|
||||
101
native/src/core/restorecon.cpp
Normal file
101
native/src/core/restorecon.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
241
native/src/core/scripting.cpp
Normal file
241
native/src/core/scripting.cpp
Normal 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
180
native/src/core/socket.cpp
Normal 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());
|
||||
}
|
||||
97
native/src/core/thread.cpp
Normal file
97
native/src/core/thread.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user