diff --git a/native/jni/Android.mk b/native/jni/Android.mk index b5fa3b084..8428b3ccf 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -82,6 +82,7 @@ LOCAL_SRC_FILES := \ init/mount.cpp \ init/rootdir.cpp \ init/getinfo.cpp \ + init/twostage.cpp \ magiskpolicy/api.cpp \ magiskpolicy/magiskpolicy.cpp \ magiskpolicy/rules.cpp \ diff --git a/native/jni/init/init.cpp b/native/jni/init/init.cpp index 7e8114827..7b4ed0075 100644 --- a/native/jni/init/init.cpp +++ b/native/jni/init/init.cpp @@ -217,16 +217,19 @@ int main(int argc, char *argv[]) { // This will also mount /sys and /proc load_kernel_info(&cmd); - if (cmd.force_normal_boot) { - init = make_unique(argv, &cmd); + if (access("/apex", F_OK) == 0) { + if (cmd.force_normal_boot) + init = make_unique(argv, &cmd); + else if (cmd.skip_initramfs) + init = make_unique(argv, &cmd); + else + init = make_unique(argv, &cmd); } else if (cmd.skip_initramfs) { init = make_unique(argv, &cmd); } else { decompress_ramdisk(); if (access("/sbin/recovery", F_OK) == 0 || access("/system/bin/recovery", F_OK) == 0) init = make_unique(argv, &cmd); - else if (access("/apex", F_OK) == 0) - init = make_unique(argv, &cmd); else init = make_unique(argv, &cmd); } diff --git a/native/jni/init/init.hpp b/native/jni/init/init.hpp index 28637b8e9..4a5992458 100644 --- a/native/jni/init/init.hpp +++ b/native/jni/init/init.hpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -66,9 +68,11 @@ public: class SARBase : public MagiskInit { protected: raw_data config; + std::vector overlays; - void backup_files(const char *self_path); + void backup_files(); void patch_rootdir(); + void mount_system_root(); public: SARBase(char *argv[], cmdline *cmd) : MagiskInit(argv, cmd) { persist_dir = MIRRDIR "/persist/magisk"; @@ -84,28 +88,41 @@ public: * 2 Stage Init * *************/ -class ABFirstStageInit : public BaseInit { +class ForcedFirstStageInit : public BaseInit { private: void prepare(); public: - ABFirstStageInit(char *argv[], cmdline *cmd) : BaseInit(argv, cmd) {}; + ForcedFirstStageInit(char *argv[], cmdline *cmd) : BaseInit(argv, cmd) {}; void start() override { prepare(); exec_init("/system/bin/init"); } }; -class AFirstStageInit : public BaseInit { +class FirstStageInit : public BaseInit { private: void prepare(); public: - AFirstStageInit(char *argv[], cmdline *cmd) : BaseInit(argv, cmd) {}; + FirstStageInit(char *argv[], cmdline *cmd) : BaseInit(argv, cmd) {}; void start() override { prepare(); exec_init(); } }; +class SARFirstStageInit : public SARBase { +private: + void traced_exec_init(); +protected: + void early_mount() override; +public: + SARFirstStageInit(char *argv[], cmdline *cmd) : SARBase(argv, cmd) {}; + void start() override { + early_mount(); + traced_exec_init(); + } +}; + class SecondStageInit : public SARBase { protected: void early_mount() override; @@ -152,3 +169,4 @@ int dump_magisk(const char *path, mode_t mode); int magisk_proxy_main(int argc, char *argv[]); void setup_klog(); void mount_sbin(); +socklen_t setup_sockaddr(struct sockaddr_un *sun); diff --git a/native/jni/init/mount.cpp b/native/jni/init/mount.cpp index 11f4dfa56..a7dcaf337 100644 --- a/native/jni/init/mount.cpp +++ b/native/jni/init/mount.cpp @@ -183,27 +183,15 @@ void RootFSInit::early_mount() { mount_list.emplace_back("/dev/mnt/cache"); } -void SARBase::backup_files(const char *self_path) { +void SARBase::backup_files() { if (access("/overlay.d", F_OK) == 0) - cp_afc("/overlay.d", "/dev/overlay.d"); + backup_folder("/overlay.d", overlays); - full_read(self_path, self.buf, self.sz); + full_read("/proc/self/exe", self.buf, self.sz); full_read("/.backup/.magisk", config.buf, config.sz); } -void SARInit::early_mount() { - // Make dev writable - xmkdir("/dev", 0755); - xmount("tmpfs", "/dev", "tmpfs", 0, "mode=755"); - mount_list.emplace_back("/dev"); - - backup_files("/init"); - - LOGD("Cleaning rootfs\n"); - int root = xopen("/", O_RDONLY | O_CLOEXEC); - frm_rf(root, { "proc", "sys", "dev" }); - close(root); - +void SARBase::mount_system_root() { LOGD("Early mount system_root\n"); sprintf(partname, "system%s", cmd->slot); strcpy(block_dev, "/dev/root"); @@ -221,6 +209,22 @@ void SARInit::early_mount() { xmkdir("/system_root", 0755); if (xmount("/dev/root", "/system_root", "ext4", MS_RDONLY, nullptr)) xmount("/dev/root", "/system_root", "erofs", MS_RDONLY, nullptr); +} + +void SARInit::early_mount() { + // Make dev writable + xmkdir("/dev", 0755); + xmount("tmpfs", "/dev", "tmpfs", 0, "mode=755"); + mount_list.emplace_back("/dev"); + + backup_files(); + + LOGD("Cleaning rootfs\n"); + int root = xopen("/", O_RDONLY | O_CLOEXEC); + frm_rf(root, { "proc", "sys", "dev" }); + close(root); + + mount_system_root(); switch_root("/system_root"); mount_root(vendor); @@ -228,15 +232,22 @@ void SARInit::early_mount() { mount_root(odm); } -void SecondStageInit::early_mount() { - // Early mounts should already be done by first stage init +void SARFirstStageInit::early_mount() { + backup_files(); + mount_system_root(); + switch_root("/system_root"); +} - backup_files("/system/bin/init"); +void SecondStageInit::early_mount() { + backup_files(); rm_rf("/system"); rm_rf("/.backup"); rm_rf("/overlay.d"); - switch_root("/system_root"); + umount2("/system/bin/init", MNT_DETACH); + + if (access("/system_root", F_OK) == 0) + switch_root("/system_root"); } void BaseInit::cleanup() { diff --git a/native/jni/init/rootdir.cpp b/native/jni/init/rootdir.cpp index f363eb85e..e32b09281 100644 --- a/native/jni/init/rootdir.cpp +++ b/native/jni/init/rootdir.cpp @@ -67,22 +67,24 @@ static void patch_init_rc(FILE *rc) { fprintf(rc, magiskrc, pfd_svc, pfd_svc, ls_svc, bc_svc, bc_svc); } -static void load_overlay_rc(int dirfd) { +static void load_overlay_rc(const char *overlay) { + auto dir = open_dir(overlay); + if (!dir) return; + + int dfd = dirfd(dir.get()); // Do not allow overwrite init.rc - unlinkat(dirfd, "init.rc", 0); - DIR *dir = fdopendir(dirfd); - for (dirent *entry; (entry = readdir(dir));) { + unlinkat(dfd, "init.rc", 0); + for (dirent *entry; (entry = readdir(dir.get()));) { 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); + int rc = xopenat(dfd, 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); + unlinkat(dfd, entry->d_name, 0); } } - rewinddir(dir); } void RootFSInit::setup_rootfs() { @@ -102,13 +104,10 @@ void RootFSInit::setup_rootfs() { } // Handle overlays - int fd = open("/overlay.d", O_RDONLY | O_CLOEXEC); - if (fd >= 0) { + if (access("/overlay.d", F_OK) == 0) { LOGD("Merge overlay.d\n"); - load_overlay_rc(fd); - mv_dir(fd, root); - close(fd); - rmdir("/overlay.d"); + load_overlay_rc("/overlay.d"); + mv_f("/overlay.d", "/"); } // Patch init.rc @@ -127,7 +126,7 @@ void RootFSInit::setup_rootfs() { close(sbin); // Dump magiskinit as magisk - fd = xopen("/sbin/magisk", O_WRONLY | O_CREAT, 0755); + int fd = xopen("/sbin/magisk", O_WRONLY | O_CREAT, 0755); write(fd, self.buf, self.sz); close(fd); } @@ -336,21 +335,26 @@ void SARBase::patch_rootdir() { patch_sepolicy(PATCHPOLICY); // 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"); + 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); } // Patch init.rc @@ -366,90 +370,6 @@ void SARBase::patch_rootdir() { close(dest); } -static void patch_fstab(const string &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()); -} - -#define FSR "/first_stage_ramdisk" - -void ABFirstStageInit::prepare() { - // It is actually possible to NOT have FSR, create it just in case - xmkdir(FSR, 0755); - - if (auto dir = xopen_dir(FSR); dir) { - string fstab(FSR "/"); - for (dirent *de; (de = xreaddir(dir.get()));) { - if (strstr(de->d_name, "fstab")) { - fstab += de->d_name; - break; - } - } - if (fstab.length() == sizeof(FSR)) - return; - - patch_fstab(fstab); - } else { - return; - } - - // Move stuffs for next stage - xmkdir(FSR "/system", 0755); - xmkdir(FSR "/system/bin", 0755); - rename("/init", FSR "/system/bin/init"); - symlink("/system/bin/init", FSR "/init"); - xmkdir(FSR "/.backup", 0); - rename("/.backup/.magisk", FSR "/.backup/.magisk"); - rename("/overlay.d", FSR "/overlay.d"); -} - -void AFirstStageInit::prepare() { - auto dir = xopen_dir("/"); - for (dirent *de; (de = xreaddir(dir.get()));) { - if (strstr(de->d_name, "fstab")) { - patch_fstab(de->d_name); - break; - } - } - - // Move stuffs for next stage - xmkdir("/system", 0755); - xmkdir("/system/bin", 0755); - rename("/init", "/system/bin/init"); - rename("/.backup/init", "/init"); -} - int magisk_proxy_main(int argc, char *argv[]) { setup_klog(); diff --git a/native/jni/init/twostage.cpp b/native/jni/init/twostage.cpp new file mode 100644 index 000000000..e7e99461c --- /dev/null +++ b/native/jni/init/twostage.cpp @@ -0,0 +1,182 @@ +#include +#include + +#include +#include + +#include "init.hpp" + +using namespace std; + +static void patch_fstab(const string &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()); +} + +#define FSR "/first_stage_ramdisk" + +void ForcedFirstStageInit::prepare() { + // It is actually possible to NOT have FSR, create it just in case + xmkdir(FSR, 0755); + + if (auto dir = xopen_dir(FSR); dir) { + string fstab(FSR "/"); + for (dirent *de; (de = xreaddir(dir.get()));) { + if (strstr(de->d_name, "fstab")) { + fstab += de->d_name; + break; + } + } + if (fstab.length() == sizeof(FSR)) + return; + + patch_fstab(fstab); + } else { + return; + } + + // Move stuffs for next stage + xmkdir(FSR "/system", 0755); + xmkdir(FSR "/system/bin", 0755); + rename("/init", FSR "/system/bin/init"); + symlink("/system/bin/init", FSR "/init"); + xmkdir(FSR "/.backup", 0); + rename("/.backup/.magisk", FSR "/.backup/.magisk"); + rename("/overlay.d", FSR "/overlay.d"); +} + +void FirstStageInit::prepare() { + auto dir = xopen_dir("/"); + for (dirent *de; (de = xreaddir(dir.get()));) { + if (strstr(de->d_name, "fstab")) { + patch_fstab(de->d_name); + break; + } + } + + // Move stuffs for next stage + xmkdir("/system", 0755); + xmkdir("/system/bin", 0755); + rename("/init", "/system/bin/init"); + rename("/.backup/init", "/init"); +} + +static inline long xptrace(int request, pid_t pid, void *addr, void *data) { + long ret = ptrace(request, pid, addr, data); + if (ret < 0) + PLOGE("ptrace %d", pid); + return ret; +} + +static inline long xptrace(int request, pid_t pid, void *addr = nullptr, intptr_t data = 0) { + return xptrace(request, pid, addr, reinterpret_cast(data)); +} + +#define INIT_SOCKET "MAGISKINIT" + +socklen_t setup_sockaddr(struct sockaddr_un *sun) { + sun->sun_family = AF_LOCAL; + strcpy(sun->sun_path + 1, INIT_SOCKET); + return sizeof(sa_family_t) + sizeof(INIT_SOCKET); +} + +void SARFirstStageInit::traced_exec_init() { + int pid = getpid(); + + // Block SIGUSR1 + sigset_t block, old; + sigemptyset(&block); + sigaddset(&block, SIGUSR1); + sigprocmask(SIG_BLOCK, &block, &old); + + if (int child = xfork(); child) { + LOGD("init tracer [%d]\n", child); + // Wait for children to attach + int sig; + sigwait(&block, &sig); + + // Restore sigmask + sigprocmask(SIG_BLOCK, &old, nullptr); + + // Re-exec init + exec_init(); + } else { + // Close all file descriptors and stop logging + no_logging(); + for (int i = 0; i < 20; ++i) + close(i); + + // Attach to parent to trace exec + xptrace(PTRACE_ATTACH, pid); + waitpid(pid, nullptr, __WALL | __WNOTHREAD); + xptrace(PTRACE_SETOPTIONS, pid, nullptr, PTRACE_O_TRACEEXEC); + xptrace(PTRACE_CONT, pid, 0, SIGUSR1); + + // Wait for execve + waitpid(pid, nullptr, __WALL | __WNOTHREAD); + + // Swap out init with bind mount + xmount("tmpfs", "/dev", "tmpfs", 0, "mode=755"); + int init = xopen("/dev/magisk", O_CREAT | O_WRONLY, 0750); + write(init, self.buf, self.sz); + close(init); + xmount("/dev/magisk", "/init", nullptr, MS_BIND, nullptr); + xumount2("/dev", MNT_DETACH); + + xptrace(PTRACE_DETACH, pid); + + // Start daemon for 2nd stage preparation + struct sockaddr_un sun{}; + auto len = setup_sockaddr(&sun); + int sockfd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); + xbind(sockfd, (struct sockaddr*) &sun, len); + xlisten(sockfd, 1); + + // Wait for second stage ack + int client = xaccept4(sockfd, nullptr, nullptr, SOCK_CLOEXEC); + + // Write backup files + int cfg = xopen(MAGISKTMP "/config", O_WRONLY | O_CREAT, 0000); + xwrite(cfg, config.buf, config.sz); + close(cfg); + restore_folder(ROOTOVL, overlays); + + // Ack and bail out! + write(sockfd, &sockfd, sizeof(sockfd)); + close(client); + close(sockfd); + + exit(0); + } +} diff --git a/native/jni/utils/file.cpp b/native/jni/utils/file.cpp index 7e338830c..118bc7a98 100644 --- a/native/jni/utils/file.cpp +++ b/native/jni/utils/file.cpp @@ -55,21 +55,18 @@ static bool is_excl(initializer_list excl, const char *name) { } static void post_order_walk(int dirfd, initializer_list excl, - int (*fn)(int, struct dirent *)) { - struct dirent *entry; - int newfd; - DIR *dir = fdopendir(dirfd); - if (dir == nullptr) return; + function &&fn) { + auto dir = xopen_dir(dirfd); + if (!dir) return; - while ((entry = xreaddir(dir))) { + for (dirent *entry; (entry = xreaddir(dir.get()));) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; if (is_excl(excl, entry->d_name)) continue; if (entry->d_type == DT_DIR) { - newfd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC); - post_order_walk(newfd, excl, fn); - close(newfd); + int newfd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC); + post_order_walk(newfd, excl, std::move(fn)); } fn(dirfd, entry); } @@ -84,15 +81,14 @@ void rm_rf(const char *path) { if (lstat(path, &st) < 0) return; if (S_ISDIR(st.st_mode)) { - int fd = open(path, O_RDONLY | O_CLOEXEC); + int fd = xopen(path, O_RDONLY | O_CLOEXEC); frm_rf(fd); - close(fd); } remove(path); } void frm_rf(int dirfd, initializer_list excl) { - post_order_walk(dirfd, excl, remove_at); + post_order_walk(dup(dirfd), excl, remove_at); } /* This will only on the same file system */ @@ -403,3 +399,50 @@ void parse_mnt(const char *file, const function &fn) { } } } + +void backup_folder(const char *dir, vector &files) { + char path[4096]; + realpath(dir, path); + int len = strlen(path); + int dirfd = xopen(dir, O_RDONLY); + post_order_walk(dirfd, {}, [&](int dfd, dirent *entry) -> int { + int fd = xopenat(dfd, entry->d_name, O_RDONLY); + if (fd < 0) + return -1; + if (fd_path(fd, path, sizeof(path)) < 0) + return -1; + raw_file file; + file.path = path + len + 1; + if (fgetattr(fd, &file.attr) < 0) + return -1; + if (entry->d_type == DT_REG) { + fd_full_read(fd, file.buf, file.sz); + } else if (entry->d_type == DT_LNK) { + xreadlinkat(dfd, entry->d_name, path, sizeof(path)); + file.sz = strlen(path) + 1; + file.buf = (uint8_t *) xmalloc(file.sz); + memcpy(file.buf, path, file.sz); + } + close(fd); + files.emplace_back(std::move(file)); + return 0; + }); + close(dirfd); +} + +void restore_folder(const char *dir, vector &files) { + string base(dir); + // Reversed post-order means folders will always be first + for (raw_file &file : reversed(files)) { + string path = base + "/" + file.path; + if (S_ISDIR(file.attr.st.st_mode)) { + mkdirs(path.data(), 0); + } else if (S_ISREG(file.attr.st.st_mode)) { + auto fp = xopen_file(path.data(), "w"); + fwrite(file.buf, 1, file.sz, fp.get()); + } else if (S_ISLNK(file.attr.st.st_mode)) { + symlink((char *)file.buf, path.data()); + } + setattr(path.data(), &file.attr); + } +} diff --git a/native/jni/utils/files.hpp b/native/jni/utils/files.hpp index bc061f726..c955282b5 100644 --- a/native/jni/utils/files.hpp +++ b/native/jni/utils/files.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "xwrap.hpp" @@ -16,6 +18,27 @@ struct file_attr { char con[128]; }; +struct raw_file { + std::string path; + file_attr attr; + uint8_t *buf = nullptr; + size_t sz = 0; + + raw_file() = default; + raw_file(const raw_file&) = delete; + raw_file(raw_file &&d) { + path = std::move(d.path); + attr = d.attr; + buf = d.buf; + sz = d.sz; + d.buf = nullptr; + d.sz = 0; + } + ~raw_file() { + free(buf); + } +}; + ssize_t fd_path(int fd, char *path, size_t size); int fd_pathat(int dirfd, const char *name, char *path, size_t size); int mkdirs(const char *pathname, mode_t mode); @@ -46,6 +69,8 @@ void *__mmap(const char *filename, size_t *size, bool rw); void frm_rf(int dirfd, std::initializer_list excl = {}); void clone_dir(int src, int dest, bool overwrite = true); void parse_mnt(const char *file, const std::function &fn); +void backup_folder(const char *dir, std::vector &files); +void restore_folder(const char *dir, std::vector &files); template void full_read(const char *filename, T &buf, size_t &size) { @@ -94,6 +119,10 @@ static inline sDIR xopen_dir(const char *path) { return sDIR(xopendir(path), closedir); } +static inline sDIR xopen_dir(int dirfd) { + return sDIR(xfdopendir(dirfd), closedir); +} + static inline sFILE open_file(const char *path, const char *mode) { return sFILE(fopen(path, mode), fclose); }