From c09a792958aaa2126e31a4062597aa4da71ff42d Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Mon, 24 Mar 2025 01:35:16 -0700 Subject: [PATCH] Reorganize magiskinit code --- native/src/base/files.rs | 42 +++++----- native/src/boot/cpio.rs | 14 ++-- native/src/init/init.rs | 17 ++-- native/src/init/twostage.rs | 161 +++++++++++++++++++++--------------- 4 files changed, 131 insertions(+), 103 deletions(-) diff --git a/native/src/base/files.rs b/native/src/base/files.rs index 693e733cc..e1470eec0 100644 --- a/native/src/base/files.rs +++ b/native/src/base/files.rs @@ -1,11 +1,11 @@ use crate::{ - cstr_buf, errno, error, Directory, FsPath, FsPathBuf, FsPathFollow, LibcReturn, Utf8CStr, - Utf8CStrBuf, + Directory, FsPath, FsPathBuf, FsPathFollow, LibcReturn, Utf8CStr, Utf8CStrBuf, cstr_buf, errno, + error, }; -use bytemuck::{bytes_of, bytes_of_mut, Pod}; +use bytemuck::{Pod, bytes_of, bytes_of_mut}; use libc::{ - c_uint, makedev, mode_t, stat, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, - O_RDWR, O_TRUNC, O_WRONLY, + EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, c_uint, + makedev, mode_t, stat, }; use mem::MaybeUninit; use num_traits::AsPrimitive; @@ -400,6 +400,20 @@ impl FsPath { self.rename_to(path) } + pub fn parent(&self, buf: &mut dyn Utf8CStrBuf) -> bool { + buf.clear(); + if let Some(parent) = Path::new(self.as_str()).parent() { + let bytes = parent.as_os_str().as_bytes(); + // SAFETY: all substring of self is valid UTF-8 + let parent = unsafe { std::str::from_utf8_unchecked(bytes) }; + buf.push_str(parent); + true + } else { + false + } + } + + // ln self path pub fn link_to(&self, path: &FsPath) -> io::Result<()> { let attr = self.get_attr()?; if attr.is_dir() { @@ -413,21 +427,9 @@ impl FsPath { } } - pub fn symlink_to(&self, path: &FsPath) -> io::Result<()> { - unsafe { libc::symlink(self.as_ptr(), path.as_ptr()).as_os_err() } - } - - pub fn parent(&self, buf: &mut dyn Utf8CStrBuf) -> bool { - buf.clear(); - if let Some(parent) = Path::new(self.as_str()).parent() { - let bytes = parent.as_os_str().as_bytes(); - // SAFETY: all substring of self is valid UTF-8 - let parent = unsafe { std::str::from_utf8_unchecked(bytes) }; - buf.push_str(parent); - true - } else { - false - } + // ln -s target self + pub fn create_symlink_to(&self, target: &FsPath) -> io::Result<()> { + unsafe { libc::symlink(target.as_ptr(), self.as_ptr()).as_os_err() } } } diff --git a/native/src/boot/cpio.rs b/native/src/boot/cpio.rs index b7ac18183..4007ca413 100644 --- a/native/src/boot/cpio.rs +++ b/native/src/boot/cpio.rs @@ -10,18 +10,18 @@ use std::process::exit; use std::str; use argh::FromArgs; -use bytemuck::{from_bytes, Pod, Zeroable}; +use bytemuck::{Pod, Zeroable, from_bytes}; use num_traits::cast::AsPrimitive; use size::{Base, Size, Style}; use base::libc::{ - c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t, O_CLOEXEC, O_CREAT, - O_RDONLY, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IRGRP, - S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR, + O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, + S_IFREG, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR, + c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t, }; use base::{ - cstr_buf, log_err, map_args, BytesExt, EarlyExitExt, FsPath, LoggedResult, MappedFile, - ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt, + BytesExt, EarlyExitExt, FsPath, LoggedResult, MappedFile, ResultExt, Utf8CStr, Utf8CStrBuf, + WriteExt, cstr_buf, log_err, map_args, }; use crate::check_env; @@ -362,7 +362,7 @@ impl Cpio { S_IFLNK => { buf.clear(); buf.push_str(str::from_utf8(entry.data.as_slice())?); - FsPath::from(&buf).symlink_to(out)?; + out.create_symlink_to(FsPath::from(&buf))?; } S_IFBLK | S_IFCHR => { let dev = makedev(entry.rdevmajor.try_into()?, entry.rdevminor.try_into()?); diff --git a/native/src/init/init.rs b/native/src/init/init.rs index 6acb0b756..482587d72 100644 --- a/native/src/init/init.rs +++ b/native/src/init/init.rs @@ -1,15 +1,15 @@ use crate::ffi::backup_init; use crate::{ - ffi::{magisk_proxy_main, BootConfig, MagiskInit}, + ffi::{BootConfig, MagiskInit, magisk_proxy_main}, logging::setup_klog, }; use base::{ - debug, info, + FsPath, LibcReturn, LoggedResult, ResultExt, debug, info, libc::{basename, getpid, mount, umask}, - path, raw_cstr, FsPath, LibcReturn, LoggedResult, ResultExt, + path, raw_cstr, }; use std::{ - ffi::{c_char, CStr}, + ffi::{CStr, c_char}, ptr::null, }; @@ -38,8 +38,9 @@ impl MagiskInit { pub(crate) fn legacy_system_as_root(&mut self) { info!("Legacy SAR Init"); self.prepare_data(); - if self.mount_system_root() { - self.redirect_second_stage(); + let is_two_stage = self.mount_system_root(); + if is_two_stage { + self.patch_init_for_second_stage(); } else { self.patch_ro_root(); } @@ -70,8 +71,8 @@ impl MagiskInit { // If the backup init is missing, this means that the boot ramdisk // was created from scratch, and the real init is in a separate CPIO, // which is guaranteed to be placed at /system/bin/init. - path!("/system/bin/init") - .symlink_to(path!("/init")) + path!("/init") + .create_symlink_to(path!("/system/bin/init")) .log_ok(); } } diff --git a/native/src/init/twostage.rs b/native/src/init/twostage.rs index 6ba64650c..2ea9db6d2 100644 --- a/native/src/init/twostage.rs +++ b/native/src/init/twostage.rs @@ -9,75 +9,106 @@ use base::{ }; use std::{ffi::c_long, io::Write, ptr::null}; -impl MagiskInit { - pub(crate) fn first_stage(&self) { - info!("First Stage Init"); - self.prepare_data(); +const RAMFS_MAGIC: u64 = 0x858458f6; - if !path!("/sdcard").exists() && !path!("/first_stage_ramdisk/sdcard").exists() { - if self.config.force_normal_boot { - path!("/first_stage_ramdisk/storage/self") - .mkdirs(0o755) - .log_ok(); - path!("/system/system/bin/init") - .symlink_to(path!("/first_stage_ramdisk/storage/self/primary")) - .log_ok(); - debug!( - "Symlink /first_stage_ramdisk/storage/self/primary -> /system/system/bin/init" - ); - path!("/first_stage_ramdisk/sdcard") - .create(O_RDONLY | O_CREAT | O_CLOEXEC, 0) - .log_ok(); - } else { - path!("/storage/self").mkdirs(0o755).log_ok(); - path!("/system/system/bin/init") - .symlink_to(path!("/storage/self/primary")) - .log_ok(); - debug!("Symlink /storage/self/primary -> /system/system/bin/init"); - } - path!("/init").rename_to(path!("/sdcard")).log_ok(); - // Try to keep magiskinit in rootfs for samsung RKP - if unsafe { +fn patch_init_path(init: &mut MappedFile) { + let from = "/system/bin/init"; + let to = "/data/magiskinit"; + + // Redirect original init to magiskinit + let v = init.patch(from.as_bytes(), to.as_bytes()); + #[allow(unused_variables)] + for off in &v { + debug!("Patch @ {:#010X} [{}] -> [{}]", off, from, to); + } +} + +impl MagiskInit { + fn hijack_init_with_switch_root(&self) { + // We make use of original init's `SwitchRoot` to help us bind mount + // magiskinit to /system/bin/init to hijack second stage init. + // + // Two important assumption about 2SI: + // - The second stage init is always /system/bin/init + // - After `SwitchRoot`, /sdcard is always a symlink to `/storage/self/primary`. + // + // `SwitchRoot` will perform the following: + // - Recursive move all mounts under `/` to `/system` + // - chroot to `/system` + // + // The trick here is that in Magisk's first stage init, we can mount magiskinit to /sdcard, + // and create a symlink at /storage/self/primary pointing to /system/system/bin/init. + // + // During init's `SwitchRoot`, it will mount move /sdcard (which is magiskinit) + // to /system/sdcard, which is a symlink to /storage/self/primary, which is a + // symlink to /system/system/bin/init, which will eventually become /system/bin/init after + // chroot to /system. The effective result is that we coerce the original init into bind + // mounting magiskinit to /system/bin/init, successfully hijacking the second stage init. + // + // An edge case is that some devices (like meizu) use 2SI but does not switch root. + // In that case, they must already have a /sdcard in ramfs, thus we can check if + // /sdcard exists and fallback to using hexpatch. + + if self.config.force_normal_boot { + path!("/first_stage_ramdisk/storage/self") + .mkdirs(0o755) + .log_ok(); + path!("/first_stage_ramdisk/storage/self/primary") + .create_symlink_to(path!("/system/system/bin/init")) + .log_ok(); + debug!("Symlink /first_stage_ramdisk/storage/self/primary -> /system/system/bin/init"); + path!("/first_stage_ramdisk/sdcard") + .create(O_RDONLY | O_CREAT | O_CLOEXEC, 0) + .log_ok(); + } else { + path!("/storage/self").mkdirs(0o755).log_ok(); + path!("/storage/self/primary") + .create_symlink_to(path!("/system/system/bin/init")) + .log_ok(); + debug!("Symlink /storage/self/primary -> /system/system/bin/init"); + } + path!("/init").rename_to(path!("/sdcard")).log_ok(); + + // First try to mount magiskinit from rootfs to workaround Samsung RKP + if unsafe { + mount( + raw_cstr!("/sdcard"), + raw_cstr!("/sdcard"), + null(), + MS_BIND, + null(), + ) + } == 0 + { + debug!("Bind mount /sdcard -> /sdcard"); + } else { + // Binding mounting from rootfs is not supported before Linux 3.12 + unsafe { mount( - raw_cstr!("/sdcard"), + raw_cstr!("/data/magiskinit"), raw_cstr!("/sdcard"), null(), MS_BIND, null(), ) - } == 0 - { - debug!("Bind mount /sdcard -> /sdcard"); - } else { - // rootfs before 3.12 - unsafe { - mount( - raw_cstr!("/data/magiskinit"), - raw_cstr!("/sdcard"), - null(), - MS_BIND, - null(), - ) - }; - debug!("Bind mount /data/magiskinit -> /sdcard"); - } + }; + debug!("Bind mount /data/magiskinit -> /sdcard"); + } + } + + pub(crate) fn first_stage(&self) { + info!("First Stage Init"); + self.prepare_data(); + + if !path!("/sdcard").exists() && !path!("/first_stage_ramdisk/sdcard").exists() { + self.hijack_init_with_switch_root(); self.restore_ramdisk_init(); } else { self.restore_ramdisk_init(); // fallback to hexpatch if /sdcard exists match MappedFile::open_rw(cstr!("/init")) { - Ok(mut map) => { - let from = "/system/bin/init"; - let to = "/data/magiskinit"; - - // Redirect original init to magiskinit - let v = map.patch(from.as_bytes(), to.as_bytes()); - #[allow(unused_variables)] - for off in &v { - debug!("Patch @ {:#010X} [{}] -> [{}]", off, from, to); - } - } + Ok(mut map) => patch_init_path(&mut map), _ => { error!("Failed to open /init for hexpatch"); } @@ -85,21 +116,13 @@ impl MagiskInit { } } - pub(crate) fn redirect_second_stage(&self) { + pub(crate) fn patch_init_for_second_stage(&self) { let src = path!("/init"); let dest = path!("/data/init"); // Patch init binary match MappedFile::open(src) { Ok(mut map) => { - let from = "/system/bin/init"; - let to = "/data/magiskinit"; - - // Redirect original init to magiskinit - let v = map.patch(from.as_bytes(), to.as_bytes()); - #[allow(unused_variables)] - for off in &v { - debug!("Patch @ {:#010X} [{}] -> [{}]", off, from, to); - } + patch_init_path(&mut map); match dest.create(O_CREAT | O_WRONLY, 0) { Ok(mut dest) => { dest.write_all(map.as_ref()).log_ok(); @@ -135,11 +158,13 @@ impl MagiskInit { // Some weird devices like meizu, uses 2SI but still have legacy rootfs let mut sfs: statfs = std::mem::zeroed(); statfs(raw_cstr!("/"), &mut sfs); - if sfs.f_type == 0x858458f6 || sfs.f_type as c_long == TMPFS_MAGIC { + if sfs.f_type as u64 == RAMFS_MAGIC || sfs.f_type as c_long == TMPFS_MAGIC { // We are still on rootfs, so make sure we will execute the init of the 2nd stage let init_path = path!("/init"); init_path.remove().ok(); - path!("/system/bin/init").symlink_to(init_path).log_ok(); + init_path + .create_symlink_to(path!("/system/bin/init")) + .log_ok(); self.patch_rw_root(); } else { self.patch_ro_root();