diff --git a/README.MD b/README.MD index 6bd8ff0874b43..782fe7a58c7e7 100644 --- a/README.MD +++ b/README.MD @@ -22,7 +22,7 @@ Click the icon below to download Magisk apk. [![](https://img.shields.io/badge/Magisk-v28.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v28.1) [![](https://img.shields.io/badge/Magisk%20Beta-v28.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v28.1) -[![](https://img.shields.io/badge/Magisk-Canary-red)](https://github.com/topjohnwu/Magisk/releases/tag/canary-28101) +[![](https://img.shields.io/badge/Magisk-Canary-red)](https://github.com/topjohnwu/Magisk/releases/tag/canary-28102) ## Useful Links diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/home/RebootMenu.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/home/RebootMenu.kt index 44f1f0a3d1a18..467a290d44e82 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/home/RebootMenu.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/home/RebootMenu.kt @@ -41,7 +41,7 @@ object RebootMenu { activity.getSystemService()?.isRebootingUserspaceSupported == true) { menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true } - if (Const.Version.isCanary()) { + if (Const.Version.atLeast_28_0()) { menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2 } else { menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/Const.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/Const.kt index 2ac6a40c5621c..2b594851197c4 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/Const.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/Const.kt @@ -28,6 +28,7 @@ object Const { fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary() fun atLeast_25_0() = Info.env.versionCode >= 25000 || isCanary() + fun atLeast_28_0() = Info.env.versionCode >= 28000 || isCanary() fun isCanary() = isCanary(Info.env.versionCode) fun isCanary(ver: Int) = ver > 0 && ver % 100 != 0 diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt index 46c6b832161af..951b84148589e 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt @@ -597,7 +597,9 @@ abstract class MagiskInstallImpl protected constructor( if (result) return true - Shell.cmd("rm -rf $installDir").submit() + // Not every operation initializes installDir + if (::installDir.isInitialized) + Shell.cmd("rm -rf $installDir").submit() return false } diff --git a/gradle.properties b/gradle.properties index dff2c9f2283b8..567ce39676044 100644 --- a/gradle.properties +++ b/gradle.properties @@ -30,5 +30,5 @@ android.nonFinalResIds=false # Magisk magisk.stubVersion=40 -magisk.versionCode=28101 +magisk.versionCode=28102 magisk.ondkVersion=r28.2 diff --git a/native/src/Android.mk b/native/src/Android.mk index 2035c1a10a1a6..ef2f23dd1a594 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -18,8 +18,6 @@ LOCAL_SRC_FILES := \ core/applets.cpp \ core/magisk.cpp \ core/daemon.cpp \ - core/bootstages.cpp \ - core/socket.cpp \ core/scripting.cpp \ core/selinux.cpp \ core/sqlite.cpp \ @@ -30,9 +28,7 @@ LOCAL_SRC_FILES := \ core/su/su.cpp \ core/su/connect.cpp \ core/su/pts.cpp \ - core/su/su_daemon.cpp \ core/zygisk/entry.cpp \ - core/zygisk/main.cpp \ core/zygisk/module.cpp \ core/zygisk/hook.cpp \ core/deny/cli.cpp \ @@ -168,6 +164,7 @@ LOCAL_SRC_FILES := \ sepolicy/policy-rs.cpp include $(BUILD_STATIC_LIBRARY) -include src/Android-rs.mk -include src/base/Android.mk -include src/external/Android.mk +CWD := $(LOCAL_PATH) +include $(CWD)/Android-rs.mk +include $(CWD)/base/Android.mk +include $(CWD)/external/Android.mk diff --git a/native/src/Cargo.lock b/native/src/Cargo.lock index e7625de605ad8..ba95d75ddaa7d 100644 --- a/native/src/Cargo.lock +++ b/native/src/Cargo.lock @@ -232,7 +232,7 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.133" +version = "1.0.137" dependencies = [ "cc", "cxxbridge-cmd", @@ -243,7 +243,7 @@ dependencies = [ [[package]] name = "cxx-gen" -version = "0.7.133" +version = "0.7.137" dependencies = [ "codespan-reporting", "proc-macro2", @@ -253,7 +253,7 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.133" +version = "1.0.137" dependencies = [ "clap", "codespan-reporting", @@ -264,11 +264,11 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.133" +version = "1.0.137" [[package]] name = "cxxbridge-macro" -version = "1.0.133" +version = "1.0.137" dependencies = [ "proc-macro2", "quote", @@ -300,6 +300,15 @@ dependencies = [ "syn", ] +[[package]] +name = "derive" +version = "0.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.11.0-pre.9" @@ -461,6 +470,7 @@ dependencies = [ "bytemuck", "cxx", "cxx-gen", + "derive", "num-derive", "num-traits", "pb-rs", diff --git a/native/src/Cargo.toml b/native/src/Cargo.toml index 85c4853fed0eb..8e3132bff7c7e 100644 --- a/native/src/Cargo.toml +++ b/native/src/Cargo.toml @@ -1,6 +1,6 @@ [workspace] exclude = ["external"] -members = ["base", "boot", "core", "init", "sepolicy"] +members = ["base", "boot", "core", "core/derive", "init", "sepolicy"] resolver = "2" [workspace.dependencies] @@ -26,6 +26,9 @@ bytemuck = "1.16" fdt = "0.1" const_format = "0.2" bit-set = "0.8" +syn = "2" +quote = "1" +proc-macro2 = "1" [workspace.dependencies.argh] git = "https://github.com/google/argh.git" diff --git a/native/src/base/files.rs b/native/src/base/files.rs index 87aa334b60e4f..e50c6fceab7fa 100644 --- a/native/src/base/files.rs +++ b/native/src/base/files.rs @@ -3,7 +3,7 @@ use crate::{ cstr, errno, error, FsPath, FsPathBuf, LibcReturn, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrWrite, }; -use bytemuck::{bytes_of_mut, Pod}; +use bytemuck::{bytes_of, bytes_of_mut, Pod}; use libc::{ c_uint, dirent, makedev, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, @@ -14,13 +14,11 @@ use std::cmp::min; use std::ffi::CStr; use std::fs::File; use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write}; -use std::mem::ManuallyDrop; use std::ops::Deref; use std::os::fd::{AsFd, BorrowedFd, IntoRawFd}; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd}; use std::path::Path; -use std::sync::Arc; use std::{io, mem, ptr, slice}; pub fn __open_fd_impl(path: &Utf8CStr, flags: i32, mode: mode_t) -> io::Result { @@ -123,6 +121,7 @@ impl BufReadExt for T { pub trait WriteExt { fn write_zeros(&mut self, len: usize) -> io::Result<()>; + fn write_pod(&mut self, data: &F) -> io::Result<()>; } impl WriteExt for T { @@ -135,6 +134,10 @@ impl WriteExt for T { } Ok(()) } + + fn write_pod(&mut self, data: &F) -> io::Result<()> { + self.write_all(bytes_of(data)) + } } pub struct FileAttr { @@ -265,7 +268,7 @@ impl DirEntry<'_> { } unsafe fn open_fd(&self, flags: i32) -> io::Result { - self.dir.open_fd(self.d_name(), flags, 0) + self.dir.open_raw_fd(self.d_name(), flags, 0) } pub fn open_as_dir(&self) -> io::Result { @@ -350,10 +353,17 @@ impl Directory { unsafe { libc::rewinddir(self.dirp) } } - unsafe fn open_fd(&self, name: &CStr, flags: i32, mode: i32) -> io::Result { + unsafe fn open_raw_fd(&self, name: &CStr, flags: i32, mode: i32) -> io::Result { libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode).check_os_err() } + pub fn open_fd(&self, name: &Utf8CStr, flags: i32, mode: i32) -> io::Result { + unsafe { + self.open_raw_fd(name.as_cstr(), flags, mode) + .map(|fd| OwnedFd::from_raw_fd(fd)) + } + } + pub fn contains_path(&self, path: &CStr) -> bool { // WARNING: Using faccessat is incorrect, because the raw linux kernel syscall // does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2. @@ -414,7 +424,7 @@ impl Directory { } else if e.is_file() { let mut src = e.open_as_file(O_RDONLY)?; let mut dest = unsafe { - File::from_raw_fd(dir.open_fd( + File::from_raw_fd(dir.open_raw_fd( e.d_name(), O_WRONLY | O_CREAT | O_TRUNC, 0o777, @@ -1030,32 +1040,3 @@ pub fn parse_mount_info(pid: &str) -> Vec { } res } - -#[derive(Default, Clone)] -pub enum SharedFd { - #[default] - None, - Shared(Arc), -} - -impl From for SharedFd { - fn from(fd: OwnedFd) -> Self { - SharedFd::Shared(Arc::new(fd)) - } -} - -impl SharedFd { - pub const fn new() -> Self { - SharedFd::None - } - - // This is unsafe because we cannot create multiple mutable references to the same fd. - // This can only be safely used if and only if the underlying fd points to a pipe, - // and the read/write operations performed on the file involves bytes less than PIPE_BUF. - pub unsafe fn as_file(&self) -> Option> { - match self { - SharedFd::None => None, - SharedFd::Shared(arc) => Some(ManuallyDrop::new(File::from_raw_fd(arc.as_raw_fd()))), - } - } -} diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index e418a00421d85..585d7e464733b 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -8,6 +8,7 @@ use num_traits::FromPrimitive; pub use cstr::*; use cxx_extern::*; +pub use ffi::fork_dont_care; pub use files::*; pub use logging::*; pub use misc::*; @@ -42,6 +43,7 @@ pub mod ffi { type Utf8CStrRef<'a> = &'a crate::cstr::Utf8CStr; fn mut_u8_patch(buf: &mut [u8], from: &[u8], to: &[u8]) -> Vec; + fn fork_dont_care() -> i32; } extern "Rust" { diff --git a/native/src/base/misc.rs b/native/src/base/misc.rs index 5413ffe923e36..e29835c00a478 100644 --- a/native/src/base/misc.rs +++ b/native/src/base/misc.rs @@ -1,13 +1,14 @@ +use crate::{ffi, StrErr, Utf8CStr}; +use argh::EarlyExit; +use libc::c_char; use std::fmt::Arguments; use std::io::Write; +use std::mem::ManuallyDrop; use std::process::exit; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::sync::Arc; use std::{fmt, io, slice, str}; -use argh::EarlyExit; -use libc::c_char; - -use crate::{ffi, StrErr, Utf8CStr}; - pub fn errno() -> &'static mut i32 { unsafe { &mut *libc::__errno() } } @@ -161,3 +162,52 @@ impl fmt::Write for FmtAdaptor<'_, T> { self.0.write_fmt(args).map_err(|_| fmt::Error) } } + +pub struct AtomicArc { + ptr: AtomicPtr, +} + +impl AtomicArc { + pub fn new(arc: Arc) -> AtomicArc { + let raw = Arc::into_raw(arc); + Self { + ptr: AtomicPtr::new(raw as *mut _), + } + } + + pub fn load(&self) -> Arc { + let raw = self.ptr.load(Ordering::Acquire); + // SAFETY: the raw pointer is always created from Arc::into_raw + let arc = ManuallyDrop::new(unsafe { Arc::from_raw(raw) }); + ManuallyDrop::into_inner(arc.clone()) + } + + fn swap_ptr(&self, raw: *const T) -> Arc { + let prev = self.ptr.swap(raw as *mut _, Ordering::AcqRel); + // SAFETY: the raw pointer is always created from Arc::into_raw + unsafe { Arc::from_raw(prev) } + } + + pub fn swap(&self, arc: Arc) -> Arc { + let raw = Arc::into_raw(arc); + self.swap_ptr(raw) + } + + pub fn store(&self, arc: Arc) { + // Drop the previous value + let _ = self.swap(arc); + } +} + +impl Drop for AtomicArc { + fn drop(&mut self) { + // Drop the internal value + let _ = self.swap_ptr(std::ptr::null()); + } +} + +impl Default for AtomicArc { + fn default() -> Self { + Self::new(Default::default()) + } +} diff --git a/native/src/boot/cpio.rs b/native/src/boot/cpio.rs index 9fcb8777109f3..18478ae13d9d2 100644 --- a/native/src/boot/cpio.rs +++ b/native/src/boot/cpio.rs @@ -755,9 +755,9 @@ impl Display for CpioEntry { } pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool { - fn inner(argc: i32, argv: *const *const c_char) -> LoggedResult<()> { + let res: LoggedResult<()> = try { if argc < 1 { - return Err(log_err!("No arguments")); + Err(log_err!("No arguments"))?; } let cmds = map_args(argc, argv)?; @@ -807,7 +807,7 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool { CpioAction::Add(Add { mode, path, file }) => cpio.add(*mode, path, file)?, CpioAction::Extract(Extract { paths }) => { if !paths.is_empty() && paths.len() != 2 { - return Err(log_err!("invalid arguments")); + Err(log_err!("invalid arguments"))?; } let mut it = paths.iter_mut(); cpio.extract(it.next(), it.next())?; @@ -819,10 +819,8 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool { }; } cpio.dump(file)?; - Ok(()) - } - inner(argc, argv) - .log_with_msg(|w| w.write_str("Failed to process cpio")) + }; + res.log_with_msg(|w| w.write_str("Failed to process cpio")) .is_ok() } diff --git a/native/src/boot/dtb.rs b/native/src/boot/dtb.rs index 371a804044be3..f44f0b6aec002 100644 --- a/native/src/boot/dtb.rs +++ b/native/src/boot/dtb.rs @@ -273,9 +273,9 @@ fn dtb_patch(file: &Utf8CStr) -> LoggedResult { } pub fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool { - fn inner(argc: i32, argv: *const *const c_char) -> LoggedResult<()> { + let res: LoggedResult<()> = try { if argc < 1 { - return Err(log_err!("No arguments")); + Err(log_err!("No arguments"))?; } let cmds = map_args(argc, argv)?; @@ -299,9 +299,7 @@ pub fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool { } } } - Ok(()) - } - inner(argc, argv) - .log_with_msg(|w| w.write_str("Failed to process dtb")) + }; + res.log_with_msg(|w| w.write_str("Failed to process dtb")) .is_ok() } diff --git a/native/src/boot/lib.rs b/native/src/boot/lib.rs index f90b16e074dee..a85ac1c904665 100644 --- a/native/src/boot/lib.rs +++ b/native/src/boot/lib.rs @@ -1,6 +1,7 @@ #![feature(format_args_nl)] #![feature(btree_extract_if)] #![feature(iter_intersperse)] +#![feature(try_blocks)] pub use base; use cpio::cpio_commands; @@ -21,6 +22,14 @@ mod sign; #[cxx::bridge] pub mod ffi { + unsafe extern "C++" { + include!("../base/include/base.hpp"); + + #[namespace = "rust"] + #[cxx_name = "Utf8CStr"] + type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>; + } + unsafe extern "C++" { include!("compress.hpp"); fn decompress(buf: &[u8], fd: i32) -> bool; @@ -51,10 +60,10 @@ pub mod ffi { #[namespace = "rust"] #[allow(unused_unsafe)] extern "Rust" { - unsafe fn extract_boot_from_payload( - partition: *const c_char, - in_path: *const c_char, - out_path: *const c_char, + fn extract_boot_from_payload( + partition: Utf8CStrRef, + in_path: Utf8CStrRef, + out_path: Utf8CStrRef, ) -> bool; unsafe fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool; unsafe fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool; @@ -70,5 +79,5 @@ pub mod ffi { #[inline(always)] pub(crate) fn check_env(env: &str) -> bool { - env::var(env).map_or(false, |var| var == "true") + env::var(env).is_ok_and(|var| var == "true") } diff --git a/native/src/boot/main.cpp b/native/src/boot/main.cpp index d198a13c1e5f6..4972f3820b781 100644 --- a/native/src/boot/main.cpp +++ b/native/src/boot/main.cpp @@ -217,8 +217,8 @@ int main(int argc, char *argv[]) { } else if (argc > 2 && action == "extract") { return rust::extract_boot_from_payload( argv[2], - argc > 3 ? argv[3] : nullptr, - argc > 4 ? argv[4] : nullptr + argc > 3 ? argv[3] : "", + argc > 4 ? argv[4] : "" ) ? 0 : 1; } else { usage(argv[0]); diff --git a/native/src/boot/patch.rs b/native/src/boot/patch.rs index cac96e9c4afc3..0740bb63a82f3 100644 --- a/native/src/boot/patch.rs +++ b/native/src/boot/patch.rs @@ -101,7 +101,7 @@ fn hex2byte(hex: &[u8]) -> Vec { } pub fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool { - fn inner(file: &[u8], from: &[u8], to: &[u8]) -> LoggedResult { + let res: LoggedResult = try { let file = Utf8CStr::from_bytes(file)?; let from = Utf8CStr::from_bytes(from)?; let to = Utf8CStr::from_bytes(to)?; @@ -114,8 +114,7 @@ pub fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool { for off in &v { eprintln!("Patch @ {:#010X} [{}] -> [{}]", off, from, to); } - - Ok(!v.is_empty()) - } - inner(file, from, to).unwrap_or(false) + !v.is_empty() + }; + res.unwrap_or(false) } diff --git a/native/src/boot/payload.rs b/native/src/boot/payload.rs index 0be68152c23e1..75b76083f094c 100644 --- a/native/src/boot/payload.rs +++ b/native/src/boot/payload.rs @@ -1,17 +1,19 @@ -use std::fs::File; -use std::io::{BufReader, Read, Seek, SeekFrom, Write}; -use std::os::fd::{AsRawFd, FromRawFd}; +use std::{ + fs::File, + io::{BufReader, Read, Seek, SeekFrom, Write}, + os::fd::{AsRawFd, FromRawFd}, +}; use byteorder::{BigEndian, ReadBytesExt}; use quick_protobuf::{BytesReader, MessageRead}; -use base::libc::c_char; -use base::{error, LoggedError, LoggedResult, ReadSeekExt, StrErr, Utf8CStr}; -use base::{ResultExt, WriteExt}; - -use crate::ffi; -use crate::proto::update_metadata::mod_InstallOperation::Type; -use crate::proto::update_metadata::DeltaArchiveManifest; +use crate::{ + ffi, + proto::update_metadata::{mod_InstallOperation::Type, DeltaArchiveManifest}, +}; +use base::{ + error, ffi::Utf8CStrRef, LoggedError, LoggedResult, ReadSeekExt, ResultExt, Utf8CStr, WriteExt, +}; macro_rules! bad_payload { ($msg:literal) => {{ @@ -178,28 +180,23 @@ fn do_extract_boot_from_payload( } pub fn extract_boot_from_payload( - in_path: *const c_char, - partition: *const c_char, - out_path: *const c_char, + in_path: Utf8CStrRef, + partition: Utf8CStrRef, + out_path: Utf8CStrRef, ) -> bool { - fn inner( - in_path: *const c_char, - partition: *const c_char, - out_path: *const c_char, - ) -> LoggedResult<()> { - let in_path = unsafe { Utf8CStr::from_ptr(in_path) }?; - let partition = match unsafe { Utf8CStr::from_ptr(partition) } { - Ok(s) => Some(s), - Err(StrErr::NullPointerError) => None, - Err(e) => Err(e)?, + let res: LoggedResult<()> = try { + let partition = if partition.is_empty() { + None + } else { + Some(partition) }; - let out_path = match unsafe { Utf8CStr::from_ptr(out_path) } { - Ok(s) => Some(s), - Err(StrErr::NullPointerError) => None, - Err(e) => Err(e)?, + let out_path = if out_path.is_empty() { + None + } else { + Some(out_path) }; - do_extract_boot_from_payload(in_path, partition, out_path) - .log_with_msg(|w| w.write_str("Failed to extract from payload")) - } - inner(in_path, partition, out_path).is_ok() + do_extract_boot_from_payload(in_path, partition, out_path)? + }; + res.log_with_msg(|w| w.write_str("Failed to extract from payload")) + .is_ok() } diff --git a/native/src/boot/sign.rs b/native/src/boot/sign.rs index da9b8c1e99b3e..89245ec8592df 100644 --- a/native/src/boot/sign.rs +++ b/native/src/boot/sign.rs @@ -254,7 +254,7 @@ impl BootSignature { } pub fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool { - fn inner(img: &BootImage, cert: *const c_char) -> LoggedResult<()> { + let res: LoggedResult<()> = try { let tail = img.tail(); // Don't use BootSignature::from_der because tail might have trailing zeros let mut reader = SliceReader::new(tail)?; @@ -268,9 +268,8 @@ pub fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool { Err(e) => Err(e)?, }; sig.verify(img.payload())?; - Ok(()) - } - inner(img, cert).is_ok() + }; + res.is_ok() } enum Bytes { @@ -296,12 +295,7 @@ pub fn sign_boot_image( cert: *const c_char, key: *const c_char, ) -> Vec { - fn inner( - payload: &[u8], - name: *const c_char, - cert: *const c_char, - key: *const c_char, - ) -> LoggedResult> { + let res: LoggedResult> = try { // Process arguments let name = unsafe { Utf8CStr::from_ptr(name) }?; let cert = match unsafe { Utf8CStr::from_ptr(cert) } { @@ -337,7 +331,7 @@ pub fn sign_boot_image( authenticated_attributes: attr, signature: OctetString::new(sig)?, }; - sig.to_der().log() - } - inner(payload, name, cert, key).unwrap_or_default() + sig.to_der()? + }; + res.unwrap_or_default() } diff --git a/native/src/core/Cargo.toml b/native/src/core/Cargo.toml index 25862b66b3f5b..08061a0194691 100644 --- a/native/src/core/Cargo.toml +++ b/native/src/core/Cargo.toml @@ -17,6 +17,7 @@ pb-rs = { workspace = true } [dependencies] base = { path = "../base", features = ["selinux"] } +derive = { path = "derive" } cxx = { workspace = true } num-traits = { workspace = true } num-derive = { workspace = true } diff --git a/native/src/core/bootstages.cpp b/native/src/core/bootstages.cpp deleted file mode 100644 index 439bff7578e7a..0000000000000 --- a/native/src/core/bootstages.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace std; - -bool zygisk_enabled = false; - -/********* - * Setup * - *********/ - -static bool magisk_env() { - char buf[4096]; - - LOGI("* Initializing Magisk environment\n"); - - ssprintf(buf, sizeof(buf), "%s/0/%s/install", APP_DATA_DIR, JAVA_PACKAGE_NAME); - // Alternative binaries paths - const char *alt_bin[] = { "/cache/data_adb/magisk", "/data/magisk", buf }; - for (auto alt : alt_bin) { - if (access(alt, F_OK) == 0) { - rm_rf(DATABIN); - cp_afc(alt, DATABIN); - rm_rf(alt); - } - } - rm_rf("/cache/data_adb"); - - // Directories in /data/adb - chmod(SECURE_DIR, 0700); - xmkdir(DATABIN, 0755); - xmkdir(MODULEROOT, 0755); - xmkdir(SECURE_DIR "/post-fs-data.d", 0755); - xmkdir(SECURE_DIR "/service.d", 0755); - restorecon(); - - if (access(DATABIN "/busybox", X_OK)) - return false; - - ssprintf(buf, sizeof(buf), "%s/" BBPATH "/busybox", get_magisk_tmp()); - mkdir(dirname(buf), 0755); - cp_afc(DATABIN "/busybox", buf); - exec_command_async(buf, "--install", "-s", dirname(buf)); - - // magisk32 and magiskpolicy are not installed into ramdisk and has to be copied - // from data to magisk tmp - if (access(DATABIN "/magisk32", X_OK) == 0) { - ssprintf(buf, sizeof(buf), "%s/magisk32", get_magisk_tmp()); - cp_afc(DATABIN "/magisk32", buf); - } - if (access(DATABIN "/magiskpolicy", X_OK) == 0) { - ssprintf(buf, sizeof(buf), "%s/magiskpolicy", get_magisk_tmp()); - cp_afc(DATABIN "/magiskpolicy", buf); - } - - return true; -} - -void unlock_blocks() { - int fd, dev, OFF = 0; - - auto dir = xopen_dir("/dev/block"); - if (!dir) - return; - dev = dirfd(dir.get()); - - for (dirent *entry; (entry = readdir(dir.get()));) { - if (entry->d_type == DT_BLK) { - if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0) - continue; - if (ioctl(fd, BLKROSET, &OFF) < 0) - PLOGE("unlock %s", entry->d_name); - close(fd); - } - } -} - -#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8))) - -static bool check_key_combo() { - uint8_t bitmask[(KEY_MAX + 1) / 8]; - vector events; - constexpr char name[] = "/dev/.ev"; - - // First collect candidate events that accepts volume down - for (int minor = 64; minor < 96; ++minor) { - if (xmknod(name, S_IFCHR | 0444, makedev(13, minor))) - continue; - int fd = open(name, O_RDONLY | O_CLOEXEC); - unlink(name); - if (fd < 0) - continue; - memset(bitmask, 0, sizeof(bitmask)); - ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask); - if (test_bit(KEY_VOLUMEDOWN, bitmask)) - events.push_back(fd); - else - close(fd); - } - if (events.empty()) - return false; - - run_finally fin([&]{ std::for_each(events.begin(), events.end(), close); }); - - // Check if volume down key is held continuously for more than 3 seconds - for (int i = 0; i < 300; ++i) { - bool pressed = false; - for (const int &fd : events) { - memset(bitmask, 0, sizeof(bitmask)); - ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask); - if (test_bit(KEY_VOLUMEDOWN, bitmask)) { - pressed = true; - break; - } - } - if (!pressed) - return false; - // Check every 10ms - usleep(10000); - } - LOGD("KEY_VOLUMEDOWN detected: enter safe mode\n"); - return true; -} - -/*********************** - * Boot Stage Handlers * - ***********************/ - -bool MagiskD::post_fs_data() const noexcept { - setup_logfile(); - - LOGI("** post-fs-data mode running\n"); - - preserve_stub_apk(); - - if (access(SECURE_DIR, F_OK) != 0) { - if (SDK_INT < 24) { - xmkdir(SECURE_DIR, 0700); - } else { - LOGE(SECURE_DIR " is not present, abort\n"); - return true; - } - } - - prune_su_access(); - - if (!magisk_env()) { - LOGE("* Magisk environment incomplete, abort\n"); - return true; - } - - // Check safe mode - int bootloop_cnt = get_db_setting(DbEntryKey::BootloopCount); - // Increment the boot counter - set_db_setting(DbEntryKey::BootloopCount, bootloop_cnt + 1); - bool safe_mode = bootloop_cnt >= 2 || get_prop("persist.sys.safemode", true) == "1" || - get_prop("ro.sys.safemode") == "1" || check_key_combo(); - - if (safe_mode) { - LOGI("* Safe mode triggered\n"); - // Disable all modules and zygisk so next boot will be clean - disable_modules(); - set_db_setting(DbEntryKey::ZygiskConfig, false); - return true; - } - - exec_common_scripts("post-fs-data"); - zygisk_enabled = get_db_setting(DbEntryKey::ZygiskConfig); - initialize_denylist(); - setup_mounts(); - handle_modules(); - load_modules(); - return false; -} - -void MagiskD::late_start() const noexcept { - setup_logfile(); - - LOGI("** late_start service mode running\n"); - - exec_common_scripts("service"); - exec_module_scripts("service"); -} - -void MagiskD::boot_complete() const noexcept { - setup_logfile(); - - LOGI("** boot-complete triggered\n"); - - // Reset the bootloop counter once we have boot-complete - set_db_setting(DbEntryKey::BootloopCount, 0); - - // At this point it's safe to create the folder - if (access(SECURE_DIR, F_OK) != 0) - xmkdir(SECURE_DIR, 0700); - - // Ensure manager exists - get_manager(0, nullptr, true); - - reset_zygisk(true); -} diff --git a/native/src/core/daemon.cpp b/native/src/core/daemon.cpp index b2a6ad50d05d9..937a35e6f7a0b 100644 --- a/native/src/core/daemon.cpp +++ b/native/src/core/daemon.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include @@ -134,23 +136,57 @@ void MagiskD::reboot() const noexcept { exec_command_sync("/system/bin/reboot"); } +bool get_client_cred(int fd, sock_cred *cred) { + socklen_t len = sizeof(ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len) != 0) + return false; + char buf[4096]; + len = sizeof(buf); + if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &len) != 0) + len = 0; + buf[len] = '\0'; + cred->context = buf; + return true; +} + +bool read_string(int fd, std::string &str) { + str.clear(); + int len = read_int(fd); + str.resize(len); + return xxread(fd, str.data(), len) == len; +} + +string read_string(int fd) { + string str; + read_string(fd, str); + return str; +} + +void write_string(int fd, string_view str) { + if (fd < 0) return; + write_int(fd, str.size()); + xwrite(fd, str.data(), str.size()); +} + static void handle_request_async(int client, int code, const sock_cred &cred) { + auto &daemon = MagiskD::Get(); switch (code) { case +RequestCode::DENYLIST: denylist_handler(client, &cred); break; case +RequestCode::SUPERUSER: - su_daemon_handler(client, &cred); + daemon.su_daemon_handler(client, cred); break; - case +RequestCode::ZYGOTE_RESTART: + case +RequestCode::ZYGOTE_RESTART: { LOGI("** zygote restarted\n"); - MagiskD().prune_su_access(); + daemon.prune_su_access(); scan_deny_apps(); - reset_zygisk(false); + daemon.zygisk_reset(false); close(client); break; + } case +RequestCode::SQLITE_CMD: - MagiskD().db_exec(client); + daemon.db_exec(client); break; case +RequestCode::REMOVE_MODULES: { int do_reboot = read_int(client); @@ -158,12 +194,12 @@ static void handle_request_async(int client, int code, const sock_cred &cred) { write_int(client, 0); close(client); if (do_reboot) { - MagiskD().reboot(); + daemon.reboot(); } break; } case +RequestCode::ZYGISK: - zygisk_handler(client, &cred); + daemon.zygisk_handler(client); break; default: __builtin_unreachable(); @@ -285,7 +321,7 @@ static void handle_request(pollfd *pfd) { exec_task([=, fd = client.release()] { handle_request_async(fd, code, cred); }); } else { exec_task([=, fd = client.release()] { - MagiskD().boot_stage_handler(fd, code); + MagiskD::Get().boot_stage_handler(fd, code); }); } } @@ -328,7 +364,7 @@ static void daemon_entry() { setcon(MAGISK_PROC_CON); rust::daemon_entry(); - SDK_INT = MagiskD().sdk_int(); + SDK_INT = MagiskD::Get().sdk_int(); // Escape from cgroup int pid = getpid(); @@ -378,7 +414,6 @@ static void daemon_entry() { default_new(poll_map); default_new(poll_fds); - default_new(module_list); // Register handler for main socket pollfd main_socket_pfd = { fd, POLLIN, 0 }; @@ -455,3 +490,116 @@ int connect_daemon(int req, bool create) { } return fd; } + +bool setup_magisk_env() { + char buf[4096]; + + LOGI("* Initializing Magisk environment\n"); + + ssprintf(buf, sizeof(buf), "%s/0/%s/install", APP_DATA_DIR, JAVA_PACKAGE_NAME); + // Alternative binaries paths + const char *alt_bin[] = { "/cache/data_adb/magisk", "/data/magisk", buf }; + for (auto alt : alt_bin) { + if (access(alt, F_OK) == 0) { + rm_rf(DATABIN); + cp_afc(alt, DATABIN); + rm_rf(alt); + } + } + rm_rf("/cache/data_adb"); + + // Directories in /data/adb + chmod(SECURE_DIR, 0700); + xmkdir(DATABIN, 0755); + xmkdir(MODULEROOT, 0755); + xmkdir(SECURE_DIR "/post-fs-data.d", 0755); + xmkdir(SECURE_DIR "/service.d", 0755); + restorecon(); + + if (access(DATABIN "/busybox", X_OK)) + return false; + + ssprintf(buf, sizeof(buf), "%s/" BBPATH "/busybox", get_magisk_tmp()); + mkdir(dirname(buf), 0755); + cp_afc(DATABIN "/busybox", buf); + exec_command_async(buf, "--install", "-s", dirname(buf)); + + // magisk32 and magiskpolicy are not installed into ramdisk and has to be copied + // from data to magisk tmp + if (access(DATABIN "/magisk32", X_OK) == 0) { + ssprintf(buf, sizeof(buf), "%s/magisk32", get_magisk_tmp()); + cp_afc(DATABIN "/magisk32", buf); + } + if (access(DATABIN "/magiskpolicy", X_OK) == 0) { + ssprintf(buf, sizeof(buf), "%s/magiskpolicy", get_magisk_tmp()); + cp_afc(DATABIN "/magiskpolicy", buf); + } + + return true; +} + +void unlock_blocks() { + int fd, dev, OFF = 0; + + auto dir = xopen_dir("/dev/block"); + if (!dir) + return; + dev = dirfd(dir.get()); + + for (dirent *entry; (entry = readdir(dir.get()));) { + if (entry->d_type == DT_BLK) { + if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0) + continue; + if (ioctl(fd, BLKROSET, &OFF) < 0) + PLOGE("unlock %s", entry->d_name); + close(fd); + } + } +} + +#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8))) + +bool check_key_combo() { + uint8_t bitmask[(KEY_MAX + 1) / 8]; + vector events; + constexpr char name[] = "/dev/.ev"; + + // First collect candidate events that accepts volume down + for (int minor = 64; minor < 96; ++minor) { + if (xmknod(name, S_IFCHR | 0444, makedev(13, minor))) + continue; + int fd = open(name, O_RDONLY | O_CLOEXEC); + unlink(name); + if (fd < 0) + continue; + memset(bitmask, 0, sizeof(bitmask)); + ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask); + if (test_bit(KEY_VOLUMEDOWN, bitmask)) + events.push_back(fd); + else + close(fd); + } + if (events.empty()) + return false; + + run_finally fin([&]{ std::for_each(events.begin(), events.end(), close); }); + + // Check if volume down key is held continuously for more than 3 seconds + for (int i = 0; i < 300; ++i) { + bool pressed = false; + for (const int &fd : events) { + memset(bitmask, 0, sizeof(bitmask)); + ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask); + if (test_bit(KEY_VOLUMEDOWN, bitmask)) { + pressed = true; + break; + } + } + if (!pressed) + return false; + // Check every 10ms + usleep(10000); + } + LOGD("KEY_VOLUMEDOWN detected: enter safe mode\n"); + return true; +} diff --git a/native/src/core/daemon.rs b/native/src/core/daemon.rs index 2b3ed4904c4d6..a5a5e813415d7 100644 --- a/native/src/core/daemon.rs +++ b/native/src/core/daemon.rs @@ -1,19 +1,23 @@ -use crate::consts::{MAGISK_FULL_VER, MAIN_CONFIG}; +use crate::consts::{MAGISK_FULL_VER, MAIN_CONFIG, SECURE_DIR}; use crate::db::Sqlite3; -use crate::ffi::{get_magisk_tmp, RequestCode}; +use crate::ffi::{ + check_key_combo, disable_modules, exec_common_scripts, exec_module_scripts, get_magisk_tmp, + initialize_denylist, setup_magisk_env, DbEntryKey, ModuleInfo, RequestCode, +}; use crate::get_prop; -use crate::logging::{magisk_logging, start_log_daemon}; +use crate::logging::{magisk_logging, setup_logfile, start_log_daemon}; +use crate::mount::setup_mounts; use crate::package::ManagerInfo; +use crate::su::SuInfo; use base::libc::{O_CLOEXEC, O_RDONLY}; use base::{ - cstr, info, libc, open_fd, BufReadExt, Directory, FsPath, FsPathBuf, LoggedResult, ReadExt, + cstr, error, info, libc, open_fd, AtomicArc, BufReadExt, FsPath, FsPathBuf, ResultExt, Utf8CStr, Utf8CStrBufArr, }; -use bit_set::BitSet; -use bytemuck::bytes_of; use std::fs::File; -use std::io; -use std::io::{BufReader, Read, Write}; +use std::io::BufReader; +use std::os::unix::net::UnixStream; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Mutex, OnceLock}; // Global magiskd singleton @@ -60,16 +64,29 @@ pub struct MagiskD { pub sql_connection: Mutex>, pub manager_info: Mutex, boot_stage_lock: Mutex, + pub module_list: OnceLock>, + pub zygiskd_sockets: Mutex<(Option, Option)>, + pub zygisk_enabled: AtomicBool, + pub zygote_start_count: AtomicU32, + pub cached_su_info: AtomicArc, sdk_int: i32, pub is_emulator: bool, is_recovery: bool, } impl MagiskD { + pub fn get() -> &'static MagiskD { + unsafe { MAGISKD.get().unwrap_unchecked() } + } + pub fn is_recovery(&self) -> bool { self.is_recovery } + pub fn zygisk_enabled(&self) -> bool { + self.zygisk_enabled.load(Ordering::Acquire) + } + pub fn sdk_int(&self) -> i32 { self.sdk_int } @@ -82,39 +99,86 @@ impl MagiskD { } } - // app_id = app_no + AID_APP_START - // app_no range: [0, 9999] - pub fn get_app_no_list(&self) -> BitSet { - let mut list = BitSet::new(); - let _: LoggedResult<()> = try { - let mut app_data_dir = Directory::open(self.app_data_dir())?; - // For each user - loop { - let entry = match app_data_dir.read()? { - None => break, - Some(e) => e, - }; - let mut user_dir = match entry.open_as_dir() { - Err(_) => continue, - Ok(dir) => dir, - }; - // For each package - loop { - match user_dir.read()? { - None => break, - Some(e) => { - let attr = e.get_attr()?; - let app_id = to_app_id(attr.st.st_uid as i32); - if (AID_APP_START..=AID_APP_END).contains(&app_id) { - let app_no = app_id - AID_APP_START; - list.insert(app_no as usize); - } - } - } - } + fn post_fs_data(&self) -> bool { + setup_logfile(); + info!("** post-fs-data mode running"); + + self.preserve_stub_apk(); + + // Check secure dir + let secure_dir = FsPath::from(cstr!(SECURE_DIR)); + if !secure_dir.exists() { + if self.sdk_int < 24 { + secure_dir.mkdir(0o700).log().ok(); + } else { + error!("* {} is not present, abort", SECURE_DIR); + return true; } - }; - list + } + + self.prune_su_access(); + + if !setup_magisk_env() { + error!("* Magisk environment incomplete, abort"); + return true; + } + + // Check safe mode + let boot_cnt = self.get_db_setting(DbEntryKey::BootloopCount); + self.set_db_setting(DbEntryKey::BootloopCount, boot_cnt + 1) + .log() + .ok(); + let safe_mode = boot_cnt >= 2 + || get_prop(cstr!("persist.sys.safemode"), true) == "1" + || get_prop(cstr!("ro.sys.safemode"), false) == "1" + || check_key_combo(); + + if safe_mode { + info!("* Safe mode triggered"); + // Disable all modules and zygisk so next boot will be clean + disable_modules(); + self.set_db_setting(DbEntryKey::ZygiskConfig, 0).log().ok(); + return true; + } + + exec_common_scripts(cstr!("post-fs-data")); + self.zygisk_enabled.store( + self.get_db_setting(DbEntryKey::ZygiskConfig) != 0, + Ordering::Release, + ); + initialize_denylist(); + setup_mounts(); + let modules = self.handle_modules(); + self.module_list.set(modules).ok(); + + false + } + + fn late_start(&self) { + setup_logfile(); + info!("** late_start service mode running"); + + exec_common_scripts(cstr!("service")); + if let Some(module_list) = self.module_list.get() { + exec_module_scripts(cstr!("service"), module_list); + } + } + + fn boot_complete(&self) { + setup_logfile(); + info!("** boot-complete triggered"); + + // Reset the bootloop counter once we have boot-complete + self.set_db_setting(DbEntryKey::BootloopCount, 0).log().ok(); + + // At this point it's safe to create the folder + let secure_dir = FsPath::from(cstr!(SECURE_DIR)); + if !secure_dir.exists() { + secure_dir.mkdir(0o700).log().ok(); + } + + self.ensure_manager(); + self.zygisk_reset(true) } pub fn boot_stage_handler(&self, client: i32, code: i32) { @@ -203,6 +267,7 @@ pub fn daemon_entry() { sdk_int, is_emulator, is_recovery, + zygote_start_count: AtomicU32::new(1), ..Default::default() }; MAGISKD.set(magiskd).ok(); @@ -238,43 +303,3 @@ fn check_data() -> bool { } false } - -pub fn get_magiskd() -> &'static MagiskD { - unsafe { MAGISKD.get().unwrap_unchecked() } -} - -pub trait IpcRead { - fn ipc_read_int(&mut self) -> io::Result; - fn ipc_read_string(&mut self) -> io::Result; -} - -impl IpcRead for T { - fn ipc_read_int(&mut self) -> io::Result { - let mut val: i32 = 0; - self.read_pod(&mut val)?; - Ok(val) - } - - fn ipc_read_string(&mut self) -> io::Result { - let len = self.ipc_read_int()?; - let mut val = "".to_string(); - self.take(len as u64).read_to_string(&mut val)?; - Ok(val) - } -} - -pub trait IpcWrite { - fn ipc_write_int(&mut self, val: i32) -> io::Result<()>; - fn ipc_write_string(&mut self, val: &str) -> io::Result<()>; -} - -impl IpcWrite for T { - fn ipc_write_int(&mut self, val: i32) -> io::Result<()> { - self.write_all(bytes_of(&val)) - } - - fn ipc_write_string(&mut self, val: &str) -> io::Result<()> { - self.ipc_write_int(val.len() as i32)?; - self.write_all(val.as_bytes()) - } -} diff --git a/native/src/core/db.rs b/native/src/core/db.rs index 9c28423cdb995..3160d3e5309ca 100644 --- a/native/src/core/db.rs +++ b/native/src/core/db.rs @@ -1,10 +1,12 @@ #![allow(improper_ctypes, improper_ctypes_definitions)] -use crate::daemon::{IpcRead, IpcWrite, MagiskD, MAGISKD}; +use crate::daemon::{MagiskD, MAGISKD}; use crate::ffi::{ - open_and_init_db, sqlite3, sqlite3_errstr, DbEntryKey, DbSettings, DbStatement, DbValues, - MntNsMode, MultiuserMode, RootAccess, + open_and_init_db, sqlite3, sqlite3_errstr, DbEntryKey, DbStatement, DbValues, MntNsMode, }; +use crate::socket::{IpcRead, IpcWrite}; use base::{LoggedResult, ResultExt, Utf8CStr}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; use std::ffi::c_void; use std::fs::File; use std::io::{BufReader, BufWriter}; @@ -54,16 +56,33 @@ where } } -impl Default for RootAccess { - fn default() -> Self { - RootAccess::AppsAndAdb - } +#[derive(Default)] +pub struct DbSettings { + pub root_access: RootAccess, + pub multiuser_mode: MultiuserMode, + pub mnt_ns: MntNsMode, + pub boot_count: i32, + pub denylist: bool, + pub zygisk: bool, } -impl Default for MultiuserMode { - fn default() -> Self { - MultiuserMode::OwnerOnly - } +#[repr(i32)] +#[derive(Default, FromPrimitive)] +pub enum RootAccess { + Disabled, + AppsOnly, + AdbOnly, + #[default] + AppsAndAdb, +} + +#[repr(i32)] +#[derive(Default, FromPrimitive)] +pub enum MultiuserMode { + #[default] + OwnerOnly, + OwnerManaged, + User, } impl Default for MntNsMode { @@ -72,10 +91,6 @@ impl Default for MntNsMode { } } -pub fn get_default_db_settings() -> DbSettings { - DbSettings::default() -} - impl DbEntryKey { fn to_str(self) -> &'static str { match self { @@ -103,8 +118,10 @@ impl SqlTable for DbSettings { } } match key { - "root_access" => self.root_access = RootAccess { repr: value }, - "multiuser_mode" => self.multiuser_mode = MultiuserMode { repr: value }, + "root_access" => self.root_access = RootAccess::from_i32(value).unwrap_or_default(), + "multiuser_mode" => { + self.multiuser_mode = MultiuserMode::from_i32(value).unwrap_or_default() + } "mnt_ns" => self.mnt_ns = MntNsMode { repr: value }, "denylist" => self.denylist = value != 0, "zygisk" => self.zygisk = value != 0, @@ -229,8 +246,8 @@ impl MagiskD { pub fn get_db_setting(&self, key: DbEntryKey) -> i32 { // Get default values let mut val = match key { - DbEntryKey::RootAccess => RootAccess::default().repr, - DbEntryKey::SuMultiuserMode => MultiuserMode::default().repr, + DbEntryKey::RootAccess => RootAccess::default() as i32, + DbEntryKey::SuMultiuserMode => MultiuserMode::default() as i32, DbEntryKey::SuMntNs => MntNsMode::default().repr, DbEntryKey::DenylistConfig => 0, DbEntryKey::ZygiskConfig => self.is_emulator as i32, @@ -285,7 +302,7 @@ impl MagiskD { fn db_exec_for_client(&self, fd: OwnedFd) -> LoggedResult<()> { let mut file = File::from(fd); let mut reader = BufReader::new(&mut file); - let sql = reader.ipc_read_string()?; + let sql: String = reader.read_decodable()?; let mut writer = BufWriter::new(&mut file); let mut output_fn = |columns: &[String], values: &DbValues| { let mut out = "".to_string(); @@ -297,22 +314,14 @@ impl MagiskD { out.push('='); out.push_str(values.get_text(i as i32)); } - writer.ipc_write_string(&out).log().ok(); + writer.write_encodable(&out).log().ok(); }; self.db_exec_with_rows(&sql, &[], &mut output_fn); - writer.ipc_write_string("").log() + writer.write_encodable("").log() } } impl MagiskD { - pub fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool { - cfg.zygisk = self.is_emulator; - self.db_exec_with_rows("SELECT * FROM settings", &[], cfg) - .sql_result() - .log() - .is_ok() - } - pub fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool { self.set_db_setting(key, value).log().is_ok() } diff --git a/native/src/core/deny/utils.cpp b/native/src/core/deny/utils.cpp index 3db579564be16..64e175622a250 100644 --- a/native/src/core/deny/utils.cpp +++ b/native/src/core/deny/utils.cpp @@ -370,7 +370,7 @@ int enable_deny() { denylist_enforced = true; - if (!zygisk_enabled) { + if (!MagiskD::Get().zygisk_enabled()) { if (new_daemon_thread(&logcat)) { denylist_enforced = false; return DenyResponse::ERROR; @@ -385,7 +385,7 @@ int enable_deny() { } } - MagiskD().set_db_setting(DbEntryKey::DenylistConfig, true); + MagiskD::Get().set_db_setting(DbEntryKey::DenylistConfig, true); return DenyResponse::OK; } @@ -393,13 +393,13 @@ int disable_deny() { if (denylist_enforced.exchange(false)) { LOGI("* Disable DenyList\n"); } - MagiskD().set_db_setting(DbEntryKey::DenylistConfig, false); + MagiskD::Get().set_db_setting(DbEntryKey::DenylistConfig, false); return DenyResponse::OK; } void initialize_denylist() { if (!denylist_enforced) { - if (MagiskD().get_db_setting(DbEntryKey::DenylistConfig)) + if (MagiskD::Get().get_db_setting(DbEntryKey::DenylistConfig)) enable_deny(); } } @@ -429,3 +429,12 @@ bool is_deny_target(int uid, string_view process) { } return false; } + +void update_deny_flags(int uid, rust::Str process, uint32_t &flags) { + if (is_deny_target(uid, { process.begin(), process.end() })) { + flags |= +ZygiskStateFlags::ProcessOnDenyList; + } + if (denylist_enforced) { + flags |= +ZygiskStateFlags::DenyListEnforced; + } +} diff --git a/native/src/core/derive/Cargo.toml b/native/src/core/derive/Cargo.toml new file mode 100644 index 0000000000000..5e881c220a8f4 --- /dev/null +++ b/native/src/core/derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "derive" +version = "0.0.0" +edition = "2021" + +[lib] +path = "lib.rs" +proc-macro = true + +[dependencies] +syn = { workspace = true } +quote = { workspace = true } +proc-macro2 = { workspace = true } diff --git a/native/src/core/derive/decodable.rs b/native/src/core/derive/decodable.rs new file mode 100644 index 0000000000000..fd2d70398aa26 --- /dev/null +++ b/native/src/core/derive/decodable.rs @@ -0,0 +1,124 @@ +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned; +use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam}; + +pub(crate) fn derive_decodable(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident; + + // Add a bound `T: Decodable` to every type parameter T. + let mut generics = input.generics; + for param in &mut generics.params { + if let GenericParam::Type(ref mut type_param) = *param { + type_param + .bounds + .push(parse_quote!(crate::socket::Decodable)); + } + } + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let sum = gen_size_sum(&input.data); + let encode = gen_encode(&input.data); + let decode = gen_decode(&input.data); + + let expanded = quote! { + // The generated impl. + impl #impl_generics crate::socket::Encodable for #name #ty_generics #where_clause { + fn encoded_len(&self) -> usize { + #sum + } + fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> { + #encode + Ok(()) + } + } + impl #impl_generics crate::socket::Decodable for #name #ty_generics #where_clause { + fn decode(r: &mut impl std::io::Read) -> std::io::Result { + let val = #decode; + Ok(val) + } + } + }; + proc_macro::TokenStream::from(expanded) +} + +// Generate an expression to sum up the size of each field. +fn gen_size_sum(data: &Data) -> TokenStream { + match *data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + // Expands to an expression like + // + // 0 + self.x.encoded_len() + self.y.encoded_len() + self.z.encoded_len() + let recurse = fields.named.iter().map(|f| { + let name = &f.ident; + quote_spanned! { f.span() => + crate::socket::Encodable::encoded_len(&self.#name) + } + }); + quote! { + 0 #(+ #recurse)* + } + } + _ => unimplemented!(), + } + } + Data::Enum(_) | Data::Union(_) => unimplemented!(), + } +} + +// Generate an expression to encode each field. +fn gen_encode(data: &Data) -> TokenStream { + match *data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + // Expands to an expression like + // + // self.x.encode(w)?; self.y.encode(w)?; self.z.encode(w)?; + let recurse = fields.named.iter().map(|f| { + let name = &f.ident; + quote_spanned! { f.span() => + crate::socket::Encodable::encode(&self.#name, w)?; + } + }); + quote! { + #(#recurse)* + } + } + _ => unimplemented!(), + } + } + Data::Enum(_) | Data::Union(_) => unimplemented!(), + } +} + +// Generate an expression to decode each field. +fn gen_decode(data: &Data) -> TokenStream { + match *data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + // Expands to an expression like + // + // Self { x: Decodable::decode(r)?, y: Decodable::decode(r)?, } + let recurse = fields.named.iter().map(|f| { + let name = &f.ident; + quote_spanned! { f.span() => + #name: crate::socket::Decodable::decode(r)?, + } + }); + quote! { + Self { #(#recurse)* } + } + } + _ => unimplemented!(), + } + } + Data::Enum(_) | Data::Union(_) => unimplemented!(), + } +} diff --git a/native/src/core/derive/lib.rs b/native/src/core/derive/lib.rs new file mode 100644 index 0000000000000..6e12156e2023d --- /dev/null +++ b/native/src/core/derive/lib.rs @@ -0,0 +1,8 @@ +use proc_macro::TokenStream; + +mod decodable; + +#[proc_macro_derive(Decodable)] +pub fn derive_decodable(input: TokenStream) -> TokenStream { + decodable::derive_decodable(input) +} diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index 81f08ec1e7109..b6d90ff5303c1 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -7,7 +8,8 @@ #include #include -#include "socket.hpp" +#include + #include "../core-rs.hpp" #define AID_ROOT 0 @@ -26,21 +28,56 @@ enum class RespondCode : int { END }; -struct module_info { - std::string name; - int z32 = -1; -#if defined(__LP64__) - int z64 = -1; -#endif -}; +struct ModuleInfo; -extern bool zygisk_enabled; -extern std::vector *module_list; extern std::string native_bridge; -void reset_zygisk(bool restore); +// Daemon int connect_daemon(int req, bool create = false); +const char *get_magisk_tmp(); void unlock_blocks(); +bool setup_magisk_env(); +bool check_key_combo(); +void restore_zygisk_prop(); + +// Sockets +struct sock_cred : public ucred { + std::string context; +}; + +template requires(std::is_trivially_copyable_v) +T read_any(int fd) { + T val; + if (xxread(fd, &val, sizeof(val)) != sizeof(val)) + return -1; + return val; +} + +template requires(std::is_trivially_copyable_v) +void write_any(int fd, T val) { + if (fd < 0) return; + xwrite(fd, &val, sizeof(val)); +} + +bool get_client_cred(int fd, sock_cred *cred); +static inline int read_int(int fd) { return read_any(fd); } +static inline void write_int(int fd, int val) { write_any(fd, val); } +std::string read_string(int fd); +bool read_string(int fd, std::string &str); +void write_string(int fd, std::string_view str); + +template requires(std::is_trivially_copyable_v) +void write_vector(int fd, const std::vector &vec) { + write_int(fd, vec.size()); + xwrite(fd, vec.data(), vec.size() * sizeof(T)); +} + +template requires(std::is_trivially_copyable_v) +bool read_vector(int fd, std::vector &vec) { + int size = read_int(fd); + vec.resize(size); + return xread(fd, vec.data(), size * sizeof(T)) == size * sizeof(T); +} // Poll control using poll_callback = void(*)(pollfd*); @@ -54,20 +91,17 @@ void exec_task(std::function &&task); // Daemon handlers void denylist_handler(int client, const sock_cred *cred); -void su_daemon_handler(int client, const sock_cred *cred); -void zygisk_handler(int client, const sock_cred *cred); // Module stuffs -void handle_modules(); -void load_modules(); void disable_modules(); void remove_modules(); -void exec_module_scripts(const char *stage); // Scripting +void install_apk(rust::Utf8CStr apk); +void uninstall_pkg(rust::Utf8CStr pkg); +void exec_common_scripts(rust::Utf8CStr stage); +void exec_module_scripts(rust::Utf8CStr stage, const rust::Vec &module_list); void exec_script(const char *script); -void exec_common_scripts(const char *stage); -void exec_module_scripts(const char *stage, const std::vector &modules); void clear_pkg(const char *pkg, int user_id); [[noreturn]] void install_module(const char *file); @@ -78,3 +112,16 @@ void initialize_denylist(); void scan_deny_apps(); bool is_deny_target(int uid, std::string_view process); void revert_unmount(int pid = -1) noexcept; +void update_deny_flags(int uid, rust::Str process, uint32_t &flags); + +// MagiskSU +void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode); +void app_log(const SuAppRequest &info, SuPolicy policy, bool notify); +void app_notify(const SuAppRequest &info, SuPolicy policy); +int app_request(const SuAppRequest &info); + +// Rust bindings +static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); } +static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) { + return resolve_preinit_dir(base_dir.c_str()); +} diff --git a/native/src/core/include/daemon.hpp b/native/src/core/include/daemon.hpp deleted file mode 100644 index be73e4b009ab7..0000000000000 --- a/native/src/core/include/daemon.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -const char *get_magisk_tmp(); -void install_apk(rust::Utf8CStr apk); -void uninstall_pkg(rust::Utf8CStr pkg); - -// Rust bindings -static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); } -static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) { - return resolve_preinit_dir(base_dir.c_str()); -} diff --git a/native/src/core/include/socket.hpp b/native/src/core/include/socket.hpp deleted file mode 100644 index 973991a6b237d..0000000000000 --- a/native/src/core/include/socket.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -struct sock_cred : public ucred { - std::string context; -}; - -bool get_client_cred(int fd, sock_cred *cred); -std::vector recv_fds(int sockfd); -int recv_fd(int sockfd); -int send_fds(int sockfd, const int *fds, int cnt); -int send_fd(int sockfd, int fd); -int read_int(int fd); -int read_int_be(int fd); -void write_int(int fd, int val); -void write_int_be(int fd, int val); -std::string read_string(int fd); -bool read_string(int fd, std::string &str); -void write_string(int fd, std::string_view str); - -template requires(std::is_trivially_copyable_v) -void write_vector(int fd, const std::vector &vec) { - write_int(fd, static_cast(vec.size())); - xwrite(fd, vec.data(), vec.size() * sizeof(T)); -} - -template requires(std::is_trivially_copyable_v) -bool read_vector(int fd, std::vector &vec) { - int size = read_int(fd); - if (size == -1) return false; - vec.resize(size); - return xread(fd, vec.data(), size * sizeof(T)) == size * sizeof(T); -} diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 589752e962804..ce6cda9e72ba3 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -2,15 +2,25 @@ #![feature(try_blocks)] #![feature(let_chains)] #![feature(fn_traits)] +#![feature(unix_socket_ancillary_data)] +#![feature(unix_socket_peek)] #![allow(clippy::missing_safety_doc)] -use base::Utf8CStr; -use daemon::{daemon_entry, get_magiskd, MagiskD}; -use db::get_default_db_settings; +use crate::ffi::SuRequest; +use crate::socket::Encodable; +use base::{libc, Utf8CStr}; +use cxx::{type_id, ExternType}; +use daemon::{daemon_entry, MagiskD}; +use derive::Decodable; use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging}; -use mount::{clean_mounts, find_preinit_device, revert_unmount, setup_mounts}; +use mount::{clean_mounts, find_preinit_device, revert_unmount}; use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop}; -use su::get_default_root_settings; +use socket::{recv_fd, recv_fds, send_fd, send_fds}; +use std::fs::File; +use std::mem::ManuallyDrop; +use std::ops::DerefMut; +use std::os::fd::FromRawFd; +use zygisk::zygisk_should_load_module; #[path = "../include/consts.rs"] mod consts; @@ -20,8 +30,11 @@ mod logging; mod mount; mod package; mod resetprop; +mod socket; mod su; +mod zygisk; +#[allow(clippy::needless_lifetimes)] #[cxx::bridge] pub mod ffi { #[repr(i32)] @@ -49,6 +62,72 @@ pub mod ffi { END, } + enum DbEntryKey { + RootAccess, + SuMultiuserMode, + SuMntNs, + DenylistConfig, + ZygiskConfig, + BootloopCount, + SuManager, + } + + #[repr(i32)] + enum MntNsMode { + Global, + Requester, + Isolate, + } + + #[repr(i32)] + enum SuPolicy { + Query, + Deny, + Allow, + } + + struct ModuleInfo { + name: String, + z32: i32, + z64: i32, + } + + #[repr(i32)] + enum ZygiskRequest { + GetInfo, + ConnectCompanion, + GetModDir, + } + + #[repr(u32)] + enum ZygiskStateFlags { + ProcessGrantedRoot = 0x00000001, + ProcessOnDenyList = 0x00000002, + DenyListEnforced = 0x40000000, + ProcessIsMagiskApp = 0x80000000, + } + + #[derive(Decodable)] + struct SuRequest { + target_uid: i32, + target_pid: i32, + login: bool, + keep_env: bool, + shell: String, + command: String, + context: String, + gids: Vec, + } + + struct SuAppRequest<'a> { + uid: i32, + pid: i32, + eval_uid: i32, + mgr_pkg: &'a str, + mgr_uid: i32, + request: &'a SuRequest, + } + extern "C++" { include!("include/resetprop.hpp"); @@ -67,152 +146,122 @@ pub mod ffi { #[namespace = "rust"] #[cxx_name = "Utf8CStr"] type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>; + #[cxx_name = "ucred"] + type UCred = crate::UCred; - include!("include/daemon.hpp"); + include!("include/core.hpp"); #[cxx_name = "get_magisk_tmp_rs"] fn get_magisk_tmp() -> Utf8CStrRef<'static>; #[cxx_name = "resolve_preinit_dir_rs"] fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String; + fn setup_magisk_env() -> bool; + fn check_key_combo() -> bool; + fn disable_modules(); + fn exec_common_scripts(stage: Utf8CStrRef); + fn exec_module_scripts(state: Utf8CStrRef, modules: &Vec); fn install_apk(apk: Utf8CStrRef); fn uninstall_pkg(apk: Utf8CStrRef); - + fn update_deny_flags(uid: i32, process: &str, flags: &mut u32); + fn initialize_denylist(); + fn restore_zygisk_prop(); fn switch_mnt_ns(pid: i32) -> i32; - } + fn app_request(req: &SuAppRequest) -> i32; + fn app_notify(req: &SuAppRequest, policy: SuPolicy); + fn app_log(req: &SuAppRequest, policy: SuPolicy, notify: bool); + fn exec_root_shell(client: i32, pid: i32, req: &mut SuRequest, mode: MntNsMode); - enum DbEntryKey { - RootAccess, - SuMultiuserMode, - SuMntNs, - DenylistConfig, - ZygiskConfig, - BootloopCount, - SuManager, - } - - #[repr(i32)] - enum RootAccess { - Disabled, - AppsOnly, - AdbOnly, - AppsAndAdb, - } - - #[repr(i32)] - enum MultiuserMode { - OwnerOnly, - OwnerManaged, - User, - } - - #[repr(i32)] - enum MntNsMode { - Global, - Requester, - Isolate, - } - - #[derive(Default)] - struct DbSettings { - root_access: RootAccess, - multiuser_mode: MultiuserMode, - mnt_ns: MntNsMode, - boot_count: i32, - denylist: bool, - zygisk: bool, - } - - #[repr(i32)] - enum SuPolicy { - Query, - Deny, - Allow, - } - - struct RootSettings { - policy: SuPolicy, - log: bool, - notify: bool, - } - - unsafe extern "C++" { include!("include/sqlite.hpp"); - fn sqlite3_errstr(code: i32) -> *const c_char; - type sqlite3; - fn open_and_init_db() -> *mut sqlite3; - type DbValues; type DbStatement; + fn sqlite3_errstr(code: i32) -> *const c_char; + fn open_and_init_db() -> *mut sqlite3; fn get_int(self: &DbValues, index: i32) -> i32; #[cxx_name = "get_str"] fn get_text(self: &DbValues, index: i32) -> &str; - fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32; fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32; } extern "Rust" { - fn rust_test_entry(); fn android_logging(); fn zygisk_logging(); fn zygisk_close_logd(); fn zygisk_get_logd() -> i32; fn setup_logfile(); - fn setup_mounts(); fn clean_mounts(); fn find_preinit_device() -> String; fn revert_unmount(pid: i32); + fn zygisk_should_load_module(flags: u32) -> bool; unsafe fn persist_get_prop(name: Utf8CStrRef, prop_cb: Pin<&mut PropCb>); unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>); unsafe fn persist_delete_prop(name: Utf8CStrRef) -> bool; unsafe fn persist_set_prop(name: Utf8CStrRef, value: Utf8CStrRef) -> bool; + fn send_fd(socket: i32, fd: i32) -> bool; + fn send_fds(socket: i32, fds: &[i32]) -> bool; + fn recv_fd(socket: i32) -> i32; + fn recv_fds(socket: i32) -> Vec; + unsafe fn write_to_fd(self: &SuRequest, fd: i32); #[namespace = "rust"] fn daemon_entry(); } + // Default constructors + extern "Rust" { + #[Self = SuRequest] + #[cxx_name = "New"] + fn default() -> SuRequest; + } + // FFI for MagiskD extern "Rust" { type MagiskD; fn is_recovery(&self) -> bool; fn sdk_int(&self) -> i32; + fn zygisk_enabled(&self) -> bool; fn boot_stage_handler(&self, client: i32, code: i32); - fn preserve_stub_apk(&self); + fn zygisk_handler(&self, client: i32); + fn zygisk_reset(&self, restore: bool); fn prune_su_access(&self); - fn uid_granted_root(&self, mut uid: i32) -> bool; + fn su_daemon_handler(&self, client: i32, cred: &UCred); #[cxx_name = "get_manager"] unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32; - #[cxx_name = "get_db_settings"] - fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool; fn get_db_setting(&self, key: DbEntryKey) -> i32; #[cxx_name = "set_db_setting"] fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool; #[cxx_name = "db_exec"] fn db_exec_for_cxx(&self, client_fd: i32); - #[cxx_name = "get_root_settings"] - fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool; - #[cxx_name = "DbSettings"] - fn get_default_db_settings() -> DbSettings; - #[cxx_name = "RootSettings"] - fn get_default_root_settings() -> RootSettings; - #[cxx_name = "MagiskD"] - fn get_magiskd() -> &'static MagiskD; + #[Self = MagiskD] + #[cxx_name = "Get"] + fn get() -> &'static MagiskD; } unsafe extern "C++" { #[allow(dead_code)] fn reboot(self: &MagiskD); - fn post_fs_data(self: &MagiskD) -> bool; - fn late_start(self: &MagiskD); - fn boot_complete(self: &MagiskD); + fn handle_modules(self: &MagiskD) -> Vec; } } -fn rust_test_entry() {} +#[repr(transparent)] +pub struct UCred(pub libc::ucred); + +unsafe impl ExternType for UCred { + type Id = type_id!("ucred"); + type Kind = cxx::kind::Trivial; +} + +impl SuRequest { + unsafe fn write_to_fd(&self, fd: i32) { + let mut w = ManuallyDrop::new(File::from_raw_fd(fd)); + self.encode(w.deref_mut()).ok(); + } +} pub fn get_prop(name: &Utf8CStr, persist: bool) -> String { unsafe { ffi::get_prop_rs(name.as_ptr(), persist) } diff --git a/native/src/core/logging.rs b/native/src/core/logging.rs index e6975c467518e..7f40a20c33613 100644 --- a/native/src/core/logging.rs +++ b/native/src/core/logging.rs @@ -6,8 +6,8 @@ use base::libc::{ timespec, tm, O_CLOEXEC, O_RDWR, O_WRONLY, PIPE_BUF, SIGPIPE, SIG_BLOCK, SIG_SETMASK, }; use base::{ - const_format::concatcp, libc, raw_cstr, FsPathBuf, LogLevel, Logger, ReadExt, SharedFd, - Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrWrite, LOGGER, + const_format::concatcp, libc, raw_cstr, FsPathBuf, LogLevel, Logger, ReadExt, Utf8CStr, + Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrWrite, WriteExt, LOGGER, }; use bytemuck::{bytes_of, write_zeroes, Pod, Zeroable}; use num_derive::{FromPrimitive, ToPrimitive}; @@ -18,10 +18,10 @@ use std::fmt::Write as FmtWrite; use std::fs::File; use std::io::{IoSlice, Read, Write}; use std::mem::ManuallyDrop; -use std::os::fd::{FromRawFd, OwnedFd, RawFd}; +use std::os::fd::{FromRawFd, RawFd}; use std::ptr::null_mut; use std::sync::atomic::{AtomicI32, Ordering}; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use std::time::{SystemTime, UNIX_EPOCH}; use std::{fs, io}; @@ -114,7 +114,7 @@ struct LogMeta { const MAX_MSG_LEN: usize = PIPE_BUF - size_of::(); -fn write_log_to_pipe(logd: &mut File, prio: i32, msg: &Utf8CStr) -> io::Result { +fn write_log_to_pipe(mut logd: &File, prio: i32, msg: &Utf8CStr) -> io::Result { // Truncate message if needed let len = min(MAX_MSG_LEN, msg.len()); let msg = &msg.as_bytes()[..len]; @@ -131,28 +131,27 @@ fn write_log_to_pipe(logd: &mut File, prio: i32, msg: &Utf8CStr) -> io::Result = Mutex::new(SharedFd::new()); +static MAGISK_LOGD_FD: Mutex>> = Mutex::new(None); -fn with_logd_fd io::Result<()>>(f: F) { +fn with_logd_fd io::Result>(f: F) { let fd = MAGISK_LOGD_FD.lock().unwrap().clone(); - // SAFETY: writing less than PIPE_BUF bytes is guaranteed to be atomic on Linux - if let Some(mut logd) = unsafe { fd.as_file() } { - if f(&mut logd).is_err() { + if let Some(logd) = fd { + if f(&logd).is_err() { // If any error occurs, shut down the logd pipe - *MAGISK_LOGD_FD.lock().unwrap() = SharedFd::default(); + *MAGISK_LOGD_FD.lock().unwrap() = None; } } } fn magisk_log_to_pipe(prio: i32, msg: &Utf8CStr) { - with_logd_fd(|logd| write_log_to_pipe(logd, prio, msg).map(|_| ())); + with_logd_fd(|logd| write_log_to_pipe(logd, prio, msg)); } // SAFETY: zygisk client code runs single threaded, so no need to prevent data race @@ -217,8 +216,8 @@ fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) { pthread_sigmask(SIG_BLOCK, &mask, &mut orig_mask); } - let mut logd = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }); - let result = write_log_to_pipe(&mut logd, prio, msg); + let logd = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }); + let result = write_log_to_pipe(&logd, prio, msg); // Consume SIGPIPE if exists, then restore mask unsafe { @@ -347,19 +346,19 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void { writer_loop(arg as RawFd).ok(); // If any error occurs, shut down the logd pipe - *MAGISK_LOGD_FD.lock().unwrap() = SharedFd::default(); + *MAGISK_LOGD_FD.lock().unwrap() = None; null_mut() } pub fn setup_logfile() { - with_logd_fd(|logd| { + with_logd_fd(|mut logd| { let meta = LogMeta { prio: -1, len: 0, pid: 0, tid: 0, }; - logd.write_all(bytes_of(&meta)) + (&mut logd).write_pod(&meta) }); } @@ -374,7 +373,7 @@ pub fn start_log_daemon() { libc::chown(path.as_ptr(), 0, 0); let read = libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC); let write = libc::open(path.as_ptr(), O_WRONLY | O_CLOEXEC); - *MAGISK_LOGD_FD.lock().unwrap() = SharedFd::from(OwnedFd::from_raw_fd(write)); + *MAGISK_LOGD_FD.lock().unwrap() = Some(Arc::new(File::from_raw_fd(write))); new_daemon_thread(logfile_writer, read as *mut c_void); } } diff --git a/native/src/core/module.cpp b/native/src/core/module.cpp index 34f886aaf876e..7eca0751a7107 100644 --- a/native/src/core/module.cpp +++ b/native/src/core/module.cpp @@ -86,7 +86,7 @@ bool dir_node::prepare() { return upgrade_to_tmpfs; } -void dir_node::collect_module_files(const char *module, int dfd) { +void dir_node::collect_module_files(std::string_view module, int dfd) { auto dir = xopen_dir(xopenat(dfd, name().data(), O_RDONLY | O_CLOEXEC)); if (!dir) return; @@ -149,7 +149,9 @@ void module_node::mount() { VLOGD("delete", "null", node_path().data()); return; } - std::string path = module + (parent()->root()->prefix + node_path()); + std::string path{module.begin(), module.end()}; + path += parent()->root()->prefix; + path += node_path(); string mnt_src = module_mnt + path; { string src = MODULEROOT "/" + path; @@ -267,9 +269,7 @@ static void inject_zygisk_libs(root_node *system) { } } -vector *module_list; - -void load_modules() { +static void load_modules(bool zygisk_enabled, const rust::Vec &module_list) { node_entry::module_mnt = get_magisk_tmp() + "/"s MODULEMNT "/"; auto root = make_unique(""); @@ -278,14 +278,14 @@ void load_modules() { char buf[4096]; LOGI("* Loading modules\n"); - for (const auto &m : *module_list) { - const char *module = m.name.data(); - char *b = buf + ssprintf(buf, sizeof(buf), "%s/" MODULEMNT "/%s/", get_magisk_tmp(), module); + for (const auto &m : module_list) { + char *b = buf + ssprintf(buf, sizeof(buf), "%s/" MODULEMNT "/%.*s/", + get_magisk_tmp(), (int) m.name.size(), m.name.data()); // Read props strcpy(b, "system.prop"); if (access(buf, F_OK) == 0) { - LOGI("%s: loading [system.prop]\n", module); + LOGI("%.*s: loading [system.prop]\n", (int) m.name.size(), m.name.data()); // Do NOT go through property service as it could cause boot lock load_prop_file(buf, true); } @@ -300,10 +300,10 @@ void load_modules() { if (access(buf, F_OK) != 0) continue; - LOGI("%s: loading mount files\n", module); + LOGI("%.*s: loading mount files\n", (int) m.name.size(), m.name.data()); b[-1] = '\0'; int fd = xopen(buf, O_RDONLY | O_CLOEXEC); - system->collect_module_files(module, fd); + system->collect_module_files({ m.name.begin(), m.name.end() }, fd); close(fd); } if (get_magisk_tmp() != "/sbin"sv || !str_contains(getenv("PATH") ?: "", "/sbin")) { @@ -392,8 +392,9 @@ static void foreach_module(Func fn) { } } -static void collect_modules(bool open_zygisk) { - foreach_module([=](int dfd, dirent *entry, int modfd) { +static rust::Vec collect_modules(bool zygisk_enabled, bool open_zygisk) { + rust::Vec modules; + foreach_module([&](int dfd, dirent *entry, int modfd) { if (faccessat(modfd, "remove", F_OK, 0) == 0) { LOGI("%s: remove\n", entry->d_name); auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh"; @@ -407,7 +408,7 @@ static void collect_modules(bool open_zygisk) { if (faccessat(modfd, "disable", F_OK, 0) == 0) return; - module_info info; + ModuleInfo info{{}, -1, -1}; if (zygisk_enabled) { // Riru and its modules are not compatible with zygisk if (entry->d_name == "riru-core"sv || faccessat(modfd, "riru", F_OK, 0) == 0) { @@ -417,11 +418,13 @@ static void collect_modules(bool open_zygisk) { if (open_zygisk) { #if defined(__arm__) info.z32 = openat(modfd, "zygisk/armeabi-v7a.so", O_RDONLY | O_CLOEXEC); + info.z64 = -1; #elif defined(__aarch64__) info.z32 = openat(modfd, "zygisk/armeabi-v7a.so", O_RDONLY | O_CLOEXEC); info.z64 = openat(modfd, "zygisk/arm64-v8a.so", O_RDONLY | O_CLOEXEC); #elif defined(__i386__) info.z32 = openat(modfd, "zygisk/x86.so", O_RDONLY | O_CLOEXEC); + info.z64 = -1; #elif defined(__x86_64__) info.z32 = openat(modfd, "zygisk/x86.so", O_RDONLY | O_CLOEXEC); info.z64 = openat(modfd, "zygisk/x86_64.so", O_RDONLY | O_CLOEXEC); @@ -441,7 +444,7 @@ static void collect_modules(bool open_zygisk) { } } info.name = entry->d_name; - module_list->push_back(info); + modules.push_back(std::move(info)); }); if (zygisk_enabled) { bool use_memfd = true; @@ -461,23 +464,22 @@ static void collect_modules(bool open_zygisk) { } return fd; }; - std::for_each(module_list->begin(), module_list->end(), [&](module_info &info) { + std::for_each(modules.begin(),modules.end(), [&](ModuleInfo &info) { info.z32 = convert_to_memfd(info.z32); -#if defined(__LP64__) info.z64 = convert_to_memfd(info.z64); -#endif }); } + return modules; } -void handle_modules() { +rust::Vec MagiskD::handle_modules() const noexcept { + bool zygisk = zygisk_enabled(); prepare_modules(); - collect_modules(false); - exec_module_scripts("post-fs-data"); - + exec_module_scripts("post-fs-data", collect_modules(zygisk, false)); // Recollect modules (module scripts could remove itself) - module_list->clear(); - collect_modules(true); + auto list = collect_modules(zygisk, true); + load_modules(zygisk, list); + return list; } static int check_rules_dir(char *buf, size_t sz) { @@ -517,10 +519,3 @@ void remove_modules() { }); rm_rf(MODULEROOT); } - -void exec_module_scripts(const char *stage) { - vector module_names; - std::transform(module_list->begin(), module_list->end(), std::back_inserter(module_names), - [](const module_info &info) -> string_view { return info.name; }); - exec_module_scripts(stage, module_names); -} diff --git a/native/src/core/mount.rs b/native/src/core/mount.rs index 344b6e65869f4..e4472930da96e 100644 --- a/native/src/core/mount.rs +++ b/native/src/core/mount.rs @@ -106,11 +106,7 @@ pub fn clean_mounts() { let module_mnt = FsPathBuf::new(&mut buf).join(magisk_tmp).join(MODULEMNT); let _: LoggedResult<()> = try { unsafe { - libc::umount2( - module_mnt.as_ptr(), - libc::MNT_DETACH, - ) - .as_os_err()?; + libc::umount2(module_mnt.as_ptr(), libc::MNT_DETACH).as_os_err()?; } }; @@ -125,11 +121,7 @@ pub fn clean_mounts() { ptr::null(), ) .as_os_err()?; - libc::umount2( - worker_dir.as_ptr(), - libc::MNT_DETACH, - ) - .as_os_err()?; + libc::umount2(worker_dir.as_ptr(), libc::MNT_DETACH).as_os_err()?; } }; } diff --git a/native/src/core/node.hpp b/native/src/core/node.hpp index 5324ef38f5d80..744df261c0b00 100644 --- a/native/src/core/node.hpp +++ b/native/src/core/node.hpp @@ -105,7 +105,7 @@ class dir_node : public node_entry { **************/ // Traverse through module directories to generate a tree of module files - void collect_module_files(const char *module, int dfd); + void collect_module_files(std::string_view module, int dfd); // Traverse through the real filesystem and prepare the tree for magic mount. // Return true to indicate that this node needs to be upgraded to tmpfs_node. @@ -279,16 +279,16 @@ class inter_node : public dir_node { class module_node : public node_entry { public: - module_node(const char *module, dirent *entry) + module_node(std::string_view module, dirent *entry) : node_entry(entry->d_name, entry->d_type, this), module(module) {} - module_node(node_entry *node, const char *module) : node_entry(this), module(module) { + module_node(node_entry *node, std::string_view module) : node_entry(this), module(module) { node_entry::consume(node); } void mount() override; private: - const char *module; + std::string_view module; }; // Don't create tmpfs_node before prepare diff --git a/native/src/core/package.rs b/native/src/core/package.rs index 461d2a393c008..c1b5c9f36714d 100644 --- a/native/src/core/package.rs +++ b/native/src/core/package.rs @@ -1,5 +1,5 @@ use crate::consts::{APP_PACKAGE_NAME, MAGISK_VER_CODE}; -use crate::daemon::{to_app_id, MagiskD, AID_USER_OFFSET}; +use crate::daemon::{to_app_id, MagiskD, AID_APP_END, AID_APP_START, AID_USER_OFFSET}; use crate::ffi::{get_magisk_tmp, install_apk, uninstall_pkg, DbEntryKey}; use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY}; use base::WalkResult::{Abort, Continue, Skip}; @@ -7,6 +7,7 @@ use base::{ cstr, error, fd_get_attr, open_fd, warn, BufReadExt, Directory, FsPath, FsPathBuf, LoggedResult, ReadExt, ResultExt, Utf8CStrBuf, Utf8CStrBufArr, }; +use bit_set::BitSet; use cxx::CxxString; use std::collections::BTreeMap; use std::fs::File; @@ -51,7 +52,7 @@ macro_rules! bad_apk { * within the APK v2 signature block. */ fn read_certificate(apk: &mut File, version: i32) -> Vec { - fn inner(apk: &mut File, version: i32) -> io::Result> { + let res: io::Result> = try { let mut u32_val = 0u32; let mut u64_val = 0u64; @@ -70,7 +71,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec { } } if i == 0xffff { - return Err(bad_apk!("invalid APK format")); + Err(bad_apk!("invalid APK format"))?; } } @@ -97,7 +98,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec { } }); if version > apk_ver { - return Err(bad_apk!("APK version too low")); + Err(bad_apk!("APK version too low"))?; } } @@ -107,7 +108,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec { let mut magic = [0u8; 16]; apk.read_exact(&mut magic)?; if magic != APK_SIGNING_BLOCK_MAGIC { - return Err(bad_apk!("invalid signing block magic")); + Err(bad_apk!("invalid signing block magic"))?; } let mut signing_blk_sz = 0u64; apk.seek(SeekFrom::Current( @@ -115,14 +116,14 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec { ))?; apk.read_pod(&mut signing_blk_sz)?; if signing_blk_sz != u64_val { - return Err(bad_apk!("invalid signing block size")); + Err(bad_apk!("invalid signing block size"))?; } // Finally, we are now at the beginning of the id-value pair sequence loop { apk.read_pod(&mut u64_val)?; // id-value pair length if u64_val == signing_blk_sz { - break; + Err(bad_apk!("cannot find certificate"))?; } let mut id = 0u32; @@ -139,7 +140,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec { let mut cert = vec![0; u32_val as usize]; apk.read_exact(cert.as_mut())?; - return Ok(cert); + break cert; } else { // Skip this id-value pair apk.seek(SeekFrom::Current( @@ -147,10 +148,8 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec { ))?; } } - - Err(bad_apk!("cannot find certificate")) - } - inner(apk, version).log().unwrap_or(vec![]) + }; + res.log().unwrap_or(vec![]) } fn find_apk_path(pkg: &str, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { @@ -473,6 +472,23 @@ impl MagiskD { apk.remove().log().ok(); } + pub fn get_manager_uid(&self, user: i32) -> i32 { + let mut info = self.manager_info.lock().unwrap(); + let (uid, _) = info.get_manager(self, user, false); + uid + } + + pub fn get_manager(&self, user: i32, install: bool) -> (i32, String) { + let mut info = self.manager_info.lock().unwrap(); + let (uid, pkg) = info.get_manager(self, user, install); + (uid, pkg.to_string()) + } + + pub fn ensure_manager(&self) { + let mut info = self.manager_info.lock().unwrap(); + let _ = info.get_manager(self, 0, true); + } + pub unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32 { let mut info = self.manager_info.lock().unwrap(); let (uid, pkg) = info.get_manager(self, user, install); @@ -483,4 +499,39 @@ impl MagiskD { } uid } + + // app_id = app_no + AID_APP_START + // app_no range: [0, 9999] + pub fn get_app_no_list(&self) -> BitSet { + let mut list = BitSet::new(); + let _: LoggedResult<()> = try { + let mut app_data_dir = Directory::open(self.app_data_dir())?; + // For each user + loop { + let entry = match app_data_dir.read()? { + None => break, + Some(e) => e, + }; + let mut user_dir = match entry.open_as_dir() { + Err(_) => continue, + Ok(dir) => dir, + }; + // For each package + loop { + match user_dir.read()? { + None => break, + Some(e) => { + let attr = e.get_attr()?; + let app_id = to_app_id(attr.st.st_uid as i32); + if (AID_APP_START..=AID_APP_END).contains(&app_id) { + let app_no = app_id - AID_APP_START; + list.insert(app_no as usize); + } + } + } + } + } + }; + list + } } diff --git a/native/src/core/resetprop/persist.rs b/native/src/core/resetprop/persist.rs index 51d4e61d29b08..1eff1b93f22a8 100644 --- a/native/src/core/resetprop/persist.rs +++ b/native/src/core/resetprop/persist.rs @@ -141,8 +141,8 @@ fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> { Ok(()) } -pub unsafe fn persist_get_prop(name: &Utf8CStr, prop_cb: Pin<&mut PropCb>) { - fn inner(name: &Utf8CStr, mut prop_cb: Pin<&mut PropCb>) -> LoggedResult<()> { +pub fn persist_get_prop(name: &Utf8CStr, mut prop_cb: Pin<&mut PropCb>) { + let res: LoggedResult<()> = try { if check_proto() { let mut props = proto_read_props()?; let prop = props.find(name)?; @@ -158,13 +158,12 @@ pub unsafe fn persist_get_prop(name: &Utf8CStr, prop_cb: Pin<&mut PropCb>) { prop_cb.exec(name, Utf8CStr::from_string(&mut value)); debug!("resetprop: found prop [{}] = [{}]", name, value); } - Ok(()) - } - inner(name, prop_cb).ok(); + }; + res.ok(); } -pub unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>) { - fn inner(mut prop_cb: Pin<&mut PropCb>) -> LoggedResult<()> { +pub fn persist_get_props(mut prop_cb: Pin<&mut PropCb>) { + let res: LoggedResult<()> = try { if check_proto() { let mut props = proto_read_props()?; props.iter_mut().for_each(|prop| { @@ -190,27 +189,26 @@ pub unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>) { Ok(WalkResult::Skip) })?; } - Ok(()) - } - inner(prop_cb).ok(); + }; + res.ok(); } -pub unsafe fn persist_delete_prop(name: &Utf8CStr) -> bool { - fn inner(name: &Utf8CStr) -> LoggedResult<()> { +pub fn persist_delete_prop(name: &Utf8CStr) -> bool { + let res: LoggedResult<()> = try { if check_proto() { let mut props = proto_read_props()?; let idx = props.find_index(name).silent()?; props.remove(idx); - proto_write_props(&props) + proto_write_props(&props)?; } else { - file_set_prop(name, None) + file_set_prop(name, None)?; } - } - inner(name).is_ok() + }; + res.is_ok() } -pub unsafe fn persist_set_prop(name: &Utf8CStr, value: &Utf8CStr) -> bool { - unsafe fn inner(name: &Utf8CStr, value: &Utf8CStr) -> LoggedResult<()> { +pub fn persist_set_prop(name: &Utf8CStr, value: &Utf8CStr) -> bool { + let res: LoggedResult<()> = try { if check_proto() { let mut props = proto_read_props()?; match props.find_index(name) { @@ -223,10 +221,10 @@ pub unsafe fn persist_set_prop(name: &Utf8CStr, value: &Utf8CStr) -> bool { }, ), } - proto_write_props(&props) + proto_write_props(&props)?; } else { - file_set_prop(name, Some(value)) + file_set_prop(name, Some(value))?; } - } - inner(name, value).is_ok() + }; + res.is_ok() } diff --git a/native/src/core/scripting.cpp b/native/src/core/scripting.cpp index d2ffd8ce9c14a..06101eb0bdd59 100644 --- a/native/src/core/scripting.cpp +++ b/native/src/core/scripting.cpp @@ -26,7 +26,7 @@ static void set_script_env() { char new_path[4096]; ssprintf(new_path, sizeof(new_path), "%s:%s", getenv("PATH"), get_magisk_tmp()); setenv("PATH", new_path, 1); - if (zygisk_enabled) + if (MagiskD::Get().zygisk_enabled()) setenv("ZYGISK_ENABLED", "1", 1); }; @@ -75,10 +75,10 @@ if (pfs) { \ exit(0); \ } -void exec_common_scripts(const char *stage) { - LOGI("* Running %s.d scripts\n", stage); +void exec_common_scripts(rust::Utf8CStr stage) { + LOGI("* Running %s.d scripts\n", stage.c_str()); char path[4096]; - char *name = path + sprintf(path, SECURE_DIR "/%s.d", stage); + char *name = path + sprintf(path, SECURE_DIR "/%s.d", stage.c_str()); auto dir = xopen_dir(path); if (!dir) return; @@ -97,7 +97,7 @@ void exec_common_scripts(const char *stage) { if (entry->d_type == DT_REG) { if (faccessat(dfd, entry->d_name, X_OK, 0) != 0) continue; - LOGI("%s.d: exec [%s]\n", stage, entry->d_name); + LOGI("%s.d: exec [%s]\n", stage.c_str(), entry->d_name); strcpy(name, entry->d_name); exec_t exec { .pre_exec = set_script_env, @@ -117,12 +117,12 @@ static bool operator>(const timespec &a, const timespec &b) { return a.tv_nsec > b.tv_nsec; } -void exec_module_scripts(const char *stage, const vector &modules) { - LOGI("* Running module %s scripts\n", stage); - if (modules.empty()) +void exec_module_scripts(rust::Utf8CStr stage, const rust::Vec &module_list) { + LOGI("* Running module %s scripts\n", stage.c_str()); + if (module_list.empty()) return; - bool pfs = stage == "post-fs-data"sv; + bool pfs = (string_view) stage == "post-fs-data"; if (pfs) { timespec now{}; clock_gettime(CLOCK_MONOTONIC, &now); @@ -134,12 +134,11 @@ void exec_module_scripts(const char *stage, const vector &modules) PFS_SETUP() char path[4096]; - for (auto &m : modules) { - const char *module = m.data(); - sprintf(path, MODULEROOT "/%s/%s.sh", module, stage); + for (auto &m : module_list) { + sprintf(path, MODULEROOT "/%.*s/%s.sh", (int) m.name.size(), m.name.data(), stage.c_str()); if (access(path, F_OK) == -1) continue; - LOGI("%s: exec [%s.sh]\n", module, stage); + LOGI("%.*s: exec [%s.sh]\n", (int) m.name.size(), m.name.data(), stage.c_str()); exec_t exec { .pre_exec = set_script_env, .fork = pfs ? xfork : fork_dont_care diff --git a/native/src/core/socket.cpp b/native/src/core/socket.cpp deleted file mode 100644 index a499b10a79737..0000000000000 --- a/native/src/core/socket.cpp +++ /dev/null @@ -1,188 +0,0 @@ -#include -#include - -#include -#include - -using namespace std; - -bool get_client_cred(int fd, sock_cred *cred) { - socklen_t len = sizeof(ucred); - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len) != 0) - return false; - char buf[4096]; - len = sizeof(buf); - if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &len) != 0) - len = 0; - buf[len] = '\0'; - cred->context = buf; - return true; -} - -static int send_fds(int sockfd, void *cmsgbuf, size_t bufsz, const int *fds, int cnt) { - iovec iov = { - .iov_base = &cnt, - .iov_len = sizeof(cnt), - }; - msghdr msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - }; - - if (cnt) { - msg.msg_control = cmsgbuf; - msg.msg_controllen = bufsz; - cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_len = CMSG_LEN(sizeof(int) * cnt); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - - memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * cnt); - } - - return xsendmsg(sockfd, &msg, 0); -} - -int send_fds(int sockfd, const int *fds, int cnt) { - if (cnt == 0) { - return send_fds(sockfd, nullptr, 0, nullptr, 0); - } - vector cmsgbuf; - cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt)); - return send_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), fds, cnt); -} - -int send_fd(int sockfd, int fd) { - if (fd < 0) { - return send_fds(sockfd, nullptr, 0, nullptr, 0); - } - char cmsgbuf[CMSG_SPACE(sizeof(int))]; - return send_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), &fd, 1); -} - -static void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) { - iovec iov = { - .iov_base = &cnt, - .iov_len = sizeof(cnt), - }; - msghdr msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = cmsgbuf, - .msg_controllen = bufsz - }; - - xrecvmsg(sockfd, &msg, MSG_WAITALL); - if (msg.msg_controllen != bufsz) { - LOGE("recv_fd: msg_flags = %d, msg_controllen(%zu) != %zu\n", - msg.msg_flags, msg.msg_controllen, bufsz); - return nullptr; - } - - cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); - if (cmsg == nullptr) { - LOGE("recv_fd: cmsg == nullptr\n"); - return nullptr; - } - if (cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt)) { - LOGE("recv_fd: cmsg_len(%zu) != %zu\n", cmsg->cmsg_len, CMSG_LEN(sizeof(int) * cnt)); - return nullptr; - } - if (cmsg->cmsg_level != SOL_SOCKET) { - LOGE("recv_fd: cmsg_level != SOL_SOCKET\n"); - return nullptr; - } - if (cmsg->cmsg_type != SCM_RIGHTS) { - LOGE("recv_fd: cmsg_type != SCM_RIGHTS\n"); - return nullptr; - } - - return CMSG_DATA(cmsg); -} - -vector recv_fds(int sockfd) { - vector results; - - // Peek fd count to allocate proper buffer - int cnt; - recv(sockfd, &cnt, sizeof(cnt), MSG_PEEK); - if (cnt == 0) { - // Consume data - recv(sockfd, &cnt, sizeof(cnt), MSG_WAITALL); - return results; - } - - vector cmsgbuf; - cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt)); - - void *data = recv_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), cnt); - if (data == nullptr) - return results; - - results.resize(cnt); - memcpy(results.data(), data, sizeof(int) * cnt); - - return results; -} - -int recv_fd(int sockfd) { - // Peek fd count - int cnt; - recv(sockfd, &cnt, sizeof(cnt), MSG_PEEK); - if (cnt == 0) { - // Consume data - recv(sockfd, &cnt, sizeof(cnt), MSG_WAITALL); - return -1; - } - - char cmsgbuf[CMSG_SPACE(sizeof(int))]; - - void *data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1); - if (data == nullptr) - return -1; - - int result; - memcpy(&result, data, sizeof(int)); - return result; -} - -int read_int(int fd) { - int val; - if (xxread(fd, &val, sizeof(val)) != sizeof(val)) - return -1; - return val; -} - -int read_int_be(int fd) { - return ntohl(read_int(fd)); -} - -void write_int(int fd, int val) { - if (fd < 0) return; - xwrite(fd, &val, sizeof(val)); -} - -void write_int_be(int fd, int val) { - write_int(fd, htonl(val)); -} - -bool read_string(int fd, std::string &str) { - int len = read_int(fd); - str.clear(); - if (len < 0) - return false; - str.resize(len); - return xxread(fd, str.data(), len) == len; -} - -string read_string(int fd) { - string str; - read_string(fd, str); - return str; -} - -void write_string(int fd, string_view str) { - if (fd < 0) return; - write_int(fd, str.size()); - xwrite(fd, str.data(), str.size()); -} diff --git a/native/src/core/socket.rs b/native/src/core/socket.rs new file mode 100644 index 0000000000000..7a7820e8bd685 --- /dev/null +++ b/native/src/core/socket.rs @@ -0,0 +1,257 @@ +use base::{libc, warn, ReadExt, ResultExt, WriteExt}; +use bytemuck::{bytes_of, bytes_of_mut, Zeroable}; +use std::io; +use std::io::{ErrorKind, IoSlice, IoSliceMut, Read, Write}; +use std::mem::ManuallyDrop; +use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use std::os::unix::net::{AncillaryData, SocketAncillary, UnixStream}; + +pub trait Encodable { + fn encoded_len(&self) -> usize; + fn encode(&self, w: &mut impl Write) -> io::Result<()>; +} + +pub trait Decodable: Sized + Encodable { + fn decode(r: &mut impl Read) -> io::Result; +} + +macro_rules! impl_pod_encodable { + ($($t:ty)*) => ($( + impl Encodable for $t { + #[inline(always)] + fn encoded_len(&self) -> usize { + size_of::() + } + + #[inline(always)] + fn encode(&self, w: &mut impl Write) -> io::Result<()> { + w.write_pod(self) + } + } + impl Decodable for $t { + #[inline(always)] + fn decode(r: &mut impl Read) -> io::Result { + let mut val = Self::zeroed(); + r.read_pod(&mut val)?; + Ok(val) + } + } + )*) +} + +impl_pod_encodable! { u8 u32 i32 usize } + +impl Encodable for bool { + #[inline(always)] + fn encoded_len(&self) -> usize { + size_of::() + } + + #[inline(always)] + fn encode(&self, w: &mut impl Write) -> io::Result<()> { + match *self { + true => 1u8.encode(w), + false => 0u8.encode(w), + } + } +} + +impl Decodable for bool { + #[inline(always)] + fn decode(r: &mut impl Read) -> io::Result { + Ok(u8::decode(r)? != 0) + } +} + +impl Encodable for Vec { + fn encoded_len(&self) -> usize { + size_of::() + size_of::() * self.len() + } + + fn encode(&self, w: &mut impl Write) -> io::Result<()> { + (self.len() as i32).encode(w)?; + self.iter().try_for_each(|e| e.encode(w)) + } +} + +impl Decodable for Vec { + fn decode(r: &mut impl Read) -> io::Result { + let len = i32::decode(r)?; + let mut val = Vec::with_capacity(len as usize); + for _ in 0..len { + val.push(T::decode(r)?); + } + Ok(val) + } +} + +impl Encodable for str { + fn encoded_len(&self) -> usize { + size_of::() + self.len() + } + + fn encode(&self, w: &mut impl Write) -> io::Result<()> { + (self.len() as i32).encode(w)?; + w.write_all(self.as_bytes()) + } +} + +impl Encodable for String { + fn encoded_len(&self) -> usize { + self.as_str().encoded_len() + } + + fn encode(&self, w: &mut impl Write) -> io::Result<()> { + self.as_str().encode(w) + } +} + +impl Decodable for String { + fn decode(r: &mut impl Read) -> io::Result { + let len = i32::decode(r)?; + let mut val = String::with_capacity(len as usize); + r.take(len as u64).read_to_string(&mut val)?; + Ok(val) + } +} + +pub trait IpcRead { + fn read_decodable(&mut self) -> io::Result; +} + +impl IpcRead for T { + #[inline(always)] + fn read_decodable(&mut self) -> io::Result { + E::decode(self) + } +} + +pub trait IpcWrite { + fn write_encodable(&mut self, val: &E) -> io::Result<()>; +} + +impl IpcWrite for T { + #[inline(always)] + fn write_encodable(&mut self, val: &E) -> io::Result<()> { + val.encode(self) + } +} + +pub trait UnixSocketExt { + fn send_fds(&mut self, fd: &[RawFd]) -> io::Result<()>; + fn recv_fd(&mut self) -> io::Result>; + fn recv_fds(&mut self) -> io::Result>; +} + +impl UnixSocketExt for UnixStream { + fn send_fds(&mut self, fds: &[RawFd]) -> io::Result<()> { + match fds.len() { + 0 => self.write_pod(&0)?, + len => { + // 4k buffer is reasonable enough + let mut buf = [0u8; 4096]; + let mut ancillary = SocketAncillary::new(&mut buf); + if !ancillary.add_fds(fds) { + return Err(ErrorKind::OutOfMemory.into()); + } + let fd_count = len as i32; + let iov = IoSlice::new(bytes_of(&fd_count)); + self.send_vectored_with_ancillary(&[iov], &mut ancillary)?; + } + }; + Ok(()) + } + + fn recv_fd(&mut self) -> io::Result> { + let mut fd_count = 0; + self.peek(bytes_of_mut(&mut fd_count))?; + if fd_count < 1 { + // Actually consume the data + self.read_pod(&mut fd_count)?; + return Ok(None); + } + if fd_count > 1 { + warn!( + "Received unexpected number of fds: expected=1 actual={}", + fd_count + ); + } + + // 4k buffer is reasonable enough + let mut buf = [0u8; 4096]; + let mut ancillary = SocketAncillary::new(&mut buf); + let iov = IoSliceMut::new(bytes_of_mut(&mut fd_count)); + self.recv_vectored_with_ancillary(&mut [iov], &mut ancillary)?; + for msg in ancillary.messages().flatten() { + if let AncillaryData::ScmRights(mut scm_rights) = msg { + // We only want the first one + let fd = if let Some(fd) = scm_rights.next() { + unsafe { OwnedFd::from_raw_fd(fd) } + } else { + return Ok(None); + }; + // Close all others + for fd in scm_rights { + unsafe { libc::close(fd) }; + } + return Ok(Some(fd)); + } + } + Ok(None) + } + + fn recv_fds(&mut self) -> io::Result> { + let mut fd_count = 0; + // 4k buffer is reasonable enough + let mut buf = [0u8; 4096]; + let mut ancillary = SocketAncillary::new(&mut buf); + let iov = IoSliceMut::new(bytes_of_mut(&mut fd_count)); + self.recv_vectored_with_ancillary(&mut [iov], &mut ancillary)?; + let mut fds: Vec = Vec::new(); + for msg in ancillary.messages().flatten() { + if let AncillaryData::ScmRights(scm_rights) = msg { + fds = scm_rights + .map(|fd| unsafe { OwnedFd::from_raw_fd(fd) }) + .collect(); + } + } + if fd_count as usize != fds.len() { + warn!( + "Received unexpected number of fds: expected={} actual={}", + fd_count, + fds.len() + ); + } + Ok(fds) + } +} + +pub fn send_fd(socket: RawFd, fd: RawFd) -> bool { + let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) }); + if fd < 0 { + socket.send_fds(&[]).log().is_ok() + } else { + socket.send_fds(&[fd]).log().is_ok() + } +} + +pub fn send_fds(socket: RawFd, fds: &[RawFd]) -> bool { + let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) }); + socket.send_fds(fds).log().is_ok() +} + +pub fn recv_fd(socket: RawFd) -> RawFd { + let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) }); + socket + .recv_fd() + .log() + .unwrap_or(None) + .map_or(-1, IntoRawFd::into_raw_fd) +} + +pub fn recv_fds(socket: RawFd) -> Vec { + let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) }); + let fds = socket.recv_fds().log().unwrap_or(Vec::new()); + // SAFETY: OwnedFd and RawFd has the same layout + unsafe { std::mem::transmute(fds) } +} diff --git a/native/src/core/su/connect.cpp b/native/src/core/su/connect.cpp index 1b5eb797400a1..f891b85398043 100644 --- a/native/src/core/su/connect.cpp +++ b/native/src/core/su/connect.cpp @@ -4,8 +4,7 @@ #include #include #include - -#include "su.hpp" +#include using namespace std; @@ -21,11 +20,6 @@ using namespace std; // 0x18800020 = FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK| // FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|FLAG_INCLUDE_STOPPED_PACKAGES -#define get_cmd(to) \ -((to).command.empty() ? \ -((to).shell.empty() ? DEFAULT_SHELL : (to).shell.data()) : \ -(to).command.data()) - class Extra { const char *key; enum { @@ -128,14 +122,15 @@ static bool check_no_error(int fd) { } static void exec_cmd(const char *action, vector &data, - const shared_ptr &info, bool provider = true) { + const SuAppRequest &info, bool provider = true) { char target[128]; char user[4]; - ssprintf(user, sizeof(user), "%d", to_user_id(info->eval_uid)); + ssprintf(user, sizeof(user), "%d", to_user_id(info.eval_uid)); // First try content provider call method if (provider) { - ssprintf(target, sizeof(target), "content://%s.provider", info->mgr_pkg.data()); + ssprintf(target, sizeof(target), "content://%.*s.provider", + (int) info.mgr_pkg.size(), info.mgr_pkg.data()); vector args{ CALL_PROVIDER }; for (auto &e : data) { e.add_bind(args); @@ -153,7 +148,7 @@ static void exec_cmd(const char *action, vector &data, } // Then try start activity with package name - strscpy(target, info->mgr_pkg.data(), sizeof(target)); + ssprintf(target, sizeof(target), "%.*s", (int) info.mgr_pkg.size(), info.mgr_pkg.data()); vector args{ START_ACTIVITY }; for (auto &e : data) { e.add_intent(args); @@ -168,53 +163,58 @@ static void exec_cmd(const char *action, vector &data, exec_command(exec); } -void app_log(const su_context &ctx) { +void app_log(const SuAppRequest &info, SuPolicy policy, bool notify) { if (fork_dont_care() == 0) { + string context = (string) info.request.context; + string command = info.request.command.empty() + ? (string) info.request.shell + : (string) info.request.command; + vector extras; extras.reserve(9); - extras.emplace_back("from.uid", ctx.info->uid); - extras.emplace_back("to.uid", static_cast(ctx.req.uid)); - extras.emplace_back("pid", ctx.pid); - extras.emplace_back("policy", +ctx.info->access.policy); - extras.emplace_back("target", ctx.req.target); - extras.emplace_back("context", ctx.req.context.data()); - extras.emplace_back("gids", &ctx.req.gids); - extras.emplace_back("command", get_cmd(ctx.req)); - extras.emplace_back("notify", (bool) ctx.info->access.notify); - - exec_cmd("log", extras, ctx.info); + extras.emplace_back("from.uid", info.uid); + extras.emplace_back("to.uid", info.request.target_uid); + extras.emplace_back("pid", info.pid); + extras.emplace_back("policy", +policy); + extras.emplace_back("target", info.request.target_pid); + extras.emplace_back("context", context.data()); + extras.emplace_back("gids", &info.request.gids); + extras.emplace_back("command", command.data()); + extras.emplace_back("notify", notify); + + exec_cmd("log", extras, info); exit(0); } } -void app_notify(const su_context &ctx) { +void app_notify(const SuAppRequest &info, SuPolicy policy) { if (fork_dont_care() == 0) { vector extras; extras.reserve(3); - extras.emplace_back("from.uid", ctx.info->uid); - extras.emplace_back("pid", ctx.pid); - extras.emplace_back("policy", +ctx.info->access.policy); + extras.emplace_back("from.uid", info.uid); + extras.emplace_back("pid", info.pid); + extras.emplace_back("policy", +policy); - exec_cmd("notify", extras, ctx.info); + exec_cmd("notify", extras, info); exit(0); } } -int app_request(const su_context &ctx) { +int app_request(const SuAppRequest &info) { // Create FIFO char fifo[64]; - ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", get_magisk_tmp(), ctx.pid); + ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", get_magisk_tmp(), info.pid); mkfifo(fifo, 0600); - chown(fifo, ctx.info->mgr_uid, ctx.info->mgr_uid); + chown(fifo, info.mgr_uid, info.mgr_uid); setfilecon(fifo, MAGISK_FILE_CON); // Send request vector extras; extras.reserve(3); extras.emplace_back("fifo", fifo); - extras.emplace_back("uid", ctx.info->eval_uid); - extras.emplace_back("pid", ctx.pid); - exec_cmd("request", extras, ctx.info, false); + extras.emplace_back("uid", info.eval_uid); + extras.emplace_back("pid", info.pid); + exec_cmd("request", extras, info, false); // Wait for data input for at most 70 seconds // Open with O_RDWR to prevent FIFO open block diff --git a/native/src/core/su/daemon.rs b/native/src/core/su/daemon.rs new file mode 100644 index 0000000000000..5a43781265c9b --- /dev/null +++ b/native/src/core/su/daemon.rs @@ -0,0 +1,300 @@ +use crate::daemon::{to_app_id, to_user_id, MagiskD, AID_ROOT, AID_SHELL}; +use crate::db::{DbSettings, MultiuserMode, RootAccess}; +use crate::ffi::{ + app_log, app_notify, app_request, exec_root_shell, SuAppRequest, SuPolicy, SuRequest, +}; +use crate::socket::IpcRead; +use crate::su::db::RootSettings; +use crate::UCred; +use base::{debug, error, exit_on_error, libc, warn, LoggedResult, ResultExt, WriteExt}; +use std::fs::File; +use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd}; +use std::os::unix::net::UnixStream; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +const DEFAULT_SHELL: &str = "/system/bin/sh"; + +impl Default for SuRequest { + fn default() -> Self { + SuRequest { + target_uid: AID_ROOT, + target_pid: -1, + login: false, + keep_env: false, + shell: DEFAULT_SHELL.to_string(), + command: "".to_string(), + context: "".to_string(), + gids: vec![], + } + } +} + +pub struct SuInfo { + uid: i32, + eval_uid: i32, + cfg: DbSettings, + mgr_pkg: String, + mgr_uid: i32, + access: Mutex, +} + +struct AccessInfo { + settings: RootSettings, + timestamp: Instant, +} + +impl Default for SuInfo { + fn default() -> Self { + SuInfo { + uid: -1, + eval_uid: -1, + cfg: Default::default(), + mgr_pkg: Default::default(), + mgr_uid: -1, + access: Default::default(), + } + } +} + +impl Default for AccessInfo { + fn default() -> Self { + AccessInfo { + settings: Default::default(), + timestamp: Instant::now(), + } + } +} + +impl SuInfo { + fn allow(uid: i32) -> SuInfo { + let access = RootSettings { + policy: SuPolicy::Allow, + log: false, + notify: false, + }; + SuInfo { + uid, + access: Mutex::new(AccessInfo::new(access)), + ..Default::default() + } + } + + fn deny(uid: i32) -> SuInfo { + let access = RootSettings { + policy: SuPolicy::Deny, + log: false, + notify: false, + }; + SuInfo { + uid, + access: Mutex::new(AccessInfo::new(access)), + ..Default::default() + } + } +} + +impl AccessInfo { + fn new(settings: RootSettings) -> AccessInfo { + AccessInfo { + settings, + timestamp: Instant::now(), + } + } + + fn is_fresh(&self) -> bool { + self.timestamp.elapsed() < Duration::from_secs(3) + } + + fn refresh(&mut self) { + self.timestamp = Instant::now(); + } +} + +impl MagiskD { + pub fn su_daemon_handler(&self, client: i32, cred: &UCred) { + let mut client = unsafe { UnixStream::from_raw_fd(client) }; + let cred = cred.0; + debug!( + "su: request from uid=[{}], pid=[{}], client=[{}]", + cred.uid, + cred.pid, + client.as_raw_fd() + ); + + let mut req = match client.read_decodable::().log() { + Ok(req) => req, + Err(_) => { + warn!("su: remote process probably died, abort"); + client.write_pod(&SuPolicy::Deny.repr).ok(); + return; + } + }; + + let info = self.get_su_info(cred.uid as i32); + let app_req = SuAppRequest { + uid: cred.uid as i32, + pid: cred.pid, + eval_uid: info.eval_uid, + mgr_pkg: &info.mgr_pkg, + mgr_uid: info.mgr_uid, + request: &req, + }; + + { + let mut access = info.access.lock().unwrap(); + + if access.settings.policy == SuPolicy::Query { + let fd = app_request(&app_req); + if fd < 0 { + access.settings.policy = SuPolicy::Deny; + } else { + let mut fd = unsafe { File::from_raw_fd(fd) }; + access.settings.policy = SuPolicy { + repr: fd + .read_decodable::() + .log() + .map(i32::from_be) + .unwrap_or(SuPolicy::Deny.repr), + }; + } + } + + if access.settings.log { + app_log(&app_req, access.settings.policy, access.settings.notify); + } else if access.settings.notify { + app_notify(&app_req, access.settings.policy); + } + + // Before unlocking, refresh the timestamp + access.refresh(); + + // Fail fast + if access.settings.policy == SuPolicy::Deny { + warn!("su: request rejected ({})", info.uid); + client.write_pod(&SuPolicy::Deny.repr).ok(); + return; + } + } + + // At this point, the root access is granted. + // Fork a child root process and monitor its exit value. + let child = unsafe { libc::fork() }; + if child == 0 { + debug!("su: fork handler"); + + // Abort upon any error occurred + exit_on_error(true); + + // ack + client.write_pod(&0).ok(); + + exec_root_shell(client.into_raw_fd(), cred.pid, &mut req, info.cfg.mnt_ns); + return; + } + if child < 0 { + error!("su: fork failed, abort"); + return; + } + + // Wait result + debug!("su: waiting child pid=[{}]", child); + let mut status = 0; + let code = unsafe { + if libc::waitpid(child, &mut status, 0) > 0 { + libc::WEXITSTATUS(status) + } else { + -1 + } + }; + debug!("su: return code=[{}]", code); + client.write_pod(&code).ok(); + } + + fn get_su_info(&self, uid: i32) -> Arc { + if uid == AID_ROOT { + return Arc::new(SuInfo::allow(AID_ROOT)); + } + + let cached = self.cached_su_info.load(); + if cached.uid == uid && cached.access.lock().unwrap().is_fresh() { + return cached; + } + + let info = self.build_su_info(uid); + self.cached_su_info.store(info.clone()); + info + } + + fn build_su_info(&self, uid: i32) -> Arc { + let result: LoggedResult> = try { + let cfg = self.get_db_settings()?; + + // Check multiuser settings + let eval_uid = match cfg.multiuser_mode { + MultiuserMode::OwnerOnly => { + if to_user_id(uid) != 0 { + return Arc::new(SuInfo::deny(uid)); + } + uid + } + MultiuserMode::OwnerManaged => to_app_id(uid), + _ => uid, + }; + + // Check su access settings + match cfg.root_access { + RootAccess::Disabled => { + warn!("Root access is disabled!"); + return Arc::new(SuInfo::deny(uid)); + } + RootAccess::AdbOnly => { + if uid != AID_SHELL { + warn!("Root access limited to ADB only!"); + return Arc::new(SuInfo::deny(uid)); + } + } + RootAccess::AppsOnly => { + if uid == AID_SHELL { + warn!("Root access is disabled for ADB!"); + return Arc::new(SuInfo::deny(uid)); + } + } + _ => {} + }; + + let mut access = RootSettings::default(); + self.get_root_settings(eval_uid, &mut access)?; + + // We need to talk to the manager, get the app info + let (mgr_uid, mgr_pkg) = + if access.policy == SuPolicy::Query || access.log || access.notify { + self.get_manager(to_user_id(eval_uid), true) + } else { + (-1, String::new()) + }; + + // If it's the manager, allow it silently + if to_app_id(uid) == to_app_id(mgr_uid) { + return Arc::new(SuInfo::allow(uid)); + } + + // If still not determined, check if manager exists + if access.policy == SuPolicy::Query && mgr_uid < 0 { + return Arc::new(SuInfo::deny(uid)); + } + + // Finally, the SuInfo + Arc::new(SuInfo { + uid, + eval_uid, + cfg, + mgr_pkg, + mgr_uid, + access: Mutex::new(AccessInfo::new(access)), + }) + }; + + result.unwrap_or(Arc::new(SuInfo::deny(uid))) + } +} diff --git a/native/src/core/su/db.rs b/native/src/core/su/db.rs new file mode 100644 index 0000000000000..620005736e531 --- /dev/null +++ b/native/src/core/su/db.rs @@ -0,0 +1,133 @@ +use crate::daemon::{ + to_app_id, to_user_id, MagiskD, AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL, +}; +use crate::db::DbArg::Integer; +use crate::db::{MultiuserMode, RootAccess, SqlTable, SqliteResult, SqliteReturn}; +use crate::ffi::{DbValues, SuPolicy}; +use base::ResultExt; + +impl Default for SuPolicy { + fn default() -> Self { + SuPolicy::Query + } +} + +#[derive(Default)] +pub struct RootSettings { + pub policy: SuPolicy, + pub log: bool, + pub notify: bool, +} + +impl SqlTable for RootSettings { + fn on_row(&mut self, columns: &[String], values: &DbValues) { + for (i, column) in columns.iter().enumerate() { + let val = values.get_int(i as i32); + match column.as_str() { + "policy" => self.policy.repr = val, + "logging" => self.log = val != 0, + "notification" => self.notify = val != 0, + _ => {} + } + } + } +} + +struct UidList(Vec); + +impl SqlTable for UidList { + fn on_row(&mut self, _: &[String], values: &DbValues) { + self.0.push(values.get_int(0)); + } +} + +impl MagiskD { + pub fn get_root_settings(&self, uid: i32, settings: &mut RootSettings) -> SqliteResult<()> { + self.db_exec_with_rows( + "SELECT policy, logging, notification FROM policies \ + WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))", + &[Integer(uid as i64)], + settings, + ) + .sql_result() + } + + pub fn prune_su_access(&self) { + let mut list = UidList(Vec::new()); + if self + .db_exec_with_rows("SELECT uid FROM policies", &[], &mut list) + .sql_result() + .log() + .is_err() + { + return; + } + + let app_list = self.get_app_no_list(); + let mut rm_uids = Vec::new(); + + for uid in list.0 { + let app_id = to_app_id(uid); + if (AID_APP_START..=AID_APP_END).contains(&app_id) { + let app_no = app_id - AID_APP_START; + if !app_list.contains(app_no as usize) { + // The app_id is no longer installed + rm_uids.push(uid); + } + } + } + + for uid in rm_uids { + self.db_exec("DELETE FROM policies WHERE uid=?", &[Integer(uid as i64)]); + } + } + + pub fn uid_granted_root(&self, mut uid: i32) -> bool { + if uid == AID_ROOT { + return true; + } + + let cfg = match self.get_db_settings().log() { + Ok(cfg) => cfg, + Err(_) => return false, + }; + + // Check user root access settings + match cfg.root_access { + RootAccess::Disabled => return false, + RootAccess::AppsOnly => { + if uid == AID_SHELL { + return false; + } + } + RootAccess::AdbOnly => { + if uid != AID_SHELL { + return false; + } + } + _ => {} + } + + // Check multiuser settings + match cfg.multiuser_mode { + MultiuserMode::OwnerOnly => { + if to_user_id(uid) != 0 { + return false; + } + } + MultiuserMode::OwnerManaged => uid = to_app_id(uid), + _ => {} + } + + let mut granted = false; + let mut output_fn = + |_: &[String], values: &DbValues| granted = values.get_int(0) == SuPolicy::Allow.repr; + self.db_exec_with_rows( + "SELECT policy FROM policies WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))", + &[Integer(uid as i64)], + &mut output_fn, + ); + + granted + } +} diff --git a/native/src/core/su/mod.rs b/native/src/core/su/mod.rs index 089c8068e7fd9..067d5b65a5403 100644 --- a/native/src/core/su/mod.rs +++ b/native/src/core/su/mod.rs @@ -1,145 +1,4 @@ -use crate::daemon::{ - to_app_id, to_user_id, MagiskD, AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL, -}; -use crate::db::DbArg::Integer; -use crate::db::{SqlTable, SqliteResult, SqliteReturn}; -use crate::ffi::{DbValues, MultiuserMode, RootAccess, RootSettings, SuPolicy}; -use base::ResultExt; +mod daemon; +mod db; -impl Default for SuPolicy { - fn default() -> Self { - SuPolicy::Query - } -} - -impl Default for RootSettings { - fn default() -> Self { - RootSettings { - policy: Default::default(), - log: true, - notify: true, - } - } -} - -impl SqlTable for RootSettings { - fn on_row(&mut self, columns: &[String], values: &DbValues) { - for (i, column) in columns.iter().enumerate() { - let val = values.get_int(i as i32); - if column == "policy" { - self.policy.repr = val; - } else if column == "logging" { - self.log = val != 0; - } else if column == "notify" { - self.notify = val != 0; - } - } - } -} - -struct UidList(Vec); - -impl SqlTable for UidList { - fn on_row(&mut self, _: &[String], values: &DbValues) { - self.0.push(values.get_int(0)); - } -} - -impl MagiskD { - fn get_root_settings(&self, uid: i32, settings: &mut RootSettings) -> SqliteResult<()> { - self.db_exec_with_rows( - "SELECT policy, logging, notification FROM policies \ - WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))", - &[Integer(uid as i64)], - settings, - ) - .sql_result() - } - - pub fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool { - self.get_root_settings(uid, settings).log().is_ok() - } - - pub fn prune_su_access(&self) { - let mut list = UidList(Vec::new()); - if self - .db_exec_with_rows("SELECT uid FROM policies", &[], &mut list) - .sql_result() - .log() - .is_err() - { - return; - } - - let app_list = self.get_app_no_list(); - let mut rm_uids = Vec::new(); - - for uid in list.0 { - let app_id = to_app_id(uid); - if (AID_APP_START..=AID_APP_END).contains(&app_id) { - let app_no = app_id - AID_APP_START; - if !app_list.contains(app_no as usize) { - // The app_id is no longer installed - rm_uids.push(uid); - } - } - } - - for uid in rm_uids { - self.db_exec("DELETE FROM policies WHERE uid=?", &[Integer(uid as i64)]); - } - } - - pub fn uid_granted_root(&self, mut uid: i32) -> bool { - if uid == AID_ROOT { - return true; - } - - let cfg = match self.get_db_settings().log() { - Ok(cfg) => cfg, - Err(_) => return false, - }; - - // Check user root access settings - match cfg.root_access { - RootAccess::Disabled => return false, - RootAccess::AppsOnly => { - if uid == AID_SHELL { - return false; - } - } - RootAccess::AdbOnly => { - if uid != AID_SHELL { - return false; - } - } - _ => {} - } - - // Check multiuser settings - match cfg.multiuser_mode { - MultiuserMode::OwnerOnly => { - if to_user_id(uid) != 0 { - return false; - } - } - MultiuserMode::OwnerManaged => uid = to_app_id(uid), - _ => {} - } - - let mut granted = false; - let mut output_fn = - |_: &[String], values: &DbValues| granted = values.get_int(0) == SuPolicy::Allow.repr; - self.db_exec_with_rows( - "SELECT policy FROM policies WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))", - &[Integer(uid as i64)], - &mut output_fn, - ); - - granted - } -} - -pub fn get_default_root_settings() -> RootSettings { - RootSettings::default() -} +pub use daemon::SuInfo; diff --git a/native/src/core/su/su.cpp b/native/src/core/su/su.cpp index 995d368f0fa7e..e85e5926c3a68 100644 --- a/native/src/core/su/su.cpp +++ b/native/src/core/su/su.cpp @@ -13,13 +13,24 @@ #include #include +#include + #include #include #include +#include -#include "su.hpp" #include "pts.hpp" +using namespace std; + +#define DEFAULT_SHELL "/system/bin/sh" + +// Constants for atty +#define ATTY_IN (1 << 0) +#define ATTY_OUT (1 << 1) +#define ATTY_ERR (1 << 2) + int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 }; [[noreturn]] static void usage(int status) { @@ -97,7 +108,7 @@ int su_client_main(int argc, char *argv[]) { { nullptr, 0, nullptr, 0 }, }; - su_request su_req; + auto req = SuRequest::New(); for (int i = 0; i < argc; i++) { // Replace -cn and -z with -Z for backwards compatibility @@ -110,25 +121,28 @@ int su_client_main(int argc, char *argv[]) { while ((c = getopt_long(argc, argv, "c:hlmps:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) { switch (c) { - case 'c': + case 'c': { + string command; for (int i = optind - 1; i < argc; ++i) { - if (!su_req.command.empty()) - su_req.command += ' '; - su_req.command += argv[i]; + if (!command.empty()) + command += ' '; + command += argv[i]; } + req.command = command; optind = argc; break; + } case 'h': usage(EXIT_SUCCESS); case 'l': - su_req.login = true; + req.login = true; break; case 'm': case 'p': - su_req.keepenv = true; + req.keep_env = true; break; case 's': - su_req.shell = optarg; + req.shell = optarg; break; case 'V': printf("%d\n", MAGISK_VER_CODE); @@ -137,33 +151,36 @@ int su_client_main(int argc, char *argv[]) { printf("%s\n", MAGISK_VERSION ":MAGISKSU"); exit(EXIT_SUCCESS); case 'Z': - su_req.context = optarg; + req.context = optarg; break; case 'M': case 't': - if (su_req.target != -1) { + if (req.target_pid != -1) { fprintf(stderr, "Can't use -M and -t at the same time\n"); usage(EXIT_FAILURE); } if (optarg == nullptr) { - su_req.target = 0; + req.target_pid = 0; } else { - su_req.target = parse_int(optarg); - if (*optarg == '-' || su_req.target == -1) { + req.target_pid = parse_int(optarg); + if (*optarg == '-' || req.target_pid == -1) { fprintf(stderr, "Invalid PID: %s\n", optarg); usage(EXIT_FAILURE); } } break; case 'g': - case 'G': + case 'G': { + vector gids; if (int gid = parse_int(optarg); gid >= 0) { - su_req.gids.insert(c == 'g' ? su_req.gids.begin() : su_req.gids.end(), gid); + gids.insert(c == 'g' ? gids.begin() : gids.end(), gid); } else { fprintf(stderr, "Invalid GID: %s\n", optarg); usage(EXIT_FAILURE); } + std::copy(gids.begin(), gids.end(), std::back_inserter(req.gids)); break; + } default: /* Bionic getopt_long doesn't terminate its error output by newline */ fprintf(stderr, "\n"); @@ -172,7 +189,7 @@ int su_client_main(int argc, char *argv[]) { } if (optind < argc && strcmp(argv[optind], "-") == 0) { - su_req.login = true; + req.login = true; optind++; } /* username or uid */ @@ -180,9 +197,9 @@ int su_client_main(int argc, char *argv[]) { struct passwd *pw; pw = getpwnam(argv[optind]); if (pw) - su_req.uid = pw->pw_uid; + req.target_uid = pw->pw_uid; else - su_req.uid = parse_int(argv[optind]); + req.target_uid = parse_int(argv[optind]); optind++; } @@ -191,12 +208,8 @@ int su_client_main(int argc, char *argv[]) { // Connect to client fd = connect_daemon(+RequestCode::SUPERUSER); - // Send su_request - xwrite(fd, &su_req, sizeof(su_req_base)); - write_string(fd, su_req.shell); - write_string(fd, su_req.command); - write_string(fd, su_req.context); - write_vector(fd, su_req.gids); + // Send request + req.write_to_fd(fd); // Wait for ack from daemon if (read_int(fd)) { @@ -239,3 +252,162 @@ int su_client_main(int argc, char *argv[]) { return code; } + +// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root +static void set_identity(int uid, const rust::Vec &groups) { + if (seteuid(0)) { + PLOGE("seteuid (root)"); + } + gid_t gid; + if (!groups.empty()) { + if (setgroups(groups.size(), groups.data())) { + PLOGE("setgroups"); + } + gid = groups[0]; + } else { + gid = uid; + } + if (setresgid(gid, gid, gid)) { + PLOGE("setresgid (%u)", uid); + } + if (setresuid(uid, uid, uid)) { + PLOGE("setresuid (%u)", uid); + } +} + +void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode) { + // Become session leader + xsetsid(); + + // The FDs for each of the streams + int infd = recv_fd(client); + int outfd = recv_fd(client); + int errfd = recv_fd(client); + + // App need a PTY + if (read_int(client)) { + string pts; + string ptmx; + auto magiskpts = get_magisk_tmp() + "/"s SHELLPTS; + if (access(magiskpts.data(), F_OK)) { + pts = "/dev/pts"; + ptmx = "/dev/ptmx"; + } else { + pts = magiskpts; + ptmx = magiskpts + "/ptmx"; + } + int ptmx_fd = xopen(ptmx.data(), O_RDWR); + grantpt(ptmx_fd); + unlockpt(ptmx_fd); + int pty_num = get_pty_num(ptmx_fd); + if (pty_num < 0) { + // Kernel issue? Fallback to /dev/pts + close(ptmx_fd); + pts = "/dev/pts"; + ptmx_fd = xopen("/dev/ptmx", O_RDWR); + grantpt(ptmx_fd); + unlockpt(ptmx_fd); + pty_num = get_pty_num(ptmx_fd); + } + send_fd(client, ptmx_fd); + close(ptmx_fd); + + string pts_slave = pts + "/" + to_string(pty_num); + LOGD("su: pts_slave=[%s]\n", pts_slave.data()); + + // Opening the TTY has to occur after the + // fork() and setsid() so that it becomes + // our controlling TTY and not the daemon's + int ptsfd = xopen(pts_slave.data(), O_RDWR); + + if (infd < 0) + infd = ptsfd; + if (outfd < 0) + outfd = ptsfd; + if (errfd < 0) + errfd = ptsfd; + } + + // Swap out stdin, stdout, stderr + xdup2(infd, STDIN_FILENO); + xdup2(outfd, STDOUT_FILENO); + xdup2(errfd, STDERR_FILENO); + + close(infd); + close(outfd); + close(errfd); + close(client); + + // Handle namespaces + if (req.target_pid == -1) + req.target_pid = pid; + else if (req.target_pid == 0) + mode = MntNsMode::Global; + else if (mode == MntNsMode::Global) + mode = MntNsMode::Requester; + + switch (mode) { + case MntNsMode::Global: + LOGD("su: use global namespace\n"); + break; + case MntNsMode::Requester: + LOGD("su: use namespace of pid=[%d]\n", req.target_pid); + switch_mnt_ns(req.target_pid); + break; + case MntNsMode::Isolate: + LOGD("su: use new isolated namespace\n"); + switch_mnt_ns(req.target_pid); + xunshare(CLONE_NEWNS); + xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr); + break; + } + + const char *argv[4] = { nullptr }; + + argv[0] = req.login ? "-" : req.shell.c_str(); + + if (!req.command.empty()) { + argv[1] = "-c"; + argv[2] = req.command.c_str(); + } + + // Setup environment + umask(022); + char path[32]; + ssprintf(path, sizeof(path), "/proc/%d/cwd", pid); + char cwd[4096]; + if (realpath(path, cwd, sizeof(cwd)) > 0) + chdir(cwd); + ssprintf(path, sizeof(path), "/proc/%d/environ", pid); + auto env = full_read(path); + clearenv(); + for (size_t pos = 0; pos < env.size(); ++pos) { + putenv(env.data() + pos); + pos = env.find_first_of('\0', pos); + if (pos == std::string::npos) + break; + } + if (!req.keep_env) { + struct passwd *pw; + pw = getpwuid(req.target_uid); + if (pw) { + setenv("HOME", pw->pw_dir, 1); + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + setenv("SHELL", req.shell.c_str(), 1); + } + } + + // Unblock all signals + sigset_t block_set; + sigemptyset(&block_set); + sigprocmask(SIG_SETMASK, &block_set, nullptr); + if (!req.context.empty()) { + auto f = xopen_file("/proc/self/attr/exec", "we"); + if (f) fprintf(f.get(), "%s", req.context.c_str()); + } + set_identity(req.target_uid, req.gids); + execvp(req.shell.c_str(), (char **) argv); + fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno)); + PLOGE("exec"); +} diff --git a/native/src/core/su/su.hpp b/native/src/core/su/su.hpp deleted file mode 100644 index b4cbccd1953f1..0000000000000 --- a/native/src/core/su/su.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -#define DEFAULT_SHELL "/system/bin/sh" - -// Constants for atty -#define ATTY_IN (1 << 0) -#define ATTY_OUT (1 << 1) -#define ATTY_ERR (1 << 2) - -#define SILENT_ALLOW { SuPolicy::Allow, false, false } -#define SILENT_DENY { SuPolicy::Deny, false, false } - -class su_info { -public: - // Unique key - const int uid; - - // These should be guarded with internal lock - int eval_uid; // The effective UID, taking multiuser settings into consideration - struct DbSettings cfg; - struct RootSettings access; - std::string mgr_pkg; - int mgr_uid; - void check_db(); - - // These should be guarded with global cache lock - bool is_fresh(); - void refresh(); - - su_info(int uid); - ~su_info(); - mutex_guard lock(); - -private: - long timestamp; - // Internal lock - pthread_mutex_t _lock; -}; - -struct su_req_base { - uid_t uid = AID_ROOT; - bool login = false; - bool keepenv = false; - pid_t target = -1; -} __attribute__((packed)); - -struct su_request : public su_req_base { - std::string shell = DEFAULT_SHELL; - std::string command; - std::string context; - std::vector gids; -}; - -struct su_context { - std::shared_ptr info; - su_request req; - int pid; -}; - -void app_log(const su_context &ctx); -void app_notify(const su_context &ctx); -int app_request(const su_context &ctx); diff --git a/native/src/core/su/su_daemon.cpp b/native/src/core/su/su_daemon.cpp deleted file mode 100644 index 2964f23b400d0..0000000000000 --- a/native/src/core/su/su_daemon.cpp +++ /dev/null @@ -1,377 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "su.hpp" -#include "pts.hpp" - -using namespace std; - -static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER; -static shared_ptr cached; - -su_info::su_info(int uid) : -uid(uid), eval_uid(-1), cfg(DbSettings()), access(RootSettings()), -mgr_uid(-1), timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {} - -su_info::~su_info() { - pthread_mutex_destroy(&_lock); -} - -mutex_guard su_info::lock() { - return mutex_guard(_lock); -} - -bool su_info::is_fresh() { - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - long current = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; - return current - timestamp < 3000; /* 3 seconds */ -} - -void su_info::refresh() { - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - timestamp = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; -} - -void su_info::check_db() { - eval_uid = uid; - auto &daemon = MagiskD(); - daemon.get_db_settings(cfg); - - // Check multiuser settings - switch (cfg.multiuser_mode) { - case MultiuserMode::OwnerOnly: - if (to_user_id(uid) != 0) { - eval_uid = -1; - access = SILENT_DENY; - } - break; - case MultiuserMode::OwnerManaged: - eval_uid = to_app_id(uid); - break; - case MultiuserMode::User: - default: - break; - } - - if (eval_uid > 0) { - if (!daemon.get_root_settings(eval_uid, access)) - return; - } - - // We need to check our manager - if (access.policy == SuPolicy::Query || access.log || access.notify) { - mgr_uid = daemon.get_manager(to_user_id(eval_uid), &mgr_pkg, true); - } -} - -static shared_ptr get_su_info(unsigned uid) { - if (uid == AID_ROOT) { - auto info = make_shared(uid); - info->access = SILENT_ALLOW; - return info; - } - - shared_ptr info; - { - mutex_guard lock(cache_lock); - if (!cached || cached->uid != uid || !cached->is_fresh()) - cached = make_shared(uid); - cached->refresh(); - info = cached; - } - - mutex_guard lock = info->lock(); - - if (info->access.policy == SuPolicy::Query) { - // Not cached, get data from database - info->check_db(); - - // If it's the manager, allow it silently - if (to_app_id(info->uid) == to_app_id(info->mgr_uid)) { - info->access = SILENT_ALLOW; - return info; - } - - // Check su access settings - switch (info->cfg.root_access) { - case RootAccess::Disabled: - LOGW("Root access is disabled!\n"); - info->access = SILENT_DENY; - break; - case RootAccess::AdbOnly: - if (info->uid != AID_SHELL) { - LOGW("Root access limited to ADB only!\n"); - info->access = SILENT_DENY; - } - break; - case RootAccess::AppsOnly: - if (info->uid == AID_SHELL) { - LOGW("Root access is disabled for ADB!\n"); - info->access = SILENT_DENY; - } - break; - case RootAccess::AppsAndAdb: - default: - break; - } - - if (info->access.policy != SuPolicy::Query) - return info; - - // If still not determined, check if manager exists - if (info->mgr_uid < 0) { - info->access = SILENT_DENY; - return info; - } - } - return info; -} - -// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root -static void set_identity(uid_t uid, const std::vector &groups) { - if (seteuid(0)) { - PLOGE("seteuid (root)"); - } - gid_t gid; - if (groups.size() > 0) { - if (setgroups(groups.size(), groups.data())) { - PLOGE("setgroups"); - } - gid = groups[0]; - } else { - gid = uid; - } - if (setresgid(gid, gid, gid)) { - PLOGE("setresgid (%u)", uid); - } - if (setresuid(uid, uid, uid)) { - PLOGE("setresuid (%u)", uid); - } -} - -void su_daemon_handler(int client, const sock_cred *cred) { - LOGD("su: request from uid=[%d], pid=[%d], client=[%d]\n", cred->uid, cred->pid, client); - - su_context ctx = { - .info = get_su_info(cred->uid), - .req = su_request(), - .pid = cred->pid - }; - - // Read su_request - if (xxread(client, &ctx.req, sizeof(su_req_base)) < 0 - || !read_string(client, ctx.req.shell) - || !read_string(client, ctx.req.command) - || !read_string(client, ctx.req.context) - || !read_vector(client, ctx.req.gids)) { - LOGW("su: remote process probably died, abort\n"); - ctx.info.reset(); - write_int(client, +SuPolicy::Deny); - close(client); - return; - } - - // If still not determined, ask manager - if (ctx.info->access.policy == SuPolicy::Query) { - int fd = app_request(ctx); - if (fd < 0) { - ctx.info->access.policy = SuPolicy::Deny; - } else { - int ret = read_int_be(fd); - ctx.info->access.policy = ret < 0 ? SuPolicy::Deny : static_cast(ret); - close(fd); - } - } - - if (ctx.info->access.log) - app_log(ctx); - else if (ctx.info->access.notify) - app_notify(ctx); - - // Fail fast - if (ctx.info->access.policy == SuPolicy::Deny) { - LOGW("su: request rejected (%u)\n", ctx.info->uid); - ctx.info.reset(); - write_int(client, +SuPolicy::Deny); - close(client); - return; - } - - // Fork a child root process - // - // The child process will need to setsid, open a pseudo-terminal - // if needed, and eventually exec shell. - // The parent process will wait for the result and - // send the return code back to our client. - - if (int child = xfork(); child) { - ctx.info.reset(); - - // Wait result - LOGD("su: waiting child pid=[%d]\n", child); - int status, code; - - if (waitpid(child, &status, 0) > 0) - code = WEXITSTATUS(status); - else - code = -1; - - LOGD("su: return code=[%d]\n", code); - write(client, &code, sizeof(code)); - close(client); - return; - } - - LOGD("su: fork handler\n"); - - // Abort upon any error occurred - exit_on_error(true); - - // ack - write_int(client, 0); - - // Become session leader - xsetsid(); - - // The FDs for each of the streams - int infd = recv_fd(client); - int outfd = recv_fd(client); - int errfd = recv_fd(client); - - // App need a PTY - if (read_int(client)) { - string pts; - string ptmx; - auto magiskpts = get_magisk_tmp() + "/"s SHELLPTS; - if (access(magiskpts.data(), F_OK)) { - pts = "/dev/pts"; - ptmx = "/dev/ptmx"; - } else { - pts = magiskpts; - ptmx = magiskpts + "/ptmx"; - } - int ptmx_fd = xopen(ptmx.data(), O_RDWR); - grantpt(ptmx_fd); - unlockpt(ptmx_fd); - int pty_num = get_pty_num(ptmx_fd); - if (pty_num < 0) { - // Kernel issue? Fallback to /dev/pts - close(ptmx_fd); - pts = "/dev/pts"; - ptmx_fd = xopen("/dev/ptmx", O_RDWR); - grantpt(ptmx_fd); - unlockpt(ptmx_fd); - pty_num = get_pty_num(ptmx_fd); - } - send_fd(client, ptmx_fd); - close(ptmx_fd); - - string pts_slave = pts + "/" + to_string(pty_num); - LOGD("su: pts_slave=[%s]\n", pts_slave.data()); - - // Opening the TTY has to occur after the - // fork() and setsid() so that it becomes - // our controlling TTY and not the daemon's - int ptsfd = xopen(pts_slave.data(), O_RDWR); - - if (infd < 0) - infd = ptsfd; - if (outfd < 0) - outfd = ptsfd; - if (errfd < 0) - errfd = ptsfd; - } - - // Swap out stdin, stdout, stderr - xdup2(infd, STDIN_FILENO); - xdup2(outfd, STDOUT_FILENO); - xdup2(errfd, STDERR_FILENO); - - close(infd); - close(outfd); - close(errfd); - close(client); - - // Handle namespaces - if (ctx.req.target == -1) - ctx.req.target = ctx.pid; - else if (ctx.req.target == 0) - ctx.info->cfg.mnt_ns = MntNsMode::Global; - else if (ctx.info->cfg.mnt_ns == MntNsMode::Global) - ctx.info->cfg.mnt_ns = MntNsMode::Requester; - switch (ctx.info->cfg.mnt_ns) { - case MntNsMode::Global: - LOGD("su: use global namespace\n"); - break; - case MntNsMode::Requester: - LOGD("su: use namespace of pid=[%d]\n", ctx.req.target); - if (switch_mnt_ns(ctx.req.target)) - LOGD("su: setns failed, fallback to global\n"); - break; - case MntNsMode::Isolate: - LOGD("su: use new isolated namespace\n"); - switch_mnt_ns(ctx.req.target); - xunshare(CLONE_NEWNS); - xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr); - break; - } - - const char *argv[4] = { nullptr }; - - argv[0] = ctx.req.login ? "-" : ctx.req.shell.data(); - - if (!ctx.req.command.empty()) { - argv[1] = "-c"; - argv[2] = ctx.req.command.data(); - } - - // Setup environment - umask(022); - char path[32]; - ssprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid); - char cwd[4096]; - if (realpath(path, cwd, sizeof(cwd)) > 0) - chdir(cwd); - ssprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid); - auto env = full_read(path); - clearenv(); - for (size_t pos = 0; pos < env.size(); ++pos) { - putenv(env.data() + pos); - pos = env.find_first_of('\0', pos); - if (pos == std::string::npos) - break; - } - if (!ctx.req.keepenv) { - struct passwd *pw; - pw = getpwuid(ctx.req.uid); - if (pw) { - setenv("HOME", pw->pw_dir, 1); - setenv("USER", pw->pw_name, 1); - setenv("LOGNAME", pw->pw_name, 1); - setenv("SHELL", ctx.req.shell.data(), 1); - } - } - - // Unblock all signals - sigset_t block_set; - sigemptyset(&block_set); - sigprocmask(SIG_SETMASK, &block_set, nullptr); - if (!ctx.req.context.empty()) { - auto f = xopen_file("/proc/self/attr/exec", "we"); - if (f) fprintf(f.get(), "%s", ctx.req.context.data()); - } - set_identity(ctx.req.uid, ctx.req.gids); - execvp(ctx.req.shell.data(), (char **) argv); - fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell.data(), strerror(errno)); - PLOGE("exec"); -} diff --git a/native/src/core/zygisk/daemon.rs b/native/src/core/zygisk/daemon.rs new file mode 100644 index 0000000000000..80d21a151c293 --- /dev/null +++ b/native/src/core/zygisk/daemon.rs @@ -0,0 +1,217 @@ +use crate::consts::MODULEROOT; +use crate::daemon::{to_user_id, MagiskD}; +use crate::ffi::{ + get_magisk_tmp, restore_zygisk_prop, update_deny_flags, ZygiskRequest, ZygiskStateFlags, +}; +use crate::socket::{IpcRead, UnixSocketExt}; +use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, STDOUT_FILENO}; +use base::{ + cstr, error, fork_dont_care, libc, open_fd, raw_cstr, warn, Directory, FsPathBuf, LoggedError, + LoggedResult, ResultExt, Utf8CStrBufArr, WriteExt, +}; +use std::fmt::Write; +use std::os::fd::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::net::UnixStream; +use std::ptr; +use std::sync::atomic::Ordering; + +const UNMOUNT_MASK: u32 = + ZygiskStateFlags::ProcessOnDenyList.repr | ZygiskStateFlags::DenyListEnforced.repr; + +pub fn zygisk_should_load_module(flags: u32) -> bool { + flags & UNMOUNT_MASK != UNMOUNT_MASK && flags & ZygiskStateFlags::ProcessIsMagiskApp.repr == 0 +} + +#[allow(unused_variables)] +fn exec_zygiskd(is_64_bit: bool, remote: UnixStream) { + // This fd has to survive exec + unsafe { + libc::fcntl(remote.as_raw_fd(), libc::F_SETFD, 0); + } + + // Start building the exec arguments + let mut exe = Utf8CStrBufArr::<64>::new(); + + #[cfg(target_pointer_width = "64")] + let magisk = if is_64_bit { "magisk" } else { "magisk32" }; + + #[cfg(target_pointer_width = "32")] + let magisk = "magisk"; + + let exe = FsPathBuf::new(&mut exe).join(get_magisk_tmp()).join(magisk); + + let mut fd_str = Utf8CStrBufArr::<16>::new(); + write!(fd_str, "{}", remote.as_raw_fd()).ok(); + unsafe { + libc::execl( + exe.as_ptr(), + raw_cstr!(""), + raw_cstr!("zygisk"), + raw_cstr!("companion"), + fd_str.as_ptr(), + ptr::null() as *const libc::c_char, + ); + libc::exit(-1); + } +} + +impl MagiskD { + pub fn zygisk_handler(&self, client: i32) { + let mut client = unsafe { UnixStream::from_raw_fd(client) }; + let _: LoggedResult<()> = try { + let code = ZygiskRequest { + repr: client.read_decodable()?, + }; + match code { + ZygiskRequest::GetInfo => self.get_process_info(client)?, + ZygiskRequest::ConnectCompanion => self.connect_zygiskd(client), + ZygiskRequest::GetModDir => self.get_mod_dir(client)?, + _ => {} + } + }; + } + + pub fn zygisk_reset(&self, mut restore: bool) { + if !self.zygisk_enabled.load(Ordering::Acquire) { + return; + } + + if restore { + self.zygote_start_count.store(1, Ordering::Release); + } else { + *self.zygiskd_sockets.lock().unwrap() = (None, None); + if self.zygote_start_count.fetch_add(1, Ordering::AcqRel) > 3 { + warn!("zygote crashes too many times, rolling-back"); + restore = true; + } + } + + if restore { + restore_zygisk_prop(); + } + } + + fn get_module_fds(&self, is_64_bit: bool) -> Option> { + self.module_list.get().map(|module_list| { + module_list + .iter() + .map(|m| if is_64_bit { m.z64 } else { m.z32 }) + .collect() + }) + } + + fn connect_zygiskd(&self, mut client: UnixStream) { + let mut zygiskd_sockets = self.zygiskd_sockets.lock().unwrap(); + let result: LoggedResult<()> = try { + let is_64_bit: bool = client.read_decodable()?; + let socket = if is_64_bit { + &mut zygiskd_sockets.1 + } else { + &mut zygiskd_sockets.0 + }; + + if let Some(fd) = socket { + // Make sure the socket is still valid + let mut pfd = libc::pollfd { + fd: fd.as_raw_fd(), + events: 0, + revents: 0, + }; + if unsafe { libc::poll(&mut pfd, 1, 0) } != 0 || pfd.revents != 0 { + // Any revent means error + *socket = None; + } + } + + let socket = if let Some(fd) = socket { + fd + } else { + // Create a new socket pair and fork zygiskd process + let (local, remote) = UnixStream::pair()?; + if fork_dont_care() == 0 { + exec_zygiskd(is_64_bit, remote); + } + *socket = Some(local); + let local = socket.as_mut().unwrap(); + if let Some(module_fds) = self.get_module_fds(is_64_bit) { + local.send_fds(&module_fds)?; + } + if local.read_decodable::()? != 0 { + Err(LoggedError::default())?; + } + local + }; + socket.send_fds(&[client.as_raw_fd()])?; + }; + if result.is_err() { + error!("zygiskd startup error"); + } + } + + fn get_process_info(&self, mut client: UnixStream) -> LoggedResult<()> { + let uid: i32 = client.read_decodable()?; + let process: String = client.read_decodable()?; + let is_64_bit: bool = client.read_decodable()?; + let mut flags: u32 = 0; + update_deny_flags(uid, &process, &mut flags); + if self.get_manager_uid(to_user_id(uid)) == uid { + flags |= ZygiskStateFlags::ProcessIsMagiskApp.repr + } + if self.uid_granted_root(uid) { + flags |= ZygiskStateFlags::ProcessGrantedRoot.repr + } + + // First send flags + client.write_pod(&flags)?; + + // Next send modules + if zygisk_should_load_module(flags) { + if let Some(module_list) = self.module_list.get() { + let module_fds: Vec = module_list + .iter() + .map(|m| if is_64_bit { m.z64 } else { m.z32 }) + // All fds passed over sockets have to be valid file descriptors. + // To work around this issue, send over STDOUT_FILENO as an indicator of an + // invalid fd as it will always be /dev/null in magiskd. + .map(|fd| if fd < 0 { STDOUT_FILENO } else { fd }) + .collect(); + client.send_fds(&module_fds)?; + } + } + + // If we're not in system_server, we are done + if uid != 1000 || process != "system_server" { + return Ok(()); + } + + // Read all failed modules + let failed_ids: Vec = client.read_decodable()?; + if let Some(module_list) = self.module_list.get() { + for id in failed_ids { + let mut buf = Utf8CStrBufArr::default(); + let path = FsPathBuf::new(&mut buf) + .join(MODULEROOT) + .join(&module_list[id as usize].name) + .join("zygisk"); + // Create the unloaded marker file + if let Ok(dir) = Directory::open(&path) { + dir.open_fd(cstr!("unloaded"), O_CREAT | O_RDONLY, 0o644) + .log() + .ok(); + } + } + } + + Ok(()) + } + + fn get_mod_dir(&self, mut client: UnixStream) -> LoggedResult<()> { + let id: i32 = client.read_decodable()?; + let module = &self.module_list.get().unwrap()[id as usize]; + let mut buf = Utf8CStrBufArr::default(); + let dir = FsPathBuf::new(&mut buf).join(MODULEROOT).join(&module.name); + let fd = open_fd!(&dir, O_RDONLY | O_CLOEXEC)?; + client.send_fds(&[fd.as_raw_fd()])?; + Ok(()) + } +} diff --git a/native/src/core/zygisk/entry.cpp b/native/src/core/zygisk/entry.cpp index 69ba7528a0d27..0f57a42161293 100644 --- a/native/src/core/zygisk/entry.cpp +++ b/native/src/core/zygisk/entry.cpp @@ -1,229 +1,120 @@ -#include -#include -#include #include -#include #include +#include -#include #include +#include +#include +#include #include "zygisk.hpp" -#include "module.hpp" using namespace std; string native_bridge = "0"; -static bool is_compatible_with(uint32_t) { - zygisk_logging(); - hook_entry(); - ZLOGD("load success\n"); - return false; -} - -extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf { - .version = 2, - .padding = {}, - .isCompatibleWith = &is_compatible_with, -}; - -// The following code runs in zygote/app process +static void zygiskd(int socket) { + if (getuid() != 0 || fcntl(socket, F_GETFD) < 0) + exit(-1); -static inline bool should_load_modules(uint32_t flags) { - return (flags & UNMOUNT_MASK) != UNMOUNT_MASK && - (flags & PROCESS_IS_MAGISK_APP) != PROCESS_IS_MAGISK_APP; -} + init_thread_pool(); -int remote_get_info(int uid, const char *process, uint32_t *flags, vector &fds) { - if (int fd = zygisk_request(ZygiskRequest::GET_INFO); fd >= 0) { - write_int(fd, uid); - write_string(fd, process); -#ifdef __LP64__ - write_int(fd, 1); +#if defined(__LP64__) + set_nice_name("zygiskd64"); + LOGI("* Launching zygiskd64\n"); #else - write_int(fd, 0); + set_nice_name("zygiskd32"); + LOGI("* Launching zygiskd32\n"); #endif - xxread(fd, flags, sizeof(*flags)); - if (should_load_modules(*flags)) { - fds = recv_fds(fd); - } - return fd; - } - return -1; -} -// The following code runs in magiskd - -static vector get_module_fds(bool is_64_bit) { - vector fds; - // All fds passed to send_fds have to be valid file descriptors. - // To workaround this issue, send over STDOUT_FILENO as an indicator of an - // invalid fd as it will always be /dev/null in magiskd -#if defined(__LP64__) - if (is_64_bit) { - std::transform(module_list->begin(), module_list->end(), std::back_inserter(fds), - [](const module_info &info) { return info.z64 < 0 ? STDOUT_FILENO : info.z64; }); - } else -#endif + // Load modules + using comp_entry = void(*)(int); + vector modules; { - std::transform(module_list->begin(), module_list->end(), std::back_inserter(fds), - [](const module_info &info) { return info.z32 < 0 ? STDOUT_FILENO : info.z32; }); + auto module_fds = recv_fds(socket); + for (int fd : module_fds) { + comp_entry entry = nullptr; + struct stat s{}; + if (fstat(fd, &s) == 0 && S_ISREG(s.st_mode)) { + android_dlextinfo info { + .flags = ANDROID_DLEXT_USE_LIBRARY_FD, + .library_fd = fd, + }; + if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { + *(void **) &entry = dlsym(h, "zygisk_companion_entry"); + } else { + LOGW("Failed to dlopen zygisk module: %s\n", dlerror()); + } + } + modules.push_back(entry); + close(fd); + } } - return fds; -} - -static pthread_mutex_t zygiskd_lock = PTHREAD_MUTEX_INITIALIZER; -static int zygiskd_sockets[] = { -1, -1 }; -#define zygiskd_socket zygiskd_sockets[is_64_bit] -static void connect_companion(int client, bool is_64_bit) { - mutex_guard g(zygiskd_lock); + // ack + write_int(socket, 0); - if (zygiskd_socket >= 0) { - // Make sure the socket is still valid - pollfd pfd = { zygiskd_socket, 0, 0 }; - poll(&pfd, 1, 0); - if (pfd.revents) { - // Any revent means error - close(zygiskd_socket); - zygiskd_socket = -1; + // Start accepting requests + pollfd pfd = { socket, POLLIN, 0 }; + for (;;) { + poll(&pfd, 1, -1); + if (pfd.revents && !(pfd.revents & POLLIN)) { + // Something bad happened in magiskd, terminate zygiskd + exit(0); } - } - if (zygiskd_socket < 0) { - int fds[2]; - socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds); - zygiskd_socket = fds[0]; - if (fork_dont_care() == 0) { - char exe[64]; -#if defined(__LP64__) - ssprintf(exe, sizeof(exe), "%s/magisk%s", get_magisk_tmp(), (is_64_bit ? "" : "32")); -#else - ssprintf(exe, sizeof(exe), "%s/magisk", get_magisk_tmp()); -#endif - // This fd has to survive exec - fcntl(fds[1], F_SETFD, 0); - char buf[16]; - ssprintf(buf, sizeof(buf), "%d", fds[1]); - execl(exe, "", "zygisk", "companion", buf, (char *) nullptr); - exit(-1); + int client = recv_fd(socket); + if (client < 0) { + // Something bad happened in magiskd, terminate zygiskd + exit(0); } - close(fds[1]); - vector module_fds = get_module_fds(is_64_bit); - send_fds(zygiskd_socket, module_fds.data(), module_fds.size()); - // Wait for ack - if (read_int(zygiskd_socket) != 0) { - LOGE("zygiskd startup error\n"); - return; + int module_id = read_int(client); + if (module_id >= 0 && module_id < modules.size() && modules[module_id]) { + exec_task([=, entry = modules[module_id]] { + struct stat s1; + fstat(client, &s1); + entry(client); + // Only close client if it is the same file so we don't + // accidentally close a re-used file descriptor. + // This check is required because the module companion + // handler could've closed the file descriptor already. + if (struct stat s2; fstat(client, &s2) == 0) { + if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) { + close(client); + } + } + }); + } else { + close(client); } } - send_fd(zygiskd_socket, client); } -static void get_process_info(int client, const sock_cred *cred) { - int uid = read_int(client); - string process = read_string(client); - int arch = read_int(client); - auto &daemon = MagiskD(); - - uint32_t flags = 0; - - if (is_deny_target(uid, process)) { - flags |= PROCESS_ON_DENYLIST; - } - if (daemon.get_manager(to_user_id(uid), nullptr, false) == uid) { - flags |= PROCESS_IS_MAGISK_APP; - } - if (denylist_enforced) { - flags |= DENYLIST_ENFORCING; - } - if (daemon.uid_granted_root(uid)) { - flags |= PROCESS_GRANTED_ROOT; - } - - xwrite(client, &flags, sizeof(flags)); - - if (should_load_modules(flags)) { - vector fds = get_module_fds(arch); - send_fds(client, fds.data(), fds.size()); - } - - if (uid != 1000 || process != "system_server") - return; - - // Collect module status from system_server - int slots = read_int(client); - dynamic_bitset bits; - for (int i = 0; i < slots; ++i) { - dynamic_bitset::slot_type l = 0; - xxread(client, &l, sizeof(l)); - bits.emplace_back(l); - } - for (int id = 0; id < module_list->size(); ++id) { - if (!as_const(bits)[id]) { - // Either not a zygisk module, or incompatible - char buf[4096]; - ssprintf(buf, sizeof(buf), MODULEROOT "/%s/zygisk", - module_list->operator[](id).name.data()); - if (int dirfd = open(buf, O_RDONLY | O_CLOEXEC); dirfd >= 0) { - close(xopenat(dirfd, "unloaded", O_CREAT | O_RDONLY, 0644)); - close(dirfd); - } - } +// Entrypoint where we need to re-exec ourselves +// This should only ever be called internally +int zygisk_main(int argc, char *argv[]) { + android_logging(); + if (argc == 3 && argv[1] == "companion"sv) { + zygiskd(parse_int(argv[2])); } + return 0; } -static void get_moddir(int client) { - int id = read_int(client); - char buf[4096]; - ssprintf(buf, sizeof(buf), MODULEROOT "/%s", module_list->operator[](id).name.data()); - int dfd = xopen(buf, O_RDONLY | O_CLOEXEC); - send_fd(client, dfd); - close(dfd); -} - -void zygisk_handler(int client, const sock_cred *cred) { - int code = read_int(client); - switch (code) { - case ZygiskRequest::GET_INFO: - get_process_info(client, cred); - break; - case ZygiskRequest::CONNECT_COMPANION: { - int arch = read_int(client); - connect_companion(client, arch); - break; - } - case ZygiskRequest::GET_MODDIR: - get_moddir(client); - break; - default: - // Unknown code - break; - } - close(client); -} +// Entrypoint of code injection +extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf { + .version = 2, + .padding = {}, + .isCompatibleWith = [](auto) { + zygisk_logging(); + hook_entry(); + ZLOGD("load success\n"); + return false; + }, +}; -void reset_zygisk(bool restore) { - if (!zygisk_enabled) return; - static atomic_uint zygote_start_count{1}; - if (!restore) { - close(zygiskd_sockets[0]); - close(zygiskd_sockets[1]); - zygiskd_sockets[0] = zygiskd_sockets[1] = -1; - } - if (restore) { - zygote_start_count = 1; - } else if (zygote_start_count.fetch_add(1) > 3) { - LOGW("zygote crashes too many times, rolling-back\n"); - restore = true; - } - if (restore) { - string native_bridge_orig = "0"; - if (native_bridge.length() > strlen(ZYGISKLDR)) { - native_bridge_orig = native_bridge.substr(strlen(ZYGISKLDR)); - } - set_prop(NBPROP, native_bridge_orig.data()); - } else { - set_prop(NBPROP, native_bridge.data()); +void restore_zygisk_prop() { + string native_bridge_orig = "0"; + if (native_bridge.length() > strlen(ZYGISKLDR)) { + native_bridge_orig = native_bridge.substr(strlen(ZYGISKLDR)); } + set_prop(NBPROP, native_bridge_orig.data()); } diff --git a/native/src/core/zygisk/main.cpp b/native/src/core/zygisk/main.cpp deleted file mode 100644 index f66a857f7ea8a..0000000000000 --- a/native/src/core/zygisk/main.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include - -#include "zygisk.hpp" - -using namespace std; - -static void zygiskd(int socket) { - if (getuid() != 0 || fcntl(socket, F_GETFD) < 0) - exit(-1); - - init_thread_pool(); - -#if defined(__LP64__) - set_nice_name("zygiskd64"); - LOGI("* Launching zygiskd64\n"); -#else - set_nice_name("zygiskd32"); - LOGI("* Launching zygiskd32\n"); -#endif - - // Load modules - using comp_entry = void(*)(int); - vector modules; - { - vector module_fds = recv_fds(socket); - for (int fd : module_fds) { - comp_entry entry = nullptr; - struct stat s{}; - if (fstat(fd, &s) == 0 && S_ISREG(s.st_mode)) { - android_dlextinfo info { - .flags = ANDROID_DLEXT_USE_LIBRARY_FD, - .library_fd = fd, - }; - if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { - *(void **) &entry = dlsym(h, "zygisk_companion_entry"); - } else { - LOGW("Failed to dlopen zygisk module: %s\n", dlerror()); - } - } - modules.push_back(entry); - close(fd); - } - } - - // ack - write_int(socket, 0); - - // Start accepting requests - pollfd pfd = { socket, POLLIN, 0 }; - for (;;) { - poll(&pfd, 1, -1); - if (pfd.revents && !(pfd.revents & POLLIN)) { - // Something bad happened in magiskd, terminate zygiskd - exit(0); - } - int client = recv_fd(socket); - if (client < 0) { - // Something bad happened in magiskd, terminate zygiskd - exit(0); - } - int module_id = read_int(client); - if (module_id >= 0 && module_id < modules.size() && modules[module_id]) { - exec_task([=, entry = modules[module_id]] { - struct stat s1; - fstat(client, &s1); - entry(client); - // Only close client if it is the same file so we don't - // accidentally close a re-used file descriptor. - // This check is required because the module companion - // handler could've closed the file descriptor already. - if (struct stat s2; fstat(client, &s2) == 0) { - if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) { - close(client); - } - } - }); - } else { - close(client); - } - } -} - -// Entrypoint where we need to re-exec ourselves -// This should only ever be called internally -int zygisk_main(int argc, char *argv[]) { - android_logging(); - if (argc == 3 && argv[1] == "companion"sv) { - zygiskd(parse_int(argv[2])); - } - return 0; -} diff --git a/native/src/core/zygisk/mod.rs b/native/src/core/zygisk/mod.rs new file mode 100644 index 0000000000000..aca428a64dea5 --- /dev/null +++ b/native/src/core/zygisk/mod.rs @@ -0,0 +1,3 @@ +mod daemon; + +pub use daemon::zygisk_should_load_module; diff --git a/native/src/core/zygisk/module.cpp b/native/src/core/zygisk/module.cpp index 5e26204f93707..bde9c8828876d 100644 --- a/native/src/core/zygisk/module.cpp +++ b/native/src/core/zygisk/module.cpp @@ -77,11 +77,11 @@ bool ZygiskModule::valid() const { } int ZygiskModule::connectCompanion() const { - if (int fd = zygisk_request(ZygiskRequest::CONNECT_COMPANION); fd >= 0) { + if (int fd = zygisk_request(+ZygiskRequest::ConnectCompanion); fd >= 0) { #ifdef __LP64__ - write_int(fd, 1); + write_any(fd, true); #else - write_int(fd, 0); + write_any(fd, false); #endif write_int(fd, id); return fd; @@ -90,11 +90,9 @@ int ZygiskModule::connectCompanion() const { } int ZygiskModule::getModuleDir() const { - if (int fd = zygisk_request(ZygiskRequest::GET_MODDIR); fd >= 0) { + if (owned_fd fd = zygisk_request(+ZygiskRequest::GetModDir); fd >= 0) { write_int(fd, id); - int dfd = recv_fd(fd); - close(fd); - return dfd; + return recv_fd(fd); } return -1; } @@ -210,6 +208,24 @@ bool ZygiskContext::plt_hook_commit() { // ----------------------------------------------------------------- +int ZygiskContext::get_module_info(int uid, rust::Vec &fds) { + if (int fd = zygisk_request(+ZygiskRequest::GetInfo); fd >= 0) { + write_int(fd, uid); + write_string(fd, process); +#ifdef __LP64__ + write_any(fd, true); +#else + write_any(fd, false); +#endif + xxread(fd, &info_flags, sizeof(info_flags)); + if (zygisk_should_load_module(info_flags)) { + fds = recv_fds(fd); + } + return fd; + } + return -1; +} + void ZygiskContext::sanitize_fds() { zygisk_close_logd(); @@ -315,16 +331,17 @@ void ZygiskContext::fork_post() { sigmask(SIG_UNBLOCK, SIGCHLD); } -void ZygiskContext::run_modules_pre(const vector &fds) { +void ZygiskContext::run_modules_pre(rust::Vec &fds) { for (int i = 0; i < fds.size(); ++i) { + owned_fd fd = fds[i]; struct stat s{}; - if (fstat(fds[i], &s) != 0 || !S_ISREG(s.st_mode)) { - close(fds[i]); + if (fstat(fd, &s) != 0 || !S_ISREG(s.st_mode)) { + fds[i] = -1; continue; } android_dlextinfo info { .flags = ANDROID_DLEXT_USE_LIBRARY_FD, - .library_fd = fds[i], + .library_fd = fd, }; if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { if (void *e = dlsym(h, "zygisk_module_entry")) { @@ -332,8 +349,8 @@ void ZygiskContext::run_modules_pre(const vector &fds) { } } else if (flags & SERVER_FORK_AND_SPECIALIZE) { ZLOGW("Failed to dlopen zygisk module: %s\n", dlerror()); + fds[i] = -1; } - close(fds[i]); } for (auto it = modules.begin(); it != modules.end();) { @@ -369,20 +386,19 @@ void ZygiskContext::run_modules_post() { void ZygiskContext::app_specialize_pre() { flags |= APP_SPECIALIZE; - vector module_fds; - int fd = remote_get_info(args.app->uid, process, &info_flags, module_fds); + rust::Vec module_fds; + owned_fd fd = get_module_info(args.app->uid, module_fds); if ((info_flags & UNMOUNT_MASK) == UNMOUNT_MASK) { ZLOGI("[%s] is on the denylist\n", process); flags |= DO_REVERT_UNMOUNT; } else if (fd >= 0) { run_modules_pre(module_fds); } - close(fd); } void ZygiskContext::app_specialize_post() { run_modules_post(); - if (info_flags & PROCESS_IS_MAGISK_APP) { + if (info_flags & +ZygiskStateFlags::ProcessIsMagiskApp) { setenv("ZYGISK_ENABLED", "1", 1); } @@ -391,25 +407,22 @@ void ZygiskContext::app_specialize_post() { } void ZygiskContext::server_specialize_pre() { - vector module_fds; - int fd = remote_get_info(1000, "system_server", &info_flags, module_fds); - if (fd >= 0) { + rust::Vec module_fds; + if (owned_fd fd = get_module_info(1000, module_fds); fd >= 0) { if (module_fds.empty()) { write_int(fd, 0); } else { run_modules_pre(module_fds); - // Send the bitset of module status back to magiskd from system_server - dynamic_bitset bits; - for (const auto &m : modules) - bits[m.getId()] = true; - write_int(fd, static_cast(bits.slots())); - for (int i = 0; i < bits.slots(); ++i) { - auto l = bits.get_slot(i); - xwrite(fd, &l, sizeof(l)); + // Find all failed module ids and send it back to magiskd + vector failed_ids; + for (int i = 0; i < module_fds.size(); ++i) { + if (module_fds[i] < 0) { + failed_ids.push_back(i); + } } + write_vector(fd, failed_ids); } - close(fd); } } @@ -435,6 +448,7 @@ void ZygiskContext::nativeSpecializeAppProcess_post() { void ZygiskContext::nativeForkSystemServer_pre() { ZLOGV("pre forkSystemServer\n"); flags |= SERVER_FORK_AND_SPECIALIZE; + process = "system_server"; fork_pre(); if (is_child()) { diff --git a/native/src/core/zygisk/module.hpp b/native/src/core/zygisk/module.hpp index fcc292abe9355..a686a202024c5 100644 --- a/native/src/core/zygisk/module.hpp +++ b/native/src/core/zygisk/module.hpp @@ -122,15 +122,13 @@ struct module_abi_v1 { void (*postServerSpecialize)(void *, const void *); }; -enum : uint32_t { - PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT, - PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST, - - DENYLIST_ENFORCING = (1u << 30), - PROCESS_IS_MAGISK_APP = (1u << 31), +// Assert the flag values to be the same as the public API +static_assert(+ZygiskStateFlags::ProcessGrantedRoot == zygisk::StateFlag::PROCESS_GRANTED_ROOT); +static_assert(+ZygiskStateFlags::ProcessOnDenyList == zygisk::StateFlag::PROCESS_ON_DENYLIST); - UNMOUNT_MASK = (PROCESS_ON_DENYLIST | DENYLIST_ENFORCING), - PRIVATE_MASK = (DENYLIST_ENFORCING | PROCESS_IS_MAGISK_APP) +enum : uint32_t { + UNMOUNT_MASK = (+ZygiskStateFlags::ProcessOnDenyList | +ZygiskStateFlags::DenyListEnforced), + PRIVATE_MASK = (+ZygiskStateFlags::DenyListEnforced | +ZygiskStateFlags::ProcessIsMagiskApp) }; struct api_abi_base { @@ -188,7 +186,6 @@ struct ZygiskModule { static uint32_t getFlags(); void tryUnload() const; void clearApi() { memset(&api, 0, sizeof(api)); } - int getId() const { return id; } ZygiskModule(int id, void *handle, void *entry); @@ -264,7 +261,7 @@ struct ZygiskContext { ZygiskContext(JNIEnv *env, void *args); ~ZygiskContext(); - void run_modules_pre(const std::vector &fds); + void run_modules_pre(rust::Vec &fds); void run_modules_post(); DCL_PRE_POST(fork) DCL_PRE_POST(app_specialize) @@ -273,6 +270,7 @@ struct ZygiskContext { DCL_PRE_POST(nativeSpecializeAppProcess) DCL_PRE_POST(nativeForkSystemServer) + int get_module_info(int uid, rust::Vec &fds); void sanitize_fds(); bool exempt_fd(int fd); bool can_exempt_fd() const; diff --git a/native/src/core/zygisk/zygisk.hpp b/native/src/core/zygisk/zygisk.hpp index 939a05fe2f60c..4cfad8fd4a1e9 100644 --- a/native/src/core/zygisk/zygisk.hpp +++ b/native/src/core/zygisk/zygisk.hpp @@ -6,15 +6,6 @@ #include #include -namespace ZygiskRequest { -enum : int { - GET_INFO, - CONNECT_COMPANION, - GET_MODDIR, - END -}; -} - #if defined(__LP64__) #define ZLOGD(...) LOGD("zygisk64: " __VA_ARGS__) #define ZLOGE(...) LOGE("zygisk64: " __VA_ARGS__) @@ -34,8 +25,6 @@ enum : int { void hook_entry(); void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods); -int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector &fds); - inline int zygisk_request(int req) { int fd = connect_daemon(+RequestCode::ZYGISK); if (fd < 0) return fd; diff --git a/native/src/external/cxx-rs b/native/src/external/cxx-rs index 08cb6347041a7..0ea6d507cee40 160000 --- a/native/src/external/cxx-rs +++ b/native/src/external/cxx-rs @@ -1 +1 @@ -Subproject commit 08cb6347041a757b1ad7f44abac56ffd8540dc71 +Subproject commit 0ea6d507cee40da9581b71bd531a4e3ac0a37164 diff --git a/native/src/include/consts.rs b/native/src/include/consts.rs index 8c4f31663e3be..7853d06c41458 100644 --- a/native/src/include/consts.rs +++ b/native/src/include/consts.rs @@ -13,7 +13,7 @@ pub const APP_PACKAGE_NAME: &str = "com.topjohnwu.magisk"; pub const LOGFILE: &str = "/cache/magisk.log"; // data paths -const SECURE_DIR: &str = "/data/adb"; +pub const SECURE_DIR: &str = "/data/adb"; pub const MODULEROOT: &str = concatcp!(SECURE_DIR, "/modules"); // tmpfs paths diff --git a/native/src/init/lib.rs b/native/src/init/lib.rs index 00ec0c1c54971..fc50dc54fd40a 100644 --- a/native/src/init/lib.rs +++ b/native/src/init/lib.rs @@ -1,9 +1,10 @@ #![feature(format_args_nl)] #![feature(once_cell_try)] +#![feature(try_blocks)] use logging::setup_klog; use mount::{is_device_mounted, switch_root}; -use rootdir::{inject_magisk_rc, collect_overlay_contexts, reset_overlay_contexts}; +use rootdir::{collect_overlay_contexts, inject_magisk_rc, reset_overlay_contexts}; // Has to be pub so all symbols in that crate is included pub use magiskpolicy; diff --git a/native/src/init/mount.rs b/native/src/init/mount.rs index 147465e22219f..b23d8653f1114 100644 --- a/native/src/init/mount.rs +++ b/native/src/init/mount.rs @@ -14,7 +14,7 @@ use base::{ }; pub fn switch_root(path: &Utf8CStr) { - fn inner(path: &Utf8CStr) -> LoggedResult<()> { + let res: LoggedResult<()> = try { debug!("Switch root to {}", path); let mut mounts = BTreeSet::new(); let mut rootfs = Directory::open(cstr!("/"))?; @@ -55,9 +55,8 @@ pub fn switch_root(path: &Utf8CStr) { debug!("Cleaning rootfs"); rootfs.remove_all()?; - Ok(()) - } - inner(path).ok(); + }; + res.ok(); } pub fn is_device_mounted(dev: u64, target: Pin<&mut CxxString>) -> bool { diff --git a/native/src/init/selinux.cpp b/native/src/init/selinux.cpp index c7c1ade76c822..8a4a0e0fb4a7a 100644 --- a/native/src/init/selinux.cpp +++ b/native/src/init/selinux.cpp @@ -9,19 +9,19 @@ using namespace std; void MagiskInit::patch_sepolicy(const char *in, const char *out) { LOGD("Patching monolithic policy\n"); - auto sepol = unique_ptr(sepolicy::from_file(in)); + auto sepol = SePolicy::from_file(in); - sepol->magisk_rules(); + sepol.magisk_rules(); // Custom rules auto rule = "/data/" PREINITMIRR "/sepolicy.rule"; if (xaccess(rule, R_OK) == 0) { LOGD("Loading custom sepolicy patch: [%s]\n", rule); - sepol->load_rule_file(rule); + sepol.load_rule_file(rule); } LOGD("Dumping sepolicy to: [%s]\n", out); - sepol->to_file(out); + sepol.to_file(out); // Remove OnePlus stupid debug sepolicy and use our own if (access("/sepolicy_debug", F_OK) == 0) { @@ -124,12 +124,12 @@ bool MagiskInit::hijack_sepolicy() { xumount2(SELINUX_ENFORCE, MNT_DETACH); // Load and patch policy - auto sepol = unique_ptr(sepolicy::from_file(MOCK_LOAD)); - sepol->magisk_rules(); - sepol->load_rules(rules); + auto sepol = SePolicy::from_file(MOCK_LOAD); + sepol.magisk_rules(); + sepol.load_rules(rules); // Load patched policy into kernel - sepol->to_file(SELINUX_LOAD); + sepol.to_file(SELINUX_LOAD); // restore mounted files' context after sepolicy loaded rust::reset_overlay_contexts(); diff --git a/native/src/sepolicy/api.cpp b/native/src/sepolicy/api.cpp index a25dae883b13e..73156331fa4d3 100644 --- a/native/src/sepolicy/api.cpp +++ b/native/src/sepolicy/api.cpp @@ -1,8 +1,10 @@ #include -#include "policy.hpp" +#include "include/sepolicy.hpp" using Str = rust::Str; +using StrVec = rust::Vec; +using Xperms = rust::Vec; #if 0 template @@ -27,26 +29,6 @@ static void print_rule(const char *action, Args ...args) { #define print_rule(...) ((void) 0) #endif -bool sepolicy::exists(const char *type) { - return hashtab_search(impl->db->p_types.table, type) != nullptr; -} - -void sepolicy::load_rule_file(const char *file) { - rust::load_rule_file(*this, file); -} - -void sepolicy::parse_statement(const char *data) { - rust::parse_statement(*this, data); -} - -void sepolicy::magisk_rules() { - rust::magisk_rules(*this); -} - -void sepolicy::load_rules(const std::string &rules) { - rust::load_rules(*this, byte_view(rules, false)); -} - template requires(std::invocable) static inline void expand(F &&f, T &&...args) { @@ -80,70 +62,70 @@ static inline void expand(const Xperms &vec, T &&...args) { } } -void sepolicy::allow(StrVec src, StrVec tgt, StrVec cls, StrVec perm) { +void SePolicy::allow(StrVec src, StrVec tgt, StrVec cls, StrVec perm) noexcept { expand(src, tgt, cls, perm, [this](auto ...args) { print_rule("allow", args...); impl->add_rule(args..., AVTAB_ALLOWED, false); }); } -void sepolicy::deny(StrVec src, StrVec tgt, StrVec cls, StrVec perm) { +void SePolicy::deny(StrVec src, StrVec tgt, StrVec cls, StrVec perm) noexcept { expand(src, tgt, cls, perm, [this](auto ...args) { print_rule("deny", args...); impl->add_rule(args..., AVTAB_ALLOWED, true); }); } -void sepolicy::auditallow(StrVec src, StrVec tgt, StrVec cls, StrVec perm) { +void SePolicy::auditallow(StrVec src, StrVec tgt, StrVec cls, StrVec perm) noexcept { expand(src, tgt, cls, perm, [this](auto ...args) { print_rule("auditallow", args...); impl->add_rule(args..., AVTAB_AUDITALLOW, false); }); } -void sepolicy::dontaudit(StrVec src, StrVec tgt, StrVec cls, StrVec perm) { +void SePolicy::dontaudit(StrVec src, StrVec tgt, StrVec cls, StrVec perm) noexcept { expand(src, tgt, cls, perm, [this](auto ...args) { print_rule("dontaudit", args...); impl->add_rule(args..., AVTAB_AUDITDENY, true); }); } -void sepolicy::permissive(StrVec types) { +void SePolicy::permissive(StrVec types) noexcept { expand(types, [this](auto ...args) { print_rule("permissive", args...); impl->set_type_state(args..., true); }); } -void sepolicy::enforce(StrVec types) { +void SePolicy::enforce(StrVec types) noexcept { expand(types, [this](auto ...args) { print_rule("enforce", args...); impl->set_type_state(args..., false); }); } -void sepolicy::typeattribute(StrVec types, StrVec attrs) { +void SePolicy::typeattribute(StrVec types, StrVec attrs) noexcept { expand(types, attrs, [this](auto ...args) { print_rule("typeattribute", args...); impl->add_typeattribute(args...); }); } -void sepolicy::type(Str type, StrVec attrs) { +void SePolicy::type(Str type, StrVec attrs) noexcept { expand(type, attrs, [this](auto name, auto attr) { print_rule("type", name, attr); impl->add_type(name, TYPE_TYPE) && impl->add_typeattribute(name, attr); }); } -void sepolicy::attribute(Str name) { +void SePolicy::attribute(Str name) noexcept { expand(name, [this](auto ...args) { print_rule("attribute", args...); impl->add_type(args..., TYPE_ATTRIB); }); } -void sepolicy::type_transition(Str src, Str tgt, Str cls, Str def, Str obj) { +void SePolicy::type_transition(Str src, Str tgt, Str cls, Str def, Str obj) noexcept { expand(src, tgt, cls, def, obj, [this](auto s, auto t, auto c, auto d, auto o) { if (o) { print_rule("type_transition", s, t, c, d, o); @@ -155,42 +137,42 @@ void sepolicy::type_transition(Str src, Str tgt, Str cls, Str def, Str obj) { }); } -void sepolicy::type_change(Str src, Str tgt, Str cls, Str def) { +void SePolicy::type_change(Str src, Str tgt, Str cls, Str def) noexcept { expand(src, tgt, cls, def, [this](auto ...args) { print_rule("type_change", args...); impl->add_type_rule(args..., AVTAB_CHANGE); }); } -void sepolicy::type_member(Str src, Str tgt, Str cls, Str def) { +void SePolicy::type_member(Str src, Str tgt, Str cls, Str def) noexcept { expand(src, tgt, cls, def, [this](auto ...args) { print_rule("type_member", args...); impl->add_type_rule(args..., AVTAB_MEMBER); }); } -void sepolicy::genfscon(Str fs_name, Str path, Str ctx) { +void SePolicy::genfscon(Str fs_name, Str path, Str ctx) noexcept { expand(fs_name, path, ctx, [this](auto ...args) { print_rule("genfscon", args...); impl->add_genfscon(args...); }); } -void sepolicy::allowxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm) { +void SePolicy::allowxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm) noexcept { expand(src, tgt, cls, xperm, [this](auto ...args) { print_rule("allowxperm", args...); impl->add_xperm_rule(args..., AVTAB_XPERMS_ALLOWED); }); } -void sepolicy::auditallowxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm) { +void SePolicy::auditallowxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm) noexcept { expand(src, tgt, cls, xperm, [this](auto ...args) { print_rule("auditallowxperm", args...); impl->add_xperm_rule(args..., AVTAB_XPERMS_AUDITALLOW); }); } -void sepolicy::dontauditxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm) { +void SePolicy::dontauditxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm) noexcept { expand(src, tgt, cls, xperm, [this](auto ...args) { print_rule("dontauditxperm", args...); impl->add_xperm_rule(args..., AVTAB_XPERMS_DONTAUDIT); diff --git a/native/src/sepolicy/include/sepolicy.hpp b/native/src/sepolicy/include/sepolicy.hpp index 582d5a1517304..ef348f2861521 100644 --- a/native/src/sepolicy/include/sepolicy.hpp +++ b/native/src/sepolicy/include/sepolicy.hpp @@ -1,10 +1,12 @@ #pragma once -#include +#include #include #include +#include "../policy-rs.hpp" + // sepolicy paths #define PLAT_POLICY_DIR "/system/etc/selinux/" #define VEND_POLICY_DIR "/vendor/etc/selinux/" @@ -19,62 +21,3 @@ #define SELINUX_POLICY SELINUX_MNT "/policy" #define SELINUX_LOAD SELINUX_MNT "/load" #define SELINUX_VERSION SELINUX_MNT "/policyvers" - -struct Xperm; - -using StrVec = rust::Vec; -using Xperms = rust::Vec; - -struct sepolicy { - using c_str = const char *; - using Str = rust::Str; - - // Public static factory functions - static sepolicy *from_data(char *data, size_t len); - static sepolicy *from_file(c_str file); - static sepolicy *from_split(); - static sepolicy *compile_split(); - - // External APIs - bool to_file(c_str file); - void load_rules(const std::string &rules); - void load_rule_file(c_str file); - void print_rules(); - void parse_statement(c_str statement); - - // Operation on types - void type(Str type, StrVec attrs); - void attribute(Str names); - void permissive(StrVec types); - void enforce(StrVec types); - void typeattribute(StrVec types, StrVec attrs); - bool exists(c_str type); - - // Access vector rules - void allow(StrVec src, StrVec tgt, StrVec cls, StrVec perm); - void deny(StrVec src, StrVec tgt, StrVec cls, StrVec perm); - void auditallow(StrVec src, StrVec tgt, StrVec cls, StrVec perm); - void dontaudit(StrVec src, StrVec tgt, StrVec cls, StrVec perm); - - // Extended permissions access vector rules - void allowxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm); - void auditallowxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm); - void dontauditxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm); - - // Type rules - void type_transition(Str src, Str tgt, Str cls, Str def, Str obj); - void type_change(Str src, Str tgt, Str cls, Str def); - void type_member(Str src, Str tgt, Str cls, Str def); - - // File system labeling - void genfscon(Str fs_name, Str path, Str ctx); - - // Magisk - void magisk_rules(); - - void strip_dontaudit(); - -protected: - // Prevent anyone from accidentally creating an instance - sepolicy() = default; -}; diff --git a/native/src/sepolicy/lib.rs b/native/src/sepolicy/lib.rs index a2680c705e8b7..1d1f6ebc0497c 100644 --- a/native/src/sepolicy/lib.rs +++ b/native/src/sepolicy/lib.rs @@ -1,113 +1,105 @@ #![feature(format_args_nl)] #![feature(try_blocks)] -use io::Cursor; -use std::fmt::Write; -use std::io; -use std::io::{BufRead, BufReader}; -use std::pin::Pin; - pub use base; use base::libc::{O_CLOEXEC, O_RDONLY}; use base::{BufReadExt, FsPath, LoggedResult, Utf8CStr}; -use statement::{parse_statement, print_statement_help}; +use cxx::CxxString; +use std::fmt::Write; +use std::io::{BufRead, BufReader, Cursor}; -use crate::ffi::sepolicy; +use crate::ffi::SePolicy; mod rules; mod statement; #[cxx::bridge] -mod ffi { +pub mod ffi { struct Xperm { low: u16, high: u16, reset: bool, } + struct SePolicy { + #[cxx_name = "impl"] + _impl: UniquePtr, + } + unsafe extern "C++" { + include!("policy.hpp"); + include!("../base/include/base.hpp"); + #[namespace = "rust"] #[cxx_name = "Utf8CStr"] type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>; - include!("include/sepolicy.hpp"); + type sepol_impl; - type sepolicy; - fn allow(self: Pin<&mut sepolicy>, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>); - fn deny(self: Pin<&mut sepolicy>, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>); - fn auditallow( - self: Pin<&mut sepolicy>, - s: Vec<&str>, - t: Vec<&str>, - c: Vec<&str>, - p: Vec<&str>, - ); - fn dontaudit( - self: Pin<&mut sepolicy>, - s: Vec<&str>, - t: Vec<&str>, - c: Vec<&str>, - p: Vec<&str>, - ); - fn allowxperm( - self: Pin<&mut sepolicy>, - s: Vec<&str>, - t: Vec<&str>, - c: Vec<&str>, - p: Vec, - ); + fn allow(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>); + fn deny(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>); + fn auditallow(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>); + fn dontaudit(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>); + fn allowxperm(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec); fn auditallowxperm( - self: Pin<&mut sepolicy>, + self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec, ); fn dontauditxperm( - self: Pin<&mut sepolicy>, + self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec, ); - fn permissive(self: Pin<&mut sepolicy>, t: Vec<&str>); - fn enforce(self: Pin<&mut sepolicy>, t: Vec<&str>); - fn typeattribute(self: Pin<&mut sepolicy>, t: Vec<&str>, a: Vec<&str>); + fn permissive(self: &mut SePolicy, t: Vec<&str>); + fn enforce(self: &mut SePolicy, t: Vec<&str>); + fn typeattribute(self: &mut SePolicy, t: Vec<&str>, a: Vec<&str>); #[cxx_name = "type"] - fn type_(self: Pin<&mut sepolicy>, t: &str, a: Vec<&str>); - fn attribute(self: Pin<&mut sepolicy>, t: &str); - fn type_transition(self: Pin<&mut sepolicy>, s: &str, t: &str, c: &str, d: &str, o: &str); - fn type_change(self: Pin<&mut sepolicy>, s: &str, t: &str, c: &str, d: &str); - fn type_member(self: Pin<&mut sepolicy>, s: &str, t: &str, c: &str, d: &str); - fn genfscon(self: Pin<&mut sepolicy>, s: &str, t: &str, c: &str); + fn type_(self: &mut SePolicy, t: &str, a: Vec<&str>); + fn attribute(self: &mut SePolicy, t: &str); + fn type_transition(self: &mut SePolicy, s: &str, t: &str, c: &str, d: &str, o: &str); + fn type_change(self: &mut SePolicy, s: &str, t: &str, c: &str, d: &str); + fn type_member(self: &mut SePolicy, s: &str, t: &str, c: &str, d: &str); + fn genfscon(self: &mut SePolicy, s: &str, t: &str, c: &str); #[allow(dead_code)] - fn strip_dontaudit(self: Pin<&mut sepolicy>); + fn strip_dontaudit(self: &mut SePolicy); + + fn print_rules(self: &SePolicy); + fn to_file(self: &SePolicy, file: Utf8CStrRef) -> bool; + + #[Self = SePolicy] + fn from_file(file: Utf8CStrRef) -> SePolicy; + #[Self = SePolicy] + fn from_split() -> SePolicy; + #[Self = SePolicy] + fn compile_split() -> SePolicy; + #[Self = SePolicy] + unsafe fn from_data(data: *mut c_char, len: usize) -> SePolicy; } - #[namespace = "rust"] extern "Rust" { - fn load_rules(sepol: Pin<&mut sepolicy>, rules: &[u8]); - fn load_rule_file(sepol: Pin<&mut sepolicy>, filename: Utf8CStrRef); - fn parse_statement(sepol: Pin<&mut sepolicy>, statement: Utf8CStrRef); - fn magisk_rules(sepol: Pin<&mut sepolicy>); + fn parse_statement(self: &mut SePolicy, statement: Utf8CStrRef); + fn magisk_rules(self: &mut SePolicy); + fn load_rule_file(self: &mut SePolicy, filename: Utf8CStrRef); + fn load_rules(self: &mut SePolicy, rules: &CxxString); + #[Self = SePolicy] fn xperm_to_string(perm: &Xperm) -> String; + #[Self = SePolicy] fn print_statement_help(); } } -trait SepolicyExt { - fn load_rules(self: Pin<&mut Self>, rules: &[u8]); - fn load_rule_file(self: Pin<&mut Self>, filename: &Utf8CStr); - fn load_rules_from_reader(self: Pin<&mut Self>, reader: &mut T); -} - -impl SepolicyExt for sepolicy { - fn load_rules(self: Pin<&mut sepolicy>, rules: &[u8]) { - let mut cursor = Cursor::new(rules); +impl SePolicy { + fn load_rules(self: &mut SePolicy, rules: &CxxString) { + let mut cursor = Cursor::new(rules.as_bytes()); self.load_rules_from_reader(&mut cursor); } - fn load_rule_file(self: Pin<&mut sepolicy>, filename: &Utf8CStr) { + pub fn load_rule_file(self: &mut SePolicy, filename: &Utf8CStr) { let result: LoggedResult<()> = try { let file = FsPath::from(filename).open(O_RDONLY | O_CLOEXEC)?; let mut reader = BufReader::new(file); @@ -116,40 +108,24 @@ impl SepolicyExt for sepolicy { result.ok(); } - fn load_rules_from_reader(mut self: Pin<&mut sepolicy>, reader: &mut T) { + fn load_rules_from_reader(self: &mut SePolicy, reader: &mut T) { reader.foreach_lines(|line| { - parse_statement(self.as_mut(), line); + self.parse_statement(line); true }); } -} - -fn load_rule_file(sepol: Pin<&mut sepolicy>, filename: &Utf8CStr) { - sepol.load_rule_file(filename); -} -fn load_rules(sepol: Pin<&mut sepolicy>, rules: &[u8]) { - sepol.load_rules(rules); -} - -trait SepolicyMagisk { - fn magisk_rules(self: Pin<&mut Self>); -} - -fn magisk_rules(sepol: Pin<&mut sepolicy>) { - sepol.magisk_rules(); -} - -fn xperm_to_string(perm: &ffi::Xperm) -> String { - let mut s = String::new(); - if perm.reset { - s.push('~'); - } - if perm.low == perm.high { - s.write_fmt(format_args!("{{ {:#06X} }}", perm.low)).ok(); - } else { - s.write_fmt(format_args!("{{ {:#06X}-{:#06X} }}", perm.low, perm.high)) - .ok(); + fn xperm_to_string(perm: &ffi::Xperm) -> String { + let mut s = String::new(); + if perm.reset { + s.push('~'); + } + if perm.low == perm.high { + s.write_fmt(format_args!("{{ {:#06X} }}", perm.low)).ok(); + } else { + s.write_fmt(format_args!("{{ {:#06X}-{:#06X} }}", perm.low, perm.high)) + .ok(); + } + s } - s } diff --git a/native/src/sepolicy/main.cpp b/native/src/sepolicy/main.cpp index 1c30da3770acb..54b5c1fec5fdc 100644 --- a/native/src/sepolicy/main.cpp +++ b/native/src/sepolicy/main.cpp @@ -1,7 +1,7 @@ #include #include -#include "policy.hpp" +#include "include/sepolicy.hpp" using namespace std; @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { cmdline_logging(); const char *out_file = nullptr; vector rule_files; - sepolicy *sepol = nullptr; + SePolicy sepol; bool magisk = false; bool live = false; bool print = false; @@ -56,21 +56,21 @@ int main(int argc, char *argv[]) { else if (option == "load"sv) { if (argv[i + 1] == nullptr) usage(argv[0]); - sepol = sepolicy::from_file(argv[i + 1]); - if (!sepol) { + sepol = SePolicy::from_file(argv[i + 1]); + if (!sepol.impl) { fprintf(stderr, "Cannot load policy from %s\n", argv[i + 1]); return 1; } ++i; } else if (option == "load-split"sv) { - sepol = sepolicy::from_split(); - if (!sepol) { + sepol = SePolicy::from_split(); + if (!sepol.impl) { fprintf(stderr, "Cannot load split cil\n"); return 1; } } else if (option == "compile-split"sv) { - sepol = sepolicy::compile_split(); - if (!sepol) { + sepol = SePolicy::compile_split(); + if (!sepol.impl) { fprintf(stderr, "Cannot compile split cil\n"); return 1; } @@ -85,7 +85,7 @@ int main(int argc, char *argv[]) { rule_files.emplace_back(argv[i + 1]); ++i; } else if (option == "help"sv) { - rust::print_statement_help(); + SePolicy::print_statement_help(); exit(0); } else { usage(argv[0]); @@ -96,36 +96,35 @@ int main(int argc, char *argv[]) { } // Use current policy if nothing is loaded - if (sepol == nullptr && !(sepol = sepolicy::from_file(SELINUX_POLICY))) { + if (!sepol.impl && !(sepol = SePolicy::from_file(SELINUX_POLICY)).impl) { fprintf(stderr, "Cannot load policy from " SELINUX_POLICY "\n"); return 1; } if (print) { - sepol->print_rules(); + sepol.print_rules(); return 0; } if (magisk) - sepol->magisk_rules(); + sepol.magisk_rules(); if (!rule_files.empty()) for (const auto &rule_file : rule_files) - sepol->load_rule_file(rule_file.data()); + sepol.load_rule_file(rule_file.data()); for (; i < argc; ++i) - sepol->parse_statement(argv[i]); + sepol.parse_statement(argv[i]); - if (live && !sepol->to_file(SELINUX_LOAD)) { + if (live && !sepol.to_file(SELINUX_LOAD)) { fprintf(stderr, "Cannot apply policy\n"); return 1; } - if (out_file && !sepol->to_file(out_file)) { + if (out_file && !sepol.to_file(out_file)) { fprintf(stderr, "Cannot dump policy to %s\n", out_file); return 1; } - delete sepol; return 0; } diff --git a/native/src/sepolicy/policy.hpp b/native/src/sepolicy/policy.hpp index ea082ed063e33..41627aba62d04 100644 --- a/native/src/sepolicy/policy.hpp +++ b/native/src/sepolicy/policy.hpp @@ -6,11 +6,10 @@ #include #include -#include -#include "policy-rs.hpp" +struct Xperm; -struct sepol_impl : public sepolicy { +class sepol_impl { avtab_ptr_t find_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms); avtab_ptr_t insert_avtab_node(avtab_key_t *key); avtab_ptr_t get_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms); @@ -30,13 +29,13 @@ struct sepol_impl : public sepolicy { void add_typeattribute(type_datum_t *type, type_datum_t *attr); bool add_typeattribute(const char *type, const char *attr); - sepol_impl(policydb *db) : db(db) {} - ~sepol_impl(); - policydb *db; -private: std::map> class_perm_names; -}; -#define impl reinterpret_cast(this) + friend struct SePolicy; + +public: + sepol_impl(policydb *db) : db(db) {} + ~sepol_impl(); +}; diff --git a/native/src/sepolicy/policydb.cpp b/native/src/sepolicy/policydb.cpp index c23b387c2a59b..78f75f5aae1f2 100644 --- a/native/src/sepolicy/policydb.cpp +++ b/native/src/sepolicy/policydb.cpp @@ -1,3 +1,5 @@ +#include "include/sepolicy.hpp" + #include #include #include @@ -8,7 +10,6 @@ #include #include -#include "policy.hpp" #define SHALEN 64 static bool cmp_sha256(const char *a, const char *b) { @@ -78,7 +79,7 @@ static void load_cil(struct cil_db *db, const char *file) { LOGD("cil_add [%s]\n", file); } -sepolicy *sepolicy::from_data(char *data, size_t len) { +SePolicy SePolicy::from_data(char *data, size_t len) noexcept { LOGD("Load policy from data\n"); policy_file_t pf; @@ -91,34 +92,32 @@ sepolicy *sepolicy::from_data(char *data, size_t len) { if (policydb_init(db) || policydb_read(db, &pf, 0)) { LOGE("Fail to load policy from data\n"); free(db); - return nullptr; + return {}; } - auto sepol = new sepol_impl(db); - return sepol; + return {std::make_unique(db)}; } -sepolicy *sepolicy::from_file(const char *file) { - LOGD("Load policy from: %s\n", file); +SePolicy SePolicy::from_file(::rust::Utf8CStr file) noexcept { + LOGD("Load policy from: %.*s\n", static_cast(file.size()), file.data()); policy_file_t pf; policy_file_init(&pf); - auto fp = xopen_file(file, "re"); + auto fp = xopen_file(file.data(), "re"); pf.fp = fp.get(); pf.type = PF_USE_STDIO; auto db = static_cast(malloc(sizeof(policydb_t))); if (policydb_init(db) || policydb_read(db, &pf, 0)) { - LOGE("Fail to load policy from %s\n", file); + LOGE("Fail to load policy from %.*s\n", static_cast(file.size()), file.data()); free(db); - return nullptr; + return {}; } - auto sepol = new sepol_impl(db); - return sepol; + return {std::make_unique(db)}; } -sepolicy *sepolicy::compile_split() { +SePolicy SePolicy::compile_split() noexcept { char path[128], plat_ver[10]; cil_db_t *db = nullptr; sepol_policydb_t *pdb = nullptr; @@ -209,23 +208,21 @@ sepolicy *sepolicy::compile_split() { load_cil(db, cil_file); if (cil_compile(db)) - return nullptr; + return {}; if (cil_build_policydb(db, &pdb)) - return nullptr; - - auto sepol = new sepol_impl(&pdb->p); - return sepol; + return {}; + return {std::make_unique(&pdb->p)}; } -sepolicy *sepolicy::from_split() { +SePolicy SePolicy::from_split() noexcept { const char *odm_pre = ODM_POLICY_DIR "precompiled_sepolicy"; const char *vend_pre = VEND_POLICY_DIR "precompiled_sepolicy"; if (access(odm_pre, R_OK) == 0 && check_precompiled(odm_pre)) - return sepolicy::from_file(odm_pre); + return SePolicy::from_file(odm_pre); else if (access(vend_pre, R_OK) == 0 && check_precompiled(vend_pre)) - return sepolicy::from_file(vend_pre); + return SePolicy::from_file(vend_pre); else - return sepolicy::compile_split(); + return SePolicy::compile_split(); } sepol_impl::~sepol_impl() { @@ -233,7 +230,7 @@ sepol_impl::~sepol_impl() { free(db); } -bool sepolicy::to_file(const char *file) { +bool SePolicy::to_file(::rust::Utf8CStr file) const noexcept { // No partial writes are allowed to /sys/fs/selinux/load, thus the reason why we // first dump everything into memory, then directly call write system call heap_data data; @@ -248,7 +245,7 @@ bool sepolicy::to_file(const char *file) { return false; } - int fd = xopen(file, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); + int fd = xopen(file.data(), O_WRONLY | O_CREAT | O_CLOEXEC, 0644); if (fd < 0) return false; if (struct stat st{}; xfstat(fd, &st) == 0 && st.st_size > 0) { diff --git a/native/src/sepolicy/rules.rs b/native/src/sepolicy/rules.rs index c4007a197921f..ae9abea4a83ea 100644 --- a/native/src/sepolicy/rules.rs +++ b/native/src/sepolicy/rules.rs @@ -1,6 +1,5 @@ -use crate::{ffi::Xperm, sepolicy, SepolicyMagisk}; +use crate::{ffi::Xperm, SePolicy}; use base::{set_log_level_state, LogLevel}; -use std::pin::Pin; macro_rules! rules { (@args all) => { @@ -38,7 +37,7 @@ macro_rules! rules { }; (@stmt $self:ident) => {}; (@stmt $self:ident $action:ident($($args:tt),*); $($res:tt)*) => { - $self.as_mut().$action($(rules!(@args $args)),*); + $self.$action($(rules!(@args $args)),*); rules!{@stmt $self $($res)* } }; (use $self:ident; $($res:tt)*) => {{ @@ -46,8 +45,8 @@ macro_rules! rules { }}; } -impl SepolicyMagisk for sepolicy { - fn magisk_rules(mut self: Pin<&mut Self>) { +impl SePolicy { + pub fn magisk_rules(&mut self) { // Temp suppress warnings set_log_level_state(LogLevel::Warn, false); rules! { @@ -138,7 +137,7 @@ impl SepolicyMagisk for sepolicy { } #[cfg(any())] - self.as_mut().strip_dontaudit(); + self.strip_dontaudit(); set_log_level_state(LogLevel::Warn, true); } diff --git a/native/src/sepolicy/sepolicy.cpp b/native/src/sepolicy/sepolicy.cpp index 523f9763e6813..5a7af5ac460fc 100644 --- a/native/src/sepolicy/sepolicy.cpp +++ b/native/src/sepolicy/sepolicy.cpp @@ -1,6 +1,6 @@ #include -#include "policy.hpp" +#include "include/sepolicy.hpp" using namespace std; @@ -653,14 +653,14 @@ bool sepol_impl::add_typeattribute(const char *type, const char *attr) { return true; } -void sepolicy::strip_dontaudit() { +void SePolicy::strip_dontaudit() noexcept { avtab_for_each(&impl->db->te_avtab, [=, this](avtab_ptr_t node) { if (node->key.specified == AVTAB_AUDITDENY || node->key.specified == AVTAB_XPERMS_DONTAUDIT) avtab_remove_node(&impl->db->te_avtab, node); }); } -void sepolicy::print_rules() { +void SePolicy::print_rules() const noexcept { hashtab_for_each(impl->db->p_types.table, [&](hashtab_ptr_t node) { type_datum_t *type = auto_cast(node->datum); if (type->flavor == TYPE_ATTRIB) { diff --git a/native/src/sepolicy/statement.rs b/native/src/sepolicy/statement.rs index f5108867d6041..cffbcab4ae958 100644 --- a/native/src/sepolicy/statement.rs +++ b/native/src/sepolicy/statement.rs @@ -1,10 +1,10 @@ use std::fmt::{Display, Formatter, Write}; use std::io::stderr; -use std::{iter::Peekable, pin::Pin, vec::IntoIter}; +use std::{iter::Peekable, vec::IntoIter}; -use base::{error, warn, FmtAdaptor}; use crate::ffi::Xperm; -use crate::sepolicy; +use crate::SePolicy; +use base::{error, warn, FmtAdaptor}; pub enum Token<'a> { AL, @@ -23,7 +23,6 @@ pub enum Token<'a> { TC, TM, GF, - IO, LB, RB, CM, @@ -201,13 +200,22 @@ fn parse_xperms<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec> { Ok(xperms) } +fn match_string<'a>(tokens: &mut Tokens<'a>, pattern: &str) -> ParseResult<'a, ()> { + if let Some(Token::ID(s)) = tokens.next() { + if s == pattern { + return Ok(()); + } + } + Err(ParseError::General) +} + // statement ::= AL sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.allow(s, t, c, p); }; // statement ::= DN sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.deny(s, t, c, p); }; // statement ::= AA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.auditallow(s, t, c, p); }; // statement ::= DA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.dontaudit(s, t, c, p); }; -// statement ::= AX sterm(s) sterm(t) sterm(c) IO xperms(p) { sepolicy.allowxperm(s, t, c, p); }; -// statement ::= AY sterm(s) sterm(t) sterm(c) IO xperms(p) { sepolicy.auditallowxperm(s, t, c, p); }; -// statement ::= DX sterm(s) sterm(t) sterm(c) IO xperms(p) { sepolicy.dontauditxperm(s, t, c, p); }; +// statement ::= AX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.allowxperm(s, t, c, p); }; +// statement ::= AY sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.auditallowxperm(s, t, c, p); }; +// statement ::= DX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.dontauditxperm(s, t, c, p); }; // statement ::= PM sterm(t) { sepolicy.permissive(t); }; // statement ::= EF sterm(t) { sepolicy.enforce(t); }; // statement ::= TA term(t) term(a) { sepolicy.typeattribute(t, a); }; @@ -219,10 +227,7 @@ fn parse_xperms<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec> { // statement ::= TC ID(s) ID(t) ID(c) ID(d) { sepolicy.type_change(s, t, c, d); }; // statement ::= TM ID(s) ID(t) ID(c) ID(d) { sepolicy.type_member(s, t, c, d);}; // statement ::= GF ID(s) ID(t) ID(c) { sepolicy.genfscon(s, t, c); }; -fn exec_statement<'a>( - sepolicy: Pin<&mut sepolicy>, - tokens: &mut Tokens<'a>, -) -> ParseResult<'a, ()> { +fn exec_statement<'a>(sepolicy: &mut SePolicy, tokens: &mut Tokens<'a>) -> ParseResult<'a, ()> { let action = match tokens.next() { Some(token) => token, _ => Err(ParseError::ShowHelp)?, @@ -259,11 +264,8 @@ fn exec_statement<'a>( let s = parse_sterm(tokens)?; let t = parse_sterm(tokens)?; let c = parse_sterm(tokens)?; - let p = if matches!(tokens.next(), Some(Token::IO)) { - parse_xperms(tokens)? - } else { - throw!() - }; + match_string(tokens, "ioctl")?; + let p = parse_xperms(tokens)?; check_additional_args(tokens)?; match action { Token::AX => sepolicy.allowxperm(s, t, c, p), @@ -396,7 +398,6 @@ fn extract_token<'a>(s: &'a str, tokens: &mut Vec>) { "type_change" => tokens.push(Token::TC), "type_member" => tokens.push(Token::TM), "genfscon" => tokens.push(Token::GF), - "ioctl" => tokens.push(Token::IO), "*" => tokens.push(Token::ST), "" => {} _ => { @@ -440,16 +441,18 @@ fn tokenize_statement(statement: &str) -> Vec { tokens } -pub fn parse_statement(sepolicy: Pin<&mut sepolicy>, statement: &str) { - let statement = statement.trim(); - if statement.is_empty() || statement.starts_with('#') { - return; - } - let mut tokens = tokenize_statement(statement).into_iter().peekable(); - let result = exec_statement(sepolicy, &mut tokens); - if let Err(e) = result { - warn!("Syntax error in: \"{}\"", statement); - error!("Hint: {}", e); +impl SePolicy { + pub fn parse_statement(self: &mut SePolicy, statement: &str) { + let statement = statement.trim(); + if statement.is_empty() || statement.starts_with('#') { + return; + } + let mut tokens = tokenize_statement(statement).into_iter().peekable(); + let result = exec_statement(self, &mut tokens); + if let Err(e) = result { + warn!("Syntax error in: \"{}\"", statement); + error!("Hint: {}", e); + } } } @@ -473,7 +476,6 @@ impl Display for Token<'_> { Token::TC => f.write_str("type_change"), Token::TM => f.write_str("type_member"), Token::GF => f.write_str("genfscon"), - Token::IO => f.write_str("ioctl"), Token::LB => f.write_char('{'), Token::RB => f.write_char('}'), Token::CM => f.write_char(','), @@ -595,7 +597,9 @@ allowxperm source target class ioctl * ) } -pub fn print_statement_help() { - format_statement_help(&mut FmtAdaptor(&mut stderr())).ok(); - eprintln!(); +impl SePolicy { + pub fn print_statement_help() { + format_statement_help(&mut FmtAdaptor(&mut stderr())).ok(); + eprintln!(); + } }