Use /data as tmpfs mount point in 2SI setup

Design credit to @yujincheng08
Close #5146. Fix #5491, fix #3752

Previously, Magisk changes the mount point from /system to /system_root
by patching fstab to prevent the original init from changing root.
The reason why we want to prevent the original init from switching the
root directory is because it will then be read-only, making patching
and injecting magiskinit into the boot chain difficult.

This commit (ab)uses the fact that the /data folder will never be part
of early mount (because it is handled very late in the boot by vold),
so that we can use it as the mount point of tmpfs to store files.

Some advantages of this method:

- No need to switch root manually
- No need to modify fstab, which significantly improves compatibility
  e.g. avoid hacks for weird devices like those using oplus.fstab,
  and avoid hacking init to bypass fstab in device trees
- Supports skip_mount.cfg
- Support DSU
This commit is contained in:
topjohnwu 2022-03-13 05:06:08 -07:00
parent 9b60c005c7
commit 810d27a618
5 changed files with 58 additions and 332 deletions

View File

@ -63,13 +63,11 @@ LOCAL_SRC_FILES := \
init/rootdir.cpp \ init/rootdir.cpp \
init/getinfo.cpp \ init/getinfo.cpp \
init/twostage.cpp \ init/twostage.cpp \
core/socket.cpp \
magiskpolicy/sepolicy.cpp \ magiskpolicy/sepolicy.cpp \
magiskpolicy/magiskpolicy.cpp \ magiskpolicy/magiskpolicy.cpp \
magiskpolicy/rules.cpp \ magiskpolicy/rules.cpp \
magiskpolicy/policydb.cpp \ magiskpolicy/policydb.cpp \
magiskpolicy/statement.cpp \ magiskpolicy/statement.cpp
magiskboot/pattern.cpp
LOCAL_LDFLAGS := -static LOCAL_LDFLAGS := -static
include $(BUILD_EXECUTABLE) include $(BUILD_EXECUTABLE)

View File

