Add hijack sepolicy support for rootfs devices

On older Android versions, pre-mounting selinuxfs will lead to errors,
so we have to use a different method to block init's control flow.
Since all devices that falls in this catagory must both:

1. Be Android 8.0 - 9.0
2. Have early mount fstab in its device tree

We can actually use the same FIFO trick, but this time not on selinuxfs,
but on the read-only device tree nodes in sysfs or procfs. By mocking
the fstab/compatible node in the device tree, we can block init when
it attempts to do early mount; at that point, we can then mock selinuxfs
as we normally would, successfully hijack and inject patched sepolicy.
This commit is contained in:
topjohnwu 2022-03-16 20:01:26 -07:00
parent 49f259065d
commit e841aab9e7
4 changed files with 105 additions and 142 deletions

View File

@ -108,12 +108,6 @@ static int test_main(int argc, char *argv[]) {
}
#endif // ENABLE_TEST
static int magisk_proxy_main(int argc, char *argv[]) {
auto init = make_unique<MagiskProxy>(argv);
init->start();
return 1;
}
int main(int argc, char *argv[]) {
umask(0);

View File

@ -40,6 +40,7 @@ struct fstab_entry {
extern std::vector<std::string> mount_list;
int magisk_proxy_main(int argc, char *argv[]);
bool unxz(int fd, const uint8_t *buf, size_t size);
void load_kernel_info(BootConfig *config);
bool check_two_stage();
@ -77,7 +78,7 @@ protected:
static constexpr bool avd_hack = false;
#endif
bool patch_sepolicy(const char *file);
void patch_sepolicy(const char *file);
void hijack_sepolicy();
void setup_tmp(const char *path);
void mount_rules_dir(const char *dev_base, const char *mnt_base);
@ -158,23 +159,14 @@ public:
class RootFSInit : public MagiskInit {
private:
void early_mount();
void prepare();
public:
RootFSInit(char *argv[], BootConfig *config) : MagiskInit(argv, config) {
LOGD("%s\n", __FUNCTION__);
}
void start() override {
early_mount();
prepare();
patch_rw_root();
exec_init();
}
};
class MagiskProxy : public MagiskInit {
public:
explicit MagiskProxy(char *argv[]) : MagiskInit(argv) {
setup_klog();
LOGD("%s\n", __FUNCTION__);
}
void start() override;
};

View File

@ -152,21 +152,6 @@ static void read_dt_fstab(BootConfig *config, vector<fstab_entry> &fstab) {
}
}
static void mount_with_dt(BootConfig *config) {
vector<fstab_entry> fstab;
read_dt_fstab(config, fstab);
for (const auto &entry : fstab) {
if (is_lnk(entry.mnt_point.data()))
continue;
// Derive partname from dev
sprintf(blk_info.partname, "%s%s", basename(entry.dev.data()), config->slot);
setup_block(true);
xmkdir(entry.mnt_point.data(), 0755);
xmount(blk_info.block_dev, entry.mnt_point.data(), entry.type.data(), MS_RDONLY, nullptr);
mount_list.push_back(entry.mnt_point);
}
}
static void avd_hack_mount(BootConfig *config) {
vector<fstab_entry> fstab;
read_dt_fstab(config, fstab);
@ -306,13 +291,12 @@ success:
xsymlink(custom_rules_dir.data(), path);
}
void RootFSInit::early_mount() {
void RootFSInit::prepare() {
self = mmap_data("/init");
magisk_cfg = mmap_data("/.backup/.magisk");
LOGD("Restoring /init\n");
rename(backup_init(), "/init");
mount_with_dt(config);
}
void SARBase::backup_files() {

View File

@ -82,34 +82,9 @@ static void load_overlay_rc(const char *overlay) {
}
}
bool MagiskInit::patch_sepolicy(const char *file) {
bool patch_init = false;
sepolicy *sepol = nullptr;
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");
sepol = sepolicy::from_file("/sepolicy");
} else {
LOGD("sepol: no selinux\n");
return false;
}
if (access(SELINUX_VERSION, F_OK) != 0) {
// Mount selinuxfs to communicate with kernel
xmount("selinuxfs", SELINUX_MNT, "selinuxfs", 0, nullptr);
mount_list.emplace_back(SELINUX_MNT);
}
if (patch_init)
sepol = sepolicy::from_split();
if (!sepol) {
LOGE("Cannot load split cil\n");
return false;
}
void MagiskInit::patch_sepolicy(const char *file) {
LOGD("Patching monolithic policy\n");
auto sepol = unique_ptr<sepolicy>(sepolicy::from_file("/sepolicy"));
sepol->magisk_rules();
@ -128,19 +103,17 @@ bool MagiskInit::patch_sepolicy(const char *file) {
LOGD("Dumping sepolicy to: [%s]\n", file);
sepol->to_file(file);
delete sepol;
// 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;
}
#define MOCK_LOAD SELINUXMOCK "/load"
#define MOCK_ENFORCE SELINUXMOCK "/enforce"
#define MOCK_COMPAT SELINUXMOCK "/compatible"
#define REAL_SELINUXFS SELINUXMOCK "/fs"
void MagiskInit::hijack_sepolicy() {
@ -161,31 +134,60 @@ void MagiskInit::hijack_sepolicy() {
// Hijack the "load" and "enforce" node in selinuxfs to manipulate
// the actual sepolicy being loaded into the kernel
xmkdir(SELINUXMOCK, 0);
auto hijack = [] {
LOGD("Hijack [" SELINUX_LOAD "] and [" SELINUX_ENFORCE "]\n");
mkfifo(MOCK_LOAD, 0600);
mkfifo(MOCK_ENFORCE, 0644);
xmount(MOCK_LOAD, SELINUX_LOAD, nullptr, MS_BIND, nullptr);
xmount(MOCK_ENFORCE, SELINUX_ENFORCE, nullptr, MS_BIND, nullptr);
};
// We need to preserve sysfs and selinuxfs after re-exec
mount_list.erase(std::remove_if(
mount_list.begin(), mount_list.end(),
[](const string &s) { return s == "/sys"; }), mount_list.end());
string dt_compat;
if (access(SELINUX_ENFORCE, F_OK) != 0) {
// selinuxfs needs to be mounted
xmount("selinuxfs", SELINUX_MNT, "selinuxfs", 0, nullptr);
// selinuxfs not mounted yet. Hijack the dt fstab nodes first
// and let the original init mount selinuxfs for us
// This only happens on Android 8.0 - 9.0
// Preserve sysfs and procfs for hijacking
mount_list.erase(std::remove_if(
mount_list.begin(), mount_list.end(),
[](const string &s) { return s == "/proc" || s == "/sys"; }), mount_list.end());
// Remount procfs with proper options
xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009");
char buf[4096];
snprintf(buf, sizeof(buf), "%s/fstab/compatible", config->dt_dir);
dt_compat = full_read(buf);
LOGD("Hijack [%s]\n", buf);
mkfifo(MOCK_COMPAT, 0444);
xmount(MOCK_COMPAT, buf, nullptr, MS_BIND, nullptr);
} else {
hijack();
}
LOGD("Hijack [" SELINUX_LOAD "] and [" SELINUX_ENFORCE "]\n");
xmkdir(SELINUXMOCK, 0);
mkfifo(MOCK_LOAD, 0600);
mkfifo(MOCK_ENFORCE, 0644);
xmount(MOCK_LOAD, SELINUX_LOAD, nullptr, MS_BIND, nullptr);
xmount(MOCK_ENFORCE, SELINUX_ENFORCE, nullptr, MS_BIND, nullptr);
// Create a new process waiting for original init to load sepolicy into our fifo
// Create a new process waiting for init operations
if (xfork()) {
// In parent, return and continue boot process
return;
}
if (!dt_compat.empty()) {
// This open will block until init calls DoFirstStageMount
// The only purpose here is actually to wait for init to mount selinuxfs for us
int fd = xopen(MOCK_COMPAT, O_WRONLY);
char buf[4096];
snprintf(buf, sizeof(buf), "%s/fstab/compatible", config->dt_dir);
xumount2(buf, MNT_DETACH);
hijack();
xwrite(fd, dt_compat.data(), dt_compat.size());
close(fd);
}
// Read full sepolicy
int fd = xopen(MOCK_LOAD, O_RDONLY);
string policy = fd_full_read(fd);
@ -199,7 +201,7 @@ void MagiskInit::hijack_sepolicy() {
xmkdir(REAL_SELINUXFS, 0755);
xmount("selinuxfs", REAL_SELINUXFS, "selinuxfs", 0, nullptr);
// This open will block until the actual init calls security_getenforce
// This open will block until init calls security_getenforce
fd = xopen(MOCK_ENFORCE, O_WRONLY);
// Cleanup the hijacks
@ -220,7 +222,7 @@ void MagiskInit::hijack_sepolicy() {
xwrite(fd, "0", 1);
close(fd);
// At this point, the actual init process will be unblocked
// At this point, the init process will be unblocked
// and continue on with restorecon + re-exec.
// Terminate process
@ -380,8 +382,7 @@ void SARBase::patch_ro_root() {
chdir("/");
}
#define TMP_MNTDIR "/dev/mnt"
#define TMP_RULESDIR "/.backup/.sepolicy.rules"
#define PRE_TMPDIR "/magisk-tmp"
void MagiskInit::patch_rw_root() {
// Create hardlink mirror of /sbin to /root
@ -389,87 +390,79 @@ void MagiskInit::patch_rw_root() {
clone_attr("/sbin", "/root");
link_path("/sbin", "/root");
// Handle custom sepolicy rules
xmkdir(TMP_MNTDIR, 0755);
xmkdir("/dev/block", 0755);
mount_rules_dir("/dev/block", TMP_MNTDIR);
// Preserve custom rule path
if (!custom_rules_dir.empty()) {
string rules_dir = "./" + custom_rules_dir.substr(sizeof(TMP_MNTDIR));
xsymlink(rules_dir.data(), TMP_RULESDIR);
}
if (patch_sepolicy("/sepolicy")) {
if (access("/system/bin/init", F_OK) == 0) {
auto init = mmap_data("/system/bin/init");
init.patch({ make_pair(SPLIT_PLAT_CIL, "xxx") });
int dest = xopen("/init", O_TRUNC | O_WRONLY | O_CLOEXEC, 0);
xwrite(dest, init.buf, init.sz);
close(dest);
} else {
auto init = mmap_data("/init", true);
init.patch({ make_pair(SPLIT_PLAT_CIL, "xxx") });
}
}
// Handle overlays
if (access("/overlay.d", F_OK) == 0) {
LOGD("Merge overlay.d\n");
load_overlay_rc("/overlay.d");
mv_path("/overlay.d", "/");
}
rm_rf("/.backup");
// Patch init.rc
patch_init_rc("/init.rc", "/init.p.rc", "/sbin");
rename("/init.p.rc", "/init.rc");
xmkdir(PRE_TMPDIR, 0);
setup_tmp(PRE_TMPDIR);
chdir(PRE_TMPDIR);
mount_rules_dir(BLOCKDIR, MIRRDIR);
{
// Extract magisk
auto magisk = mmap_data("/sbin/magisk32.xz");
unlink("/sbin/magisk32.xz");
int fd = xopen("magisk32", O_WRONLY | O_CREAT, 0755);
unxz(fd, magisk.buf, magisk.sz);
close(fd);
patch_socket_name("magisk32");
if (access("/sbin/magisk64.xz", F_OK) == 0) {
magisk = mmap_data("/sbin/magisk64.xz");
unlink("/sbin/magisk64.xz");
fd = xopen("magisk64", O_WRONLY | O_CREAT, 0755);
unxz(fd, magisk.buf, magisk.sz);
close(fd);
patch_socket_name("magisk64");
xsymlink("./magisk64", "magisk");
} else {
xsymlink("./magisk32", "magisk");
}
}
if (access("/sepolicy", F_OK) == 0) {
patch_sepolicy("/sepolicy");
} else {
hijack_sepolicy();
}
chdir("/");
// Dump magiskinit as magisk
int fd = xopen("/sbin/magisk", O_WRONLY | O_CREAT, 0755);
write(fd, self.buf, self.sz);
close(fd);
}
void MagiskProxy::start() {
int magisk_proxy_main(int argc, char *argv[]) {
setup_klog();
LOGD("%s\n", __FUNCTION__);
// Mount rootfs as rw to do post-init rootfs patches
xmount(nullptr, "/", nullptr, MS_REMOUNT, nullptr);
// Backup stuffs before removing them
self = mmap_data("/sbin/magisk");
magisk_cfg = mmap_data("/.backup/.magisk");
auto magisk = mmap_data("/sbin/magisk32.xz");
auto magisk64 = mmap_data("/sbin/magisk64.xz");
char custom_rules_dir[64];
custom_rules_dir[0] = '\0';
xreadlink(TMP_RULESDIR, custom_rules_dir, sizeof(custom_rules_dir));
unlink("/sbin/magisk");
unlink("/sbin/magisk32.xz");
unlink("/sbin/magisk64.xz");
rm_rf("/.backup");
setup_tmp("/sbin");
// Extract magisk
int fd = xopen("/sbin/magisk32", O_WRONLY | O_CREAT, 0755);
unxz(fd, magisk.buf, magisk.sz);
close(fd);
patch_socket_name("/sbin/magisk32");
if (magisk64.sz) {
fd = xopen("/sbin/magisk64", O_WRONLY | O_CREAT, 0755);
unxz(fd, magisk64.buf, magisk64.sz);
close(fd);
patch_socket_name("/sbin/magisk64");
xsymlink("./magisk64", "/sbin/magisk");
} else {
xsymlink("./magisk32", "/sbin/magisk");
}
// Move tmpfs to /sbin
// For some reason MS_MOVE won't work, as a workaround bind mount then unmount
xmount(PRE_TMPDIR, "/sbin", nullptr, MS_BIND | MS_REC, nullptr);
xumount2(PRE_TMPDIR, MNT_DETACH);
rmdir(PRE_TMPDIR);
// Create symlinks pointing back to /root
recreate_sbin("/root", false);
if (custom_rules_dir[0])
xsymlink(custom_rules_dir, "/sbin/" RULESDIR);
// Tell magiskd to remount rootfs
setenv("REMOUNT_ROOT", "1", 1);
execv("/sbin/magisk", argv);
return 1;
}