mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-25 02:17:38 +00:00
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:
parent
49f259065d
commit
e841aab9e7
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user