diff --git a/build.py b/build.py index 34d32d02f..04883ecb3 100755 --- a/build.py +++ b/build.py @@ -383,24 +383,6 @@ def build_stub(args): build_apk(args, 'stub') -def build_snet(args): - if not op.exists(op.join('stub', 'src', 'main', 'java', 'com', 'topjohnwu', 'snet')): - error('snet sources have to be bind mounted on top of the stub folder') - header('* Building snet extension') - proc = execv([gradlew, 'stub:assembleRelease']) - if proc.returncode != 0: - error('Build snet extention failed!') - source = op.join('stub', 'build', 'outputs', 'apk', - 'release', 'stub-release.apk') - target = op.join(config['outdir'], 'snet.jar') - # Extract classes.dex - with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zout: - with zipfile.ZipFile(source) as zin: - zout.writestr('classes.dex', zin.read('classes.dex')) - rm(source) - header('Output: ' + target) - - def cleanup(args): support_targets = {'native', 'java'} if args.target: @@ -470,13 +452,54 @@ def setup_avd(args): abi = cmd_out([adb_path, 'shell', 'getprop', 'ro.product.cpu.abi']) proc = execv([adb_path, 'push', f'native/out/{abi}/busybox', 'out/app-debug.apk', - 'scripts/emulator.sh', '/data/local/tmp']) + 'scripts/avd_magisk.sh', '/data/local/tmp']) if proc.returncode != 0: error('adb push failed!') - proc = execv([adb_path, 'shell', 'sh', '/data/local/tmp/emulator.sh']) + proc = execv([adb_path, 'shell', 'sh', '/data/local/tmp/avd_magisk.sh']) if proc.returncode != 0: - error('emulator.sh failed!') + error('avd_magisk.sh failed!') + + +def patch_avd_ramdisk(args): + build_binary(args) + build_app(args) + + header('* Patching emulator ramdisk.img') + + # Create a backup to prevent accidental overwrites + backup = args.ramdisk + '.bak' + if not op.exists(backup): + cp(args.ramdisk, backup) + + ini = op.join(op.dirname(args.ramdisk), 'advancedFeatures.ini') + with open(ini, 'r') as f: + adv_ft = f.read() + + # Need to turn off system as root + if 'SystemAsRoot = on' in adv_ft: + # Create a backup + cp(ini, ini + '.bak') + adv_ft = adv_ft.replace('SystemAsRoot = on', 'SystemAsRoot = off') + with open(ini, 'w') as f: + f.write(adv_ft) + + abi = cmd_out([adb_path, 'shell', 'getprop', 'ro.product.cpu.abi']) + proc = execv([adb_path, 'push', f'native/out/{abi}/busybox', 'out/app-debug.apk', + 'scripts/avd_patch.sh', '/data/local/tmp']) + if proc.returncode != 0: + error('adb push failed!') + proc = execv([adb_path, 'push', backup, '/data/local/tmp/ramdisk.cpio.gz']) + if proc.returncode != 0: + error('adb push failed!') + + proc = execv([adb_path, 'shell', 'sh', '/data/local/tmp/avd_patch.sh']) + if proc.returncode != 0: + error('avd_patch.sh failed!') + + proc = execv([adb_path, 'pull', '/data/local/tmp/ramdisk.cpio.gz', args.ramdisk]) + if proc.returncode != 0: + error('adb pull failed!') def build_all(args): @@ -512,13 +535,13 @@ stub_parser = subparsers.add_parser('stub', help='build the stub app') stub_parser.set_defaults(func=build_stub) avd_parser = subparsers.add_parser( - 'emulator', help='build and setup AVD for development') + 'emulator', help='setup AVD for development') avd_parser.set_defaults(func=setup_avd) -# Need to bind mount snet sources on top of stub folder -# Note: source code for the snet extension is *NOT* public -snet_parser = subparsers.add_parser('snet', help='build snet extension') -snet_parser.set_defaults(func=build_snet) +avd_patch_parser = subparsers.add_parser( + 'avd_patch', help='patch AVD ramdisk.img') +avd_patch_parser.add_argument('ramdisk', help='path to ramdisk.img') +avd_patch_parser.set_defaults(func=patch_avd_ramdisk) clean_parser = subparsers.add_parser('clean', help='cleanup') clean_parser.add_argument( diff --git a/native/jni/init/getinfo.cpp b/native/jni/init/getinfo.cpp index b385432df..8d2591805 100644 --- a/native/jni/init/getinfo.cpp +++ b/native/jni/init/getinfo.cpp @@ -180,6 +180,8 @@ void BootConfig::set(const kv_pairs &kv) { strlcpy(hardware_plat, value.data(), sizeof(hardware_plat)); } else if (key == "androidboot.fstab_suffix") { strlcpy(fstab_suffix, value.data(), sizeof(fstab_suffix)); + } else if (key == "qemu") { + emulator = true; } } } @@ -193,6 +195,7 @@ void BootConfig::print() { LOGD("fstab_suffix=[%s]\n", fstab_suffix); LOGD("hardware=[%s]\n", hardware); LOGD("hardware.platform=[%s]\n", hardware_plat); + LOGD("emulator=[%d]\n", emulator); } #define read_dt(name, key) \ @@ -230,7 +233,7 @@ void load_kernel_info(BootConfig *config) { parse_prop_file("/.backup/.magisk", [=](auto key, auto value) -> bool { if (key == "RECOVERYMODE" && value == "true") { LOGD("Running in recovery mode, waiting for key...\n"); - config->skip_initramfs = !check_key_combo(); + config->skip_initramfs = config->emulator || !check_key_combo(); return false; } return true; diff --git a/native/jni/init/init.hpp b/native/jni/init/init.hpp index 343e2b10a..f3217890b 100644 --- a/native/jni/init/init.hpp +++ b/native/jni/init/init.hpp @@ -6,6 +6,7 @@ struct BootConfig { bool skip_initramfs; bool force_normal_boot; bool rootwait; + bool emulator; char slot[3]; char dt_dir[64]; char fstab_suffix[32]; @@ -66,6 +67,10 @@ protected: mmap_data magisk_config; std::string custom_rules_dir; + // When this boolean is set, this means we are currently + // running magiskinit on legacy SAR AVD emulator + bool avd_hack = false; + void mount_with_dt(); bool patch_sepolicy(const char *file); void setup_tmp(const char *path); diff --git a/native/jni/init/mount.cpp b/native/jni/init/mount.cpp index 58ec5ca59..06410a318 100644 --- a/native/jni/init/mount.cpp +++ b/native/jni/init/mount.cpp @@ -158,12 +158,19 @@ void MagiskInit::mount_with_dt() { for (const auto &entry : fstab) { if (is_lnk(entry.mnt_point.data())) continue; + // When we force AVD to disable SystemAsRoot, it will always add system + // to dt fstab, which we actually have already mounted as root + if (avd_hack && entry.mnt_point == "/system") + 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); + // When avd_hack is true, do not add any early mount partitions to mount_list + // as we will actually forcefully disable original init's early mount + if (!avd_hack) + mount_list.push_back(entry.mnt_point); } } @@ -372,6 +379,7 @@ void SARInit::early_mount() { xmkdir("/dev", 0755); xmount("tmpfs", "/dev", "tmpfs", 0, "mode=755"); mount_list.emplace_back("/dev"); + avd_hack = config->emulator; mount_with_dt(); } } @@ -399,7 +407,7 @@ bool SecondStageInit::prepare() { void BaseInit::exec_init() { // Unmount in reverse order for (auto &p : reversed(mount_list)) { - if (xumount(p.data()) == 0) + if (xumount2(p.data(), MNT_DETACH) == 0) LOGD("Unmount [%s]\n", p.data()); } execv("/init", argv); diff --git a/native/jni/init/rootdir.cpp b/native/jni/init/rootdir.cpp index be06d80fa..f1f5abc4d 100644 --- a/native/jni/init/rootdir.cpp +++ b/native/jni/init/rootdir.cpp @@ -234,6 +234,10 @@ void SARBase::patch_rootdir() { make_pair(SPLIT_PLAT_CIL, "xxx"), /* Force loading monolithic sepolicy */ make_pair(MONOPOLICY, sepol) /* Redirect /sepolicy to custom path */ }); + if (avd_hack) { + // Force disable early mount on original init + init.patch({ make_pair("android,fstab", "xxx") }); + } xmkdir(ROOTOVL, 0); int dest = xopen(ROOTOVL "/init", O_CREAT | O_WRONLY | O_CLOEXEC, 0); xwrite(dest, init.buf, init.sz); diff --git a/scripts/emulator.sh b/scripts/avd_magisk.sh similarity index 95% rename from scripts/emulator.sh rename to scripts/avd_magisk.sh index 34b93e999..2c5baaa31 100755 --- a/scripts/emulator.sh +++ b/scripts/avd_magisk.sh @@ -6,14 +6,17 @@ # Support emulator ABI: x86_64 and arm64 # Support API level: 23 - 31 (21 and 22 images do not have SELinux) # +# With an emulator booted and accessible via ADB, usage: +# ./build.py emulator +# # This script will stop zygote, simulate the Magisk start up process # that would've happened before zygote was started, and finally # restart zygote. This is useful for setting up the emulator for # developing Magisk, testing modules, and developing root apps using # the official Android emulator (AVD) instead of a real device. # -# This only covers the "core" features of Magisk. Testing magiskinit -# and magiskboot require additional setups that are not covered here. +# This only covers the "core" features of Magisk. For testing +# magiskinit, please checkout avd_init.sh. # ##################################################################### diff --git a/scripts/avd_patch.sh b/scripts/avd_patch.sh new file mode 100644 index 000000000..f73bc1855 --- /dev/null +++ b/scripts/avd_patch.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +##################################################################### +# AVD MagiskInit Setup +##################################################################### +# +# Support emulator ABI: x86_64 and arm64 +# Support API level: 23 - 31 (21 and 22 images do not have SELinux) +# +# With an emulator booted and accessible via ADB, usage: +# ./build.py avd_patch path/to/booted/avd-image/ramdisk.img +# +# The purpose of this script is to patch AVD ramdisk.img and do a +# full integration test of magiskinit under several circumstances. +# After patching ramdisk.img, close the emulator, then select +# "Cold Boot Now" in AVD Manager to force a full reboot. +# +##################################################################### +# AVD Init Configurations: +# +# rootfs w/o early mount: API 23 - 25 +# rootfs with early mount: API 26 - 27 +# Legacy system-as-root: API 28 +# 2 stage init: API 29 - 31 +##################################################################### + +if [ ! -f /system/build.prop ]; then + # Running on PC + echo 'Please run `./build.py avd_patch` instead of directly executing the script!' + exit 1 +fi + +cd /data/local/tmp +chmod 755 busybox + +if [ -z "$FIRST_STAGE" ]; then + export FIRST_STAGE=1 + export ASH_STANDALONE=1 + # Re-exec script with busybox + exec ./busybox sh $0 +fi + +# Extract files from APK +unzip -oj app-debug.apk 'assets/util_functions.sh' +. ./util_functions.sh + +api_level_arch_detect + +unzip -oj app-debug.apk "lib/$ABI/*" "lib/$ABI32/libmagisk32.so" -x "lib/$ABI/busybox.so" +for file in lib*.so; do + chmod 755 $file + mv "$file" "${file:3:${#file}-6}" +done + +gzip -d ramdisk.cpio.gz +cp ramdisk.cpio ramdisk.cpio.orig + +touch config + +# For API 28, we also patch advancedFeatures.ini to disable SAR +# Manually override skip_initramfs by setting RECOVERYMODE=true +[ $API = "28" ] && echo 'RECOVERYMODE=true' >> config + +./magiskboot compress=xz magisk32 magisk32.xz +./magiskboot compress=xz magisk64 magisk64.xz + +export KEEPVERITY=false +export KEEPFORCEENCRYPT=true + +./magiskboot cpio ramdisk.cpio \ +"add 0750 init magiskinit" \ +"mkdir 0750 overlay.d" \ +"mkdir 0750 overlay.d/sbin" \ +"add 0644 overlay.d/sbin/magisk32.xz magisk32.xz" \ +"add 0644 overlay.d/sbin/magisk64.xz magisk64.xz" \ +"patch" \ +"backup ramdisk.cpio.orig" \ +"mkdir 000 .backup" \ +"add 000 .backup/.magisk config" + +rm -f ramdisk.cpio.orig config magisk*.xz +gzip -9 ramdisk.cpio