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/getinfo.cpp \
init/twostage.cpp \
core/socket.cpp \
magiskpolicy/sepolicy.cpp \
magiskpolicy/magiskpolicy.cpp \
magiskpolicy/rules.cpp \
magiskpolicy/policydb.cpp \
magiskpolicy/statement.cpp \
magiskboot/pattern.cpp
magiskpolicy/statement.cpp
LOCAL_LDFLAGS := -static
include $(BUILD_EXECUTABLE)

View File

@ -34,7 +34,6 @@ struct fstab_entry {
fstab_entry(fstab_entry &&) = default;
fstab_entry &operator=(const fstab_entry&) = delete;
fstab_entry &operator=(fstab_entry&&) = default;
void to_file(FILE *fp);
};
#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);
void load_kernel_info(BootConfig *config);
bool is_dsu();
bool check_two_stage();
void setup_klog();
const char *backup_init();
@ -107,7 +105,6 @@ public:
class FirstStageInit : public BaseInit {
private:
void prepare();
void get_default_fstab(char *buf, size_t len);
public:
FirstStageInit(char *argv[], BootConfig *cmd) : BaseInit(argv, cmd) {
LOGD("%s\n", __FUNCTION__);
@ -124,17 +121,14 @@ public:
class SARInit : public SARBase {
private:
bool is_two_stage;
void early_mount();
bool early_mount();
void first_stage_prep();
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__);
};
void start() override {
early_mount();
if (is_two_stage)
if (early_mount())
first_stage_prep();
else
patch_rootdir();
@ -178,8 +172,10 @@ public:
};
void start() override {
if (prepare()) patch_rootfs();
else patch_rootdir();
if (prepare())
patch_rootfs();
else
patch_rootdir();
exec_init();
}
};

View File

@ -205,27 +205,6 @@ static void switch_root(const string &path) {
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) {
char path[128];
xrealpath(dev_base, blk_info.block_dev);
@ -327,10 +306,14 @@ void RootFSInit::early_mount() {
void SARBase::backup_files() {
if (access("/overlay.d", F_OK) == 0)
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");
if (access("/.backup/.magisk", R_OK) == 0)
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() {
@ -367,13 +350,13 @@ mount_root:
xmount("/dev/root", "/system_root", "erofs", MS_RDONLY, nullptr);
}
void SARInit::early_mount() {
bool SARInit::early_mount() {
backup_files();
mount_system_root();
switch_root("/system_root");
// 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);
if (!is_two_stage) {
@ -386,26 +369,8 @@ void SARInit::early_mount() {
#endif
mount_with_dt();
}
}
bool SecondStageInit::prepare() {
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;
return is_two_stage;
}
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(MONOPOLICY, sepol) /* Redirect /sepolicy to custom path */
});
if constexpr (avd_hack) {
if (avd_hack) {
// Force disable early mount on original init
init.patch({ make_pair("android,fstab", "xxx") });
}
@ -276,23 +276,9 @@ void SARBase::patch_rootdir() {
// sepolicy
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
restore_folder(ROOTOVL, overlays);
overlays.clear();
load_overlay_rc(ROOTOVL);
if (access(ROOTOVL "/sbin", F_OK) == 0) {
// Move files in overlay.d/sbin into tmp_dir

View File

@ -1,6 +1,4 @@
#include <sys/mount.h>
#include <map>
#include <set>
#include <magisk.hpp>
#include <utils.hpp>
@ -10,228 +8,34 @@
using namespace std;
void fstab_entry::to_file(FILE *fp) {
fprintf(fp, "%s %s %s %s %s\n", dev.data(), mnt_point.data(),
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);
}
#define INIT_PATH "/system/bin/init"
#define REDIR_PATH "/data/magiskinit"
void FirstStageInit::prepare() {
if (is_dsu()) {
rename(backup_init(), "/init");
LOGI("Skip loading Magisk because of DSU\n");
return;
}
xmkdirs("/data", 0755);
xmount("tmpfs", "/data", "tmpfs", 0, "mode=755");
cp_afc("/init" /* magiskinit */, REDIR_PATH);
run_finally finally([]{ chdir("/"); });
if (config->force_normal_boot) {
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);
unlink("/init");
xrename(backup_init(), "/init");
{
LOGD("Write fstab file: %s\n", fstab_file);
auto fp = xopen_file(fstab_file, "we");
for (auto &entry : fstab) {
// 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());
}
auto init = mmap_data("/init", true);
// Redirect original init to magiskinit
init.patch({ make_pair(INIT_PATH, REDIR_PATH) });
}
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() {
xmount("tmpfs", "/dev", "tmpfs", 0, "mode=755");
xmount("tmpfs", "/data", "tmpfs", 0, "mode=755");
// Patch init binary
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");
init.patch({ make_pair(INIT_PATH, REDIR_PATH) });
@ -239,58 +43,35 @@ void SARInit::first_stage_prep() {
fclone_attr(src, dest);
close(dest);
}
xmount("/data/init", "/init", nullptr, MS_BIND, nullptr);
// 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);
fclone_attr(src, dest);
close(src);
close(dest);
xmount("/dev/init", "/init", nullptr, MS_BIND, nullptr);
xmount("/dev/magiskinit", REDIR_PATH, nullptr, MS_BIND, nullptr);
xumount2("/dev", MNT_DETACH);
// Block SIGUSR1
sigset_t block, old;
sigemptyset(&block);
sigaddset(&block, SIGUSR1);
sigprocmask(SIG_BLOCK, &block, &old);
if (int child = xfork()) {
LOGD("init daemon [%d]\n", child);
// Wait for children signal
int sig;
sigwait(&block, &sig);
// Restore sigmask
sigprocmask(SIG_SETMASK, &old, nullptr);
} else {
// Establish socket for 2nd stage ack
struct sockaddr_un sun{};
int sockfd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
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);
}
// Copy files to tmpfs
xmkdir("/data/.backup", 0);
xmkdir("/data/overlay.d", 0);
restore_folder("/data/overlay.d", overlays);
int cfg = xopen("/data/.backup/config", O_WRONLY | O_CREAT, 0);
xwrite(cfg, magisk_config.buf, magisk_config.sz);
close(cfg);
}
bool SecondStageInit::prepare() {
backup_files();
umount2("/init", MNT_DETACH);
umount2("/proc/self/exe", MNT_DETACH);
umount2("/data", MNT_DETACH);
// Some weird devices like meizu, uses 2SI but still have legacy rootfs
// Check if root and system are on the same filesystem
struct stat root{}, system{};
xstat("/", &root);
xstat("/system", &system);
return root.st_dev != system.st_dev;
}