Magisk/native/jni/init/rootdir.cpp

521 lines
13 KiB
C++
Raw Normal View History

2019-05-27 09:29:43 +02:00
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
2019-07-16 10:08:28 +02:00
#include <vector>
2019-05-27 09:29:43 +02:00
#include <magisk.h>
2019-07-01 04:09:31 +02:00
#include <magiskpolicy.h>
2019-05-27 09:29:43 +02:00
#include <utils.h>
#include "init.h"
2019-06-22 12:14:33 +02:00
#include "flags.h"
2019-05-27 09:29:43 +02:00
#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) {
char *buf;
2019-05-27 09:29:43 +02: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, sizeof(MAIN_SOCKET));
i += sizeof(MAIN_SOCKET);
2019-05-27 09:29:43 +02:00
}
}
munmap(buf, size);
}
2019-07-16 10:08:28 +02:00
static vector<raw_data> rc_list;
2019-06-26 08:31:59 +02: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 09:47:29 +02:00
file_readline("/init.rc", [=](string_view line) -> bool {
2019-06-26 08:31:59 +02: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 10:08:28 +02: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 08:31:59 +02: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 09:29:43 +02:00
2019-07-16 10:08:28 +02:00
static void load_overlay_rc(int dirfd) {
// Do not allow overwrite init.rc
unlinkat(dirfd, "init.rc", 0);
DIR *dir = fdopendir(dirfd);
for (dirent *entry; (entry = readdir(dir));) {
if (strend(entry->d_name, ".rc") == 0) {
LOGD("Found rc script [%s]\n", entry->d_name);
int rc = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
raw_data data;
fd_full_read(rc, data.buf, data.sz);
close(rc);
rc_list.push_back(std::move(data));
unlinkat(dirfd, entry->d_name, 0);
}
}
rewinddir(dir);
}
2019-09-22 11:20:51 +02:00
void RootFSBase::setup_rootfs() {
2019-06-24 10:31:42 +02:00
if (patch_sepolicy()) {
2019-05-27 09:29:43 +02: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 11:38:34 +02:00
break;
2019-05-27 09:29:43 +02:00
}
}
munmap(addr, size);
}
2019-07-16 10:08:28 +02:00
// Handle legacy overlays
2019-05-27 09:29:43 +02:00
int fd = open("/overlay", O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
LOGD("Merge overlay folder\n");
mv_dir(fd, root);
close(fd);
rmdir("/overlay");
}
2019-07-16 10:08:28 +02:00
// Handle overlays
fd = open("/overlay.d", O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
LOGD("Merge overlay.d\n");
load_overlay_rc(fd);
mv_dir(fd, root);
close(fd);
rmdir("/overlay.d");
}
2019-05-27 09:29:43 +02:00
// Patch init.rc
FILE *rc = xfopen("/init.p.rc", "we");
2019-06-26 08:31:59 +02:00
patch_init_rc(rc);
2019-05-27 09:29:43 +02: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");
int rootdir = xopen("/root", O_RDONLY | O_CLOEXEC);
int sbin = xopen("/sbin", O_RDONLY | O_CLOEXEC);
link_dir(sbin, rootdir);
close(sbin);
2019-06-22 12:14:33 +02:00
// Dump magiskinit as magisk
fd = xopen("/sbin/magisk", O_WRONLY | O_CREAT, 0755);
2019-05-27 09:29:43 +02:00
write(fd, self.buf, self.sz);
close(fd);
}
2019-06-24 10:50:47 +02:00
void SARCompatInit::setup_rootfs() {
// 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);
2019-09-22 11:20:51 +02:00
RootFSBase::setup_rootfs();
2019-06-24 10:50:47 +02:00
}
2019-06-26 06:34:02 +02:00
bool MagiskInit::patch_sepolicy(const char *file) {
bool patch_init = false;
2019-05-27 09:29:43 +02:00
if (access(SPLIT_PLAT_CIL, R_OK) == 0) {
LOGD("sepol: split policy\n");
patch_init = true;
2019-05-27 09:29:43 +02: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);
if (patch_init)
load_split_cil();
2019-05-27 09:29:43 +02:00
sepol_magisk_rules();
sepol_allow(SEPOL_PROC_DOMAIN, ALL, ALL, ALL);
2019-06-26 06:34:02 +02:00
dump_policydb(file);
2019-05-27 09:29:43 +02: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 09:29:43 +02:00
}
2019-06-22 12:14:33 +02:00
2019-06-26 08:31:59 +02:00
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"
;
2019-06-24 10:21:33 +02:00
static void sbin_overlay(const raw_data &self, const raw_data &config) {
LOGD("Mount /sbin tmpfs overlay\n");
xmount("tmpfs", "/sbin", "tmpfs", 0, "mode=755");
// 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);
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);
xwrite(fd, wrapper, sizeof(wrapper) - 1);
close(fd);
} else {
dump_magisk("/sbin/magisk", 0755);
patch_socket_name("/sbin/magisk");
}
// Create applet symlinks
char path[64];
for (int i = 0; applet_names[i]; ++i) {
sprintf(path, "/sbin/%s", applet_names[i]);
2019-07-05 02:58:38 +02:00
xsymlink("./magisk", path);
2019-06-24 10:21:33 +02:00
}
2019-07-05 02:58:38 +02:00
xsymlink("./magiskinit", "/sbin/magiskpolicy");
xsymlink("./magiskinit", "/sbin/supolicy");
2019-06-24 10:21:33 +02:00
}
#define ROOTMIR MIRRDIR "/system_root"
#define ROOTBLK BLOCKDIR "/system_root"
2019-06-26 06:34:02 +02:00
#define MONOPOLICY "/sepolicy"
#define PATCHPOLICY "/sbin/.se"
#define LIBSELINUX "/system/" LIBNAME "/libselinux.so"
2019-06-24 10:21:33 +02:00
static string mount_list;
2019-07-16 10:08:28 +02:00
static void magic_mount(int dirfd, const string &path) {
DIR *dir = xfdopendir(dirfd);
for (dirent *entry; (entry = readdir(dir));) {
if (entry->d_name == "."sv || entry->d_name == ".."sv)
continue;
string dest = path + "/" + entry->d_name;
if (access(dest.data(), F_OK) == 0) {
if (entry->d_type == DT_DIR) {
// Recursive
int fd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
magic_mount(fd, dest);
close(fd);
} else {
string src = ROOTOVL + dest;
LOGD("Mount [%s] -> [%s]\n", src.data(), dest.data());
xmount(src.data(), dest.data(), nullptr, MS_BIND, nullptr);
mount_list += dest;
mount_list += '\n';
2019-07-16 10:08:28 +02:00
}
}
}
}
2019-09-22 11:20:51 +02:00
void SARBase::patch_rootdir() {
2019-06-24 10:21:33 +02:00
sbin_overlay(self, config);
// Mount system_root mirror
2019-06-29 23:19:10 +02:00
xmkdir(MIRRDIR, 0);
2019-07-16 10:08:28 +02:00
xmkdir(ROOTMIR, 0);
2019-06-29 23:19:10 +02:00
xmkdir(BLOCKDIR, 0);
2019-06-24 10:21:33 +02:00
mknod(ROOTBLK, S_IFBLK | 0600, system_dev);
if (xmount(ROOTBLK, ROOTMIR, "ext4", MS_RDONLY, nullptr))
xmount(ROOTBLK, ROOTMIR, "erofs", MS_RDONLY, nullptr);
// Recreate original sbin structure
2019-06-25 12:35:25 +02:00
int src = xopen(ROOTMIR "/sbin", O_RDONLY | O_CLOEXEC);
int dest = xopen("/sbin", O_RDONLY | O_CLOEXEC);
2019-06-24 10:21:33 +02:00
DIR *fp = fdopendir(src);
struct dirent *entry;
struct stat st;
char buf[256];
while ((entry = xreaddir(fp))) {
if (entry->d_name == "."sv || entry->d_name == ".."sv)
continue;
fstatat(src, entry->d_name, &st, AT_SYMLINK_NOFOLLOW);
if (S_ISLNK(st.st_mode)) {
xreadlinkat(src, entry->d_name, buf, sizeof(buf));
xsymlinkat(buf, dest, entry->d_name);
} else {
2019-07-01 04:24:14 +02:00
char spath[256];
2019-06-24 10:21:33 +02:00
sprintf(buf, "/sbin/%s", entry->d_name);
2019-07-01 04:24:14 +02:00
sprintf(spath, ROOTMIR "/sbin/%s", entry->d_name);
2019-06-24 10:21:33 +02:00
// Create dummy
if (S_ISDIR(st.st_mode))
2019-07-01 04:24:14 +02:00
xmkdir(buf, st.st_mode & 0777);
2019-06-24 10:21:33 +02:00
else
2019-07-01 04:24:14 +02:00
close(xopen(buf, O_CREAT | O_WRONLY | O_CLOEXEC, st.st_mode & 0777));
xmount(spath, buf, nullptr, MS_BIND, nullptr);
2019-06-24 10:21:33 +02:00
}
}
close(src);
close(dest);
2019-06-26 06:34:02 +02:00
2019-07-16 10:08:28 +02:00
// Patch init
raw_data init;
2019-06-26 06:34:02 +02:00
file_attr attr;
bool redirect = false;
src = xopen("/init", O_RDONLY | O_CLOEXEC);
2019-07-16 10:08:28 +02:00
fd_full_read(src, init.buf, init.sz);
2019-06-26 06:34:02 +02:00
fgetattr(src, &attr);
close(src);
2019-07-16 10:08:28 +02:00
uint8_t *eof = init.buf + init.sz;
for (uint8_t *p = init.buf; p < eof; ++p) {
2019-06-26 06:34:02 +02: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 10:08:28 +02:00
xmkdir(ROOTOVL, 0);
dest = xopen(ROOTOVL "/init", O_CREAT | O_WRONLY | O_CLOEXEC);
xwrite(dest, init.buf, init.sz);
2019-06-26 06:34:02 +02:00
fsetattr(dest, &attr);
close(dest);
2019-07-16 10:08:28 +02:00
// Patch libselinux
2019-06-26 06:34:02 +02:00
if (!redirect) {
2019-07-16 10:08:28 +02:00
raw_data lib;
2019-06-26 06:34:02 +02:00
// init is dynamically linked, need to patch libselinux
2019-07-16 10:08:28 +02:00
full_read(LIBSELINUX, lib.buf, lib.sz);
2019-06-26 06:34:02 +02:00
getattr(LIBSELINUX, &attr);
2019-07-16 10:08:28 +02:00
eof = lib.buf + lib.sz;
for (uint8_t *p = lib.buf; p < eof; ++p) {
2019-06-26 06:34:02 +02: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 10:08:28 +02: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 06:34:02 +02:00
fsetattr(dest, &attr);
close(dest);
}
2019-07-16 10:08:28 +02:00
// sepolicy
2019-06-26 06:34:02 +02:00
patch_sepolicy(PATCHPOLICY);
2019-06-26 08:31:59 +02:00
2019-07-16 10:08:28 +02:00
// Handle overlay
if ((src = xopen("/dev/overlay.d", O_RDONLY | O_CLOEXEC)) >= 0) {
load_overlay_rc(src);
if (int fd = xopen("/dev/overlay.d/sbin", O_RDONLY | O_CLOEXEC); fd >= 0) {
dest = xopen("/sbin", O_RDONLY | O_CLOEXEC);
clone_dir(fd, dest);
close(fd);
close(dest);
xmkdir(ROOTOVL "/sbin", 0); // Prevent copying
}
dest = xopen(ROOTOVL, O_RDONLY | O_CLOEXEC);
clone_dir(src, dest, false);
rmdir(ROOTOVL "/sbin");
close(src);
close(dest);
rm_rf("/dev/overlay.d");
}
// Patch init.rc
FILE *rc = xfopen(ROOTOVL "/init.rc", "we");
2019-06-26 08:31:59 +02:00
patch_init_rc(rc);
fclose(rc);
2019-07-16 10:08:28 +02:00
clone_attr("/init.rc", ROOTOVL "/init.rc");
// Mount rootdir
src = xopen(ROOTOVL, O_RDONLY | O_CLOEXEC);
magic_mount(src, "");
close(src);
dest = xopen(ROOTMNT, O_WRONLY | O_CREAT | O_CLOEXEC);
write(dest, mount_list.data(), mount_list.length());
close(dest);
2019-06-24 10:21:33 +02:00
}
static void patch_fstab(const string &fstab) {
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 09:47:29 +02:00
string patched = fstab + ".p";
FILE *fp = xfopen(patched.data(), "we");
file_readline(fstab.data(), [=](string_view l) -> bool {
if (l[0] == '#' || l.length() == 1)
return true;
char *line = (char *) l.data();
int src0, src1, mnt0, mnt1, type0, type1, opt0, opt1, flag0, flag1;
sscanf(line, "%n%*s%n %n%*s%n %n%*s%n %n%*s%n %n%*s%n",
&src0, &src1, &mnt0, &mnt1, &type0, &type1, &opt0, &opt1, &flag0, &flag1);
const char *src, *mnt, *type, *opt, *flag;
src = &line[src0];
line[src1] = '\0';
mnt = &line[mnt0];
line[mnt1] = '\0';
type = &line[type0];
line[type1] = '\0';
opt = &line[opt0];
line[opt1] = '\0';
flag = &line[flag0];
line[flag1] = '\0';
// Redirect system to system_root
if (mnt == "/system"sv)
mnt = "/system_root";
fprintf(fp, "%s %s %s %s %s\n", src, mnt, type, opt, flag);
return true;
});
fclose(fp);
// Replace old fstab
clone_attr(fstab.data(), patched.data());
rename(patched.data(), fstab.data());
}
#define FSR "/first_stage_ramdisk"
void ABFirstStageInit::prepare() {
DIR *dir = xopendir(FSR);
if (!dir)
return;
string fstab(FSR "/");
for (dirent *de; (de = readdir(dir));) {
if (strstr(de->d_name, "fstab")) {
fstab += de->d_name;
break;
}
}
closedir(dir);
if (fstab.length() == sizeof(FSR))
return;
patch_fstab(fstab);
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 09:47:29 +02:00
// Move stuffs for next stage
xmkdir(FSR "/system", 0755);
xmkdir(FSR "/system/bin", 0755);
rename("/init", FSR "/system/bin/init");
2019-07-16 10:08:28 +02:00
symlink("/system/bin/init", FSR "/init");
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 09:47:29 +02:00
xmkdir(FSR "/.backup", 0);
rename("/.backup/.magisk", FSR "/.backup/.magisk");
2019-07-16 10:08:28 +02:00
rename("/overlay.d", FSR "/overlay.d");
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 09:47:29 +02:00
}
2019-09-22 11:15:31 +02:00
void AFirstStageInit::prepare() {
DIR *dir = xopendir("/");
for (dirent *de; (de = readdir(dir));) {
if (strstr(de->d_name, "fstab")) {
patch_fstab(de->d_name);
break;
}
}
closedir(dir);
2019-09-22 11:15:31 +02:00
// Move stuffs for next stage
xmkdir("/system", 0755);
xmkdir("/system/bin", 0755);
rename("/init", "/system/bin/init");
rename("/.backup/init", "/init");
}
2019-06-22 12:14:33 +02:00
#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() {
int fd = xopen("/proc/kmsg", O_WRONLY | O_CLOEXEC);
kmsg = fdopen(fd, "w");
setbuf(kmsg, nullptr);
log_cb.d = log_cb.i = log_cb.w = log_cb.e = vprintk;
log_cb.ex = nop_ex;
}
#else
#define setup_klog(...)
#endif
int magisk_proxy_main(int argc, char *argv[]) {
setup_klog();
raw_data config;
raw_data self;
2019-07-16 10:08:28 +02:00
full_read("/sbin/magisk", self.buf, self.sz);
full_read("/.backup/.magisk", config.buf, config.sz);
2019-06-22 12:14:33 +02:00
xmount(nullptr, "/", nullptr, MS_REMOUNT, nullptr);
unlink("/sbin/magisk");
rm_rf("/.backup");
2019-06-24 10:21:33 +02:00
sbin_overlay(self, config);
2019-06-22 12:14:33 +02:00
// Create symlinks pointing back to /root
2019-06-30 20:39:13 +02:00
char path[256];
int sbin = xopen("/sbin", O_RDONLY | O_CLOEXEC);
DIR *dir = xopendir("/root");
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);
2019-06-22 12:14:33 +02:00
}
2019-06-30 20:39:13 +02:00
close(sbin);
closedir(dir);
2019-06-22 12:14:33 +02:00
setenv("REMOUNT_ROOT", "1", 1);
execv("/sbin/magisk", argv);
return 1;
}