From 16e4c67992ead7705d6c8b10bd200f300aad451a Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Mon, 2 Nov 2020 23:20:38 -0800 Subject: [PATCH] Significantly broaden sepolicy.rule compatibility Previously, Magisk uses persist or cache for storing modules' custom sepolicy rules. In this commit, we significantly broaden its compatibility and also prevent mounting errors. The persist partition is non-standard and also critical for Snapdragon devices, so we prefer not to use it by default. We will go through the following logic to find the best suitable non-volatile, writable location to store and load sepolicy.rule files: Unencrypted data -> FBE data unencrypted dir -> cache -> metadata -> persist This should cover almost all possible cases: very old devices have cache partitions; newer devices will use FBE; latest devices will use metadata FBE (which guarantees a metadata parition); and finally, all Snapdragon devices have the persist partition (as a last resort). Fix #3179 --- .../magisk/core/model/module/LocalModule.kt | 20 +++- native/jni/core/bootstages.cpp | 27 ++++- native/jni/include/magisk.hpp | 1 + native/jni/init/init.hpp | 6 +- native/jni/init/mount.cpp | 105 +++++++++++++----- native/jni/init/rootdir.cpp | 96 ++++++++++------ scripts/boot_patch.sh | 3 + scripts/magisk_uninstaller.sh | 13 ++- scripts/util_functions.sh | 71 +++++++----- 9 files changed, 237 insertions(+), 105 deletions(-) diff --git a/app/src/main/java/com/topjohnwu/magisk/core/model/module/LocalModule.kt b/app/src/main/java/com/topjohnwu/magisk/core/model/module/LocalModule.kt index c48fad7fc..a69ec0a9e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/model/module/LocalModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/model/module/LocalModule.kt @@ -26,11 +26,17 @@ class LocalModule(path: String) : Module() { set(enable) { val dir = "$PERSIST/$id" if (enable) { - Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit() disableFile.delete() + if (Const.Version.isCanary()) + Shell.su("copy_sepolicy_rules").submit() + else + Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit() } else { - Shell.su("rm -rf $dir").submit() !disableFile.createNewFile() + if (Const.Version.isCanary()) + Shell.su("copy_sepolicy_rules").submit() + else + Shell.su("rm -rf $dir").submit() } } @@ -38,11 +44,17 @@ class LocalModule(path: String) : Module() { get() = removeFile.exists() set(remove) { if (remove) { - Shell.su("rm -rf $PERSIST/$id").submit() removeFile.createNewFile() + if (Const.Version.isCanary()) + Shell.su("copy_sepolicy_rules").submit() + else + Shell.su("rm -rf $PERSIST/$id").submit() } else { - Shell.su("cp -af $ruleFile $PERSIST/$id").submit() !removeFile.delete() + if (Const.Version.isCanary()) + Shell.su("copy_sepolicy_rules").submit() + else + Shell.su("cp -af $ruleFile $PERSIST/$id").submit() } } diff --git a/native/jni/core/bootstages.cpp b/native/jni/core/bootstages.cpp index 421689caa..3837cb686 100644 --- a/native/jni/core/bootstages.cpp +++ b/native/jni/core/bootstages.cpp @@ -22,14 +22,15 @@ static bool safe_mode = false; * Setup * *********/ -#define DIR_IS(part) (me->mnt_dir == "/" #part ""sv) +#define MNT_DIR_IS(dir) (me->mnt_dir == string_view(dir)) #define SETMIR(b, part) sprintf(b, "%s/" MIRRDIR "/" #part, MAGISKTMP.data()) #define SETBLK(b, part) sprintf(b, "%s/" BLOCKDIR "/" #part, MAGISKTMP.data()) #define mount_mirror(part, flag) \ -else if (DIR_IS(part) && me->mnt_type != "tmpfs"sv && lstat(me->mnt_dir, &st) == 0) { \ +else if (MNT_DIR_IS("/" #part) && me->mnt_type != "tmpfs"sv && lstat(me->mnt_dir, &st) == 0) { \ SETMIR(buf1, part); \ SETBLK(buf2, part); \ + unlink(buf2); \ mknod(buf2, S_IFBLK | 0600, st.st_dev); \ xmkdir(buf1, 0755); \ xmount(buf2, buf1, me->mnt_type, flag, nullptr); \ @@ -43,6 +44,16 @@ if (access("/system/" #part, F_OK) == 0 && access(buf1, F_OK) != 0) { \ LOGI("link: %s\n", buf1); \ } +#define link_orig_dir(dir, part) \ +else if (MNT_DIR_IS(dir) && me->mnt_type != "tmpfs"sv) { \ + SETMIR(buf1, part); \ + rmdir(buf1); \ + xsymlink(dir, buf1); \ + LOGI("link: %s\n", buf1); \ +} + +#define link_orig(part) link_orig_dir("/" #part, part) + static bool magisk_env() { LOGI("* Initializing Magisk environment\n"); @@ -98,7 +109,11 @@ static bool magisk_env() { mount_mirror(product, MS_RDONLY) mount_mirror(system_ext, MS_RDONLY) mount_mirror(data, 0) - else if (SDK_INT >= 24 && DIR_IS(proc) && !strstr(me->mnt_opts, "hidepid=2")) { + link_orig(cache) + link_orig(metadata) + link_orig(persist) + link_orig_dir("/mnt/vendor/persist", persist) + else if (SDK_INT >= 24 && MNT_DIR_IS("/proc") && !strstr(me->mnt_opts, "hidepid=2")) { xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009"); } return true; @@ -109,9 +124,9 @@ static bool magisk_env() { xsymlink("./system_root/system", buf1); LOGI("link: %s\n", buf1); } - link_mirror(vendor); - link_mirror(product); - link_mirror(system_ext); + link_mirror(vendor) + link_mirror(product) + link_mirror(system_ext) // Disable/remove magiskhide, resetprop if (SDK_INT < 19) { diff --git a/native/jni/include/magisk.hpp b/native/jni/include/magisk.hpp index 2203f966b..a86688edb 100644 --- a/native/jni/include/magisk.hpp +++ b/native/jni/include/magisk.hpp @@ -17,6 +17,7 @@ extern std::string MAGISKTMP; #define INTLROOT ".magisk" #define MIRRDIR INTLROOT "/mirror" +#define RULESDIR MIRRDIR "/sepolicy.rules" #define BLOCKDIR INTLROOT "/block" #define MODULEMNT INTLROOT "/modules" #define BBPATH INTLROOT "/busybox" diff --git a/native/jni/init/init.hpp b/native/jni/init/init.hpp index 8459f2702..81fe1bc73 100644 --- a/native/jni/init/init.hpp +++ b/native/jni/init/init.hpp @@ -68,11 +68,12 @@ class MagiskInit : public BaseInit { protected: auto_data self; auto_data config; - std::string persist_dir; + std::string custom_rules_dir; void mount_with_dt(); bool patch_sepolicy(const char *file); void setup_tmp(const char *path); + void mount_rules_dir(const char *dev_base, const char *mnt_base); public: MagiskInit(char *argv[], cmdline *cmd) : BaseInit(argv, cmd) {} }; @@ -114,10 +115,11 @@ public: class SecondStageInit : public SARBase { protected: void early_mount() override; - void cleanup() override { /* Do not do any cleanup */ } public: SecondStageInit(char *argv[]) : SARBase(argv, nullptr) { LOGD("%s\n", __FUNCTION__); + // Do not unmount /sys and /proc + mount_list.clear(); }; }; diff --git a/native/jni/init/mount.cpp b/native/jni/init/mount.cpp index dce678a9d..dd665102e 100644 --- a/native/jni/init/mount.cpp +++ b/native/jni/init/mount.cpp @@ -205,27 +205,89 @@ static void switch_root(const string &path) { frm_rf(root); } -static void mount_persist(const char *dev_base, const char *mnt_base) { - string mnt_point = mnt_base + "/persist"s; - strcpy(blk_info.partname, "persist"); +void MagiskInit::mount_rules_dir(const char *dev_base, const char *mnt_base) { + char path[128]; xrealpath(dev_base, blk_info.block_dev); - char *s = blk_info.block_dev + strlen(blk_info.block_dev); - strcpy(s, "/persist"); + xrealpath(mnt_base, path); + char *b = blk_info.block_dev + strlen(blk_info.block_dev); + char *p = path + strlen(path); + + auto do_mount = [&](const char *type) -> bool { + xmkdir(path, 0755); + bool success = xmount(blk_info.block_dev, path, type, 0, nullptr) == 0; + if (success) + mount_list.emplace_back(path); + return success; + }; + + // First try userdata + strcpy(blk_info.partname, "userdata"); + strcpy(b, "/data"); + strcpy(p, "/data"); if (setup_block(false) < 0) { - // Fallback to cache - strcpy(blk_info.partname, "cache"); - strcpy(s, "/cache"); - if (setup_block(false) < 0) { - // Try NVIDIA's BS - strcpy(blk_info.partname, "CAC"); - if (setup_block(false) < 0) - return; - } - xsymlink("./cache", mnt_point.data()); - mnt_point = mnt_base + "/cache"s; + // Try NVIDIA naming scheme + strcpy(blk_info.partname, "UDA"); + if (setup_block(false) < 0) + goto cache; } - xmkdir(mnt_point.data(), 0755); - xmount(blk_info.block_dev, mnt_point.data(), "ext4", 0, nullptr); + // Try to mount with either ext4 or f2fs + // Failure means either FDE or metadata encryption + if (!do_mount("ext4") && !do_mount("f2fs")) + goto cache; + + strcpy(p, "/data/unencrypted"); + if (access(path, F_OK) == 0) { + // FBE, need to use an unencrypted path + custom_rules_dir = path + "/magisk"s; + } else { + // Skip if /data/adb does not exist + strcpy(p, "/data/adb"); + if (access(path, F_OK) != 0) + return; + // Unencrypted, directly use module paths + custom_rules_dir = string(mnt_base) + MODULEROOT; + } + goto success; + +cache: + // Fallback to cache + strcpy(blk_info.partname, "cache"); + strcpy(b, "/cache"); + strcpy(p, "/cache"); + if (setup_block(false) < 0) { + // Try NVIDIA naming scheme + strcpy(blk_info.partname, "CAC"); + if (setup_block(false) < 0) + goto metadata; + } + if (!do_mount("ext4")) + goto metadata; + custom_rules_dir = path + "/magisk"s; + goto success; + +metadata: + // Fallback to metadata + strcpy(blk_info.partname, "metadata"); + strcpy(b, "/metadata"); + strcpy(p, "/metadata"); + if (setup_block(false) < 0 || !do_mount("ext4")) + goto persist; + custom_rules_dir = path + "/magisk"s; + goto success; + +persist: + // Fallback to persist + strcpy(blk_info.partname, "persist"); + strcpy(b, "/persist"); + strcpy(p, "/persist"); + if (setup_block(false) < 0 || !do_mount("ext4")) + return; + custom_rules_dir = path + "/magisk"s; + +success: + // Create symlinks so we don't need to go through this logic again + strcpy(p, "/sepolicy.rules"); + xsymlink(custom_rules_dir.data(), path); } void RootFSInit::early_mount() { @@ -235,11 +297,6 @@ void RootFSInit::early_mount() { rename("/.backup/init", "/init"); mount_with_dt(); - - xmkdir("/dev/mnt", 0755); - mount_persist("/dev/block", "/dev/mnt"); - mount_list.emplace_back("/dev/mnt/persist"); - persist_dir = "/dev/mnt/persist/magisk"; } void SARBase::backup_files() { @@ -337,8 +394,6 @@ void MagiskInit::setup_tmp(const char *path) { xmkdir(MIRRDIR, 0); xmkdir(BLOCKDIR, 0); - mount_persist(BLOCKDIR, MIRRDIR); - int fd = xopen(INTLROOT "/config", O_WRONLY | O_CREAT, 0); xwrite(fd, config.buf, config.sz); close(fd); diff --git a/native/jni/init/rootdir.cpp b/native/jni/init/rootdir.cpp index a7c5172d1..1c3f76836 100644 --- a/native/jni/init/rootdir.cpp +++ b/native/jni/init/rootdir.cpp @@ -81,33 +81,6 @@ static void load_overlay_rc(const char *overlay) { } } -void RootFSInit::setup_rootfs() { - if (patch_sepolicy("/sepolicy")) { - auto init = raw_data::mmap_rw("/init"); - 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", "/"); - } - - patch_init_rc("/init.rc", "/init.p.rc", "/sbin"); - rename("/init.p.rc", "/init.rc"); - - // Create hardlink mirror of /sbin to /root - mkdir("/root", 0750); - clone_attr("/sbin", "/root"); - link_path("/sbin", "/root"); - - // Dump magiskinit as magisk - int fd = xopen("/sbin/magisk", O_WRONLY | O_CREAT, 0755); - write(fd, self.buf, self.sz); - close(fd); -} - bool MagiskInit::patch_sepolicy(const char *file) { bool patch_init = false; sepolicy *sepol = nullptr; @@ -135,12 +108,14 @@ bool MagiskInit::patch_sepolicy(const char *file) { sepol->magisk_rules(); // Custom rules - if (auto dir = open_dir(persist_dir.data()); dir) { - for (dirent *entry; (entry = xreaddir(dir.get()));) { - auto rule = persist_dir + "/" + entry->d_name + "/sepolicy.rule"; - if (access(rule.data(), R_OK) == 0) { - LOGD("Loading custom sepolicy patch: %s\n", rule.data()); - sepol->load_rule_file(rule.data()); + if (!custom_rules_dir.empty()) { + if (auto dir = open_dir(custom_rules_dir.data())) { + for (dirent *entry; (entry = xreaddir(dir.get()));) { + auto rule = custom_rules_dir + "/" + entry->d_name + "/sepolicy.rule"; + if (access(rule.data(), R_OK) == 0) { + LOGD("Loading custom sepolicy patch: %s\n", rule.data()); + sepol->load_rule_file(rule.data()); + } } } } @@ -229,9 +204,8 @@ void SARBase::patch_rootdir() { } setup_tmp(tmp_dir); - persist_dir = MIRRDIR "/persist/magisk"; - chdir(tmp_dir); + mount_rules_dir(BLOCKDIR, MIRRDIR); // Mount system_root mirror struct stat st; @@ -317,11 +291,55 @@ void SARBase::patch_rootdir() { chdir("/"); } +#define TMP_MNTDIR "/dev/mnt" +#define TMP_RULESDIR "/.backup/.sepolicy.rules" + +void RootFSInit::setup_rootfs() { + // Handle custom sepolicy rules + xmkdir(TMP_MNTDIR, 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")) { + auto init = raw_data::mmap_rw("/init"); + 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", "/"); + } + + patch_init_rc("/init.rc", "/init.p.rc", "/sbin"); + rename("/init.p.rc", "/init.rc"); + + // Create hardlink mirror of /sbin to /root + mkdir("/root", 0750); + clone_attr("/sbin", "/root"); + link_path("/sbin", "/root"); + + // 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() { + // Mount rootfs as rw to do post-init rootfs patches + xmount(nullptr, "/", nullptr, MS_REMOUNT, nullptr); + + // Backup stuffs before removing them self = raw_data::read("/sbin/magisk"); config = raw_data::read("/.backup/.magisk"); - - xmount(nullptr, "/", nullptr, MS_REMOUNT, nullptr); + char custom_rules_dir[64]; + custom_rules_dir[0] = '\0'; + xreadlink(TMP_RULESDIR, custom_rules_dir, sizeof(custom_rules_dir)); unlink("/sbin/magisk"); rm_rf("/.backup"); @@ -331,6 +349,10 @@ void MagiskProxy::start() { // 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); } diff --git a/scripts/boot_patch.sh b/scripts/boot_patch.sh index f0189691d..3bdba05bb 100644 --- a/scripts/boot_patch.sh +++ b/scripts/boot_patch.sh @@ -178,5 +178,8 @@ ui_print "- Repacking boot image" # Sign chromeos boot $CHROMEOS && sign_chromeos +# Copy existing rules for migration +$BOOTMODE && copy_sepolicy_rules + # Reset any error code true diff --git a/scripts/magisk_uninstaller.sh b/scripts/magisk_uninstaller.sh index 03f176128..0c3683126 100644 --- a/scripts/magisk_uninstaller.sh +++ b/scripts/magisk_uninstaller.sh @@ -15,7 +15,6 @@ TMPDIR=/dev/tmp INSTALLER=$TMPDIR/install CHROMEDIR=$INSTALLER/chromeos -PERSISTDIR=/sbin/.magisk/mirror/persist # Default permissions umask 022 @@ -36,7 +35,12 @@ setup_flashable print_title "Magisk Uninstaller" is_mounted /data || mount /data || abort "! Unable to mount /data, please uninstall with Magisk Manager" -is_mounted /cache || mount /cache 2>/dev/null +if ! $BOOTMODE; then + # Mounting stuffs in recovery (best effort) + mount_name metadata /metadata + mount_name "cache cac" /cache + mount_name persist /persist +fi mount_partitions api_level_arch_detect @@ -141,7 +145,8 @@ ui_print "- Removing Magisk files" rm -rf \ /cache/*magisk* /cache/unblock /data/*magisk* /data/cache/*magisk* /data/property/*magisk* \ /data/Magisk.apk /data/busybox /data/custom_ramdisk_patch.sh /data/adb/*magisk* \ -/data/adb/post-fs-data.d /data/adb/service.d /data/adb/modules* $PERSISTDIR/magisk 2>/dev/null +/data/adb/post-fs-data.d /data/adb/service.d /data/adb/modules* \ +/data/unencrypted/magisk /metadata/magisk /persist/magisk /mnt/vendor/persist/magisk if [ -f /system/addon.d/99-magisk.sh ]; then blockdev --setrw /dev/block/mapper/system$SLOT 2>/dev/null @@ -158,7 +163,7 @@ if $BOOTMODE; then ui_print "********************************************" (sleep 8; /system/bin/reboot)& else - rm -rf /data/user*/*/*magisk* /data/app/*magisk* + rm -rf /data/data/*magisk* /data/user*/*/*magisk* /data/app/*magisk* /data/app/*/*magisk* recovery_cleanup ui_print "- Done" fi diff --git a/scripts/util_functions.sh b/scripts/util_functions.sh index 2491f3215..00e3cbd9b 100644 --- a/scripts/util_functions.sh +++ b/scripts/util_functions.sh @@ -152,6 +152,7 @@ recovery_cleanup() { fi umount -l /vendor umount -l /persist + umount -l /metadata for DIR in /apex /system /system_root; do if [ -L "${DIR}_link" ]; then rmdir $DIR @@ -217,13 +218,13 @@ mount_name() { local FLAG=$3 setup_mntpoint $POINT is_mounted $POINT && return - ui_print "- Mounting $POINT" # First try mounting with fstab mount $FLAG $POINT 2>/dev/null if ! is_mounted $POINT; then - local BLOCK=`find_block $PART` - mount $FLAG $BLOCK $POINT + local BLOCK=$(find_block $PART) + mount $FLAG $BLOCK $POINT || return fi + ui_print "- Mounting $POINT" } # mount_ro_ensure @@ -266,18 +267,6 @@ mount_partitions() { # Allow /system/bin commands (dalvikvm) on Android 10+ in recovery $BOOTMODE || mount_apex - - # Mount persist partition in recovery - if ! $BOOTMODE && [ ! -z $PERSISTDIR ]; then - # Try to mount persist - PERSISTDIR=/persist - mount_name persist /persist - if ! is_mounted /persist; then - # Fallback to cache - mount_name "cache cac" /cache - is_mounted /cache && PERSISTDIR=/cache || PERSISTDIR= - fi - fi } # loop_setup , sets LOOPDEV @@ -575,6 +564,41 @@ run_migrations() { done } +copy_sepolicy_rules() { + # Remove all existing rule folders + rm -rf /data/unencrypted/magisk /metadata/magisk /persist/magisk /mnt/vendor/persist/magisk + + # Find current active RULESDIR + local RULESDIR + local active_dir=$(magisk --path)/.magisk/mirror/sepolicy.rules + if [ -e $active_dir ]; then + RULESDIR=$(readlink -f $active_dir) + elif [ -d /data/unencrypted ] && ! grep ' /data ' /proc/mounts | grep -q 'dm-'; then + RULESDIR=/data/unencrypted/magisk + elif grep -q ' /cache ' /proc/mounts; then + RULESDIR=/cache/magisk + elif grep -q ' /metadata ' /proc/mounts; then + RULESDIR=/metadata/magisk + elif grep -q ' /persist ' /proc/mounts; then + RULESDIR=/persist/magisk + elif grep -q ' /mnt/vendor/persist ' /proc/mounts; then + RULESDIR=/mnt/vendor/persist/magisk + else + return + fi + + # Copy all enabled sepolicy.rule + for r in /data/adb/modules*/*/sepolicy.rule; do + [ -f "$r" ] || continue + local MODDIR=${r%/*} + [ -f $MODDIR/disable ] && continue + [ -f $MODDIR/remove ] && continue + local MODNAME=${MODDIR##*/} + mkdir -p $RULESDIR/$MODNAME + cp -f $r $RULESDIR/$MODNAME/sepolicy.rule + done +} + ################# # Module Related ################# @@ -620,9 +644,6 @@ is_legacy_script() { # Require OUTFD, ZIPFILE to be set install_module() { - local PERSISTDIR - command -v magisk >/dev/null && PERSISTDIR=$(magisk --path)/mirror/persist - rm -rf $TMPDIR mkdir -p $TMPDIR @@ -646,7 +667,7 @@ install_module() { MODPATH=$MODULEROOT/$MODID # Create mod paths - rm -rf $MODPATH 2>/dev/null + rm -rf $MODPATH mkdir -p $MODPATH if is_legacy_script; then @@ -699,19 +720,15 @@ install_module() { fi # Copy over custom sepolicy rules - if [ -f $MODPATH/sepolicy.rule -a -e "$PERSISTDIR" ]; then - ui_print "- Installing custom sepolicy patch" - # Remove old recovery logs (which may be filling partition) to make room - rm -f $PERSISTDIR/cache/recovery/* - PERSISTMOD=$PERSISTDIR/magisk/$MODID - mkdir -p $PERSISTMOD - cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule || abort "! Insufficient partition size" + if [ -f $MODPATH/sepolicy.rule ]; then + ui_print "- Installing custom sepolicy rules" + copy_sepolicy_rules fi # Remove stuffs that don't belong to modules rm -rf \ $MODPATH/system/placeholder $MODPATH/customize.sh \ - $MODPATH/README.md $MODPATH/.git* 2>/dev/null + $MODPATH/README.md $MODPATH/.git* cd / $BOOTMODE || recovery_cleanup