Magisk/native/jni/init/rootdir.cpp

417 lines
11 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>
#include <magisk.h>
2019-07-01 02:09:31 +00:00
#include <magiskpolicy.h>
2019-05-27 07:29:43 +00:00
#include <utils.h>
#include "init.h"
2019-06-22 10:14:33 +00:00
#include "flags.h"
2019-05-27 07:29:43 +00: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) {
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);
}
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;
});
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);
}
2019-05-27 07:29:43 +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);
}
// 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");
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");
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 10:14:33 +00:00
// Dump magiskinit as magisk
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-24 08:50:47 +00: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);
RootFSInit::setup_rootfs();
}
2019-06-26 04:34:02 +00:00
bool MagiskInit::patch_sepolicy(const char *file) {
2019-05-27 07:29:43 +00:00
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);
2019-06-26 04:34:02 +00:00
dump_policydb(file);
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-06-22 10:14:33 +00:00
2019-06-26 06:31:59 +00: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 08:21:33 +00: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 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
}
#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
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
void SARCommon::patch_rootdir() {
2019-06-24 08:21:33 +00:00
sbin_overlay(self, config);
// Mount system_root mirror
2019-06-29 21:19:10 +00:00
xmkdir(MIRRDIR, 0);
xmkdir(ROOTMIR, 0755);
xmkdir(BLOCKDIR, 0);
2019-06-24 08:21:33 +00: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 10:35:25 +00:00
int src = xopen(ROOTMIR "/sbin", O_RDONLY | O_CLOEXEC);
int dest = xopen("/sbin", O_RDONLY | O_CLOEXEC);
2019-06-24 08:21:33 +00: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 02:24:14 +00:00
char spath[256];
2019-06-24 08:21:33 +00:00
sprintf(buf, "/sbin/%s", entry->d_name);
2019-07-01 02:24:14 +00:00
sprintf(spath, ROOTMIR "/sbin/%s", entry->d_name);
2019-06-24 08:21:33 +00:00
// Create dummy
if (S_ISDIR(st.st_mode))
2019-07-01 02:24:14 +00:00
xmkdir(buf, st.st_mode & 0777);
2019-06-24 08:21:33 +00:00
else
2019-07-01 02:24:14 +00:00
close(xopen(buf, O_CREAT | O_WRONLY | O_CLOEXEC, st.st_mode & 0777));
xmount(spath, buf, nullptr, MS_BIND, nullptr);
2019-06-24 08:21:33 +00:00
}
}
close(src);
close(dest);
2019-06-26 04:34:02 +00:00
2019-06-26 06:31:59 +00:00
/* ******************
* Customize rootdir
* ******************/
2019-06-26 04:34:02 +00:00
char *addr;
size_t size;
file_attr attr;
bool redirect = false;
xmkdir(ROOTOVERLAY, 0);
src = xopen("/init", O_RDONLY | O_CLOEXEC);
fgetattr(src, &attr);
fd_full_read(src, (void**)&addr, &size);
close(src);
for (char *p = addr; p < addr + size; ++p) {
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;
}
}
dest = xopen(ROOTOVERLAY "/init", O_CREAT | O_WRONLY | O_CLOEXEC);
xwrite(dest, addr, size);
free(addr);
fsetattr(dest, &attr);
close(dest);
xmount(ROOTOVERLAY "/init", "/init", nullptr, MS_BIND, nullptr);
if (!redirect) {
// init is dynamically linked, need to patch libselinux
full_read(LIBSELINUX, (void**)&addr, &size);
getattr(LIBSELINUX, &attr);
for (char *p = addr; p < addr + size; ++p) {
if (memcmp(p, MONOPOLICY, sizeof(MONOPOLICY)) == 0) {
// Redirect /sepolicy to tmpfs
LOGD("Patch libselinux.so [" MONOPOLICY "] -> [" PATCHPOLICY "]\n");
memcpy(p, PATCHPOLICY, sizeof(PATCHPOLICY));
break;
}
}
dest = xopen(ROOTOVERLAY "/libselinux.so", O_CREAT | O_WRONLY | O_CLOEXEC);
xwrite(dest, addr, size);
free(addr);
fsetattr(dest, &attr);
close(dest);
xmount(ROOTOVERLAY "/libselinux.so", LIBSELINUX, nullptr, MS_BIND, nullptr);
}
patch_sepolicy(PATCHPOLICY);
2019-06-26 06:31:59 +00:00
FILE *rc = xfopen(ROOTOVERLAY "/init.rc", "we");
patch_init_rc(rc);
fclose(rc);
clone_attr("/init.rc", ROOTOVERLAY "/init.rc");
xmount(ROOTOVERLAY "/init.rc", "/init.rc", nullptr, MS_BIND, nullptr);
2019-06-24 08:21:33 +00:00
}
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
#define FSR "/first_stage_ramdisk"
void FirstStageInit::patch_fstab() {
// Find fstab
DIR *dir = xopendir(FSR);
if (!dir)
return;
dirent *de;
string fstab(FSR "/");
while ((de = readdir(dir))) {
if (strstr(de->d_name, "fstab")) {
fstab += de->d_name;
break;
}
}
closedir(dir);
if (fstab.length() == sizeof(FSR))
return;
// Patch fstab
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());
// Move stuffs for next stage
xmkdir(FSR "/system", 0755);
xmkdir(FSR "/system/bin", 0755);
rename("/init", FSR "/system/bin/init");
xmkdir(FSR "/.backup", 0);
rename("/.backup/.magisk", FSR "/.backup/.magisk");
}
2019-06-22 10:14:33 +00: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;
full_read("/sbin/magisk", &self.buf, &self.sz);
full_read("/.backup/.magisk", &config.buf, &config.sz);
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
2019-06-30 18:39:13 +00: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 10:14:33 +00:00
}
2019-06-30 18:39:13 +00:00
close(sbin);
closedir(dir);
2019-06-22 10:14:33 +00:00
setenv("REMOUNT_ROOT", "1", 1);
execv("/sbin/magisk", argv);
return 1;
}