@ -34,7 +34,6 @@ struct fstab_entry {
fstab_entry(fstab_entry &&) = default; fstab_entry(fstab_entry &&) = default;
fstab_entry &operator=(const fstab_entry&) = delete; fstab_entry &operator=(const fstab_entry&) = delete;
fstab_entry &operator=(fstab_entry&&) = default; fstab_entry &operator=(fstab_entry&&) = default;
void to_file(FILE *fp);
}; };
#define INIT_SOCKET "MAGISKINIT" #define INIT_SOCKET "MAGISKINIT"
@ -44,7 +43,6 @@ extern std::vector<std::string> mount_list;
bool unxz(int fd, const uint8_t *buf, size_t size); bool unxz(int fd, const uint8_t *buf, size_t size);
void load_kernel_info(BootConfig *config); void load_kernel_info(BootConfig *config);
bool is_dsu();
bool check_two_stage(); bool check_two_stage();
void setup_klog(); void setup_klog();
const char *backup_init(); const char *backup_init();
@ -107,7 +105,6 @@ public:
class FirstStageInit : public BaseInit { class FirstStageInit : public BaseInit {
private: private:
void prepare(); void prepare();
void get_default_fstab(char *buf, size_t len);
public: public:
FirstStageInit(char *argv[], BootConfig *cmd) : BaseInit(argv, cmd) { FirstStageInit(char *argv[], BootConfig *cmd) : BaseInit(argv, cmd) {
LOGD("%s\n", __FUNCTION__); LOGD("%s\n", __FUNCTION__);
@ -124,17 +121,14 @@ public:
class SARInit : public SARBase { class SARInit : public SARBase {
private: private:
bool is_two_stage; bool early_mount();
void early_mount();
void first_stage_prep(); void first_stage_prep();
public: public:
SARInit(char *argv[], BootConfig *cmd) : MagiskInit(argv, cmd), is_two_stage(false) { SARInit(char *argv[], BootConfig *cmd) : MagiskInit(argv, cmd) {
LOGD("%s\n", __FUNCTION__); LOGD("%s\n", __FUNCTION__);
}; };
void start() override { void start() override {
early_mount(); if (early_mount())
if (is_two_stage)
first_stage_prep(); first_stage_prep();
else else
patch_rootdir(); patch_rootdir();
@ -178,8 +172,10 @@ public:
}; };
void start() override { void start() override {
if (prepare()) patch_rootfs(); if (prepare())
else patch_rootdir(); patch_rootfs();
else
patch_rootdir();
exec_init(); exec_init();
} }
}; };

View File

@ -205,27 +205,6 @@ static void switch_root(const string &path) {
frm_rf(root); frm_rf(root);
} }
bool is_dsu() {
strcpy(blk_info.partname, "metadata");
xmkdir("/metadata", 0755);
if (setup_block(true) < 0 ||
xmount(blk_info.block_dev, "/metadata", "ext4", MS_RDONLY, nullptr)) {
PLOGE("Failed to mount /metadata");
return false;
} else {
run_finally f([]{ xumount2("/metadata", MNT_DETACH); });
constexpr auto dsu_status = "/metadata/gsi/dsu/install_status";
if (xaccess(dsu_status, F_OK) == 0) {
char status[PATH_MAX] = {0};
auto fp = xopen_file(dsu_status, "r");
fgets(status, sizeof(status), fp.get());
if (status == "ok"sv || status == "0"sv)
return true;
}
}
return false;
}
void MagiskInit::mount_rules_dir(const char *dev_base, const char *mnt_base) { void MagiskInit::mount_rules_dir(const char *dev_base, const char *mnt_base) {
char path[128]; char path[128];
xrealpath(dev_base, blk_info.block_dev); xrealpath(dev_base, blk_info.block_dev);
@ -327,10 +306,14 @@ void RootFSInit::early_mount() {
void SARBase::backup_files() { void SARBase::backup_files() {
if (access("/overlay.d", F_OK) == 0) if (access("/overlay.d", F_OK) == 0)
backup_folder("/overlay.d", overlays); backup_folder("/overlay.d", overlays);
else if (access("/data/overlay.d", F_OK) == 0)
backup_folder("/data/overlay.d", overlays);
self = mmap_data("/proc/self/exe"); self = mmap_data("/proc/self/exe");
if (access("/.backup/.magisk", R_OK) == 0) if (access("/.backup/.magisk", R_OK) == 0)
magisk_config = mmap_data("/.backup/.magisk"); magisk_config = mmap_data("/.backup/.magisk");
else if (access("/data/.backup/.magisk", R_OK) == 0)
magisk_config = mmap_data("/data/.backup/.magisk");
} }
void SARBase::mount_system_root() { void SARBase::mount_system_root() {
@ -367,13 +350,13 @@ mount_root:
xmount("/dev/root", "/system_root", "erofs", MS_RDONLY, nullptr); xmount("/dev/root", "/system_root", "erofs", MS_RDONLY, nullptr);
} }
void SARInit::early_mount() { bool SARInit::early_mount() {
backup_files(); backup_files();
mount_system_root(); mount_system_root();
switch_root("/system_root"); switch_root("/system_root");
// Use the apex folder to determine whether 2SI (Android 10+) // Use the apex folder to determine whether 2SI (Android 10+)
is_two_stage = access("/apex", F_OK) == 0; bool is_two_stage = access("/apex", F_OK) == 0;
LOGD("is_two_stage: [%d]\n", is_two_stage); LOGD("is_two_stage: [%d]\n", is_two_stage);
if (!is_two_stage) { if (!is_two_stage) {
@ -386,26 +369,8 @@ void SARInit::early_mount() {
#endif #endif
mount_with_dt(); mount_with_dt();
} }
}
bool SecondStageInit::prepare() { return is_two_stage;
backup_files();
umount2("/init", MNT_DETACH);
umount2("/proc/self/exe", MNT_DETACH);
// some weird devices, like meizu, embrace two stage init but still have legacy rootfs behaviour
bool legacy = false;
if (access("/system_root", F_OK) == 0) {
if (access("/system_root/proc", F_OK) == 0) {
switch_root("/system_root");
} else {
xmount("/system_root", "/system", nullptr, MS_MOVE, nullptr);
rmdir("/system_root");
legacy = true;
}
}
return legacy;
} }
void BaseInit::exec_init() { void BaseInit::exec_init() {

View File

@ -240,7 +240,7 @@ void SARBase::patch_rootdir() {
make_pair(SPLIT_PLAT_CIL, "xxx"), /* Force loading monolithic sepolicy */ make_pair(SPLIT_PLAT_CIL, "xxx"), /* Force loading monolithic sepolicy */
make_pair(MONOPOLICY, sepol) /* Redirect /sepolicy to custom path */ make_pair(MONOPOLICY, sepol) /* Redirect /sepolicy to custom path */
}); });
if constexpr (avd_hack) { if (avd_hack) {
// Force disable early mount on original init // Force disable early mount on original init
init.patch({ make_pair("android,fstab", "xxx") }); init.patch({ make_pair("android,fstab", "xxx") });
} }
@ -276,23 +276,9 @@ void SARBase::patch_rootdir() {
// sepolicy // sepolicy
patch_sepolicy(sepol); patch_sepolicy(sepol);
// Restore backup files
struct sockaddr_un sun;
int sockfd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (connect(sockfd, (struct sockaddr*) &sun, setup_sockaddr(&sun, INIT_SOCKET)) == 0) {
LOGD("ACK init daemon to write backup files\n");
// Let daemon know where tmp_dir is
write_string(sockfd, tmp_dir);
// Wait for daemon to finish restoring files
read_int(sockfd);
} else {
LOGD("Restore backup files locally\n");
restore_folder(ROOTOVL, overlays);
overlays.clear();
}
close(sockfd);
// Handle overlay.d // Handle overlay.d
restore_folder(ROOTOVL, overlays);
overlays.clear();
load_overlay_rc(ROOTOVL); load_overlay_rc(ROOTOVL);
if (access(ROOTOVL "/sbin", F_OK) == 0) { if (access(ROOTOVL "/sbin", F_OK) == 0) {
// Move files in overlay.d/sbin into tmp_dir // Move files in overlay.d/sbin into tmp_dir

View File

@ -1,6 +1,4 @@
#include <sys/mount.h> #include <sys/mount.h>
#include <map>
#include <set>
#include <magisk.hpp> #include <magisk.hpp>
#include <utils.hpp> #include <utils.hpp>
@ -10,228 +8,34 @@
using namespace std; using namespace std;
void fstab_entry::to_file(FILE *fp) { #define INIT_PATH "/system/bin/init"
fprintf(fp, "%s %s %s %s %s\n", dev.data(), mnt_point.data(), #define REDIR_PATH "/data/magiskinit"
type.data(), mnt_flags.data(), fsmgr_flags.data());
}
#define set_info(val) \
line[val##1] = '\0'; \
entry.val = &line[val##0];
static void read_fstab_file(const char *fstab_file, vector<fstab_entry> &fstab) {
file_readline(fstab_file, [&](string_view l) -> bool {
if (l[0] == '#' || l.length() == 1)
return true;
char *line = (char *) l.data();
int dev0, dev1, mnt_point0, mnt_point1, type0, type1,
mnt_flags0, mnt_flags1, fsmgr_flags0, fsmgr_flags1;
sscanf(line, "%n%*s%n %n%*s%n %n%*s%n %n%*s%n %n%*s%n",
&dev0, &dev1, &mnt_point0, &mnt_point1, &type0, &type1,
&mnt_flags0, &mnt_flags1, &fsmgr_flags0, &fsmgr_flags1);
fstab_entry entry;
set_info(dev)
set_info(mnt_point)
set_info(type)
set_info(mnt_flags)
set_info(fsmgr_flags)
fstab.emplace_back(std::move(entry));
return true;
});
}
#define FSR "/first_stage_ramdisk"
extern uint32_t patch_verity(void *buf, uint32_t size);
static void append_oplus(vector<fstab_entry> &fstab) {
LOGD("Found fstab file: oplus.fstab\n");
map<string, string> bind_map;
vector<fstab_entry> entry_list;
{
// Make sure no duplicate mnt_point exists
set<string_view> mount_points;
// Add main fstab entry mnt_points
for (auto &entry: fstab) {
mount_points.emplace(entry.mnt_point);
}
vector<fstab_entry> oplus_fstab;
read_fstab_file("oplus.fstab", oplus_fstab);
for (auto &entry : oplus_fstab) {
if (mount_points.count(entry.mnt_point))
continue;
if (str_contains(entry.mnt_flags, "bind")) {
bind_map.emplace(std::move(entry.dev), std::move(entry.mnt_point));
} else {
mount_points.emplace(entry.mnt_point);
entry_list.emplace_back(std::move(entry));
}
}
}
for (auto &entry : entry_list) {
// Mount before switch root, fix img path
if (str_starts(entry.dev, "loop@/system/"))
entry.dev.insert(5, "/system_root");
// change bind mount entries to dev mount since some users reported bind is not working
// in this case, we drop the original mount point and leave only the one from bind entry
// because some users reported keeping the original mount point causes bootloop
if (auto it = bind_map.find(entry.mnt_point); it != bind_map.end()) {
entry.mnt_point = it->second;
}
fstab.push_back(std::move(entry));
}
unlink("oplus.fstab");
}
void FirstStageInit::get_default_fstab(char *buf, size_t len) {
buf[0] = '\0';
// Find existing fstab file
for (const char *suffix : { config->fstab_suffix, config->hardware, config->hardware_plat }) {
if (suffix[0] == '\0')
continue;
for (const char *prefix: { "odm/etc/fstab", "vendor/etc/fstab", "system/etc/fstab", "fstab" }) {
snprintf(buf, len, "%s.%s", prefix, suffix);
if (access(buf, F_OK) == 0) {
LOGD("Found fstab file: %s\n", buf);
return;
}
}
}
buf[0] = '\0';
// No existing fstab file is found, create a valid path
const char *suffix = [&]() -> const char * {
if (config->fstab_suffix[0])
return config->fstab_suffix;
if (config->hardware[0])
return config->hardware;
if (config->hardware_plat[0])
return config->hardware_plat;
return nullptr;
}();
if (suffix == nullptr) {
LOGE("Cannot determine fstab suffix!\n");
return;
}
snprintf(buf, len, "fstab.%s", suffix);
}
void FirstStageInit::prepare() { void FirstStageInit::prepare() {
if (is_dsu()) { xmkdirs("/data", 0755);
rename(backup_init(), "/init"); xmount("tmpfs", "/data", "tmpfs", 0, "mode=755");
LOGI("Skip loading Magisk because of DSU\n"); cp_afc("/init" /* magiskinit */, REDIR_PATH);
return;
}
run_finally finally([]{ chdir("/"); }); unlink("/init");
if (config->force_normal_boot) { xrename(backup_init(), "/init");
xmkdirs(FSR "/system/bin", 0755);
rename("/init" /* magiskinit */, FSR "/system/bin/init");
symlink("/system/bin/init", FSR "/init");
rename(backup_init(), "/init");
rename("/.backup", FSR "/.backup");
rename("/overlay.d", FSR "/overlay.d");
chdir(FSR);
} else {
xmkdir("/system", 0755);
xmkdir("/system/bin", 0755);
rename("/init" /* magiskinit */ , "/system/bin/init");
rename(backup_init(), "/init");
}
char fstab_file[128];
get_default_fstab(fstab_file, sizeof(fstab_file));
// Empty fstab file path is an error
if (fstab_file[0] == '\0')
return;
// Try to load dt fstab
vector<fstab_entry> fstab;
read_dt_fstab(fstab);
if (!fstab.empty()) {
// Dump dt fstab to fstab file in rootfs and force init to use it instead
// If there's any error in dt fstab, skip loading it
bool skip = false;
// All dt fstab entries should be first_stage_mount
for (auto &entry : fstab) {
if (!str_contains(entry.fsmgr_flags, "first_stage_mount")) {
if (!entry.fsmgr_flags.empty())
entry.fsmgr_flags += ',';
entry.fsmgr_flags += "first_stage_mount";
}
// If the entry contains slotselect but the current slot is empty, error occurs
if (config->slot[0] == '\0' && str_contains(entry.fsmgr_flags, "slotselect")) {
skip = true;
break;
} // TODO: else if expected_field checks and fs_mgr_flags checks
}
if (skip) {
// When dt fstab fails, fall back to default fstab
LOGI("dt fstab contains error, fall back to default fstab\n");
fstab.clear();
read_fstab_file(fstab_file, fstab);
} else {
// Patch init to force IsDtFstabCompatible() return false
auto init = mmap_data("/init", true);
init.patch({make_pair("android,fstab", "xxx")});
}
} else {
read_fstab_file(fstab_file, fstab);
}
// Append oppo's custom fstab
if (access("oplus.fstab", F_OK) == 0)
append_oplus(fstab);
{ {
LOGD("Write fstab file: %s\n", fstab_file); auto init = mmap_data("/init", true);
auto fp = xopen_file(fstab_file, "we"); // Redirect original init to magiskinit
for (auto &entry : fstab) { init.patch({ make_pair(INIT_PATH, REDIR_PATH) });
// Redirect system mnt_point so init won't switch root in first stage init
if (entry.mnt_point == "/system")
entry.mnt_point = "/system_root";
// Force remove AVB for 2SI since it may bootloop some devices
auto len = patch_verity(entry.fsmgr_flags.data(), entry.fsmgr_flags.length());
entry.fsmgr_flags.resize(len);
entry.to_file(fp.get());
}
} }
chmod(fstab_file, 0644);
// Copy files to tmpfs
cp_afc(".backup", "/data/.backup");
cp_afc("overlay.d", "/data/overlay.d");
} }
#define INIT_PATH "/system/bin/init"
#define REDIR_PATH "/system/bin/am"
void SARInit::first_stage_prep() { void SARInit::first_stage_prep() {
xmount("tmpfs", "/dev", "tmpfs", 0, "mode=755"); xmount("tmpfs", "/data", "tmpfs", 0, "mode=755");
// Patch init binary // Patch init binary
int src = xopen("/init", O_RDONLY); int src = xopen("/init", O_RDONLY);
int dest = xopen("/dev/init", O_CREAT | O_WRONLY, 0); int dest = xopen("/data/init", O_CREAT | O_WRONLY, 0);
{ {
auto init = mmap_data("/init"); auto init = mmap_data("/init");
init.patch({ make_pair(INIT_PATH, REDIR_PATH) }); init.patch({ make_pair(INIT_PATH, REDIR_PATH) });
@ -239,58 +43,35 @@ void SARInit::first_stage_prep() {
fclone_attr(src, dest); fclone_attr(src, dest);
close(dest); close(dest);
} }
xmount("/data/init", "/init", nullptr, MS_BIND, nullptr);
// Replace redirect init with magiskinit // Replace redirect init with magiskinit
dest = xopen("/dev/magiskinit", O_CREAT | O_WRONLY, 0); dest = xopen(REDIR_PATH, O_CREAT | O_WRONLY, 0);
write(dest, self.buf, self.sz); write(dest, self.buf, self.sz);
fclone_attr(src, dest); fclone_attr(src, dest);
close(src); close(src);
close(dest); close(dest);
xmount("/dev/init", "/init", nullptr, MS_BIND, nullptr); // Copy files to tmpfs
xmount("/dev/magiskinit", REDIR_PATH, nullptr, MS_BIND, nullptr); xmkdir("/data/.backup", 0);
xumount2("/dev", MNT_DETACH); xmkdir("/data/overlay.d", 0);
restore_folder("/data/overlay.d", overlays);
// Block SIGUSR1 int cfg = xopen("/data/.backup/config", O_WRONLY | O_CREAT, 0);
sigset_t block, old; xwrite(cfg, magisk_config.buf, magisk_config.sz);
sigemptyset(&block); close(cfg);
sigaddset(&block, SIGUSR1); }
sigprocmask(SIG_BLOCK, &block, &old);
bool SecondStageInit::prepare() {
if (int child = xfork()) { backup_files();
LOGD("init daemon [%d]\n", child);
// Wait for children signal umount2("/init", MNT_DETACH);
int sig; umount2("/proc/self/exe", MNT_DETACH);
sigwait(&block, &sig); umount2("/data", MNT_DETACH);
// Restore sigmask // Some weird devices like meizu, uses 2SI but still have legacy rootfs
sigprocmask(SIG_SETMASK, &old, nullptr); // Check if root and system are on the same filesystem
} else { struct stat root{}, system{};
// Establish socket for 2nd stage ack xstat("/", &root);
struct sockaddr_un sun{}; xstat("/system", &system);
int sockfd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); return root.st_dev != system.st_dev;
xbind(sockfd, (struct sockaddr*) &sun, setup_sockaddr(&sun, INIT_SOCKET));
xlisten(sockfd, 1);
// Resume parent
kill(getppid(), SIGUSR1);
// Wait for second stage ack
int client = xaccept4(sockfd, nullptr, nullptr, SOCK_CLOEXEC);
// Write backup files
string tmp_dir = read_string(client);
chdir(tmp_dir.data());
int cfg = xopen(INTLROOT "/config", O_WRONLY | O_CREAT, 0);
xwrite(cfg, magisk_config.buf, magisk_config.sz);
close(cfg);
restore_folder(ROOTOVL, overlays);
// Ack and bail out!
write_int(client, 0);
close(client);
close(sockfd);
exit(0);
}
} }