Fix overlay.d context preservation

This commit is contained in:
topjohnwu
2025-02-18 15:39:59 -08:00
committed by John Wu
parent e55c413261
commit b6b34f7612
10 changed files with 208 additions and 193 deletions
+1
View File
@@ -18,6 +18,7 @@ impl MagiskInit {
Self {
preinit_dev: String::new(),
mount_list: Vec::new(),
overlay_con: Vec::new(),
argv,
config: BootConfig {
skip_initramfs: false,
+8 -3
View File
@@ -7,8 +7,10 @@ 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::{collect_overlay_contexts, inject_magisk_rc, reset_overlay_contexts};
use rootdir::{inject_magisk_rc, OverlayAttr};
#[path = "../include/consts.rs"]
mod consts;
mod getinfo;
mod init;
mod logging;
@@ -43,6 +45,7 @@ pub mod ffi {
mount_list: Vec<String>,
argv: *mut *mut c_char,
config: BootConfig,
overlay_con: Vec<OverlayAttr>,
}
unsafe extern "C++" {
@@ -62,8 +65,6 @@ pub mod ffi {
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;
fn collect_overlay_contexts(src: Utf8CStrRef);
fn reset_overlay_contexts();
}
// BootConfig
@@ -78,8 +79,11 @@ pub mod ffi {
// MagiskInit
extern "Rust" {
type OverlayAttr;
fn patch_sepolicy(self: &MagiskInit, src: Utf8CStrRef, out: Utf8CStrRef);
fn parse_config_file(self: &mut MagiskInit);
fn mount_overlay(self: &mut MagiskInit, dest: Utf8CStrRef);
fn restore_overlay_contexts(self: &MagiskInit);
}
unsafe extern "C++" {
// Used in Rust
@@ -93,5 +97,6 @@ pub mod ffi {
fn mount_preinit_dir(self: &MagiskInit);
unsafe fn find_block(self: &MagiskInit, partname: *const c_char) -> u64;
fn hijack_sepolicy(self: &mut MagiskInit) -> bool;
unsafe fn patch_fissiond(self: &mut MagiskInit, tmp_path: *const c_char);
}
}
+49 -71
View File
@@ -13,7 +13,6 @@
using namespace std;
static vector<string> rc_list;
static string magic_mount_list;
#define NEW_INITRC_DIR "/system/etc/init/hw"
#define INIT_RC "init.rc"
@@ -43,33 +42,10 @@ static bool unxz(out_stream &strm, rust::Slice<const uint8_t> bytes) {
return true;
}
static void magic_mount(const string &sdir, const string &ddir = "") {
auto dir = xopen_dir(sdir.data());
if (!dir) return;
for (dirent *entry; (entry = xreaddir(dir.get()));) {
string src = sdir + "/" + entry->d_name;
string dest = ddir + "/" + entry->d_name;
if (access(dest.data(), F_OK) == 0) {
if (entry->d_type == DT_DIR) {
// Recursive
magic_mount(src, dest);
} else {
LOGD("Mount [%s] -> [%s]\n", src.data(), dest.data());
struct stat st;
xstat(dest.data(), &st);
chmod(src.data(), st.st_mode & 0777);
chown(src.data(), st.st_uid, st.st_gid);
xmount(src.data(), dest.data(), nullptr, MS_BIND, nullptr);
magic_mount_list += dest;
magic_mount_list += '\n';
}
}
}
}
static void patch_rc_scripts(const char *src_path, const char *tmp_path, bool writable) {
// 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;
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
@@ -81,17 +57,17 @@ static void patch_rc_scripts(const char *src_path, const char *tmp_path, bool wr
xmkdirs(buf, 0755);
return xopen_dir(buf);
}();
if (!dest_dir) return;
if (!dest_dir) return false;
int dest_fd = dirfd(dest_dir.get());
// First patch init.rc
{
auto src = xopen_file(xopenat(src_fd, INIT_RC, O_RDONLY | O_CLOEXEC, 0), "re");
if (!src) return;
if (!src) return false;
if (writable) unlinkat(src_fd, INIT_RC, 0);
auto dest = xopen_file(
xopenat(dest_fd, INIT_RC, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0), "we");
if (!dest) return;
if (!dest) return false;
LOGD("Patching " INIT_RC " in %s\n", src_path);
file_readline(false, src.get(), [&dest](string_view line) -> bool {
// Do not start vaultkeeper
@@ -157,42 +133,46 @@ static void patch_rc_scripts(const char *src_path, const char *tmp_path, bool wr
fclone_attr(fileno(src.get()), fileno(dest.get()));
}
if (faccessat(src_fd, "init.fission_host.rc", F_OK, 0) == 0) {
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("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.buf(), 1, fissiond.sz(), target_fissiond.get());
clone_attr("/system/bin/fissiond", ROOTOVL "/system/bin/fissiond");
}
LOGD("Patch @ %08zX [ro.build.system.fission_single_os] -> "
"[ro.build.system.xxxxxxxxxxxxxxxxx]\n", off);
}
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()), content);
{
string target = "/dev/cells/cell2"s + tmp_path;
xmkdirs(target.data(), 0);
xmount(tmp_path, target.data(), nullptr, MS_BIND | MS_REC,nullptr);
magic_mount(ROOTOVL, "/dev/cells/cell2");
auto mount = xopen_file(ROOTMNT, "w");
fwrite(magic_mount_list.data(), 1, magic_mount_list.length(), mount.get());
}
fprintf(dest.get(), "%s", content.data());
exit(0);
mkdirs(ROOTOVL "/system/bin", 0755);
if (auto target_fissiond = xopen_file(ROOTOVL "/system/bin/fissiond", "we")) {
fwrite(fissiond.buf(), 1, fissiond.sz(), 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()), content);
{
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) {
@@ -337,18 +317,18 @@ void MagiskInit::patch_ro_root() noexcept {
}
// Patch init.rc
bool p;
if (access(NEW_INITRC_DIR "/" INIT_RC, F_OK) == 0) {
// Android 11's new init.rc
patch_rc_scripts(NEW_INITRC_DIR, tmp_dir.data(), false);
p = patch_rc_scripts(NEW_INITRC_DIR, tmp_dir.data(), false);
} else {
patch_rc_scripts("/", tmp_dir.data(), false);
p = patch_rc_scripts("/", tmp_dir.data(), false);
}
if (p) patch_fissiond(tmp_dir.data());
// Extract overlay archives
extract_files(false);
rust::collect_overlay_contexts(ROOTOVL);
// Oculus Go will use a special sepolicy if unlocked
if (access("/sepolicy.unlocked", F_OK) == 0) {
patch_sepolicy("/sepolicy.unlocked", ROOTOVL "/sepolicy.unlocked");
@@ -361,10 +341,7 @@ void MagiskInit::patch_ro_root() noexcept {
unlink("init-ld");
// Mount rootdir
magic_mount(ROOTOVL);
int dest = xopen(ROOTMNT, O_WRONLY | O_CREAT, 0);
write(dest, magic_mount_list.data(), magic_mount_list.length());
close(dest);
mount_overlay("/");
chdir("/");
}
@@ -388,7 +365,8 @@ void MagiskInit::patch_rw_root() noexcept {
rm_rf("/.backup");
// Patch init.rc
patch_rc_scripts("/", "/sbin", true);
if (patch_rc_scripts("/", "/sbin", true))
patch_fissiond("/sbin");
bool treble;
{
+67 -75
View File
@@ -1,8 +1,9 @@
use crate::consts::{ROOTMNT, ROOTOVL};
use crate::ffi::MagiskInit;
use base::libc::O_RDONLY;
use base::libc::{O_CREAT, O_RDONLY, O_WRONLY};
use base::{
cstr_buf, debug, libc, path, BufReadExt, Directory, LibcReturn, LoggedResult, ResultExt,
Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, WalkResult,
clone_attr, cstr, cstr_buf, debug, libc, path, BufReadExt, Directory, FsPath, FsPathBuf,
LibcReturn, LoggedResult, ResultExt, Utf8CStr, Utf8CString,
};
use std::io::BufReader;
use std::{
@@ -10,80 +11,9 @@ use std::{
io::Write,
mem,
os::fd::{FromRawFd, RawFd},
sync::OnceLock,
ptr,
};
pub static OVERLAY_ATTRS: OnceLock<Vec<(String, String)>> = OnceLock::new();
const XATTR_NAME_SELINUX: &[u8] = b"security.selinux\0";
fn get_context<const N: usize>(path: &str, con: &mut Utf8CStrBufArr<N>) -> std::io::Result<()> {
unsafe {
let sz = libc::lgetxattr(
path.as_ptr().cast(),
XATTR_NAME_SELINUX.as_ptr().cast(),
con.as_mut_ptr().cast(),
con.capacity(),
)
.check_os_err()?;
con.set_len((sz - 1) as usize);
}
Ok(())
}
fn set_context(path: &str, con: &str) -> std::io::Result<()> {
unsafe {
libc::lsetxattr(
path.as_ptr().cast(),
XATTR_NAME_SELINUX.as_ptr().cast(),
con.as_ptr().cast(),
con.len() + 1,
0,
)
.as_os_err()
}
}
pub fn collect_overlay_contexts(src: &Utf8CStr) {
OVERLAY_ATTRS
.get_or_try_init(|| -> LoggedResult<_> {
let mut contexts = vec![];
let mut con = cstr_buf::default();
let mut path = cstr_buf::default();
let mut src = Directory::open(src)?;
src.path(&mut path)?;
let src_len = path.len();
src.post_order_walk(|f| {
f.path(&mut path)?;
let path = &path[src_len..];
if get_context(path, &mut con)
.log_with_msg(|w| w.write_fmt(format_args!("collect context {}", path)))
.is_ok()
{
debug!("collect context: {:?} -> {:?}", path, con);
contexts.push((path.to_string(), con.to_string()));
}
Ok(WalkResult::Continue)
})?;
Ok(contexts)
})
.ok();
}
pub fn reset_overlay_contexts() {
OVERLAY_ATTRS.get().map(|attrs| {
for (path, con) in attrs.iter() {
debug!("set context: {} -> {}", path, con);
set_context(path, con)
.log_with_msg(|w| w.write_fmt(format_args!("reset context {}", path)))
.ok();
}
Some(())
});
}
pub fn inject_magisk_rc(fd: RawFd, tmp_dir: &Utf8CStr) {
debug!("Injecting magisk rc");
@@ -114,6 +44,8 @@ on property:init.svc.zygote=stopped
mem::forget(file)
}
pub struct OverlayAttr(Utf8CString, Utf8CString);
impl MagiskInit {
pub(crate) fn parse_config_file(&mut self) {
if let Ok(fd) = path!("/data/.backup/.magisk").open(O_RDONLY) {
@@ -127,4 +59,64 @@ impl MagiskInit {
})
}
}
fn mount_impl(
&mut self,
src_dir: &Utf8CStr,
dest_dir: &Utf8CStr,
mount_list: &mut String,
) -> LoggedResult<()> {
let mut dir = Directory::open(src_dir)?;
let mut con = cstr_buf::default();
loop {
match &dir.read()? {
None => return Ok(()),
Some(e) => {
let name = e.name().to_str()?;
let src = FsPathBuf::new_dynamic(256).join(src_dir).join(name);
let dest = FsPathBuf::new_dynamic(256).join(dest_dir).join(name);
if dest.exists() {
if e.is_dir() {
// Recursive
self.mount_impl(&src, &dest, mount_list)?;
} else {
debug!("Mount [{}] -> [{}]", src, dest);
clone_attr(&dest, &src)?;
dest.get_secontext(&mut con)?;
unsafe {
libc::mount(
src.as_ptr(),
dest.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
.as_os_err()?;
};
self.overlay_con
.push(OverlayAttr(dest.to_owned(), con.to_owned()));
mount_list.push_str(dest.as_str());
mount_list.push('\n');
}
}
}
}
}
}
pub(crate) fn mount_overlay(&mut self, dest: &Utf8CStr) {
let mut mount_list = String::new();
self.mount_impl(cstr!(ROOTOVL), dest, &mut mount_list)
.log_ok();
if let Ok(mut fd) = path!(ROOTMNT).create(O_CREAT | O_WRONLY, 0) {
fd.write(mount_list.as_bytes()).log_ok();
}
}
pub(crate) fn restore_overlay_contexts(&self) {
self.overlay_con.iter().for_each(|attr| {
let OverlayAttr(path, con) = attr;
FsPath::from(path).set_secontext(con).log_ok();
})
}
}
+1 -1
View File
@@ -111,7 +111,7 @@ bool MagiskInit::hijack_sepolicy() noexcept {
sepol.to_file(SELINUX_LOAD);
// restore mounted files' context after sepolicy loaded
rust::reset_overlay_contexts();
restore_overlay_contexts();
// Write to the enforce node ONLY after sepolicy is loaded. We need to make sure
// the actual init process is blocked until sepolicy is loaded, or else