diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 8ed7591bd..901b4bcf9 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -80,7 +80,10 @@ LOCAL_C_INCLUDES := \ $(LIBUTILS) LOCAL_SRC_FILES := \ - core/init.cpp \ + init/init.cpp \ + init/early_mount.cpp \ + init/rootfs.cpp \ + init/getinfo.cpp \ magiskpolicy/api.cpp \ magiskpolicy/magiskpolicy.cpp \ magiskpolicy/rules.cpp \ diff --git a/native/jni/core/init.cpp b/native/jni/core/init.cpp deleted file mode 100644 index e7f160a5b..000000000 --- a/native/jni/core/init.cpp +++ /dev/null @@ -1,749 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#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 int vprintk(const char *fmt, va_list ap) { - fprintf(kmsg, "magiskinit: "); - return vfprintf(kmsg, fmt, ap); -} - -static void setup_klog() { - mknod("/kmsg", S_IFCHR | 0666, makedev(1, 11)); - int fd = xopen("/kmsg", O_WRONLY | O_CLOEXEC); - kmsg = fdopen(fd, "w"); - 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(); - void re_exec_init(); - -public: - explicit MagiskInit(char *argv[]) : argv(argv) {} - void start(); - void test(); -}; - -static inline void parse_cmdline(const std::function &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]; - vector events; - constexpr const char *name = "/event"; - - for (int minor = 64; minor < 96; ++minor) { - if (mknod(name, S_IFCHR | 0444, makedev(13, minor))) { - PLOGE("mknod"); - 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); - } - - if (events.empty()) - return false; - - RunFinally fin([&]() -> void { - for (const int &fd : events) - close(fd); - }); - - // Return true if volume key up is hold 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; -} - -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") || strstr(value, "hi6250"); - } - }); - - 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 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 - char *part = rtrim(strrchr(path, '/') + 1); - sprintf(partname, "%s%s", part, strend(part, 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); -} - -static 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); -} - -constexpr 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_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); - LOGD("Inject magisk services: [%s] [%s] [%s]\n", pfd_svc, ls_svc, bc_svc); - 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); - close(sbin); - - LOGD("Mount /sbin tmpfs overlay\n"); - xmount("tmpfs", "/sbin", "tmpfs", 0, "mode=755"); - sbin = xopen("/sbin", O_RDONLY | O_CLOEXEC); - - char path[64]; - - // Create symlinks pointing back to /root - DIR *dir = xfdopendir(rootdir); - struct dirent *entry; - while((entry = xreaddir(dir))) { - if (entry->d_name == "."sv || entry->d_name == ".."sv) - continue; - sprintf(path, "/root/%s", entry->d_name); - xsymlinkat(path, sbin, entry->d_name); - } - - // 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"); - 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"); - } - - // Create applet symlinks - for (int i = 0; applet_names[i]; ++i) { - sprintf(path, "/sbin/%s", applet_names[i]); - xsymlink("/sbin/magisk", path); - } - xsymlink("/sbin/magiskinit", "/sbin/magiskpolicy"); - xsymlink("/sbin/magiskinit", "/sbin/supolicy"); - - close(rootdir); - close(sbin); -} - -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); -} - -void MagiskInit::re_exec_init() { - LOGD("Re-exec /init\n"); - 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(); - re_exec_init(); -} - -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(); -} diff --git a/native/jni/init/early_mount.cpp b/native/jni/init/early_mount.cpp new file mode 100644 index 000000000..b3164839f --- /dev/null +++ b/native/jni/init/early_mount.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include + +#include +#include + +#include "init.h" + +using namespace std; + +static 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 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 + char *part = rtrim(strrchr(path, '/') + 1); + sprintf(partname, "%s%s", part, strend(part, 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; +} + +#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); +} diff --git a/native/jni/init/getinfo.cpp b/native/jni/init/getinfo.cpp new file mode 100644 index 000000000..a90c7b916 --- /dev/null +++ b/native/jni/init/getinfo.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "init.h" + +using namespace std; + +#define DEFAULT_DT_DIR "/proc/device-tree/firmware/android" + +static void parse_cmdline(const std::function &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]; + vector events; + constexpr const char *name = "/event"; + + for (int minor = 64; minor < 96; ++minor) { + if (mknod(name, S_IFCHR | 0444, makedev(13, minor))) { + PLOGE("mknod"); + 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); + } + + if (events.empty()) + return false; + + RunFinally fin([&]() -> void { + for (const int &fd : events) + close(fd); + }); + + // Return true if volume key up is hold 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; +} + +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") || strstr(value, "hi6250"); + } + }); + + 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); +} diff --git a/native/jni/init/init.cpp b/native/jni/init/init.cpp new file mode 100644 index 000000000..80d50754a --- /dev/null +++ b/native/jni/init/init.cpp @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "binaries.h" +#ifdef USE_64BIT +#include "binaries_arch64.h" +#else +#include "binaries_arch.h" +#endif + +#include "init.h" + +using namespace std; + +#ifdef MAGISK_DEBUG +static FILE *kmsg; +static int vprintk(const char *fmt, va_list ap) { + fprintf(kmsg, "magiskinit: "); + return vfprintf(kmsg, fmt, ap); +} + +static void setup_klog() { + mknod("/kmsg", S_IFCHR | 0666, makedev(1, 11)); + int fd = xopen("/kmsg", O_WRONLY | O_CLOEXEC); + kmsg = fdopen(fd, "w"); + 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 }; + +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); +} + +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; +} + +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(); + } + } +} + +#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); +} + +void MagiskInit::re_exec_init() { + LOGD("Re-exec /init\n"); + 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(); + re_exec_init(); +} + +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(); +} diff --git a/native/jni/init/init.h b/native/jni/init/init.h new file mode 100644 index 000000000..56b657a56 --- /dev/null +++ b/native/jni/init/init.h @@ -0,0 +1,56 @@ +#include + +struct cmdline { + bool system_as_root; + char slot[3]; + char dt_dir[128]; +}; + +struct raw_data { + void *buf; + size_t sz; +}; + +struct device { + int major; + int minor; + char devname[32]; + char partname[32]; +}; + +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(); + void re_exec_init(); + +public: + explicit MagiskInit(char *argv[]) : argv(argv) {} + void start(); + void test(); +}; + +static inline bool is_lnk(const char *name) { + struct stat st; + if (lstat(name, &st)) + return false; + return S_ISLNK(st.st_mode); +} + +int dump_magisk(const char *path, mode_t mode); diff --git a/native/jni/core/magiskrc.h b/native/jni/init/magiskrc.h similarity index 100% rename from native/jni/core/magiskrc.h rename to native/jni/init/magiskrc.h diff --git a/native/jni/init/rootfs.cpp b/native/jni/init/rootfs.cpp new file mode 100644 index 000000000..609c39b3b --- /dev/null +++ b/native/jni/init/rootfs.cpp @@ -0,0 +1,216 @@ +#include +#include +#include + +#include +#include + +#include "init.h" +#include "magiskrc.h" + +#ifdef USE_64BIT +#define LIBNAME "lib64" +#else +#define LIBNAME "lib" +#endif + +using namespace std; + +static 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); +} + +constexpr 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_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); + LOGD("Inject magisk services: [%s] [%s] [%s]\n", pfd_svc, ls_svc, bc_svc); + 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); + close(sbin); + + LOGD("Mount /sbin tmpfs overlay\n"); + xmount("tmpfs", "/sbin", "tmpfs", 0, "mode=755"); + sbin = xopen("/sbin", O_RDONLY | O_CLOEXEC); + + char path[64]; + + // Create symlinks pointing back to /root + DIR *dir = xfdopendir(rootdir); + struct dirent *entry; + while((entry = xreaddir(dir))) { + if (entry->d_name == "."sv || entry->d_name == ".."sv) + continue; + sprintf(path, "/root/%s", entry->d_name); + xsymlinkat(path, sbin, entry->d_name); + } + + // 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"); + 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"); + } + + // Create applet symlinks + for (int i = 0; applet_names[i]; ++i) { + sprintf(path, "/sbin/%s", applet_names[i]); + xsymlink("/sbin/magisk", path); + } + xsymlink("/sbin/magiskinit", "/sbin/magiskpolicy"); + xsymlink("/sbin/magiskinit", "/sbin/supolicy"); + + close(rootdir); + close(sbin); +} + +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; +}