Magisk/native/jni/core/init.cpp
topjohnwu 24f5bc98d8 Add boot_complete trigger back
Samsung does not like running cmd before system services are started.
Instead of failing, it will enter an infinite wait on binder.
Move APK installation to boot complete to make sure pm can be run
without blocking process.
2019-04-05 07:00:30 -04:00

774 lines
19 KiB
C++

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/sendfile.h>
#include <sys/sysmacros.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <functional>
#include <string_view>
#include <xz.h>
#include <magisk.h>
#include <magiskpolicy.h>
#include <selinux.h>
#include <cpio.h>
#include <utils.h>
#include <flags.h>
#include "binaries.h"
#ifdef USE_64BIT
#include "binaries_arch64.h"
#define LIBNAME "lib64"
#else
#include "binaries_arch.h"
#define LIBNAME "lib"
#endif
#include "magiskrc.h"
using namespace std;
#define DEFAULT_DT_DIR "/proc/device-tree/firmware/android"
#ifdef MAGISK_DEBUG
static FILE *kmsg;
static char kbuf[4096];
static int vprintk(const char *fmt, va_list ap) {
vsprintf(kbuf, fmt, ap);
return fprintf(kmsg, "magiskinit: %s", kbuf);
}
static void setup_klog() {
mknod("/kmsg", S_IFCHR | 0666, makedev(1, 11));
kmsg = xfopen("/kmsg", "ae");
setbuf(kmsg, nullptr);
unlink("/kmsg");
log_cb.d = log_cb.i = log_cb.w = log_cb.e = vprintk;
log_cb.ex = nop_ex;
}
#else
#define setup_klog(...)
#endif
static int test_main(int argc, char *argv[]);
constexpr const char *init_applet[] =
{ "magiskpolicy", "supolicy", "init_test", nullptr };
constexpr int (*init_applet_main[])(int, char *[]) =
{ magiskpolicy_main, magiskpolicy_main, test_main, nullptr };
struct cmdline {
bool system_as_root;
char slot[3];
char dt_dir[128];
};
struct raw_data {
void *buf;
size_t sz;
};
static 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 void decompress_ramdisk() {
constexpr char tmp[] = "tmp.cpio";
constexpr char ramdisk_xz[] = "ramdisk.cpio.xz";
if (access(ramdisk_xz, F_OK))
return;
LOGD("Decompressing ramdisk from %s\n", ramdisk_xz);
uint8_t *buf;
size_t sz;
mmap_ro(ramdisk_xz, buf, sz);
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC);
unxz(fd, buf, sz);
munmap(buf, sz);
close(fd);
cpio_mmap cpio(tmp);
cpio.extract();
unlink(tmp);
unlink(ramdisk_xz);
}
static int dump_magisk(const char *path, mode_t mode) {
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
if (fd < 0)
return 1;
if (!unxz(fd, magisk_xz, sizeof(magisk_xz)))
return 1;
close(fd);
return 0;
}
static int dump_manager(const char *path, mode_t mode) {
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
if (fd < 0)
return 1;
if (!unxz(fd, manager_xz, sizeof(manager_xz)))
return 1;
close(fd);
return 0;
}
class MagiskInit {
private:
cmdline cmd{};
raw_data self{};
raw_data config{};
int root = -1;
char **argv;
bool load_sepol = false;
bool mnt_system = false;
bool mnt_vendor = false;
bool mnt_product = false;
bool mnt_odm = false;
void load_kernel_info();
void preset();
void early_mount();
void setup_rootfs();
bool read_dt_fstab(const char *name, char *partname, char *partfs);
bool patch_sepolicy();
void cleanup();
public:
explicit MagiskInit(char *argv[]) : argv(argv) {}
void setup_overlay();
void re_exec_init();
void start();
void test();
};
static inline void parse_cmdline(const std::function<void (std::string_view, const char *)> &fn) {
char cmdline[4096];
int fd = open("/proc/cmdline", O_RDONLY | O_CLOEXEC);
cmdline[read(fd, cmdline, sizeof(cmdline))] = '\0';
close(fd);
char *tok, *eql, *tmp, *saveptr;
saveptr = cmdline;
while ((tok = strtok_r(nullptr, " \n", &saveptr)) != nullptr) {
eql = strchr(tok, '=');
if (eql) {
*eql = '\0';
if (eql[1] == '"') {
tmp = strchr(saveptr, '"');
if (tmp != nullptr) {
*tmp = '\0';
saveptr[-1] = ' ';
saveptr = tmp + 1;
eql++;
}
}
fn(tok, eql + 1);
} else {
fn(tok, "");
}
}
}
#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))
static bool check_key_combo() {
uint8_t bitmask[(KEY_MAX + 1) / 8];
int eventfd = -1;
for (int minor = 64; minor < 96; ++minor) {
if (mknod("/event", S_IFCHR | 0444, makedev(13, minor))) {
PLOGE("mknod");
continue;
}
eventfd = xopen("/event", O_RDWR | O_CLOEXEC);
unlink("/event");
if (eventfd < 0)
continue;
memset(bitmask, 0, sizeof(bitmask));
ioctl(eventfd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);
if (test_bit(KEY_POWER, bitmask) && test_bit(KEY_VOLUMEUP, bitmask)) {
// Check KEY_POWER because KEY_VOLUMEUP could be headphone input
break;
}
}
if (eventfd < 0)
return false;
// Return true if volume key up is hold for more than 3 seconds
int count = 0;
for (int i = 0; i < 500; ++i) {
memset(bitmask, 0, sizeof(bitmask));
ioctl(eventfd, EVIOCGKEY(sizeof(bitmask)), bitmask);
count = test_bit(KEY_VOLUMEUP, bitmask) ? count + 1 : 0;
if (count >= 300) {
LOGD("KEY_VOLUMEUP detected: disable system-as-root\n");
close(eventfd);
return true;
}
// Check every 10ms
usleep(10000);
}
close(eventfd);
return false;
}
void MagiskInit::load_kernel_info() {
// Communicate with kernel using procfs and sysfs
xmkdir("/proc", 0755);
xmount("proc", "/proc", "proc", 0, nullptr);
xmkdir("/sys", 0755);
xmount("sysfs", "/sys", "sysfs", 0, nullptr);
bool enter_recovery = false;
bool kirin = false;
bool recovery_mode = false;
parse_cmdline([&](auto key, auto value) -> void {
LOGD("cmdline: [%s]=[%s]\n", key.data(), value);
if (key == "androidboot.slot_suffix") {
strcpy(cmd.slot, value);
} else if (key == "androidboot.slot") {
cmd.slot[0] = '_';
strcpy(cmd.slot + 1, value);
} else if (key == "skip_initramfs") {
cmd.system_as_root = true;
} else if (key == "androidboot.android_dt_dir") {
strcpy(cmd.dt_dir, value);
} else if (key == "enter_recovery") {
enter_recovery = value[0] == '1';
} else if (key == "androidboot.hardware") {
kirin = strstr(value, "kirin") || strstr(value, "hi3660");
}
});
parse_prop_file("/.backup/.magisk", [&](auto key, auto value) -> bool {
if (key == "RECOVERYMODE" && value == "true")
recovery_mode = true;
return true;
});
if (kirin && enter_recovery) {
// Inform that we are actually booting as recovery
if (!recovery_mode) {
if (FILE *f = fopen("/.backup/.magisk", "ae"); f) {
fprintf(f, "RECOVERYMODE=true\n");
fclose(f);
}
recovery_mode = true;
}
}
if (recovery_mode) {
LOGD("Running in recovery mode, waiting for key...\n");
cmd.system_as_root = !check_key_combo();
}
if (cmd.dt_dir[0] == '\0')
strcpy(cmd.dt_dir, DEFAULT_DT_DIR);
LOGD("system_as_root=[%d]\n", cmd.system_as_root);
LOGD("slot=[%s]\n", cmd.slot);
LOGD("dt_dir=[%s]\n", cmd.dt_dir);
}
void MagiskInit::preset() {
root = open("/", O_RDONLY | O_CLOEXEC);
if (cmd.system_as_root) {
// Clear rootfs
LOGD("Cleaning rootfs\n");
frm_rf(root, { "overlay", "proc", "sys" });
} else {
decompress_ramdisk();
// Revert original init binary
rename("/.backup/init", "/init");
rm_rf("/.backup");
// Do not go further if device is booting into recovery
if (access("/sbin/recovery", F_OK) == 0) {
LOGD("Ramdisk is recovery, abort\n");
re_exec_init();
}
}
}
struct device {
int major;
int minor;
char devname[32];
char partname[32];
};
static inline void parse_device(device *dev, const char *uevent) {
dev->partname[0] = '\0';
FILE *fp = xfopen(uevent, "re");
char buf[64];
while (fgets(buf, sizeof(buf), fp)) {
if (strncmp(buf, "MAJOR", 5) == 0) {
sscanf(buf, "MAJOR=%d", &dev->major);
} else if (strncmp(buf, "MINOR", 5) == 0) {
sscanf(buf, "MINOR=%d", &dev->minor);
} else if (strncmp(buf, "DEVNAME", 7) == 0) {
sscanf(buf, "DEVNAME=%s", dev->devname);
} else if (strncmp(buf, "PARTNAME", 8) == 0) {
sscanf(buf, "PARTNAME=%s", dev->partname);
}
}
fclose(fp);
}
static vector<device> dev_list;
static void collect_devices() {
char path[128];
struct dirent *entry;
device dev;
DIR *dir = xopendir("/sys/dev/block");
if (dir == nullptr)
return;
while ((entry = readdir(dir))) {
if (entry->d_name == "."sv || entry->d_name == ".."sv)
continue;
sprintf(path, "/sys/dev/block/%s/uevent", entry->d_name);
parse_device(&dev, path);
dev_list.push_back(dev);
}
closedir(dir);
}
static bool setup_block(const char *partname, char *block_dev) {
if (dev_list.empty())
collect_devices();
for (auto &dev : dev_list) {
if (strcasecmp(dev.partname, partname) == 0) {
sprintf(block_dev, "/dev/block/%s", dev.devname);
LOGD("Found %s: [%s] (%d, %d)\n", dev.partname, dev.devname, dev.major, dev.minor);
xmkdir("/dev", 0755);
xmkdir("/dev/block", 0755);
mknod(block_dev, S_IFBLK | 0600, makedev(dev.major, dev.minor));
return true;
}
}
return false;
}
bool MagiskInit::read_dt_fstab(const char *name, char *partname, char *partfs) {
char path[128];
int fd;
sprintf(path, "%s/fstab/%s/dev", cmd.dt_dir, name);
if ((fd = xopen(path, O_RDONLY | O_CLOEXEC)) >= 0) {
read(fd, path, sizeof(path));
close(fd);
// Some custom treble use different names, so use what we read
name = rtrim(strrchr(path, '/') + 1);
sprintf(partname, "%s%s", name, strend(name, cmd.slot) ? cmd.slot : "");
sprintf(path, "%s/fstab/%s/type", cmd.dt_dir, name);
if ((fd = xopen(path, O_RDONLY | O_CLOEXEC)) >= 0) {
read(fd, partfs, 32);
close(fd);
return true;
}
}
return false;
}
static inline bool is_lnk(const char *name) {
struct stat st;
if (lstat(name, &st))
return false;
return S_ISLNK(st.st_mode);
}
#define link_root(name) \
if (is_lnk("/system_root" name)) \
cp_afc("/system_root" name, name)
#define mount_root(name) \
if (!is_lnk("/" #name) && read_dt_fstab(#name, partname, fstype)) { \
LOGD("Early mount " #name "\n"); \
setup_block(partname, block_dev); \
xmkdir("/" #name, 0755); \
xmount(block_dev, "/" #name, fstype, MS_RDONLY, nullptr); \
mnt_##name = true; \
}
void MagiskInit::early_mount() {
char partname[32];
char fstype[32];
char block_dev[64];
if (cmd.system_as_root) {
LOGD("Early mount system_root\n");
sprintf(partname, "system%s", cmd.slot);
setup_block(partname, block_dev);
xmkdir("/system_root", 0755);
xmount(block_dev, "/system_root", "ext4", MS_RDONLY, nullptr);
xmkdir("/system", 0755);
xmount("/system_root/system", "/system", nullptr, MS_BIND, nullptr);
// Android Q
if (is_lnk("/system_root/init"))
load_sepol = true;
// System-as-root with monolithic sepolicy
if (access("/system_root/sepolicy", F_OK) == 0)
cp_afc("/system_root/sepolicy", "/sepolicy");
// Copy if these partitions are symlinks
link_root("/vendor");
link_root("/product");
link_root("/odm");
} else {
mount_root(system);
}
mount_root(vendor);
mount_root(product);
mount_root(odm);
}
void MagiskInit::setup_rootfs() {
bool patch_init = patch_sepolicy();
if (cmd.system_as_root) {
// Clone rootfs
LOGD("Clone root dir from system to rootfs\n");
int system_root = xopen("/system_root", O_RDONLY | O_CLOEXEC);
clone_dir(system_root, root, false);
close(system_root);
}
if (patch_init) {
constexpr char SYSTEM_INIT[] = "/system/bin/init";
// If init is symlink, copy it to rootfs so we can patch
if (is_lnk("/init"))
cp_afc(SYSTEM_INIT, "/init");
char *addr;
size_t size;
mmap_rw("/init", addr, size);
for (char *p = addr; p < addr + size; ++p) {
if (memcmp(p, SPLIT_PLAT_CIL, sizeof(SPLIT_PLAT_CIL)) == 0) {
// Force init to load /sepolicy
LOGD("Remove from init: " SPLIT_PLAT_CIL "\n");
memset(p, 'x', sizeof(SPLIT_PLAT_CIL) - 1);
p += sizeof(SPLIT_PLAT_CIL) - 1;
} else if (memcmp(p, SYSTEM_INIT, sizeof(SYSTEM_INIT)) == 0) {
// Force execute /init instead of /system/bin/init
LOGD("Patch init: [/system/bin/init] -> [/init]\n");
strcpy(p, "/init");
p += sizeof(SYSTEM_INIT) - 1;
}
}
munmap(addr, size);
}
// Handle ramdisk overlays
int fd = open("/overlay", O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
LOGD("Merge overlay folder\n");
mv_dir(fd, root);
close(fd);
rmdir("/overlay");
}
// Patch init.rc
FILE *rc = xfopen("/init.p.rc", "we");
file_readline("/init.rc", [&](auto 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;
}
// Else just write the line
fprintf(rc, "%s", line.data());
return true;
});
char pfd_svc[8], ls_svc[8], bc_svc[8];
// Make sure to be unique
pfd_svc[0] = 'a';
ls_svc[0] = '0';
bc_svc[0] = 'A';
gen_rand_str(pfd_svc + 1, sizeof(pfd_svc) - 1);
gen_rand_str(ls_svc + 1, sizeof(ls_svc) - 1);
gen_rand_str(bc_svc + 1, sizeof(bc_svc) - 1);
fprintf(rc, magiskrc, pfd_svc, pfd_svc, ls_svc, bc_svc, bc_svc);
fclose(rc);
clone_attr("/init.rc", "/init.p.rc");
rename("/init.p.rc", "/init.rc");
// Don't let init run in init yet
lsetfilecon("/init", "u:object_r:rootfs:s0");
// Create hardlink mirror of /sbin to /root
mkdir("/root", 0750);
clone_attr("/sbin", "/root");
int rootdir = xopen("/root", O_RDONLY | O_CLOEXEC);
int sbin = xopen("/sbin", O_RDONLY | O_CLOEXEC);
link_dir(sbin, rootdir);
}
bool MagiskInit::patch_sepolicy() {
bool patch_init = false;
if (access(SPLIT_PLAT_CIL, R_OK) == 0) {
LOGD("sepol: split policy\n");
patch_init = true;
} else if (access("/sepolicy", R_OK) == 0) {
LOGD("sepol: monolithic policy\n");
load_policydb("/sepolicy");
} else {
LOGD("sepol: no selinux\n");
return false;
}
// Mount selinuxfs to communicate with kernel
xmount("selinuxfs", SELINUX_MNT, "selinuxfs", 0, nullptr);
if (patch_init)
load_split_cil();
sepol_magisk_rules();
sepol_allow(SEPOL_PROC_DOMAIN, ALL, ALL, ALL);
dump_policydb("/sepolicy");
// Load policy to kernel so we can label rootfs
if (load_sepol) {
LOGD("sepol: preload sepolicy\n");
dump_policydb(SELINUX_LOAD);
}
// Remove OnePlus stupid debug sepolicy and use our own
if (access("/sepolicy_debug", F_OK) == 0) {
unlink("/sepolicy_debug");
link("/sepolicy", "/sepolicy_debug");
}
// Enable selinux functions
selinux_builtin_impl();
return patch_init;
}
#define umount_root(name) \
if (mnt_##name) \
umount("/" #name);
void MagiskInit::cleanup() {
umount(SELINUX_MNT);
umount("/sys");
umount("/proc");
umount_root(system);
umount_root(vendor);
umount_root(product);
umount_root(odm);
}
static inline void patch_socket_name(const char *path) {
uint8_t *buf;
char name[sizeof(MAIN_SOCKET)];
size_t size;
mmap_rw(path, buf, size);
for (int i = 0; i < size; ++i) {
if (memcmp(buf + i, MAIN_SOCKET, sizeof(MAIN_SOCKET)) == 0) {
gen_rand_str(name, sizeof(name));
memcpy(buf + i, name, sizeof(name));
i += sizeof(name);
}
}
munmap(buf, size);
}
static const char wrapper[] =
"#!/system/bin/sh\n"
"export LD_LIBRARY_PATH=\"$LD_LIBRARY_PATH:/apex/com.android.runtime/" LIBNAME "\"\n"
"exec /sbin/magisk.bin \"$0\" \"$@\"\n";
void MagiskInit::setup_overlay() {
char path[128];
int fd;
// Wait for early-init start
while (access(EARLYINIT, F_OK) != 0)
usleep(10);
setcon("u:r:" SEPOL_PROC_DOMAIN ":s0");
unlink(EARLYINIT);
#ifdef MAGISK_DEBUG
kmsg = xfopen("/dev/kmsg", "ae");
setbuf(kmsg, nullptr);
#endif
LOGD("Setting up overlay\n");
// Mount the /sbin tmpfs overlay
xmount("tmpfs", "/sbin", "tmpfs", 0, nullptr);
chmod("/sbin", 0755);
setfilecon("/sbin", "u:object_r:rootfs:s0");
// Dump binaries
mkdir(MAGISKTMP, 0755);
fd = xopen(MAGISKTMP "/config", O_WRONLY | O_CREAT, 0000);
write(fd, config.buf, config.sz);
close(fd);
fd = xopen("/sbin/magiskinit", O_WRONLY | O_CREAT, 0755);
write(fd, self.buf, self.sz);
close(fd);
if (access("/system/apex", F_OK) == 0) {
LOGD("APEX detected, use wrapper\n");
dump_magisk("/sbin/magisk.bin", 0755);
patch_socket_name("/sbin/magisk.bin");
setfilecon("/sbin/magisk.bin", "u:object_r:" SEPOL_FILE_DOMAIN ":s0");
fd = xopen("/sbin/magisk", O_WRONLY | O_CREAT, 0755);
write(fd, wrapper, sizeof(wrapper) - 1);
close(fd);
} else {
dump_magisk("/sbin/magisk", 0755);
patch_socket_name("/sbin/magisk");
}
setfilecon("/sbin/magisk", "u:object_r:" SEPOL_FILE_DOMAIN ":s0");
setfilecon("/sbin/magiskinit", "u:object_r:" SEPOL_FILE_DOMAIN ":s0");
// Create applet symlinks
for (int i = 0; applet_names[i]; ++i) {
sprintf(path, "/sbin/%s", applet_names[i]);
xsymlink("/sbin/magisk", path);
}
for (int i = 0; init_applet[i]; ++i) {
sprintf(path, "/sbin/%s", init_applet[i]);
xsymlink("/sbin/magiskinit", path);
}
// Create symlinks pointing back to /root
DIR *dir = xopendir("/root");
struct dirent *entry;
fd = xopen("/sbin", O_RDONLY);
while((entry = xreaddir(dir))) {
if (entry->d_name == "."sv || entry->d_name == ".."sv)
continue;
sprintf(path, "/root/%s", entry->d_name);
xsymlinkat(path, fd, entry->d_name);
}
closedir(dir);
close(fd);
close(xopen(EARLYINITDONE, O_RDONLY | O_CREAT, 0));
exit(0);
}
void MagiskInit::re_exec_init() {
cleanup();
execv("/init", argv);
exit(1);
}
void MagiskInit::start() {
// Prevent file descriptor confusion
mknod("/null", S_IFCHR | 0666, makedev(1, 3));
int null = open("/null", O_RDWR | O_CLOEXEC);
unlink("/null");
xdup3(null, STDIN_FILENO, O_CLOEXEC);
xdup3(null, STDOUT_FILENO, O_CLOEXEC);
xdup3(null, STDERR_FILENO, O_CLOEXEC);
if (null > STDERR_FILENO)
close(null);
setup_klog();
load_kernel_info();
full_read("/init", &self.buf, &self.sz);
full_read("/.backup/.magisk", &config.buf, &config.sz);
preset();
early_mount();
setup_rootfs();
}
void MagiskInit::test() {
cmdline_logging();
log_cb.ex = nop_ex;
chdir(dirname(argv[0]));
chroot(".");
chdir("/");
load_kernel_info();
preset();
early_mount();
setup_rootfs();
cleanup();
}
static int test_main(int, char *argv[]) {
MagiskInit init(argv);
init.test();
return 0;
}
int main(int argc, char *argv[]) {
umask(0);
for (int i = 0; init_applet[i]; ++i) {
if (strcmp(basename(argv[0]), init_applet[i]) == 0)
return (*init_applet_main[i])(argc, argv);
}
if (argc > 1 && strcmp(argv[1], "-x") == 0) {
if (strcmp(argv[2], "magisk") == 0)
return dump_magisk(argv[3], 0755);
else if (strcmp(argv[2], "manager") == 0)
return dump_manager(argv[3], 0644);
}
if (getpid() != 1)
return 1;
MagiskInit init(argv);
// Run the main routine
init.start();
// Close all file descriptors
for (int i = 0; i < 30; ++i)
close(i);
// Launch daemon to setup overlay
if (fork_dont_care() == 0)
init.setup_overlay();
init.re_exec_init();
}