Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.test.uiautomator.Until
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.test.Environment.Companion.EMPTY_ZYGISK
import com.topjohnwu.magisk.test.Environment.Companion.INIT_RC
import com.topjohnwu.magisk.test.Environment.Companion.INVALID_ZYGISK
import com.topjohnwu.magisk.test.Environment.Companion.MOUNT_TEST
import com.topjohnwu.magisk.test.Environment.Companion.REMOVE_TEST
Expand Down Expand Up @@ -59,7 +60,7 @@ class AdditionalTest : BaseTest {
fun testModuleCount() {
var expected = 4
if (Environment.mount()) expected++
if (Environment.preinit()) expected++
if (Environment.preinit()) expected += 2
if (Environment.lsposed()) expected++
if (Environment.shamiko()) expected++
assertEquals("Module count incorrect", expected, modules.size)
Expand Down Expand Up @@ -118,6 +119,21 @@ class AdditionalTest : BaseTest {
)
}

@Test
fun testInitRc() {
assumeTrue(Environment.preinit())

assertNotNull("$INIT_RC is not installed", modules.find { it.id == INIT_RC })
assertEquals(
"Module init.rc is not applied",
"1", Shell.cmd("getprop ro.debug.magisk.rc").exec().out.first()
)
assertEquals(
"Module product init.rc is not applied",
"1", Shell.cmd("getprop ro.debug.magisk.product.rc").exec().out.first()
)
}

@Test
fun testEmptyZygiskModule() {
val module = modules.find { it.id == EMPTY_ZYGISK }
Expand Down
43 changes: 42 additions & 1 deletion app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class Environment : BaseTest {
private const val MODULE_ERROR = "Module zip processing incorrect"
const val MOUNT_TEST = "mount_test"
const val SEPOLICY_RULE = "sepolicy_rule"
const val INIT_RC = "init_rc"
const val INVALID_ZYGISK = "invalid_zygisk"
const val REMOVE_TEST = "remove_test"
const val REMOVE_TEST_MARKER = "/dev/.remove_test_removed"
Expand Down Expand Up @@ -144,6 +145,43 @@ class Environment : BaseTest {
).exec().isSuccess)
}

private fun setupInitRcModule(root: ExtendedFile) {
val error = "$INIT_RC setup failed"
val path = root.getChildFile(INIT_RC)
assertTrue(error, path.mkdirs())
var initDir = path
val initRcOnR = RootUtils.fs.getFile("/system").getChildFile("etc").getChildFile("init").getChildFile("hw").getChildFile("init.rc")
if (initRcOnR.exists() && initRcOnR.isFile) {
// Android R+ /system/etc/init/hw
initDir = path.getChildFile("system").getChildFile("etc").getChildFile("init").getChildFile("hw")
assertTrue(error, initDir.mkdirs())
}

assertTrue(error, initDir.getChildFile("init_new.rc").createNewFile())

// Add init.rc patch
PrintStream(initDir.getChildFile("init_new.rc").newOutputStream()).use {
it.println("on post-fs")
it.println(" setprop ro.debug.magisk.rc 1")
it.println()
}

// rc in product
val productInitDir = path.getChildFile("system").getChildFile("product").getChildFile("etc").getChildFile("init")
assertTrue(error, productInitDir.mkdirs())
assertTrue(error, productInitDir.getChildFile("init_new_p.rc").createNewFile())
PrintStream(productInitDir.getChildFile("init_new_p.rc").newOutputStream()).use {
it.println("on post-fs")
it.println(" setprop ro.debug.magisk.product.rc 1")
it.println()
}

assertTrue(error, Shell.cmd(
"set_default_perm $path",
"copy_preinit_files"
).exec().isSuccess)
}

private fun setupEmptyZygiskModule(root: ExtendedFile) {
val error = "$EMPTY_ZYGISK setup failed"
val path = root.getChildFile(EMPTY_ZYGISK)
Expand Down Expand Up @@ -254,7 +292,10 @@ class Environment : BaseTest {
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
val update = RootUtils.fs.getFile(MODULE_UPDATE_PATH)
if (mount()) { setupMountTest(update) }
if (preinit()) { setupSepolicyRuleModule(update) }
if (preinit()) {
setupSepolicyRuleModule(update)
setupInitRcModule(update)
}
setupSystemlessHost()
setupEmptyZygiskModule(update)
setupInvalidZygiskModule(update)
Expand Down
6 changes: 6 additions & 0 deletions native/src/base/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ pub fn errno() -> &'static mut i32 {
unsafe { &mut *libc::__errno() }
}

// Simple fork without double-fork or waitpid; nolibc-compatible.
// Returns 0 in child, child pid in parent, -1 on error.
pub fn xfork() -> i32 {
unsafe { libc::fork() }
}

// When len is 0, don't care whether buf is null or not
#[inline]
pub unsafe fn slice_from_ptr<'a, T>(buf: *const T, len: usize) -> &'a [T] {
Expand Down
1 change: 1 addition & 0 deletions native/src/init/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ impl MagiskInit {
Self {
preinit_dev: String::new(),
mount_list: Vec::new(),
rc_list: Vec::new(),
overlay_con: Vec::new(),
argv,
config: BootConfig {
Expand Down
9 changes: 6 additions & 3 deletions native/src/init/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use logging::setup_klog;
// Has to be pub so all symbols in that crate is included
pub use magiskpolicy;
use mount::{is_device_mounted, switch_root};
use rootdir::{OverlayAttr, inject_magisk_rc};
use rootdir::OverlayAttr;

#[path = "../include/consts.rs"]
mod consts;
Expand Down Expand Up @@ -41,6 +41,7 @@ pub mod ffi {
struct MagiskInit {
preinit_dev: String,
mount_list: Vec<String>,
rc_list: Vec<String>,
argv: *mut *mut c_char,
config: BootConfig,
overlay_con: Vec<OverlayAttr>,
Expand All @@ -65,7 +66,6 @@ pub mod ffi {
#[namespace = "rust"]
extern "Rust" {
fn setup_klog();
fn inject_magisk_rc(fd: i32, tmp_dir: Utf8CStrRef);
fn switch_root(path: Utf8CStrRef);
fn is_device_mounted(dev: u64, target: Pin<&mut CxxString>) -> bool;
}
Expand All @@ -87,6 +87,10 @@ pub mod ffi {
fn mount_overlay(self: &mut MagiskInit, dest: Utf8CStrRef);
fn handle_sepolicy(self: &mut MagiskInit);
fn restore_overlay_contexts(self: &MagiskInit);
fn load_overlay_rc(self: &mut MagiskInit, overlay: &Utf8CStrRef, module_path: &Utf8CStrRef);
fn handle_modules_rc(self: &mut MagiskInit, root_dir: &Utf8CStrRef);
fn patch_rc_scripts(self: &mut MagiskInit, src_path: Utf8CStrRef, tmp_path: Utf8CStrRef, writable: bool) -> bool;
fn patch_fissiond(self: &mut MagiskInit, tmp_path: Utf8CStrRef);
}
unsafe extern "C++" {
// Used in Rust
Expand All @@ -99,6 +103,5 @@ pub mod ffi {
fn collect_devices(self: &MagiskInit);
fn mount_preinit_dir(self: &mut MagiskInit);
unsafe fn find_block(self: &MagiskInit, partname: *const c_char) -> u64;
unsafe fn patch_fissiond(self: &mut MagiskInit, tmp_path: *const c_char);
}
}
181 changes: 11 additions & 170 deletions native/src/init/rootdir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

using namespace std;

static vector<string> rc_list;

#define NEW_INITRC_DIR "/system/etc/init/hw"
#define INIT_RC "init.rc"

Expand Down Expand Up @@ -40,168 +38,6 @@ static bool unxz(int fd, rust::Slice<const uint8_t> bytes) {
return true;
}

// When return true, run patch_fissiond
static bool patch_rc_scripts(const char *src_path, const char *tmp_path, bool writable) {
auto src_dir = xopen_dir(src_path);
if (!src_dir) return false;
int src_fd = dirfd(src_dir.get());

// If writable, directly modify the file in src_path, or else add to rootfs overlay
auto dest_dir = writable ? [&] {
return xopen_dir(src_path);
}() : [&] {
char buf[PATH_MAX] = {};
ssprintf(buf, sizeof(buf), ROOTOVL "%s", src_path);
xmkdirs(buf, 0755);
return xopen_dir(buf);
}();
if (!dest_dir) return false;
int dest_fd = dirfd(dest_dir.get());

// First patch init.rc
{
owned_fd src_rc = xopenat(src_fd, INIT_RC, O_RDONLY | O_CLOEXEC, 0);
if (src_rc < 0) return false;
if (writable) unlinkat(src_fd, INIT_RC, 0);
auto dest_rc = xopen_file(
xopenat(dest_fd, INIT_RC, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0), "we");
if (!dest_rc) return false;
LOGD("Patching " INIT_RC " in %s\n", src_path);
file_readline(src_rc, [&dest_rc](Utf8CStr line) -> bool {
// Do not start vaultkeeper
if (line.sv().contains("start vaultkeeper")) {
LOGD("Remove vaultkeeper\n");
return true;
}
// Do not run flash_recovery
if (line.sv().starts_with("service flash_recovery")) {
LOGD("Remove flash_recovery\n");
fprintf(dest_rc.get(), "service flash_recovery /system/bin/true\n");
return true;
}
// Samsung's persist.sys.zygote.early will cause Zygote to start before post-fs-data
if (line.sv().starts_with("on property:persist.sys.zygote.early=")) {
LOGD("Invalidate persist.sys.zygote.early\n");
fprintf(dest_rc.get(), "on property:persist.sys.zygote.early.xxxxx=true\n");
return true;
}
// Else just write the line
fprintf(dest_rc.get(), "%s", line.c_str());
return true;
});

fprintf(dest_rc.get(), "\n");

// Inject custom rc scripts
for (auto &script : rc_list) {
// Replace template arguments of rc scripts with dynamic paths
replace_all(script, "${MAGISKTMP}", tmp_path);
fprintf(dest_rc.get(), "\n%s\n", script.data());
}
rc_list.clear();

// Inject Magisk rc scripts
rust::inject_magisk_rc(fileno(dest_rc.get()), tmp_path);

fclone_attr(src_rc, fileno(dest_rc.get()));
}

// Then patch init.zygote*.rc
for (dirent *entry; (entry = readdir(src_dir.get()));) {
{
auto name = std::string_view(entry->d_name);
if (!name.starts_with("init.zygote") || !name.ends_with(".rc")) continue;
}
owned_fd src_rc = xopenat(src_fd, entry->d_name, O_RDONLY | O_CLOEXEC, 0);
if (src_rc < 0) continue;
if (writable) unlinkat(src_fd, entry->d_name, 0);
auto dest_rc = xopen_file(
xopenat(dest_fd, entry->d_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0), "we");
if (!dest_rc) continue;
LOGD("Patching %s in %s\n", entry->d_name, src_path);
file_readline(src_rc, [&dest_rc, &tmp_path](Utf8CStr line) -> bool {
if (line.sv().starts_with("service zygote ")) {
LOGD("Inject zygote restart\n");
fprintf(dest_rc.get(), "%s", line.c_str());
fprintf(dest_rc.get(),
" onrestart exec " MAGISK_PROC_CON " 0 0 -- %s/magisk --zygote-restart\n", tmp_path);
return true;
}
fprintf(dest_rc.get(), "%s", line.c_str());
return true;
});
fclone_attr(src_rc, fileno(dest_rc.get()));
}

return faccessat(src_fd, "init.fission_host.rc", F_OK, 0) == 0;
}

void MagiskInit::patch_fissiond(const char *tmp_path) noexcept {
{
LOGD("Patching fissiond\n");
mmap_data fissiond("/system/bin/fissiond", false);
for (size_t off : fissiond.patch(
"ro.build.system.fission_single_os",
"ro.build.system.xxxxxxxxxxxxxxxxx"))
{
LOGD("Patch @ %08zX [ro.build.system.fission_single_os] -> "
"[ro.build.system.xxxxxxxxxxxxxxxxx]\n", off);
}
mkdirs(ROOTOVL "/system/bin", 0755);
if (auto target_fissiond = xopen_file(ROOTOVL "/system/bin/fissiond", "we")) {
fwrite(fissiond.data(), 1, fissiond.size(), target_fissiond.get());
clone_attr("/system/bin/fissiond", ROOTOVL "/system/bin/fissiond");
}
}
LOGD("hijack isolated\n");
auto hijack = xopen_file("/sys/devices/system/cpu/isolated", "re");
mkfifo(INTLROOT "/isolated", 0777);
xmount(INTLROOT "/isolated", "/sys/devices/system/cpu/isolated", nullptr, MS_BIND, nullptr);
if (!xfork()) {
auto dest = xopen_file(INTLROOT "/isolated", "we");
LOGD("hijacked isolated\n");
xumount2("/sys/devices/system/cpu/isolated", MNT_DETACH);
unlink(INTLROOT "/isolated");
string content = full_read(fileno(hijack.get()));
{
string target = "/dev/cells/cell2"s + tmp_path;
xmkdirs(target.data(), 0);
xmount(tmp_path, target.data(), nullptr, MS_BIND | MS_REC, nullptr);
mount_overlay("/dev/cells/cell2");
}
fprintf(dest.get(), "%s", content.data());
exit(0);
}
}

static void load_overlay_rc(const char *overlay) {
auto dir = open_dir(overlay);
if (!dir) return;

int dfd = dirfd(dir.get());
// Do not allow overwrite init.rc
unlinkat(dfd, INIT_RC, 0);

// '/' + name + '\0'
char buf[NAME_MAX + 2];
buf[0] = '/';
for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (!string_view(entry->d_name).ends_with(".rc")) {
continue;
}
strscpy(buf + 1, entry->d_name, sizeof(buf) - 1);
if (access(buf, F_OK) == 0) {
LOGD("Replace rc script [%s]\n", entry->d_name);
} else {
LOGD("Found rc script [%s]\n", entry->d_name);
int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
rc_list.push_back(full_read(rc));
close(rc);
unlinkat(dfd, entry->d_name, 0);
}
}
}

static void recreate_sbin(const char *mirror, bool use_bind_mount) {
auto dp = xopen_dir(mirror);
int src = dirfd(dp.get());
Expand Down Expand Up @@ -306,12 +142,14 @@ void MagiskInit::patch_ro_root() noexcept {
close(dest);
}

load_overlay_rc(ROOTOVL);
load_overlay_rc(ROOTOVL, "");
if (access(ROOTOVL "/sbin", F_OK) == 0) {
// Move files in overlay.d/sbin into tmp_dir
mv_path(ROOTOVL "/sbin", ".");
}

handle_modules_rc(ROOTOVL);

// Patch init.rc
bool p;
if (access(NEW_INITRC_DIR "/" INIT_RC, F_OK) == 0) {
Expand Down Expand Up @@ -347,19 +185,22 @@ void MagiskInit::patch_rw_root() noexcept {
link_path("/sbin", "/root");

// Handle overlays
load_overlay_rc("/overlay.d");
load_overlay_rc("/overlay.d", "");
mv_path("/overlay.d", "/");
rm_rf("/data/overlay.d");
rm_rf("/.backup");

// Patch init.rc
if (patch_rc_scripts("/", "/sbin", true))
patch_fissiond("/sbin");

xmkdir(PRE_TMPSRC, 0);
xmount("tmpfs", PRE_TMPSRC, "tmpfs", 0, "mode=755");
xmkdir(PRE_TMPDIR, 0);
setup_tmp(PRE_TMPDIR);

handle_modules_rc("/");

// Patch init.rc
if (patch_rc_scripts("/", "/sbin", true))
patch_fissiond("/sbin");

chdir(PRE_TMPDIR);

// Extract overlay archives
Expand Down
Loading
Loading