mirror of
https://github.com/topjohnwu/Magisk.git
synced 2026-01-01 23:40:33 -08:00
Restructure the native module
Consolidate all code into the src folder
This commit is contained in:
12
native/src/init/Cargo.toml
Normal file
12
native/src/init/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "magiskinit"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
base = { path = "../base" }
|
||||
cxx = "1.0.69"
|
||||
273
native/src/init/getinfo.cpp
Normal file
273
native/src/init/getinfo.cpp
Normal file
@@ -0,0 +1,273 @@
|
||||
#include <sys/sysmacros.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/input.h>
|
||||
#include <fcntl.h>
|
||||
#include <vector>
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
#include "init.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
vector<string> mount_list;
|
||||
|
||||
template<char... cs> using chars = integer_sequence<char, cs...>;
|
||||
|
||||
// If quoted, parsing ends when we find char in [breaks]
|
||||
// If not quoted, parsing ends when we find char in [breaks] + [escapes]
|
||||
template<char... escapes, char... breaks>
|
||||
static string extract_quoted_str_until(chars<escapes...>, chars<breaks...>,
|
||||
string_view str, size_t &pos, bool "ed) {
|
||||
string result;
|
||||
char match_array[] = {escapes..., breaks..., '"'};
|
||||
string_view match(match_array, std::size(match_array));
|
||||
for (size_t cur = pos;; ++cur) {
|
||||
cur = str.find_first_of(match, cur);
|
||||
if (cur == string_view::npos ||
|
||||
((str[cur] == breaks) || ...) ||
|
||||
(!quoted && ((str[cur] == escapes) || ...))) {
|
||||
result.append(str.substr(pos, cur - pos));
|
||||
pos = cur;
|
||||
return result;
|
||||
}
|
||||
if (str[cur] == '"') {
|
||||
quoted = !quoted;
|
||||
result.append(str.substr(pos, cur - pos));
|
||||
pos = cur + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse string into key value pairs.
|
||||
// The string format: [delim][key][padding]=[padding][value][delim]
|
||||
template<char delim, char... padding>
|
||||
static kv_pairs parse_impl(chars<padding...>, string_view str) {
|
||||
kv_pairs kv;
|
||||
char skip_array[] = {'=', padding...};
|
||||
string_view skip(skip_array, std::size(skip_array));
|
||||
bool quoted = false;
|
||||
for (size_t pos = 0u; pos < str.size(); pos = str.find_first_not_of(delim, pos)) {
|
||||
auto key = extract_quoted_str_until(
|
||||
chars<padding..., delim>{}, chars<'='>{}, str, pos, quoted);
|
||||
pos = str.find_first_not_of(skip, pos);
|
||||
if (pos == string_view::npos || str[pos] == delim) {
|
||||
kv.emplace_back(key, "");
|
||||
continue;
|
||||
}
|
||||
auto value = extract_quoted_str_until(chars<delim>{}, chars<>{}, str, pos, quoted);
|
||||
kv.emplace_back(key, value);
|
||||
}
|
||||
return kv;
|
||||
}
|
||||
|
||||
static kv_pairs parse_cmdline(string_view str) {
|
||||
return parse_impl<' '>(chars<>{}, str);
|
||||
}
|
||||
static kv_pairs parse_bootconfig(string_view str) {
|
||||
return parse_impl<'\n'>(chars<' '>{}, str);
|
||||
}
|
||||
|
||||
#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))
|
||||
|
||||
static bool check_key_combo() {
|
||||
LOGD("Running in recovery mode, waiting for key...\n");
|
||||
uint8_t bitmask[(KEY_MAX + 1) / 8];
|
||||
vector<int> events;
|
||||
constexpr const char *name = "/event";
|
||||
|
||||
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_VOLUMEUP, bitmask))
|
||||
events.push_back(fd);
|
||||
else
|
||||
close(fd);
|
||||
}
|
||||
if (events.empty())
|
||||
return false;
|
||||
|
||||
run_finally fin([&] { for_each(events.begin(), events.end(), close); });
|
||||
|
||||
// Return true if volume up key is held for more than 3 seconds
|
||||
int count = 0;
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
for (const int &fd : events) {
|
||||
memset(bitmask, 0, sizeof(bitmask));
|
||||
ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);
|
||||
if (test_bit(KEY_VOLUMEUP, bitmask)) {
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (count >= 300) {
|
||||
LOGD("KEY_VOLUMEUP detected: disable system-as-root\n");
|
||||
return true;
|
||||
}
|
||||
// Check every 10ms
|
||||
usleep(10000);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static FILE *kmsg;
|
||||
extern "C" void klog_write(const char *msg, int len) {
|
||||
fprintf(kmsg, "%.*s", len, msg);
|
||||
}
|
||||
|
||||
static int klog_with_rs(LogLevel level, const char *fmt, va_list ap) {
|
||||
char buf[4096];
|
||||
strlcpy(buf, "magiskinit: ", sizeof(buf));
|
||||
int len = vsnprintf(buf + 12, sizeof(buf) - 12, fmt, ap) + 12;
|
||||
log_with_rs(level, rust::Str(buf, len));
|
||||
return len;
|
||||
}
|
||||
|
||||
void setup_klog() {
|
||||
// Shut down first 3 fds
|
||||
int fd;
|
||||
if (access("/dev/null", W_OK) == 0) {
|
||||
fd = xopen("/dev/null", O_RDWR | O_CLOEXEC);
|
||||
} else {
|
||||
mknod("/null", S_IFCHR | 0666, makedev(1, 3));
|
||||
fd = xopen("/null", O_RDWR | O_CLOEXEC);
|
||||
unlink("/null");
|
||||
}
|
||||
xdup3(fd, STDIN_FILENO, O_CLOEXEC);
|
||||
xdup3(fd, STDOUT_FILENO, O_CLOEXEC);
|
||||
xdup3(fd, STDERR_FILENO, O_CLOEXEC);
|
||||
if (fd > STDERR_FILENO)
|
||||
close(fd);
|
||||
|
||||
if (access("/dev/kmsg", W_OK) == 0) {
|
||||
fd = xopen("/dev/kmsg", O_WRONLY | O_CLOEXEC);
|
||||
} else {
|
||||
mknod("/kmsg", S_IFCHR | 0666, makedev(1, 11));
|
||||
fd = xopen("/kmsg", O_WRONLY | O_CLOEXEC);
|
||||
unlink("/kmsg");
|
||||
}
|
||||
|
||||
kmsg = fdopen(fd, "w");
|
||||
setbuf(kmsg, nullptr);
|
||||
rust::setup_klog();
|
||||
cpp_logger = klog_with_rs;
|
||||
|
||||
// Disable kmsg rate limiting
|
||||
if (FILE *rate = fopen("/proc/sys/kernel/printk_devkmsg", "w")) {
|
||||
fprintf(rate, "on\n");
|
||||
fclose(rate);
|
||||
}
|
||||
}
|
||||
|
||||
void BootConfig::set(const kv_pairs &kv) {
|
||||
for (const auto &[key, value] : kv) {
|
||||
if (key == "androidboot.slot_suffix") {
|
||||
// Many Amlogic devices are A-only but have slot_suffix...
|
||||
if (value == "normal") {
|
||||
LOGW("Skip invalid androidboot.slot_suffix=[normal]\n");
|
||||
continue;
|
||||
}
|
||||
strlcpy(slot, value.data(), sizeof(slot));
|
||||
} else if (key == "androidboot.slot") {
|
||||
slot[0] = '_';
|
||||
strlcpy(slot + 1, value.data(), sizeof(slot) - 1);
|
||||
} else if (key == "skip_initramfs") {
|
||||
skip_initramfs = true;
|
||||
} else if (key == "androidboot.force_normal_boot") {
|
||||
force_normal_boot = !value.empty() && value[0] == '1';
|
||||
} else if (key == "rootwait") {
|
||||
rootwait = true;
|
||||
} else if (key == "androidboot.android_dt_dir") {
|
||||
strlcpy(dt_dir, value.data(), sizeof(dt_dir));
|
||||
} else if (key == "androidboot.hardware") {
|
||||
strlcpy(hardware, value.data(), sizeof(hardware));
|
||||
} else if (key == "androidboot.hardware.platform") {
|
||||
strlcpy(hardware_plat, value.data(), sizeof(hardware_plat));
|
||||
} else if (key == "androidboot.fstab_suffix") {
|
||||
strlcpy(fstab_suffix, value.data(), sizeof(fstab_suffix));
|
||||
} else if (key == "qemu") {
|
||||
emulator = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BootConfig::print() {
|
||||
LOGD("skip_initramfs=[%d]\n", skip_initramfs);
|
||||
LOGD("force_normal_boot=[%d]\n", force_normal_boot);
|
||||
LOGD("rootwait=[%d]\n", rootwait);
|
||||
LOGD("slot=[%s]\n", slot);
|
||||
LOGD("dt_dir=[%s]\n", dt_dir);
|
||||
LOGD("fstab_suffix=[%s]\n", fstab_suffix);
|
||||
LOGD("hardware=[%s]\n", hardware);
|
||||
LOGD("hardware.platform=[%s]\n", hardware_plat);
|
||||
LOGD("emulator=[%d]\n", emulator);
|
||||
}
|
||||
|
||||
#define read_dt(name, key) \
|
||||
snprintf(file_name, sizeof(file_name), "%s/" name, config->dt_dir); \
|
||||
if (access(file_name, R_OK) == 0) { \
|
||||
string data = full_read(file_name); \
|
||||
if (!data.empty()) { \
|
||||
data.pop_back(); \
|
||||
strlcpy(config->key, data.data(), sizeof(config->key)); \
|
||||
} \
|
||||
}
|
||||
|
||||
void load_kernel_info(BootConfig *config) {
|
||||
// Get kernel data using procfs and sysfs
|
||||
xmkdir("/proc", 0755);
|
||||
xmount("proc", "/proc", "proc", 0, nullptr);
|
||||
xmkdir("/sys", 0755);
|
||||
xmount("sysfs", "/sys", "sysfs", 0, nullptr);
|
||||
|
||||
mount_list.emplace_back("/proc");
|
||||
mount_list.emplace_back("/sys");
|
||||
|
||||
// Log to kernel
|
||||
setup_klog();
|
||||
|
||||
config->set(parse_cmdline(full_read("/proc/cmdline")));
|
||||
config->set(parse_bootconfig(full_read("/proc/bootconfig")));
|
||||
|
||||
parse_prop_file("/.backup/.magisk", [=](auto key, auto value) -> bool {
|
||||
if (key == "RECOVERYMODE" && value == "true") {
|
||||
config->skip_initramfs = config->emulator || !check_key_combo();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (config->dt_dir[0] == '\0')
|
||||
strlcpy(config->dt_dir, DEFAULT_DT_DIR, sizeof(config->dt_dir));
|
||||
|
||||
char file_name[128];
|
||||
read_dt("fstab_suffix", fstab_suffix)
|
||||
read_dt("hardware", hardware)
|
||||
read_dt("hardware.platform", hardware_plat)
|
||||
|
||||
LOGD("Device config:\n");
|
||||
config->print();
|
||||
}
|
||||
|
||||
bool check_two_stage() {
|
||||
if (access("/apex", F_OK) == 0)
|
||||
return true;
|
||||
if (access("/system/bin/init", F_OK) == 0)
|
||||
return true;
|
||||
// If we still have no indication, parse the original init and see what's up
|
||||
auto init = mmap_data(backup_init());
|
||||
return init.contains("selinux_setup");
|
||||
}
|
||||
|
||||
const char *backup_init() {
|
||||
if (access("/.backup/init.real", F_OK) == 0)
|
||||
return "/.backup/init.real";
|
||||
return "/.backup/init";
|
||||
}
|
||||
135
native/src/init/init.cpp
Normal file
135
native/src/init/init.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <libgen.h>
|
||||
#include <vector>
|
||||
|
||||
#include <xz.h>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <binaries.h>
|
||||
|
||||
#if defined(__arm__)
|
||||
#include <armeabi-v7a_binaries.h>
|
||||
#elif defined(__aarch64__)
|
||||
#include <arm64-v8a_binaries.h>
|
||||
#elif defined(__i386__)
|
||||
#include <x86_binaries.h>
|
||||
#elif defined(__x86_64__)
|
||||
#include <x86_64_binaries.h>
|
||||
#else
|
||||
#error Unsupported ABI
|
||||
#endif
|
||||
|
||||
#include "init.hpp"
|
||||
|
||||
#include <init-rs.cpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool unxz(int fd, const uint8_t *buf, size_t size) {
|
||||
uint8_t out[8192];
|
||||
xz_crc32_init();
|
||||
struct xz_dec *dec = xz_dec_init(XZ_DYNALLOC, 1 << 26);
|
||||
struct xz_buf b = {
|
||||
.in = buf,
|
||||
.in_pos = 0,
|
||||
.in_size = size,
|
||||
.out = out,
|
||||
.out_pos = 0,
|
||||
.out_size = sizeof(out)
|
||||
};
|
||||
enum xz_ret ret;
|
||||
do {
|
||||
ret = xz_dec_run(dec, &b);
|
||||
if (ret != XZ_OK && ret != XZ_STREAM_END)
|
||||
return false;
|
||||
write(fd, out, b.out_pos);
|
||||
b.out_pos = 0;
|
||||
} while (b.in_pos != size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int dump_bin(const uint8_t *buf, size_t sz, const char *path, mode_t mode) {
|
||||
int fd = xopen(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
|
||||
if (fd < 0)
|
||||
return 1;
|
||||
if (!unxz(fd, buf, sz))
|
||||
return 1;
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void restore_ramdisk_init() {
|
||||
unlink("/init");
|
||||
|
||||
const char *orig_init = backup_init();
|
||||
if (access(orig_init, F_OK) == 0) {
|
||||
xrename(orig_init, "/init");
|
||||
} else {
|
||||
// If the backup init is missing, this means that the boot ramdisk
|
||||
// was created from scratch, and the real init is in a separate CPIO,
|
||||
// which is guaranteed to be placed at /system/bin/init.
|
||||
xsymlink(INIT_PATH, "/init");
|
||||
}
|
||||
}
|
||||
|
||||
int dump_manager(const char *path, mode_t mode) {
|
||||
return dump_bin(manager_xz, sizeof(manager_xz), path, mode);
|
||||
}
|
||||
|
||||
int dump_preload(const char *path, mode_t mode) {
|
||||
return dump_bin(preload_xz, sizeof(preload_xz), path, mode);
|
||||
}
|
||||
|
||||
class RecoveryInit : public BaseInit {
|
||||
public:
|
||||
using BaseInit::BaseInit;
|
||||
void start() override {
|
||||
LOGD("Ramdisk is recovery, abort\n");
|
||||
restore_ramdisk_init();
|
||||
rm_rf("/.backup");
|
||||
exec_init();
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
umask(0);
|
||||
|
||||
auto name = basename(argv[0]);
|
||||
if (name == "magisk"sv)
|
||||
return magisk_proxy_main(argc, argv);
|
||||
|
||||
if (argc > 1 && argv[1] == "-x"sv) {
|
||||
if (argc > 2 && argv[2] == "manager"sv)
|
||||
return dump_manager(argv[3], 0644);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (getpid() != 1)
|
||||
return 1;
|
||||
|
||||
BaseInit *init;
|
||||
BootConfig config{};
|
||||
|
||||
if (argc > 1 && argv[1] == "selinux_setup"sv) {
|
||||
init = new SecondStageInit(argv);
|
||||
} else {
|
||||
// This will also mount /sys and /proc
|
||||
load_kernel_info(&config);
|
||||
|
||||
if (config.skip_initramfs)
|
||||
init = new LegacySARInit(argv, &config);
|
||||
else if (config.force_normal_boot)
|
||||
init = new FirstStageInit(argv, &config);
|
||||
else if (access("/sbin/recovery", F_OK) == 0 || access("/system/bin/recovery", F_OK) == 0)
|
||||
init = new RecoveryInit(argv, &config);
|
||||
else if (check_two_stage())
|
||||
init = new FirstStageInit(argv, &config);
|
||||
else
|
||||
init = new RootFSInit(argv, &config);
|
||||
}
|
||||
|
||||
// Run the main routine
|
||||
init->start();
|
||||
exit(1);
|
||||
}
|
||||
161
native/src/init/init.hpp
Normal file
161
native/src/init/init.hpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include <base.hpp>
|
||||
#include <init-rs.hpp>
|
||||
|
||||
using kv_pairs = std::vector<std::pair<std::string, std::string>>;
|
||||
|
||||
// For API 28 AVD, it uses legacy SAR setup that requires
|
||||
// special hacks in magiskinit to work properly. We do not
|
||||
// necessarily want this enabled in production builds.
|
||||
#define ENABLE_AVD_HACK 0
|
||||
|
||||
struct BootConfig {
|
||||
bool skip_initramfs;
|
||||
bool force_normal_boot;
|
||||
bool rootwait;
|
||||
bool emulator;
|
||||
char slot[3];
|
||||
char dt_dir[64];
|
||||
char fstab_suffix[32];
|
||||
char hardware[32];
|
||||
char hardware_plat[32];
|
||||
|
||||
void set(const kv_pairs &);
|
||||
void print();
|
||||
};
|
||||
|
||||
#define DEFAULT_DT_DIR "/proc/device-tree/firmware/android"
|
||||
#define INIT_PATH "/system/bin/init"
|
||||
|
||||
extern std::vector<std::string> mount_list;
|
||||
|
||||
int magisk_proxy_main(int argc, char *argv[]);
|
||||
bool unxz(int fd, const uint8_t *buf, size_t size);
|
||||
void load_kernel_info(BootConfig *config);
|
||||
bool check_two_stage();
|
||||
void setup_klog();
|
||||
const char *backup_init();
|
||||
void restore_ramdisk_init();
|
||||
int dump_manager(const char *path, mode_t mode);
|
||||
int dump_preload(const char *path, mode_t mode);
|
||||
|
||||
/***************
|
||||
* Base classes
|
||||
***************/
|
||||
|
||||
class BaseInit {
|
||||
protected:
|
||||
BootConfig *config = nullptr;
|
||||
char **argv = nullptr;
|
||||
|
||||
[[noreturn]] void exec_init();
|
||||
public:
|
||||
BaseInit(char *argv[], BootConfig *config = nullptr) : config(config), argv(argv) {}
|
||||
virtual ~BaseInit() = default;
|
||||
virtual void start() = 0;
|
||||
};
|
||||
|
||||
class MagiskInit : public BaseInit {
|
||||
private:
|
||||
void mount_rules_dir();
|
||||
protected:
|
||||
mmap_data self;
|
||||
mmap_data magisk_cfg;
|
||||
std::string custom_rules_dir;
|
||||
|
||||
#if ENABLE_AVD_HACK
|
||||
// When this boolean is set, this means we are currently
|
||||
// running magiskinit on legacy SAR AVD emulator
|
||||
bool avd_hack = false;
|
||||
#endif
|
||||
|
||||
void patch_sepolicy(const char *in, const char *out);
|
||||
bool hijack_sepolicy();
|
||||
void setup_tmp(const char *path);
|
||||
void patch_rw_root();
|
||||
public:
|
||||
using BaseInit::BaseInit;
|
||||
};
|
||||
|
||||
class SARBase : public MagiskInit {
|
||||
protected:
|
||||
std::vector<raw_file> overlays;
|
||||
|
||||
void backup_files();
|
||||
void patch_ro_root();
|
||||
public:
|
||||
using MagiskInit::MagiskInit;
|
||||
};
|
||||
|
||||
/***************
|
||||
* 2 Stage Init
|
||||
***************/
|
||||
|
||||
class FirstStageInit : public BaseInit {
|
||||
private:
|
||||
void prepare();
|
||||
public:
|
||||
FirstStageInit(char *argv[], BootConfig *config) : BaseInit(argv, config) {
|
||||
LOGD("%s\n", __FUNCTION__);
|
||||
};
|
||||
void start() override {
|
||||
prepare();
|
||||
exec_init();
|
||||
}
|
||||
};
|
||||
|
||||
class SecondStageInit : public SARBase {
|
||||
private:
|
||||
bool prepare();
|
||||
public:
|
||||
SecondStageInit(char *argv[]) : SARBase(argv) {
|
||||
setup_klog();
|
||||
LOGD("%s\n", __FUNCTION__);
|
||||
};
|
||||
|
||||
void start() override {
|
||||
if (prepare())
|
||||
patch_rw_root();
|
||||
else
|
||||
patch_ro_root();
|
||||
exec_init();
|
||||
}
|
||||
};
|
||||
|
||||
/*************
|
||||
* Legacy SAR
|
||||
*************/
|
||||
|
||||
class LegacySARInit : public SARBase {
|
||||
private:
|
||||
bool mount_system_root();
|
||||
void first_stage_prep();
|
||||
public:
|
||||
LegacySARInit(char *argv[], BootConfig *config) : SARBase(argv, config) {
|
||||
LOGD("%s\n", __FUNCTION__);
|
||||
};
|
||||
void start() override {
|
||||
if (mount_system_root())
|
||||
first_stage_prep();
|
||||
else
|
||||
patch_ro_root();
|
||||
exec_init();
|
||||
}
|
||||
};
|
||||
|
||||
/************
|
||||
* Initramfs
|
||||
************/
|
||||
|
||||
class RootFSInit : public MagiskInit {
|
||||
private:
|
||||
void prepare();
|
||||
public:
|
||||
RootFSInit(char *argv[], BootConfig *config) : MagiskInit(argv, config) {
|
||||
LOGD("%s\n", __FUNCTION__);
|
||||
}
|
||||
void start() override {
|
||||
prepare();
|
||||
patch_rw_root();
|
||||
exec_init();
|
||||
}
|
||||
};
|
||||
11
native/src/init/lib.rs
Normal file
11
native/src/init/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
pub use base;
|
||||
pub use logging::*;
|
||||
|
||||
mod logging;
|
||||
|
||||
#[cxx::bridge(namespace = "rust")]
|
||||
pub mod ffi2 {
|
||||
extern "Rust" {
|
||||
fn setup_klog();
|
||||
}
|
||||
}
|
||||
37
native/src/init/logging.rs
Normal file
37
native/src/init/logging.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use base::ffi::LogLevel;
|
||||
use base::*;
|
||||
use std::fmt::Arguments;
|
||||
|
||||
extern "C" {
|
||||
fn klog_write(msg: *const u8, len: i32);
|
||||
}
|
||||
|
||||
pub fn setup_klog() {
|
||||
const PREFIX: &[u8; 12] = b"magiskinit: ";
|
||||
const PFX_LEN: usize = PREFIX.len();
|
||||
|
||||
fn klog_fmt(_: LogLevel, args: Arguments) {
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
buf[..PFX_LEN].copy_from_slice(PREFIX);
|
||||
let len = fmt_to_buf(&mut buf[PFX_LEN..], args) + PFX_LEN;
|
||||
unsafe {
|
||||
klog_write(buf.as_ptr(), len as i32);
|
||||
}
|
||||
}
|
||||
|
||||
fn klog_write_impl(_: LogLevel, msg: &[u8]) {
|
||||
unsafe {
|
||||
klog_write(msg.as_ptr(), msg.len() as i32);
|
||||
}
|
||||
}
|
||||
|
||||
let logger = Logger {
|
||||
fmt: klog_fmt,
|
||||
write: klog_write_impl,
|
||||
flags: 0,
|
||||
};
|
||||
exit_on_error(false);
|
||||
unsafe {
|
||||
LOGGER = logger;
|
||||
}
|
||||
}
|
||||
42
native/src/init/magiskrc.inc
Normal file
42
native/src/init/magiskrc.inc
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <magisk.hpp>
|
||||
#include <selinux.hpp>
|
||||
|
||||
#define quote(s) #s
|
||||
#define str(s) quote(s)
|
||||
|
||||
constexpr char MAGISK_RC[] =
|
||||
"\n"
|
||||
|
||||
"on post-fs-data\n"
|
||||
" start logd\n"
|
||||
" rm " UNBLOCKFILE "\n"
|
||||
" start %2$s\n"
|
||||
" wait " UNBLOCKFILE " " str(POST_FS_DATA_WAIT_TIME) "\n"
|
||||
" rm " UNBLOCKFILE "\n"
|
||||
"\n"
|
||||
|
||||
"service %2$s %1$s/magisk --post-fs-data\n"
|
||||
" user root\n"
|
||||
" seclabel u:r:" SEPOL_PROC_DOMAIN ":s0\n"
|
||||
" oneshot\n"
|
||||
"\n"
|
||||
|
||||
"service %3$s %1$s/magisk --service\n"
|
||||
" class late_start\n"
|
||||
" user root\n"
|
||||
" seclabel u:r:" SEPOL_PROC_DOMAIN ":s0\n"
|
||||
" oneshot\n"
|
||||
"\n"
|
||||
|
||||
"on property:sys.boot_completed=1\n"
|
||||
" exec %1$s/magisk --boot-complete\n"
|
||||
"\n"
|
||||
|
||||
"on property:init.svc.zygote=restarting\n"
|
||||
" exec %1$s/magisk --zygote-restart\n"
|
||||
"\n"
|
||||
|
||||
"on property:init.svc.zygote=stopped\n"
|
||||
" exec %1$s/magisk --zygote-restart\n"
|
||||
"\n"
|
||||
;
|
||||
321
native/src/init/mount.cpp
Normal file
321
native/src/init/mount.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
#include <sys/mount.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <magisk.hpp>
|
||||
|
||||
#include "init.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct devinfo {
|
||||
int major;
|
||||
int minor;
|
||||
char devname[32];
|
||||
char partname[32];
|
||||
char dmname[32];
|
||||
};
|
||||
|
||||
static vector<devinfo> dev_list;
|
||||
|
||||
static void parse_device(devinfo *dev, const char *uevent) {
|
||||
dev->partname[0] = '\0';
|
||||
parse_prop_file(uevent, [=](string_view key, string_view value) -> bool {
|
||||
if (key == "MAJOR")
|
||||
dev->major = parse_int(value.data());
|
||||
else if (key == "MINOR")
|
||||
dev->minor = parse_int(value.data());
|
||||
else if (key == "DEVNAME")
|
||||
strcpy(dev->devname, value.data());
|
||||
else if (key == "PARTNAME")
|
||||
strcpy(dev->partname, value.data());
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void collect_devices() {
|
||||
char path[128];
|
||||
devinfo dev{};
|
||||
if (auto dir = xopen_dir("/sys/dev/block"); dir) {
|
||||
for (dirent *entry; (entry = readdir(dir.get()));) {
|
||||
if (entry->d_name == "."sv || entry->d_name == ".."sv)
|
||||
continue;
|
||||
sprintf(path, "/sys/dev/block/%s/uevent", entry->d_name);
|
||||
parse_device(&dev, path);
|
||||
sprintf(path, "/sys/dev/block/%s/dm/name", entry->d_name);
|
||||
if (access(path, F_OK) == 0) {
|
||||
auto name = rtrim(full_read(path));
|
||||
strcpy(dev.dmname, name.data());
|
||||
}
|
||||
dev_list.push_back(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct {
|
||||
char partname[32];
|
||||
char block_dev[64];
|
||||
} blk_info;
|
||||
|
||||
static int64_t setup_block() {
|
||||
if (dev_list.empty())
|
||||
collect_devices();
|
||||
|
||||
for (int tries = 0; tries < 3; ++tries) {
|
||||
for (auto &dev : dev_list) {
|
||||
if (strcasecmp(dev.partname, blk_info.partname) == 0)
|
||||
LOGD("Setup %s: [%s] (%d, %d)\n", dev.partname, dev.devname, dev.major, dev.minor);
|
||||
else if (strcasecmp(dev.dmname, blk_info.partname) == 0)
|
||||
LOGD("Setup %s: [%s] (%d, %d)\n", dev.dmname, dev.devname, dev.major, dev.minor);
|
||||
else
|
||||
continue;
|
||||
|
||||
dev_t rdev = makedev(dev.major, dev.minor);
|
||||
xmknod(blk_info.block_dev, S_IFBLK | 0600, rdev);
|
||||
return rdev;
|
||||
}
|
||||
// Wait 10ms and try again
|
||||
usleep(10000);
|
||||
dev_list.clear();
|
||||
collect_devices();
|
||||
}
|
||||
|
||||
// The requested partname does not exist
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void switch_root(const string &path) {
|
||||
LOGD("Switch root to %s\n", path.data());
|
||||
int root = xopen("/", O_RDONLY);
|
||||
vector<string> mounts;
|
||||
parse_mnt("/proc/mounts", [&](mntent *me) {
|
||||
// Skip root and self
|
||||
if (me->mnt_dir == "/"sv || me->mnt_dir == path)
|
||||
return true;
|
||||
// Do not include subtrees
|
||||
for (const auto &m : mounts) {
|
||||
if (strncmp(me->mnt_dir, m.data(), m.length()) == 0 && me->mnt_dir[m.length()] == '/')
|
||||
return true;
|
||||
}
|
||||
mounts.emplace_back(me->mnt_dir);
|
||||
return true;
|
||||
});
|
||||
for (auto &dir : mounts) {
|
||||
auto new_path = path + dir;
|
||||
xmkdir(new_path.data(), 0755);
|
||||
xmount(dir.data(), new_path.data(), nullptr, MS_MOVE, nullptr);
|
||||
}
|
||||
chdir(path.data());
|
||||
xmount(path.data(), "/", nullptr, MS_MOVE, nullptr);
|
||||
chroot(".");
|
||||
|
||||
LOGD("Cleaning rootfs\n");
|
||||
frm_rf(root);
|
||||
}
|
||||
|
||||
void MagiskInit::mount_rules_dir() {
|
||||
char path[128];
|
||||
xrealpath(BLOCKDIR, blk_info.block_dev);
|
||||
xrealpath(MIRRDIR, path);
|
||||
char *b = blk_info.block_dev + strlen(blk_info.block_dev);
|
||||
char *p = path + strlen(path);
|
||||
|
||||
auto do_mount = [&](const char *type) -> bool {
|
||||
xmkdir(path, 0755);
|
||||
bool success = xmount(blk_info.block_dev, path, type, 0, nullptr) == 0;
|
||||
if (success)
|
||||
mount_list.emplace_back(path);
|
||||
return success;
|
||||
};
|
||||
|
||||
// First try userdata
|
||||
strcpy(blk_info.partname, "userdata");
|
||||
strcpy(b, "/data");
|
||||
strcpy(p, "/data");
|
||||
if (setup_block() < 0) {
|
||||
// Try NVIDIA naming scheme
|
||||
strcpy(blk_info.partname, "UDA");
|
||||
if (setup_block() < 0)
|
||||
goto cache;
|
||||
}
|
||||
// WARNING: DO NOT ATTEMPT TO MOUNT F2FS AS IT MAY CRASH THE KERNEL
|
||||
// Failure means either f2fs, FDE, or metadata encryption
|
||||
if (!do_mount("ext4"))
|
||||
goto cache;
|
||||
|
||||
strcpy(p, "/data/unencrypted");
|
||||
if (xaccess(path, F_OK) == 0) {
|
||||
// FBE, need to use an unencrypted path
|
||||
custom_rules_dir = path + "/magisk"s;
|
||||
} else {
|
||||
// Skip if /data/adb does not exist
|
||||
strcpy(p, SECURE_DIR);
|
||||
if (xaccess(path, F_OK) != 0)
|
||||
return;
|
||||
strcpy(p, MODULEROOT);
|
||||
if (xaccess(path, F_OK) != 0) {
|
||||
goto cache;
|
||||
}
|
||||
// Unencrypted, directly use module paths
|
||||
custom_rules_dir = string(path);
|
||||
}
|
||||
goto success;
|
||||
|
||||
cache:
|
||||
// Fallback to cache
|
||||
strcpy(blk_info.partname, "cache");
|
||||
strcpy(b, "/cache");
|
||||
strcpy(p, "/cache");
|
||||
if (setup_block() < 0) {
|
||||
// Try NVIDIA naming scheme
|
||||
strcpy(blk_info.partname, "CAC");
|
||||
if (setup_block() < 0)
|
||||
goto metadata;
|
||||
}
|
||||
if (!do_mount("ext4"))
|
||||
goto metadata;
|
||||
custom_rules_dir = path + "/magisk"s;
|
||||
goto success;
|
||||
|
||||
metadata:
|
||||
// Fallback to metadata
|
||||
strcpy(blk_info.partname, "metadata");
|
||||
strcpy(b, "/metadata");
|
||||
strcpy(p, "/metadata");
|
||||
if (setup_block() < 0 || !do_mount("ext4"))
|
||||
goto persist;
|
||||
custom_rules_dir = path + "/magisk"s;
|
||||
goto success;
|
||||
|
||||
persist:
|
||||
// Fallback to persist
|
||||
strcpy(blk_info.partname, "persist");
|
||||
strcpy(b, "/persist");
|
||||
strcpy(p, "/persist");
|
||||
if (setup_block() < 0 || !do_mount("ext4"))
|
||||
return;
|
||||
custom_rules_dir = path + "/magisk"s;
|
||||
|
||||
success:
|
||||
// Create symlinks so we don't need to go through this logic again
|
||||
strcpy(p, "/sepolicy.rules");
|
||||
if (char *rel = strstr(custom_rules_dir.data(), MIRRDIR)) {
|
||||
// Create symlink with relative path
|
||||
char s[128];
|
||||
s[0] = '.';
|
||||
strlcpy(s + 1, rel + sizeof(MIRRDIR) - 1, sizeof(s) - 1);
|
||||
xsymlink(s, path);
|
||||
} else {
|
||||
xsymlink(custom_rules_dir.data(), path);
|
||||
}
|
||||
}
|
||||
|
||||
bool LegacySARInit::mount_system_root() {
|
||||
backup_files();
|
||||
|
||||
LOGD("Mounting system_root\n");
|
||||
|
||||
// there's no /dev in stub cpio
|
||||
xmkdir("/dev", 0777);
|
||||
|
||||
strcpy(blk_info.block_dev, "/dev/root");
|
||||
|
||||
do {
|
||||
// Try legacy SAR dm-verity
|
||||
strcpy(blk_info.partname, "vroot");
|
||||
auto dev = setup_block();
|
||||
if (dev >= 0)
|
||||
goto mount_root;
|
||||
|
||||
// Try NVIDIA naming scheme
|
||||
strcpy(blk_info.partname, "APP");
|
||||
dev = setup_block();
|
||||
if (dev >= 0)
|
||||
goto mount_root;
|
||||
|
||||
sprintf(blk_info.partname, "system%s", config->slot);
|
||||
dev = setup_block();
|
||||
if (dev >= 0)
|
||||
goto mount_root;
|
||||
|
||||
// Poll forever if rootwait was given in cmdline
|
||||
} while (config->rootwait);
|
||||
|
||||
// We don't really know what to do at this point...
|
||||
LOGE("Cannot find root partition, abort\n");
|
||||
exit(1);
|
||||
|
||||
mount_root:
|
||||
xmkdir("/system_root", 0755);
|
||||
|
||||
if (xmount("/dev/root", "/system_root", "ext4", MS_RDONLY, nullptr)) {
|
||||
if (xmount("/dev/root", "/system_root", "erofs", MS_RDONLY, nullptr)) {
|
||||
// We don't really know what to do at this point...
|
||||
LOGE("Cannot mount root partition, abort\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
switch_root("/system_root");
|
||||
|
||||
// Make dev writable
|
||||
xmount("tmpfs", "/dev", "tmpfs", 0, "mode=755");
|
||||
mount_list.emplace_back("/dev");
|
||||
|
||||
// Use the apex folder to determine whether 2SI (Android 10+)
|
||||
bool is_two_stage = access("/apex", F_OK) == 0;
|
||||
LOGD("is_two_stage: [%d]\n", is_two_stage);
|
||||
|
||||
#if ENABLE_AVD_HACK
|
||||
if (!is_two_stage) {
|
||||
if (config->emulator) {
|
||||
avd_hack = true;
|
||||
// These values are hardcoded for API 28 AVD
|
||||
xmkdir("/dev/block", 0755);
|
||||
strcpy(blk_info.block_dev, "/dev/block/vde1");
|
||||
strcpy(blk_info.partname, "vendor");
|
||||
setup_block();
|
||||
xmount(blk_info.block_dev, "/vendor", "ext4", MS_RDONLY, nullptr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return is_two_stage;
|
||||
}
|
||||
|
||||
void BaseInit::exec_init() {
|
||||
// Unmount in reverse order
|
||||
for (auto &p : reversed(mount_list)) {
|
||||
if (xumount2(p.data(), MNT_DETACH) == 0)
|
||||
LOGD("Unmount [%s]\n", p.data());
|
||||
}
|
||||
execv("/init", argv);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void MagiskInit::setup_tmp(const char *path) {
|
||||
LOGD("Setup Magisk tmp at %s\n", path);
|
||||
xmount("tmpfs", path, "tmpfs", 0, "mode=755");
|
||||
|
||||
chdir(path);
|
||||
|
||||
xmkdir(INTLROOT, 0755);
|
||||
xmkdir(MIRRDIR, 0);
|
||||
xmkdir(BLOCKDIR, 0);
|
||||
|
||||
mount_rules_dir();
|
||||
|
||||
int fd = xopen(INTLROOT "/config", O_WRONLY | O_CREAT, 0);
|
||||
xwrite(fd, magisk_cfg.buf, magisk_cfg.sz);
|
||||
close(fd);
|
||||
|
||||
// Create applet symlinks
|
||||
for (int i = 0; applet_names[i]; ++i)
|
||||
xsymlink("./magisk", applet_names[i]);
|
||||
xsymlink("./magiskpolicy", "supolicy");
|
||||
|
||||
chdir("/");
|
||||
}
|
||||
23
native/src/init/preload.c
Normal file
23
native/src/init/preload.c
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
int security_load_policy(void *data, size_t len) {
|
||||
// Make sure our next exec won't get bugged
|
||||
unsetenv("LD_PRELOAD");
|
||||
unlink("/dev/preload.so");
|
||||
|
||||
int (*load_policy)(void *, size_t) = dlsym(RTLD_NEXT, "security_load_policy");
|
||||
// Skip checking errors, because if we cannot find the symbol, there
|
||||
// isn't much we can do other than crashing anyways.
|
||||
int result = load_policy(data, len);
|
||||
|
||||
// Wait for ack
|
||||
int fd = open("/sys/fs/selinux/enforce", O_RDONLY);
|
||||
char c;
|
||||
read(fd, &c, 1);
|
||||
close(fd);
|
||||
|
||||
return result;
|
||||
}
|
||||
337
native/src/init/rootdir.cpp
Normal file
337
native/src/init/rootdir.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
#include <sys/mount.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <base.hpp>
|
||||
|
||||
#include "init.hpp"
|
||||
#include "magiskrc.inc"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static vector<string> rc_list;
|
||||
|
||||
static void patch_init_rc(const char *src, const char *dest, const char *tmp_dir) {
|
||||
FILE *rc = xfopen(dest, "we");
|
||||
if (!rc) {
|
||||
PLOGE("%s: open %s failed", __PRETTY_FUNCTION__, src);
|
||||
return;
|
||||
}
|
||||
file_readline(src, [=](string_view line) -> bool {
|
||||
// Do not start vaultkeeper
|
||||
if (str_contains(line, "start vaultkeeper")) {
|
||||
LOGD("Remove vaultkeeper\n");
|
||||
return true;
|
||||
}
|
||||
// Do not run flash_recovery
|
||||
if (str_starts(line, "service flash_recovery")) {
|
||||
LOGD("Remove flash_recovery\n");
|
||||
fprintf(rc, "service flash_recovery /system/bin/xxxxx\n");
|
||||
return true;
|
||||
}
|
||||
// Samsung's persist.sys.zygote.early will cause Zygote to start before post-fs-data
|
||||
if (str_starts(line, "on property:persist.sys.zygote.early=")) {
|
||||
LOGD("Invalidate persist.sys.zygote.early\n");
|
||||
fprintf(rc, "on property:persist.sys.zygote.early.xxxxx=true\n");
|
||||
return true;
|
||||
}
|
||||
// Else just write the line
|
||||
fprintf(rc, "%s", line.data());
|
||||
return true;
|
||||
});
|
||||
|
||||
fprintf(rc, "\n");
|
||||
|
||||
// Inject custom rc scripts
|
||||
for (auto &script : rc_list) {
|
||||
// Replace template arguments of rc scripts with dynamic paths
|
||||
replace_all(script, "${MAGISKTMP}", tmp_dir);
|
||||
fprintf(rc, "\n%s\n", script.data());
|
||||
}
|
||||
rc_list.clear();
|
||||
|
||||
// Inject Magisk rc scripts
|
||||
char pfd_svc[16], ls_svc[16];
|
||||
gen_rand_str(pfd_svc, sizeof(pfd_svc));
|
||||
gen_rand_str(ls_svc, sizeof(ls_svc));
|
||||
LOGD("Inject magisk services: [%s] [%s]\n", pfd_svc, ls_svc);
|
||||
fprintf(rc, MAGISK_RC, tmp_dir, pfd_svc, ls_svc);
|
||||
|
||||
fclose(rc);
|
||||
clone_attr(src, dest);
|
||||
}
|
||||
|
||||
static void load_overlay_rc(const char *overlay) {
|
||||
auto dir = open_dir(overlay);
|
||||
if (!dir) return;
|
||||
|
||||
int dfd = dirfd(dir.get());
|
||||
// Do not allow overwrite init.rc
|
||||
unlinkat(dfd, "init.rc", 0);
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
if (str_ends(entry->d_name, ".rc")) {
|
||||
LOGD("Found rc script [%s]\n", entry->d_name);
|
||||
int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||
rc_list.push_back(full_read(rc));
|
||||
close(rc);
|
||||
unlinkat(dfd, entry->d_name, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void recreate_sbin(const char *mirror, bool use_bind_mount) {
|
||||
auto dp = xopen_dir(mirror);
|
||||
int src = dirfd(dp.get());
|
||||
char buf[4096];
|
||||
for (dirent *entry; (entry = xreaddir(dp.get()));) {
|
||||
string sbin_path = "/sbin/"s + entry->d_name;
|
||||
struct stat st;
|
||||
fstatat(src, entry->d_name, &st, AT_SYMLINK_NOFOLLOW);
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
xreadlinkat(src, entry->d_name, buf, sizeof(buf));
|
||||
xsymlink(buf, sbin_path.data());
|
||||
} else {
|
||||
sprintf(buf, "%s/%s", mirror, entry->d_name);
|
||||
if (use_bind_mount) {
|
||||
auto mode = st.st_mode & 0777;
|
||||
// Create dummy
|
||||
if (S_ISDIR(st.st_mode))
|
||||
xmkdir(sbin_path.data(), mode);
|
||||
else
|
||||
close(xopen(sbin_path.data(), O_CREAT | O_WRONLY | O_CLOEXEC, mode));
|
||||
|
||||
xmount(buf, sbin_path.data(), nullptr, MS_BIND, nullptr);
|
||||
} else {
|
||||
xsymlink(buf, sbin_path.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static string magic_mount_list;
|
||||
|
||||
static void magic_mount(const string &sdir, const string &ddir = "") {
|
||||
auto dir = xopen_dir(sdir.data());
|
||||
if (!dir) return;
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
string src = sdir + "/" + entry->d_name;
|
||||
string dest = ddir + "/" + entry->d_name;
|
||||
if (access(dest.data(), F_OK) == 0) {
|
||||
if (entry->d_type == DT_DIR) {
|
||||
// Recursive
|
||||
magic_mount(src, dest);
|
||||
} else {
|
||||
LOGD("Mount [%s] -> [%s]\n", src.data(), dest.data());
|
||||
xmount(src.data(), dest.data(), nullptr, MS_BIND, nullptr);
|
||||
magic_mount_list += dest;
|
||||
magic_mount_list += '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SARBase::backup_files() {
|
||||
if (access("/overlay.d", F_OK) == 0)
|
||||
backup_folder("/overlay.d", overlays);
|
||||
else if (access("/data/overlay.d", F_OK) == 0)
|
||||
backup_folder("/data/overlay.d", overlays);
|
||||
|
||||
self = mmap_data("/proc/self/exe");
|
||||
if (access("/.backup/.magisk", R_OK) == 0)
|
||||
magisk_cfg = mmap_data("/.backup/.magisk");
|
||||
else if (access("/data/.backup/.magisk", R_OK) == 0)
|
||||
magisk_cfg = mmap_data("/data/.backup/.magisk");
|
||||
}
|
||||
|
||||
static void patch_socket_name(const char *path) {
|
||||
static char rstr[16] = { 0 };
|
||||
if (rstr[0] == '\0')
|
||||
gen_rand_str(rstr, sizeof(rstr));
|
||||
auto bin = mmap_data(path, true);
|
||||
bin.patch({ make_pair(MAIN_SOCKET, rstr) });
|
||||
}
|
||||
|
||||
static void extract_files(bool sbin) {
|
||||
const char *m32 = sbin ? "/sbin/magisk32.xz" : "magisk32.xz";
|
||||
const char *m64 = sbin ? "/sbin/magisk64.xz" : "magisk64.xz";
|
||||
|
||||
if (access(m32, F_OK) == 0) {
|
||||
auto magisk = mmap_data(m32);
|
||||
unlink(m32);
|
||||
int fd = xopen("magisk32", O_WRONLY | O_CREAT, 0755);
|
||||
unxz(fd, magisk.buf, magisk.sz);
|
||||
close(fd);
|
||||
patch_socket_name("magisk32");
|
||||
}
|
||||
if (access(m64, F_OK) == 0) {
|
||||
auto magisk = mmap_data(m64);
|
||||
unlink(m64);
|
||||
int fd = xopen("magisk64", O_WRONLY | O_CREAT, 0755);
|
||||
unxz(fd, magisk.buf, magisk.sz);
|
||||
close(fd);
|
||||
patch_socket_name("magisk64");
|
||||
xsymlink("./magisk64", "magisk");
|
||||
} else {
|
||||
xsymlink("./magisk32", "magisk");
|
||||
}
|
||||
|
||||
dump_manager("stub.apk", 0);
|
||||
}
|
||||
|
||||
#define ROOTMIR MIRRDIR "/system_root"
|
||||
#define NEW_INITRC "/system/etc/init/hw/init.rc"
|
||||
|
||||
void SARBase::patch_ro_root() {
|
||||
string tmp_dir;
|
||||
|
||||
if (access("/sbin", F_OK) == 0) {
|
||||
tmp_dir = "/sbin";
|
||||
} else {
|
||||
char buf[8];
|
||||
gen_rand_str(buf, sizeof(buf));
|
||||
tmp_dir = "/dev/"s + buf;
|
||||
xmkdir(tmp_dir.data(), 0);
|
||||
}
|
||||
|
||||
setup_tmp(tmp_dir.data());
|
||||
chdir(tmp_dir.data());
|
||||
|
||||
// Mount system_root mirror
|
||||
xmkdir(ROOTMIR, 0755);
|
||||
xmount("/", ROOTMIR, nullptr, MS_BIND, nullptr);
|
||||
mount_list.emplace_back(tmp_dir + "/" ROOTMIR);
|
||||
|
||||
// Recreate original sbin structure if necessary
|
||||
if (tmp_dir == "/sbin")
|
||||
recreate_sbin(ROOTMIR "/sbin", true);
|
||||
|
||||
xmkdir(ROOTOVL, 0);
|
||||
|
||||
#if ENABLE_AVD_HACK
|
||||
// Handle avd hack
|
||||
if (avd_hack) {
|
||||
int src = xopen("/init", O_RDONLY | O_CLOEXEC);
|
||||
auto init = mmap_data("/init");
|
||||
// Force disable early mount on original init
|
||||
init.patch({ make_pair("android,fstab", "xxx") });
|
||||
int dest = xopen(ROOTOVL "/init", O_CREAT | O_WRONLY | O_CLOEXEC, 0);
|
||||
xwrite(dest, init.buf, init.sz);
|
||||
fclone_attr(src, dest);
|
||||
close(src);
|
||||
close(dest);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Handle overlay.d
|
||||
restore_folder(ROOTOVL, overlays);
|
||||
overlays.clear();
|
||||
load_overlay_rc(ROOTOVL);
|
||||
if (access(ROOTOVL "/sbin", F_OK) == 0) {
|
||||
// Move files in overlay.d/sbin into tmp_dir
|
||||
mv_path(ROOTOVL "/sbin", ".");
|
||||
}
|
||||
|
||||
// Patch init.rc
|
||||
if (access(NEW_INITRC, F_OK) == 0) {
|
||||
// Android 11's new init.rc
|
||||
xmkdirs(dirname(ROOTOVL NEW_INITRC), 0755);
|
||||
patch_init_rc(NEW_INITRC, ROOTOVL NEW_INITRC, tmp_dir.data());
|
||||
} else {
|
||||
patch_init_rc("/init.rc", ROOTOVL "/init.rc", tmp_dir.data());
|
||||
}
|
||||
|
||||
// Extract magisk
|
||||
extract_files(false);
|
||||
|
||||
// Oculus Go will use a special sepolicy if unlocked
|
||||
if (access("/sepolicy.unlocked", F_OK) == 0) {
|
||||
patch_sepolicy("/sepolicy.unlocked", ROOTOVL "/sepolicy.unlocked");
|
||||
} else if ((access(SPLIT_PLAT_CIL, F_OK) != 0 && access("/sepolicy", F_OK) == 0) || !hijack_sepolicy()) {
|
||||
patch_sepolicy("/sepolicy", ROOTOVL "/sepolicy");
|
||||
}
|
||||
|
||||
// Mount rootdir
|
||||
magic_mount(ROOTOVL);
|
||||
int dest = xopen(ROOTMNT, O_WRONLY | O_CREAT, 0);
|
||||
write(dest, magic_mount_list.data(), magic_mount_list.length());
|
||||
close(dest);
|
||||
|
||||
chdir("/");
|
||||
}
|
||||
|
||||
void RootFSInit::prepare() {
|
||||
self = mmap_data("/init");
|
||||
magisk_cfg = mmap_data("/.backup/.magisk");
|
||||
|
||||
LOGD("Restoring /init\n");
|
||||
rename(backup_init(), "/init");
|
||||
}
|
||||
|
||||
#define PRE_TMPDIR "/magisk-tmp"
|
||||
|
||||
void MagiskInit::patch_rw_root() {
|
||||
// Create hardlink mirror of /sbin to /root
|
||||
mkdir("/root", 0777);
|
||||
clone_attr("/sbin", "/root");
|
||||
link_path("/sbin", "/root");
|
||||
|
||||
// Handle overlays
|
||||
if (access("/overlay.d", F_OK) == 0) {
|
||||
LOGD("Merge overlay.d\n");
|
||||
load_overlay_rc("/overlay.d");
|
||||
mv_path("/overlay.d", "/");
|
||||
}
|
||||
rm_rf("/.backup");
|
||||
|
||||
// Patch init.rc
|
||||
patch_init_rc("/init.rc", "/init.p.rc", "/sbin");
|
||||
rename("/init.p.rc", "/init.rc");
|
||||
|
||||
bool treble;
|
||||
{
|
||||
auto init = mmap_data("/init");
|
||||
treble = init.contains(SPLIT_PLAT_CIL);
|
||||
}
|
||||
|
||||
xmkdir(PRE_TMPDIR, 0);
|
||||
setup_tmp(PRE_TMPDIR);
|
||||
chdir(PRE_TMPDIR);
|
||||
|
||||
// Extract magisk
|
||||
extract_files(true);
|
||||
|
||||
if ((!treble && access("/sepolicy", F_OK) == 0) || !hijack_sepolicy()) {
|
||||
patch_sepolicy("/sepolicy", "/sepolicy");
|
||||
}
|
||||
|
||||
chdir("/");
|
||||
|
||||
// Dump magiskinit as magisk
|
||||
int fd = xopen("/sbin/magisk", O_WRONLY | O_CREAT, 0755);
|
||||
write(fd, self.buf, self.sz);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int magisk_proxy_main(int argc, char *argv[]) {
|
||||
setup_klog();
|
||||
LOGD("%s\n", __FUNCTION__);
|
||||
|
||||
// Mount rootfs as rw to do post-init rootfs patches
|
||||
xmount(nullptr, "/", nullptr, MS_REMOUNT, nullptr);
|
||||
|
||||
unlink("/sbin/magisk");
|
||||
|
||||
// Move tmpfs to /sbin
|
||||
// For some reason MS_MOVE won't work, as a workaround bind mount then unmount
|
||||
xmount(PRE_TMPDIR, "/sbin", nullptr, MS_BIND | MS_REC, nullptr);
|
||||
xumount2(PRE_TMPDIR, MNT_DETACH);
|
||||
rmdir(PRE_TMPDIR);
|
||||
|
||||
// Create symlinks pointing back to /root
|
||||
recreate_sbin("/root", false);
|
||||
|
||||
// Tell magiskd to remount rootfs
|
||||
setenv("REMOUNT_ROOT", "1", 1);
|
||||
execv("/sbin/magisk", argv);
|
||||
return 1;
|
||||
}
|
||||
165
native/src/init/selinux.cpp
Normal file
165
native/src/init/selinux.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <sepolicy.hpp>
|
||||
#include <base.hpp>
|
||||
|
||||
#include "init.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void MagiskInit::patch_sepolicy(const char *in, const char *out) {
|
||||
LOGD("Patching monolithic policy\n");
|
||||
auto sepol = unique_ptr<sepolicy>(sepolicy::from_file(in));
|
||||
|
||||
sepol->magisk_rules();
|
||||
|
||||
// Custom rules
|
||||
if (!custom_rules_dir.empty()) {
|
||||
if (auto dir = xopen_dir(custom_rules_dir.data())) {
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
auto rule = custom_rules_dir + "/" + entry->d_name + "/sepolicy.rule";
|
||||
if (xaccess(rule.data(), R_OK) == 0) {
|
||||
LOGD("Loading custom sepolicy patch: [%s]\n", rule.data());
|
||||
sepol->load_rule_file(rule.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOGD("Dumping sepolicy to: [%s]\n", out);
|
||||
sepol->to_file(out);
|
||||
|
||||
// Remove OnePlus stupid debug sepolicy and use our own
|
||||
if (access("/sepolicy_debug", F_OK) == 0) {
|
||||
unlink("/sepolicy_debug");
|
||||
link("/sepolicy", "/sepolicy_debug");
|
||||
}
|
||||
}
|
||||
|
||||
#define MOCK_COMPAT SELINUXMOCK "/compatible"
|
||||
#define MOCK_LOAD SELINUXMOCK "/load"
|
||||
#define MOCK_ENFORCE SELINUXMOCK "/enforce"
|
||||
|
||||
bool MagiskInit::hijack_sepolicy() {
|
||||
xmkdir(SELINUXMOCK, 0);
|
||||
|
||||
if (access("/system/bin/init", F_OK) == 0) {
|
||||
// On 2SI devices, the 2nd stage init file is always a dynamic executable.
|
||||
// This meant that instead of going through convoluted methods trying to alter
|
||||
// and block init's control flow, we can just LD_PRELOAD and replace the
|
||||
// security_load_policy function with our own implementation.
|
||||
dump_preload("/dev/preload.so", 0644);
|
||||
setenv("LD_PRELOAD", "/dev/preload.so", 1);
|
||||
}
|
||||
|
||||
// Hijack the "load" and "enforce" node in selinuxfs to manipulate
|
||||
// the actual sepolicy being loaded into the kernel
|
||||
auto hijack = [&] {
|
||||
LOGD("Hijack [" SELINUX_LOAD "]\n");
|
||||
close(xopen(MOCK_LOAD, O_CREAT | O_RDONLY, 0600));
|
||||
xmount(MOCK_LOAD, SELINUX_LOAD, nullptr, MS_BIND, nullptr);
|
||||
LOGD("Hijack [" SELINUX_ENFORCE "]\n");
|
||||
mkfifo(MOCK_ENFORCE, 0644);
|
||||
xmount(MOCK_ENFORCE, SELINUX_ENFORCE, nullptr, MS_BIND, nullptr);
|
||||
};
|
||||
|
||||
string dt_compat;
|
||||
if (access(SELINUX_ENFORCE, F_OK) != 0) {
|
||||
// selinuxfs not mounted yet. Hijack the dt fstab nodes first
|
||||
// and let the original init mount selinuxfs for us.
|
||||
// This only happens on Android 8.0 - 9.0
|
||||
|
||||
char buf[4096];
|
||||
snprintf(buf, sizeof(buf), "%s/fstab/compatible", config->dt_dir);
|
||||
dt_compat = full_read(buf);
|
||||
if (dt_compat.empty()) {
|
||||
// Device does not do early mount and uses monolithic policy
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remount procfs with proper options
|
||||
xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009");
|
||||
|
||||
LOGD("Hijack [%s]\n", buf);
|
||||
|
||||
// Preserve sysfs and procfs for hijacking
|
||||
mount_list.erase(std::remove_if(
|
||||
mount_list.begin(), mount_list.end(),
|
||||
[](const string &s) { return s == "/proc" || s == "/sys"; }), mount_list.end());
|
||||
|
||||
mkfifo(MOCK_COMPAT, 0444);
|
||||
xmount(MOCK_COMPAT, buf, nullptr, MS_BIND, nullptr);
|
||||
} else {
|
||||
hijack();
|
||||
}
|
||||
|
||||
// Read all custom rules into memory
|
||||
string rules;
|
||||
if (!custom_rules_dir.empty()) {
|
||||
if (auto dir = xopen_dir(custom_rules_dir.data())) {
|
||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||
auto rule_file = custom_rules_dir + "/" + entry->d_name + "/sepolicy.rule";
|
||||
if (xaccess(rule_file.data(), R_OK) == 0) {
|
||||
LOGD("Load custom sepolicy patch: [%s]\n", rule_file.data());
|
||||
full_read(rule_file.data(), rules);
|
||||
rules += '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new process waiting for init operations
|
||||
if (xfork()) {
|
||||
// In parent, return and continue boot process
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!dt_compat.empty()) {
|
||||
// This open will block until init calls DoFirstStageMount
|
||||
// The only purpose here is actually to wait for init to mount selinuxfs for us
|
||||
int fd = xopen(MOCK_COMPAT, O_WRONLY);
|
||||
|
||||
char buf[4096];
|
||||
snprintf(buf, sizeof(buf), "%s/fstab/compatible", config->dt_dir);
|
||||
xumount2(buf, MNT_DETACH);
|
||||
|
||||
hijack();
|
||||
|
||||
xwrite(fd, dt_compat.data(), dt_compat.size());
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// This open will block until init calls security_getenforce
|
||||
int fd = xopen(MOCK_ENFORCE, O_WRONLY);
|
||||
|
||||
// Cleanup the hijacks
|
||||
umount2("/init", MNT_DETACH);
|
||||
xumount2(SELINUX_LOAD, MNT_DETACH);
|
||||
xumount2(SELINUX_ENFORCE, MNT_DETACH);
|
||||
|
||||
// Load and patch policy
|
||||
auto sepol = unique_ptr<sepolicy>(sepolicy::from_file(MOCK_LOAD));
|
||||
sepol->magisk_rules();
|
||||
sepol->load_rules(rules);
|
||||
|
||||
// Load patched policy into kernel
|
||||
sepol->to_file(SELINUX_LOAD);
|
||||
|
||||
// Write to the enforce node ONLY after sepolicy is loaded. We need to make sure
|
||||
// the actual init process is blocked until sepolicy is loaded, or else
|
||||
// restorecon will fail and re-exec won't change context, causing boot failure.
|
||||
// We (ab)use the fact that init reads the enforce node, and because
|
||||
// it has been replaced with our FIFO file, init will block until we
|
||||
// write something into the pipe, effectively hijacking its control flow.
|
||||
|
||||
string enforce = full_read(SELINUX_ENFORCE);
|
||||
xwrite(fd, enforce.data(), enforce.length());
|
||||
close(fd);
|
||||
|
||||
// At this point, the init process will be unblocked
|
||||
// and continue on with restorecon + re-exec.
|
||||
|
||||
// Terminate process
|
||||
exit(0);
|
||||
}
|
||||
85
native/src/init/twostage.cpp
Normal file
85
native/src/init/twostage.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include <magisk.hpp>
|
||||
#include <base.hpp>
|
||||
#include <socket.hpp>
|
||||
|
||||
#include "init.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define REDIR_PATH "/data/magiskinit"
|
||||
|
||||
void FirstStageInit::prepare() {
|
||||
xmkdirs("/data", 0755);
|
||||
xmount("tmpfs", "/data", "tmpfs", 0, "mode=755");
|
||||
cp_afc("/init" /* magiskinit */, REDIR_PATH);
|
||||
|
||||
restore_ramdisk_init();
|
||||
|
||||
{
|
||||
auto init = mmap_data("/init", true);
|
||||
// Redirect original init to magiskinit
|
||||
init.patch({ make_pair(INIT_PATH, REDIR_PATH) });
|
||||
}
|
||||
|
||||
// Copy files to tmpfs
|
||||
cp_afc(".backup", "/data/.backup");
|
||||
cp_afc("overlay.d", "/data/overlay.d");
|
||||
}
|
||||
|
||||
void LegacySARInit::first_stage_prep() {
|
||||
xmkdir("/data", 0755);
|
||||
xmount("tmpfs", "/data", "tmpfs", 0, "mode=755");
|
||||
|
||||
// Patch init binary
|
||||
int src = xopen("/init", O_RDONLY);
|
||||
int dest = xopen("/data/init", O_CREAT | O_WRONLY, 0);
|
||||
{
|
||||
auto init = mmap_data("/init");
|
||||
init.patch({ make_pair(INIT_PATH, REDIR_PATH) });
|
||||
write(dest, init.buf, init.sz);
|
||||
fclone_attr(src, dest);
|
||||
close(dest);
|
||||
}
|
||||
xmount("/data/init", "/init", nullptr, MS_BIND, nullptr);
|
||||
|
||||
// Replace redirect init with magiskinit
|
||||
dest = xopen(REDIR_PATH, O_CREAT | O_WRONLY, 0);
|
||||
write(dest, self.buf, self.sz);
|
||||
fclone_attr(src, dest);
|
||||
close(src);
|
||||
close(dest);
|
||||
|
||||
// Copy files to tmpfs
|
||||
xmkdir("/data/.backup", 0);
|
||||
xmkdir("/data/overlay.d", 0);
|
||||
restore_folder("/data/overlay.d", overlays);
|
||||
int cfg = xopen("/data/.backup/.magisk", O_WRONLY | O_CREAT, 0);
|
||||
xwrite(cfg, magisk_cfg.buf, magisk_cfg.sz);
|
||||
close(cfg);
|
||||
}
|
||||
|
||||
bool SecondStageInit::prepare() {
|
||||
backup_files();
|
||||
|
||||
umount2("/init", MNT_DETACH);
|
||||
umount2("/proc/self/exe", MNT_DETACH);
|
||||
umount2("/data", MNT_DETACH);
|
||||
|
||||
// Make sure init dmesg logs won't get messed up
|
||||
argv[0] = (char *) INIT_PATH;
|
||||
|
||||
// Some weird devices like meizu, uses 2SI but still have legacy rootfs
|
||||
// Check if root and system are on different filesystems
|
||||
struct stat root{}, system{};
|
||||
xstat("/", &root);
|
||||
xstat("/system", &system);
|
||||
if (root.st_dev != system.st_dev) {
|
||||
// We are still on rootfs, so make sure we will execute the init of the 2nd stage
|
||||
unlink("/init");
|
||||
xsymlink(INIT_PATH, "/init");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user