Do you know of a way we can force it to never skip at source level? when I did it, it just boots recovery.I will spend some time on Pixel 2 (XL) a while later, maybe recruiting more testers too.
I didn't have much time to update my official documentation to include magiskinit, but here is everything you need to know about it, with more details than normal documentations will contain.
I hope providing the process and how I think at that time will guide developers to investigate porting Magisk over to Pixel 2.
Warning: The following is a huge block of words with technical terms. After reading the whole block, you should have a clear idea how I ported Magisk to the first gen Pixel. Also, you will know the process and struggles I've gone through chronologically, so you can know how I tackle the problems one by one
The ramdisk stored in boot image only contains files for recovery. The actual root folder files are in the system partition.
If the bootloader set the kernel cmdline with the skip_initramfs flag, the kernel will mount system to root (doing verity at the same time), then execute /init to continue the booting.
So the first thing to do is to let the kernel ignore the skip_initramfs cmdline. The patch is simply just replacing the string to anything else, in my case I used want_initramfs.
At this point, the device will boot into recovery under any circumstances.
The next step is to make the device to actually boot up. We need to replace the init binary in the ramdisk, since this is the next thing the kernel will do once rootfs is setup. Here are the steps our init has to do:
At this point, the device will properly boot up, but it is pure stock without any modifications. You might notice that I didn't mention the dtb patch anywhere, because is not actually necessary. The only thing it does is to make init mount /vendor without dm-verity. The kernel thinks it is booting to recovery, so it will not handle mounting the system and verify the partition.
- Mount procfs to /proc, so we can read kernel cmdline (/proc/cmdline) to check skip_initramfs flag, and also the current boot slot
- If skip_initramfs flag detected, it means we need to boot to system not recovery. Remove everything in rootfs (we will add in contents later)
- Mount sysfs to /sys, so we can access block uevents. Traverse and parse /sys/dev/block/*/uevent, until we found the major and minor node id for target system partition (with the correct slot)
- Make a new device block under /dev based on the major and minor id we got in the last step, and mount it to /system_root
- Copy everything in /system_root (except /system_root/system, obviously) to rootfs. These are the actual files and folders for rootfs
- Bind mount /system_root/system to /system, so the contents for the actual system is actually in the correct place
- Finally, execute /init (which should be the one copied from system partition)
It took me nearly 3 full days to get to this stage, and this is the most difficult part in porting Magisk to first gen Pixel. If anyone can find a way to this stage, it will definitely help a lot!
Regarding Pixel 2, people can start with the same steps I've went through: the first step would be making the device to boot into recovery as we need to force the kernel to think it is booting into recovery mode.
I have checked the posts above, people have mentioned AVB 2.0. I do not have a device on hand and haven't checked any code yet (I'm no expert in AOSP and kernel code reviewing, people can help me out with this), but I don't think AVB will be enforced in recovery mode. We need to transfer all the heavy work for booting up the device to our modified init binary, which are the steps I've mentioned above.
The code for the modified init binary is located here: magiskinit.c. Note that this code also contains a large portion that is not related to the procedures mentioned above, they are stuffs to bring up Magisk, which I will continue to explain in detail in the following passage.
But please note our primary goal should be: make the kernel load rootfs from ramdisk, and boot up properly.
The following is just for information for developers to know what steps to continue once previous stages have been overcome.
To add some Magisk magic, we need to add files into rootfs. A new folder named overlay is created, and new files (init.magisk.rc and sbin/magisk) are added into the folder. magiskinit will skip this folder when it is cleaning rootfs (the second step in the list above). Before the last step, we move all files in the overlay folder to root. So now we have a device that can properly boot, with a few additional files in rootfs.
Next we need to tackle selinux. Usually, a monolithic sepolicy binary is placed in root directory in ramdisk, so logically it is expected that sepolicy should be in the root folder in system partition. However the first gen Pixel doesn't (fun fact: a similar device with A/B partition - Xiaomi A1 actually does place sepolicy in system root folder, just as expected). Instead, it splits the policies into several parts: platform policies (placed in system), non-platform policies (placed in vendor), mappings (placed in system) in Common Intermediate Language (CIL) format. The stock init binary will detect there is split policies available, so it will compile these cil files and load them as selinux policies. In reality though, Google have shipped a precompiled sepolicy in vendor, so init will load that instead.
We do not want to modify system or vendor, what can we do before init loads the stock policies? We either patch the precompiled sepolicy or compile the split cil files, and then dump the complete stock policies along with new rules into a monolithic binary to /sepolicy. We then have to patch the stock init (which is possible, since it is now copied to rootfs, not in the actual system partition) to always load from /sepolicy, not anywhere else.
The final step is to load the rc scripts to launch Magisk daemon. We inject a new line into the init.rc (same as /init, it is copied to rootfs so we are not modifying system), and things are all done!
To summarize, in addition to the list above, before we execute /init to give control back, we still need to do these to start Magisk:
- Patch init binary to not load from split or precompiled policies
- Inject a new line in init.rc to load init.magisk.rc
- Parse sysfs to create node for vendor block (same as step 3 above), and mount vendor
- Load / compile sepolicy, and dump monolithic sepolicy to root folder
- Finally execute /init
Conclusion, since magiskinit have done so much, patching a Pixel boot image with Magisk support is actually much easier than normal traditional devices! We only need to
- Hex patch kernel to never skip_initramfs
- For ramdisk: Replace init with magiskinit; add two files: overlay/init.magisk.rc, overlay/sbin/magisk
- (Optional): Patch dtb to remove vendor dm-verity
That's all! Hope people has a more clear idea to start working with Pixel 2