Magisk/native/jni/init/rootdir.cpp

394 lines
10 KiB
C++
Raw Normal View History

2019-05-27 07:29:43 +00:00
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
2019-07-16 08:08:28 +00:00
#include <vector>
2019-05-27 07:29:43 +00:00
2020-03-09 08:50:30 +00:00
#include <magisk.hpp>
#include <magiskpolicy.hpp>
#include <utils.hpp>
2019-05-27 07:29:43 +00:00
2020-03-09 08:50:30 +00:00
#include "init.hpp"
#include "magiskrc.inc"
2019-05-27 07:29:43 +00:00
#ifdef USE_64BIT
#define LIBNAME "lib64"
#else
#define LIBNAME "lib"
#endif
using namespace std;
static void patch_socket_name(const char *path) {
char *buf;
2019-05-27 07:29:43 +00:00
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(buf + i, 16);
i += sizeof(MAIN_SOCKET);
2019-05-27 07:29:43 +00:00
}
}
munmap(buf, size);
}
2019-07-16 08:08:28 +00:00
static vector<raw_data> rc_list;
2019-06-26 06:31:59 +00:00
static void patch_init_rc(FILE *rc) {
Logical Resizable Android Partitions support The way how logical partition, or "Logical Resizable Android Partitions" as they say in AOSP source code, is setup makes it impossible to early mount the partitions from the shared super partition with just a few lines of code; in fact, AOSP has a whole "fs_mgr" folder which consist of multiple complex libraries, with 15K lines of code just to deal with the device mapper shenanigans. In order to keep the already overly complicated MagiskInit more managable, I chose NOT to go the route of including fs_mgr directly into MagiskInit. Luckily, starting from Android Q, Google decided to split init startup into 3 stages, with the first stage doing _only_ early mount. This is great news, because we can simply let the stock init do its own thing for us, and we intercept the bootup sequence. So the workflow can be visualized roughly below: Magisk First Stage --> First Stage Mount --> Magisk Second Stage --+ (MagiskInit) (Original Init) (MagiskInit) + + + ...Rest of the boot... <-- Second Stage <-- Selinux Setup <--+ (__________________ Original Init ____________________) The catch here is that after doing all the first stage mounting, /init will pivot /system as root directory (/), leaving us impossible to regain control after we hand it over. So the solution here is to patch fstab in /first_stage_ramdisk on-the-fly to redirect /system to /system_root, making the original init do all the hard work for us and mount required early mount partitions, but skips the step of switching root directory. It will also conveniently hand over execution back to MagiskInit, which we will reuse the routine for patching root directory in normal system-as-root situations.
2019-06-29 07:47:29 +00:00
file_readline("/init.rc", [=](string_view line) -> bool {
2019-06-26 06:31:59 +00:00
// 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;
});
2019-07-16 08:08:28 +00:00
fprintf(rc, "\n");
// Inject custom rc scripts
for (auto &d : rc_list)
fprintf(rc, "\n%s\n", d.buf);
rc_list.clear();
// Inject Magisk rc scripts
char pfd_svc[16], ls_svc[16], bc_svc[16];
gen_rand_str(pfd_svc, sizeof(pfd_svc));
gen_rand_str(ls_svc, sizeof(ls_svc));
gen_rand_str(bc_svc, sizeof(bc_svc));
2019-06-26 06:31:59 +00:00
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);
}
2019-05-27 07:29:43 +00:00
Introduce new boot flow to handle SAR 2SI The existing method for handling legacy SAR is: 1. Mount /sbin tmpfs overlay 2. Dump all patched/new files into /sbin 3. Magic mount root dir and re-exec patched stock init With Android 11 removing the /sbin folder, it is quite obvious that things completely break down right in step 1. To overcome this issue, we have to find a way to swap out the init binary AFTER we re-exec stock init. This is where 2SI comes to rescue! 2SI normal boot procedure is: 1st stage -> Load sepolicy -> 2nd stage -> boot continue... 2SI Magisk boot procedure is: MagiskInit 1st stage -> Stock 1st stage -> MagiskInit 2nd Stage -> -> Stock init load sepolicy -> Stock 2nd stage -> boot continue... As you can see, the trick is to make stock 1st stage init re-exec back into MagiskInit so we can do our setup. This is possible by manipulating some ramdisk files on initramfs based 2SI devices (old ass non SAR devices AND super modern devices like Pixel 3/4), but not possible on device that are stuck using legacy SAR (device that are not that modern but not too old, like Pixel 1/2. Fucking Google logic!!) This commit introduces a new way to intercept stock init re-exec flow: ptrace init with forked tracer, monitor PTRACE_EVENT_EXEC, then swap out the init file with bind mounts right before execv returns! Going through this flow however will lose some necessary backup files, so some bookkeeping has to be done by making the tracer hold these files in memory and act as a daemon. 2nd stage MagiskInit will ack the daemon to release these files at the correct time. It just works™ ¯\_(ツ)_/¯
2020-04-01 11:39:28 +00:00
static void load_overlay_rc(const char *overlay) {
auto dir = open_dir(overlay);
if (!dir) return;
int dfd = dirfd(dir.get());
2019-07-16 08:08:28 +00:00
// Do not allow overwrite init.rc
Introduce new boot flow to handle SAR 2SI The existing method for handling legacy SAR is: 1. Mount /sbin tmpfs overlay 2. Dump all patched/new files into /sbin 3. Magic mount root dir and re-exec patched stock init With Android 11 removing the /sbin folder, it is quite obvious that things completely break down right in step 1. To overcome this issue, we have to find a way to swap out the init binary AFTER we re-exec stock init. This is where 2SI comes to rescue! 2SI normal boot procedure is: 1st stage -> Load sepolicy -> 2nd stage -> boot continue... 2SI Magisk boot procedure is: MagiskInit 1st stage -> Stock 1st stage -> MagiskInit 2nd Stage -> -> Stock init load sepolicy -> Stock 2nd stage -> boot continue... As you can see, the trick is to make stock 1st stage init re-exec back into MagiskInit so we can do our setup. This is possible by manipulating some ramdisk files on initramfs based 2SI devices (old ass non SAR devices AND super modern devices like Pixel 3/4), but not possible on device that are stuck using legacy SAR (device that are not that modern but not too old, like Pixel 1/2. Fucking Google logic!!) This commit introduces a new way to intercept stock init re-exec flow: ptrace init with forked tracer, monitor PTRACE_EVENT_EXEC, then swap out the init file with bind mounts right before execv returns! Going through this flow however will lose some necessary backup files, so some bookkeeping has to be done by making the tracer hold these files in memory and act as a daemon. 2nd stage MagiskInit will ack the daemon to release these files at the correct time. It just works™ ¯\_(ツ)_/¯
2020-04-01 11:39:28 +00:00
unlinkat(dfd, "init.rc", 0);
for (dirent *entry; (entry = readdir(dir.get()));) {
2019-07-16 08:08:28 +00:00
if (strend(entry->d_name, ".rc") == 0) {
LOGD("Found rc script [%s]\n", entry->d_name);
Introduce new boot flow to handle SAR 2SI The existing method for handling legacy SAR is: 1. Mount /sbin tmpfs overlay 2. Dump all patched/new files into /sbin 3. Magic mount root dir and re-exec patched stock init With Android 11 removing the /sbin folder, it is quite obvious that things completely break down right in step 1. To overcome this issue, we have to find a way to swap out the init binary AFTER we re-exec stock init. This is where 2SI comes to rescue! 2SI normal boot procedure is: 1st stage -> Load sepolicy -> 2nd stage -> boot continue... 2SI Magisk boot procedure is: MagiskInit 1st stage -> Stock 1st stage -> MagiskInit 2nd Stage -> -> Stock init load sepolicy -> Stock 2nd stage -> boot continue... As you can see, the trick is to make stock 1st stage init re-exec back into MagiskInit so we can do our setup. This is possible by manipulating some ramdisk files on initramfs based 2SI devices (old ass non SAR devices AND super modern devices like Pixel 3/4), but not possible on device that are stuck using legacy SAR (device that are not that modern but not too old, like Pixel 1/2. Fucking Google logic!!) This commit introduces a new way to intercept stock init re-exec flow: ptrace init with forked tracer, monitor PTRACE_EVENT_EXEC, then swap out the init file with bind mounts right before execv returns! Going through this flow however will lose some necessary backup files, so some bookkeeping has to be done by making the tracer hold these files in memory and act as a daemon. 2nd stage MagiskInit will ack the daemon to release these files at the correct time. It just works™ ¯\_(ツ)_/¯
2020-04-01 11:39:28 +00:00
int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
2019-07-16 08:08:28 +00:00
raw_data data;
fd_full_read(rc, data.buf, data.sz);
close(rc);
rc_list.push_back(std::move(data));
Introduce new boot flow to handle SAR 2SI The existing method for handling legacy SAR is: 1. Mount /sbin tmpfs overlay 2. Dump all patched/new files into /sbin 3. Magic mount root dir and re-exec patched stock init With Android 11 removing the /sbin folder, it is quite obvious that things completely break down right in step 1. To overcome this issue, we have to find a way to swap out the init binary AFTER we re-exec stock init. This is where 2SI comes to rescue! 2SI normal boot procedure is: 1st stage -> Load sepolicy -> 2nd stage -> boot continue... 2SI Magisk boot procedure is: MagiskInit 1st stage -> Stock 1st stage -> MagiskInit 2nd Stage -> -> Stock init load sepolicy -> Stock 2nd stage -> boot continue... As you can see, the trick is to make stock 1st stage init re-exec back into MagiskInit so we can do our setup. This is possible by manipulating some ramdisk files on initramfs based 2SI devices (old ass non SAR devices AND super modern devices like Pixel 3/4), but not possible on device that are stuck using legacy SAR (device that are not that modern but not too old, like Pixel 1/2. Fucking Google logic!!) This commit introduces a new way to intercept stock init re-exec flow: ptrace init with forked tracer, monitor PTRACE_EVENT_EXEC, then swap out the init file with bind mounts right before execv returns! Going through this flow however will lose some necessary backup files, so some bookkeeping has to be done by making the tracer hold these files in memory and act as a daemon. 2nd stage MagiskInit will ack the daemon to release these files at the correct time. It just works™ ¯\_(ツ)_/¯
2020-04-01 11:39:28 +00:00
unlinkat(dfd, entry->d_name, 0);
2019-07-16 08:08:28 +00:00
}
}
}
2019-12-12 08:25:48 +00:00
void RootFSInit::setup_rootfs() {
2019-06-24 08:31:42 +00:00
if (patch_sepolicy()) {
2019-05-27 07:29:43 +00:00
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);
2019-06-25 09:38:34 +00:00
break;
2019-05-27 07:29:43 +00:00
}
}
munmap(addr, size);
}
2019-07-16 08:08:28 +00:00
// Handle overlays
Introduce new boot flow to handle SAR 2SI The existing method for handling legacy SAR is: 1. Mount /sbin tmpfs overlay 2. Dump all patched/new files into /sbin 3. Magic mount root dir and re-exec patched stock init With Android 11 removing the /sbin folder, it is quite obvious that things completely break down right in step 1. To overcome this issue, we have to find a way to swap out the init binary AFTER we re-exec stock init. This is where 2SI comes to rescue! 2SI normal boot procedure is: 1st stage -> Load sepolicy -> 2nd stage -> boot continue... 2SI Magisk boot procedure is: MagiskInit 1st stage -> Stock 1st stage -> MagiskInit 2nd Stage -> -> Stock init load sepolicy -> Stock 2nd stage -> boot continue... As you can see, the trick is to make stock 1st stage init re-exec back into MagiskInit so we can do our setup. This is possible by manipulating some ramdisk files on initramfs based 2SI devices (old ass non SAR devices AND super modern devices like Pixel 3/4), but not possible on device that are stuck using legacy SAR (device that are not that modern but not too old, like Pixel 1/2. Fucking Google logic!!) This commit introduces a new way to intercept stock init re-exec flow: ptrace init with forked tracer, monitor PTRACE_EVENT_EXEC, then swap out the init file with bind mounts right before execv returns! Going through this flow however will lose some necessary backup files, so some bookkeeping has to be done by making the tracer hold these files in memory and act as a daemon. 2nd stage MagiskInit will ack the daemon to release these files at the correct time. It just works™ ¯\_(ツ)_/¯
2020-04-01 11:39:28 +00:00
if (access("/overlay.d", F_OK) == 0) {
2019-07-16 08:08:28 +00:00
LOGD("Merge overlay.d\n");
Introduce new boot flow to handle SAR 2SI The existing method for handling legacy SAR is: 1. Mount /sbin tmpfs overlay 2. Dump all patched/new files into /sbin 3. Magic mount root dir and re-exec patched stock init With Android 11 removing the /sbin folder, it is quite obvious that things completely break down right in step 1. To overcome this issue, we have to find a way to swap out the init binary AFTER we re-exec stock init. This is where 2SI comes to rescue! 2SI normal boot procedure is: 1st stage -> Load sepolicy -> 2nd stage -> boot continue... 2SI Magisk boot procedure is: MagiskInit 1st stage -> Stock 1st stage -> MagiskInit 2nd Stage -> -> Stock init load sepolicy -> Stock 2nd stage -> boot continue... As you can see, the trick is to make stock 1st stage init re-exec back into MagiskInit so we can do our setup. This is possible by manipulating some ramdisk files on initramfs based 2SI devices (old ass non SAR devices AND super modern devices like Pixel 3/4), but not possible on device that are stuck using legacy SAR (device that are not that modern but not too old, like Pixel 1/2. Fucking Google logic!!) This commit introduces a new way to intercept stock init re-exec flow: ptrace init with forked tracer, monitor PTRACE_EVENT_EXEC, then swap out the init file with bind mounts right before execv returns! Going through this flow however will lose some necessary backup files, so some bookkeeping has to be done by making the tracer hold these files in memory and act as a daemon. 2nd stage MagiskInit will ack the daemon to release these files at the correct time. It just works™ ¯\_(ツ)_/¯
2020-04-01 11:39:28 +00:00
load_overlay_rc("/overlay.d");
2020-04-02 09:17:45 +00:00
mv_path("/overlay.d", "/");
2019-07-16 08:08:28 +00:00
}
2019-05-27 07:29:43 +00:00
// Patch init.rc
FILE *rc = xfopen("/init.p.rc", "we");
2019-06-26 06:31:59 +00:00
patch_init_rc(rc);
2019-05-27 07:29:43 +00:00
fclose(rc);
clone_attr("/init.rc", "/init.p.rc");
rename("/init.p.rc", "/init.rc");
// Create hardlink mirror of /sbin to /root
mkdir("/root", 0750);
clone_attr("/sbin", "/root");
2020-04-02 09:17:45 +00:00
link_path("/sbin", "/root");
2019-05-27 07:29:43 +00:00
2019-06-22 10:14:33 +00:00
// Dump magiskinit as magisk
Introduce new boot flow to handle SAR 2SI The existing method for handling legacy SAR is: 1. Mount /sbin tmpfs overlay 2. Dump all patched/new files into /sbin 3. Magic mount root dir and re-exec patched stock init With Android 11 removing the /sbin folder, it is quite obvious that things completely break down right in step 1. To overcome this issue, we have to find a way to swap out the init binary AFTER we re-exec stock init. This is where 2SI comes to rescue! 2SI normal boot procedure is: 1st stage -> Load sepolicy -> 2nd stage -> boot continue... 2SI Magisk boot procedure is: MagiskInit 1st stage -> Stock 1st stage -> MagiskInit 2nd Stage -> -> Stock init load sepolicy -> Stock 2nd stage -> boot continue... As you can see, the trick is to make stock 1st stage init re-exec back into MagiskInit so we can do our setup. This is possible by manipulating some ramdisk files on initramfs based 2SI devices (old ass non SAR devices AND super modern devices like Pixel 3/4), but not possible on device that are stuck using legacy SAR (device that are not that modern but not too old, like Pixel 1/2. Fucking Google logic!!) This commit introduces a new way to intercept stock init re-exec flow: ptrace init with forked tracer, monitor PTRACE_EVENT_EXEC, then swap out the init file with bind mounts right before execv returns! Going through this flow however will lose some necessary backup files, so some bookkeeping has to be done by making the tracer hold these files in memory and act as a daemon. 2nd stage MagiskInit will ack the daemon to release these files at the correct time. It just works™ ¯\_(ツ)_/¯
2020-04-01 11:39:28 +00:00
int fd = xopen("/sbin/magisk", O_WRONLY | O_CREAT, 0755);
2019-05-27 07:29:43 +00:00
write(fd, self.buf, self.sz);
close(fd);
}
2019-06-26 04:34:02 +00:00
bool MagiskInit::patch_sepolicy(const char *file) {
bool patch_init = false;
2019-05-27 07:29:43 +00:00
if (access(SPLIT_PLAT_CIL, R_OK) == 0) {
LOGD("sepol: split policy\n");
patch_init = true;
2019-05-27 07:29:43 +00:00
} 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);
mount_list.emplace_back(SELINUX_MNT);
if (patch_init)
load_split_cil();
2019-05-27 07:29:43 +00:00
sepol_magisk_rules();
sepol_allow(SEPOL_PROC_DOMAIN, ALL, ALL, ALL);
// Custom rules
if (auto dir = xopen_dir(persist_dir); dir) {
char path[4096];
for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (entry->d_name == "."sv || entry->d_name == ".."sv)
continue;
snprintf(path, sizeof(path), "%s/%s/sepolicy.rule", persist_dir, entry->d_name);
if (access(path, R_OK) == 0) {
LOGD("Loading custom sepolicy patch: %s\n", path);
load_rule_file(path);
}
}
}
2019-06-26 04:34:02 +00:00
dump_policydb(file);
destroy_policydb();
2019-05-27 07:29:43 +00:00
// Remove OnePlus stupid debug sepolicy and use our own
if (access("/sepolicy_debug", F_OK) == 0) {
unlink("/sepolicy_debug");
link("/sepolicy", "/sepolicy_debug");
}
return patch_init;
2019-05-27 07:29:43 +00:00
}
2019-06-22 10:14:33 +00:00
2019-06-24 08:21:33 +00:00
static void sbin_overlay(const raw_data &self, const raw_data &config) {
mount_sbin();
2019-06-24 08:21:33 +00:00
// Dump binaries
xmkdir(MAGISKTMP, 0755);
int fd = xopen(MAGISKTMP "/config", O_WRONLY | O_CREAT, 0000);
xwrite(fd, config.buf, config.sz);
close(fd);
fd = xopen("/sbin/magiskinit", O_WRONLY | O_CREAT, 0755);
xwrite(fd, self.buf, self.sz);
close(fd);
2020-01-10 19:20:59 +00:00
dump_magisk("/sbin/magisk", 0755);
patch_socket_name("/sbin/magisk");
2019-06-24 08:21:33 +00:00
// Create applet symlinks
char path[64];
for (int i = 0; applet_names[i]; ++i) {
sprintf(path, "/sbin/%s", applet_names[i]);
2019-07-05 00:58:38 +00:00
xsymlink("./magisk", path);
2019-06-24 08:21:33 +00:00
}
2019-07-05 00:58:38 +00:00
xsymlink("./magiskinit", "/sbin/magiskpolicy");
xsymlink("./magiskinit", "/sbin/supolicy");
2019-06-24 08:21:33 +00:00
}
2020-01-09 15:42:27 +00:00
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()));) {
2019-12-06 20:31:49 +00:00
if (entry->d_name == "."sv || entry->d_name == ".."sv)
continue;
2020-01-09 15:42:27 +00:00
string sbin_path = "/sbin/"s + entry->d_name;
2019-12-06 20:31:49 +00:00
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));
2020-01-09 15:42:27 +00:00
xsymlink(buf, sbin_path.data());
2019-12-06 20:31:49 +00:00
} else {
sprintf(buf, "%s/%s", mirror, entry->d_name);
if (use_bind_mount) {
2020-01-09 15:42:27 +00:00
auto mode = st.st_mode & 0777;
2019-12-06 20:31:49 +00:00
// Create dummy
if (S_ISDIR(st.st_mode))
2020-01-09 15:42:27 +00:00
xmkdir(sbin_path.data(), mode);
2019-12-06 20:31:49 +00:00
else
2020-01-09 15:42:27 +00:00
close(xopen(sbin_path.data(), O_CREAT | O_WRONLY | O_CLOEXEC, mode));
2019-12-06 20:31:49 +00:00
2020-01-09 15:42:27 +00:00
xmount(buf, sbin_path.data(), nullptr, MS_BIND, nullptr);
} else {
xsymlink(buf, sbin_path.data());
}
2019-12-06 20:31:49 +00:00
}
}
}
2019-06-24 08:21:33 +00:00
#define ROOTMIR MIRRDIR "/system_root"
#define ROOTBLK BLOCKDIR "/system_root"
2019-06-26 04:34:02 +00:00
#define MONOPOLICY "/sepolicy"
#define PATCHPOLICY "/sbin/.se"
#define LIBSELINUX "/system/" LIBNAME "/libselinux.so"
2019-06-24 08:21:33 +00:00
static string magic_mount_list;
static void magic_mount(const string &sdir, const string &ddir = "") {
auto dir = xopen_dir(sdir.data());
for (dirent *entry; (entry = readdir(dir.get()));) {
2019-07-16 08:08:28 +00:00
if (entry->d_name == "."sv || entry->d_name == ".."sv)
continue;
string src = sdir + "/" + entry->d_name;
string dest = ddir + "/" + entry->d_name;
2019-07-16 08:08:28 +00:00
if (access(dest.data(), F_OK) == 0) {
if (entry->d_type == DT_DIR) {
// Recursive
magic_mount(src, dest);
2019-07-16 08:08:28 +00:00
} 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';
2019-07-16 08:08:28 +00:00
}
}
}
}
2019-09-22 09:20:51 +00:00
void SARBase::patch_rootdir() {
2019-06-24 08:21:33 +00:00
sbin_overlay(self, config);
// Mount system_root mirror
2020-04-01 05:41:25 +00:00
struct stat st;
xstat("/", &st);
xmkdir(ROOTMIR, 0755);
2020-04-01 05:41:25 +00:00
mknod(ROOTBLK, S_IFBLK | 0600, st.st_dev);
2019-06-24 08:21:33 +00:00
if (xmount(ROOTBLK, ROOTMIR, "ext4", MS_RDONLY, nullptr))
xmount(ROOTBLK, ROOTMIR, "erofs", MS_RDONLY, nullptr);
// Recreate original sbin structure
2020-01-09 15:42:27 +00:00
recreate_sbin(ROOTMIR "/sbin", true);
2019-06-26 04:34:02 +00:00
2019-07-16 08:08:28 +00:00
// Patch init
raw_data init;
2019-06-26 04:34:02 +00:00
file_attr attr;
bool redirect = false;
2019-12-06 20:31:49 +00:00
int src = xopen("/init", O_RDONLY | O_CLOEXEC);
2019-07-16 08:08:28 +00:00
fd_full_read(src, init.buf, init.sz);
2019-06-26 04:34:02 +00:00
fgetattr(src, &attr);
close(src);
2019-07-16 08:08:28 +00:00
uint8_t *eof = init.buf + init.sz;
for (uint8_t *p = init.buf; p < eof; ++p) {
2019-06-26 04:34:02 +00:00
if (memcmp(p, SPLIT_PLAT_CIL, sizeof(SPLIT_PLAT_CIL)) == 0) {
// Force init to load monolithic policy
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, MONOPOLICY, sizeof(MONOPOLICY)) == 0) {
// Redirect /sepolicy to tmpfs
LOGD("Patch init [" MONOPOLICY "] -> [" PATCHPOLICY "]\n");
memcpy(p, PATCHPOLICY, sizeof(PATCHPOLICY));
redirect = true;
p += sizeof(MONOPOLICY) - 1;
}
}
2019-07-16 08:08:28 +00:00
xmkdir(ROOTOVL, 0);
2019-12-06 20:31:49 +00:00
int dest = xopen(ROOTOVL "/init", O_CREAT | O_WRONLY | O_CLOEXEC);
2019-07-16 08:08:28 +00:00
xwrite(dest, init.buf, init.sz);
2019-06-26 04:34:02 +00:00
fsetattr(dest, &attr);
close(dest);
2019-07-16 08:08:28 +00:00
// Patch libselinux
2019-06-26 04:34:02 +00:00
if (!redirect) {
2019-07-16 08:08:28 +00:00
raw_data lib;
2019-06-26 04:34:02 +00:00
// init is dynamically linked, need to patch libselinux
2019-07-16 08:08:28 +00:00
full_read(LIBSELINUX, lib.buf, lib.sz);
2019-06-26 04:34:02 +00:00
getattr(LIBSELINUX, &attr);
2019-07-16 08:08:28 +00:00
eof = lib.buf + lib.sz;
for (uint8_t *p = lib.buf; p < eof; ++p) {
2019-06-26 04:34:02 +00:00
if (memcmp(p, MONOPOLICY, sizeof(MONOPOLICY)) == 0) {
// Redirect /sepolicy to tmpfs
LOGD("Patch libselinux.so [" MONOPOLICY "] -> [" PATCHPOLICY "]\n");
memcpy(p, PATCHPOLICY, sizeof(PATCHPOLICY));
break;
}
}
2019-07-16 08:08:28 +00:00
xmkdir(ROOTOVL "/system", 0755);
xmkdir(ROOTOVL "/system/" LIBNAME, 0755);
dest = xopen(ROOTOVL LIBSELINUX, O_CREAT | O_WRONLY | O_CLOEXEC);
xwrite(dest, lib.buf, lib.sz);
2019-06-26 04:34:02 +00:00
fsetattr(dest, &attr);
close(dest);
}
2019-07-16 08:08:28 +00:00
// sepolicy
2019-06-26 04:34:02 +00:00
patch_sepolicy(PATCHPOLICY);
2019-06-26 06:31:59 +00:00
2019-07-16 08:08:28 +00:00
// Handle overlay
Introduce new boot flow to handle SAR 2SI The existing method for handling legacy SAR is: 1. Mount /sbin tmpfs overlay 2. Dump all patched/new files into /sbin 3. Magic mount root dir and re-exec patched stock init With Android 11 removing the /sbin folder, it is quite obvious that things completely break down right in step 1. To overcome this issue, we have to find a way to swap out the init binary AFTER we re-exec stock init. This is where 2SI comes to rescue! 2SI normal boot procedure is: 1st stage -> Load sepolicy -> 2nd stage -> boot continue... 2SI Magisk boot procedure is: MagiskInit 1st stage -> Stock 1st stage -> MagiskInit 2nd Stage -> -> Stock init load sepolicy -> Stock 2nd stage -> boot continue... As you can see, the trick is to make stock 1st stage init re-exec back into MagiskInit so we can do our setup. This is possible by manipulating some ramdisk files on initramfs based 2SI devices (old ass non SAR devices AND super modern devices like Pixel 3/4), but not possible on device that are stuck using legacy SAR (device that are not that modern but not too old, like Pixel 1/2. Fucking Google logic!!) This commit introduces a new way to intercept stock init re-exec flow: ptrace init with forked tracer, monitor PTRACE_EVENT_EXEC, then swap out the init file with bind mounts right before execv returns! Going through this flow however will lose some necessary backup files, so some bookkeeping has to be done by making the tracer hold these files in memory and act as a daemon. 2nd stage MagiskInit will ack the daemon to release these files at the correct time. It just works™ ¯\_(ツ)_/¯
2020-04-01 11:39:28 +00:00
struct sockaddr_un sun{};
socklen_t len = setup_sockaddr(&sun);
int socketfd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (connect(socketfd, (struct sockaddr*) &sun, len) == 0) {
LOGD("ACK init tracer to write backup files\n");
int ack;
// Wait for init tracer finish copying files
read(socketfd, &ack, sizeof(ack));
} else {
LOGD("Restore backup files locally\n");
restore_folder(ROOTOVL, overlays);
overlays.clear();
}
close(socketfd);
if (access(ROOTOVL "/sbin", F_OK) == 0) {
file_attr a;
getattr("/sbin", &a);
cp_afc(ROOTOVL "/sbin", "/sbin");
rm_rf(ROOTOVL "/sbin");
setattr("/sbin", &a);
2019-07-16 08:08:28 +00:00
}
// Patch init.rc
FILE *rc = xfopen(ROOTOVL "/init.rc", "we");
2019-06-26 06:31:59 +00:00
patch_init_rc(rc);
fclose(rc);
2019-07-16 08:08:28 +00:00
clone_attr("/init.rc", ROOTOVL "/init.rc");
// Mount rootdir
magic_mount(ROOTOVL);
dest = xopen(ROOTMNT, O_WRONLY | O_CREAT | O_CLOEXEC);
write(dest, magic_mount_list.data(), magic_mount_list.length());
close(dest);
2019-06-24 08:21:33 +00:00
}
2019-06-22 10:14:33 +00:00
int magisk_proxy_main(int argc, char *argv[]) {
setup_klog();
raw_data config;
raw_data self;
2019-07-16 08:08:28 +00:00
full_read("/sbin/magisk", self.buf, self.sz);
full_read("/.backup/.magisk", config.buf, config.sz);
2019-06-22 10:14:33 +00:00
xmount(nullptr, "/", nullptr, MS_REMOUNT, nullptr);
unlink("/sbin/magisk");
rm_rf("/.backup");
2019-06-24 08:21:33 +00:00
sbin_overlay(self, config);
2019-06-22 10:14:33 +00:00
// Create symlinks pointing back to /root
2020-01-09 15:42:27 +00:00
recreate_sbin("/root", false);
2019-06-22 10:14:33 +00:00
setenv("REMOUNT_ROOT", "1", 1);
execv("/sbin/magisk", argv);
return 1;
}