diff --git a/native/src/core/Cargo.toml b/native/src/core/Cargo.toml index f1f7284d3..8f5d97a32 100644 --- a/native/src/core/Cargo.toml +++ b/native/src/core/Cargo.toml @@ -7,6 +7,10 @@ edition = "2021" crate-type = ["staticlib"] path = "lib.rs" +[features] +default = ["check-signature"] +check-signature = [] + [build-dependencies] cxx-gen = { workspace = true } pb-rs = { workspace = true } diff --git a/native/src/core/cert.rs b/native/src/core/cert.rs deleted file mode 100644 index b91a4d191..000000000 --- a/native/src/core/cert.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::fs::File; -use std::io; -use std::io::{Cursor, Read, Seek, SeekFrom}; -use std::mem::size_of_val; -use std::os::fd::{FromRawFd, RawFd}; - -use base::*; - -const EOCD_MAGIC: u32 = 0x06054B50; -const APK_SIGNING_BLOCK_MAGIC: [u8; 16] = *b"APK Sig Block 42"; -const SIGNATURE_SCHEME_V2_MAGIC: u32 = 0x7109871A; - -macro_rules! bad_apk { - ($msg:literal) => { - io::Error::new(io::ErrorKind::InvalidData, concat!("cert: ", $msg)) - }; -} - -/* - * A v2/v3 signed APK has the format as following - * - * +---------------+ - * | zip content | - * +---------------+ - * | signing block | - * +---------------+ - * | central dir | - * +---------------+ - * | EOCD | - * +---------------+ - * - * Scan from end of file to find EOCD, and figure our way back to the - * offset of the signing block. Next, directly extract the certificate - * from the v2 signature block. - * - * All structures above are mostly just for documentation purpose. - * - * This method extracts the first certificate of the first signer - * within the APK v2 signature block. - */ -pub fn read_certificate(fd: RawFd, version: i32) -> Vec { - fn inner(apk: &mut File, version: i32) -> io::Result> { - let mut u32_val = 0u32; - let mut u64_val = 0u64; - - // Find EOCD - for i in 0u16.. { - let mut comment_sz = 0u16; - apk.seek(SeekFrom::End(-(size_of_val(&comment_sz) as i64) - i as i64))?; - apk.read_pod(&mut comment_sz)?; - - if comment_sz == i { - apk.seek(SeekFrom::Current(-22))?; - let mut magic = 0u32; - apk.read_pod(&mut magic)?; - if magic == EOCD_MAGIC { - break; - } - } - if i == 0xffff { - return Err(bad_apk!("invalid APK format")); - } - } - - // We are now at EOCD + sizeof(magic) - // Seek and read central_dir_off to find the start of the central directory - let mut central_dir_off = 0u32; - apk.seek(SeekFrom::Current(12))?; - apk.read_pod(&mut central_dir_off)?; - - // Code for parse APK comment to get version code - if version >= 0 { - let mut comment_sz = 0u16; - apk.read_pod(&mut comment_sz)?; - let mut comment = vec![0u8; comment_sz as usize]; - apk.read_exact(&mut comment)?; - let mut comment = Cursor::new(&comment); - let mut apk_ver = 0; - comment.foreach_props(|k, v| { - if k == "versionCode" { - apk_ver = v.parse::().unwrap_or(0); - false - } else { - true - } - }); - if version > apk_ver { - return Err(bad_apk!("APK version too low")); - } - } - - // Next, find the start of the APK signing block - apk.seek(SeekFrom::Start((central_dir_off - 24) as u64))?; - apk.read_pod(&mut u64_val)?; // u64_value = block_sz_ - let mut magic = [0u8; 16]; - apk.read_exact(&mut magic)?; - if magic != APK_SIGNING_BLOCK_MAGIC { - return Err(bad_apk!("invalid signing block magic")); - } - let mut signing_blk_sz = 0u64; - apk.seek(SeekFrom::Current( - -(u64_val as i64) - (size_of_val(&signing_blk_sz) as i64), - ))?; - apk.read_pod(&mut signing_blk_sz)?; - if signing_blk_sz != u64_val { - return 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; - } - - let mut id = 0u32; - apk.read_pod(&mut id)?; - if id == SIGNATURE_SCHEME_V2_MAGIC { - // Skip [signer sequence length] + [1st signer length] + [signed data length] - apk.seek(SeekFrom::Current((size_of_val(&u32_val) * 3) as i64))?; - - apk.read_pod(&mut u32_val)?; // digest sequence length - apk.seek(SeekFrom::Current(u32_val as i64))?; // skip all digests - - apk.seek(SeekFrom::Current(size_of_val(&u32_val) as i64))?; // cert sequence length - apk.read_pod(&mut u32_val)?; // 1st cert length - - let mut cert = vec![0; u32_val as usize]; - apk.read_exact(cert.as_mut())?; - return Ok(cert); - } else { - // Skip this id-value pair - apk.seek(SeekFrom::Current( - u64_val as i64 - (size_of_val(&id) as i64), - ))?; - } - } - - Err(bad_apk!("cannot find certificate")) - } - if fd == -1 { - return vec![]; - } - let mut file = unsafe { File::from_raw_fd(fd) }; - let r = inner(&mut file, version).log().unwrap_or(vec![]); - std::mem::forget(file); - r -} diff --git a/native/src/core/daemon.rs b/native/src/core/daemon.rs index 49b80ba6f..abcf65b60 100644 --- a/native/src/core/daemon.rs +++ b/native/src/core/daemon.rs @@ -3,11 +3,9 @@ use crate::db::Sqlite3; use crate::ffi::{get_magisk_tmp, RequestCode}; use crate::get_prop; use crate::logging::{magisk_logging, start_log_daemon}; +use crate::package::ManagerInfo; use base::libc::{O_CLOEXEC, O_RDONLY}; -use base::{ - cstr, info, libc, open_fd, BufReadExt, Directory, FsPath, FsPathBuf, ReadExt, ResultExt, - Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrBufRef, WalkResult, -}; +use base::{cstr, info, libc, open_fd, BufReadExt, FsPath, FsPathBuf, ReadExt, Utf8CStrBufArr}; use bytemuck::bytes_of; use std::fs::File; use std::io; @@ -39,9 +37,16 @@ impl BootStateFlags { } } +pub const AID_USER_OFFSET: i32 = 100000; + +pub const fn to_app_id(uid: i32) -> i32 { + uid % AID_USER_OFFSET +} + #[derive(Default)] pub struct MagiskD { pub sql_connection: Mutex>, + pub manager_info: Mutex, boot_stage_lock: Mutex, sdk_int: i32, pub is_emulator: bool, @@ -57,6 +62,14 @@ impl MagiskD { self.sdk_int } + pub fn app_data_dir(&self) -> &'static str { + if self.sdk_int >= 24 { + "/data/user_de" + } else { + "/data/user" + } + } + pub fn boot_stage_handler(&self, client: i32, code: i32) { // Make sure boot stage execution is always serialized let mut state = self.boot_stage_lock.lock().unwrap(); @@ -183,34 +196,6 @@ pub fn get_magiskd() -> &'static MagiskD { unsafe { MAGISKD.get().unwrap_unchecked() } } -pub fn find_apk_path(pkg: &Utf8CStr, data: &mut [u8]) -> usize { - use WalkResult::*; - fn inner(pkg: &Utf8CStr, buf: &mut dyn Utf8CStrBuf) -> io::Result { - Directory::open(cstr!("/data/app"))?.pre_order_walk(|e| { - if !e.is_dir() { - return Ok(Skip); - } - let d_name = e.d_name().to_bytes(); - if d_name.starts_with(pkg.as_bytes()) && d_name[pkg.len()] == b'-' { - // Found the APK path, we can abort now - e.path(buf)?; - return Ok(Abort); - } - if d_name.starts_with(b"~~") { - return Ok(Continue); - } - Ok(Skip) - })?; - if !buf.is_empty() { - buf.push_str("/base.apk"); - } - Ok(buf.len()) - } - inner(pkg, &mut Utf8CStrBufRef::from(data)) - .log() - .unwrap_or(0) -} - pub trait IpcRead { fn ipc_read_int(&mut self) -> io::Result; fn ipc_read_string(&mut self) -> io::Result; diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index a287f521a..baeafe4b2 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -61,9 +61,7 @@ void su_daemon_handler(int client, const sock_cred *cred); void zygisk_handler(int client, const sock_cred *cred); // Package -void preserve_stub_apk(); std::vector get_app_no_list(); -int get_manager(int user, std::string *pkg = nullptr, bool install = false); void prune_su_access(); // Module stuffs @@ -77,8 +75,6 @@ void exec_module_scripts(const char *stage); 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 install_apk(const char *apk); -void uninstall_pkg(const char *pkg); void clear_pkg(const char *pkg, int user_id); [[noreturn]] void install_module(const char *file); diff --git a/native/src/core/include/daemon.hpp b/native/src/core/include/daemon.hpp index 93c86e307..be73e4b00 100644 --- a/native/src/core/include/daemon.hpp +++ b/native/src/core/include/daemon.hpp @@ -3,6 +3,8 @@ #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(); } diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index d091afde9..def3578cc 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -5,21 +5,20 @@ #![allow(clippy::missing_safety_doc)] use base::Utf8CStr; -use cert::read_certificate; -use daemon::{daemon_entry, find_apk_path, get_magiskd, MagiskD}; +use daemon::{daemon_entry, get_magiskd, MagiskD}; use db::get_default_db_settings; 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 resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop}; use su::get_default_root_settings; -mod cert; #[path = "../include/consts.rs"] mod consts; mod daemon; mod db; mod logging; mod mount; +mod package; mod resetprop; mod su; @@ -75,6 +74,8 @@ pub mod ffi { fn get_magisk_tmp() -> Utf8CStrRef<'static>; #[cxx_name = "resolve_preinit_dir_rs"] fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String; + fn install_apk(apk: Utf8CStrRef); + fn uninstall_pkg(apk: Utf8CStrRef); fn switch_mnt_ns(pid: i32) -> i32; } @@ -160,8 +161,6 @@ pub mod ffi { fn zygisk_close_logd(); fn zygisk_get_logd() -> i32; fn setup_logfile(); - fn find_apk_path(pkg: Utf8CStrRef, data: &mut [u8]) -> usize; - fn read_certificate(fd: i32, version: i32) -> Vec; fn setup_mounts(); fn clean_mounts(); fn find_preinit_device() -> String; @@ -181,6 +180,9 @@ pub mod ffi { fn is_recovery(&self) -> bool; fn sdk_int(&self) -> i32; fn boot_stage_handler(&self, client: i32, code: i32); + fn preserve_stub_apk(&self); + #[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; diff --git a/native/src/core/package.cpp b/native/src/core/package.cpp index da84bcf1e..5abbbd05a 100644 --- a/native/src/core/package.cpp +++ b/native/src/core/package.cpp @@ -5,49 +5,6 @@ #include using namespace std; -using rust::Vec; - -#define ENFORCE_SIGNATURE (!MAGISK_DEBUG) - -// These functions will be called on every single zygote process specialization and su request, -// so performance is absolutely critical. Most operations should either have its result cached -// or simply skipped unless necessary. - -struct file_info { - const string path; - - file_info() = default; - file_info(string path, const struct stat *st) : path(std::move(path)) { - memcpy(×tamp, &st->st_ctim, sizeof(timestamp)); - } - - bool is_same() const { - if (path.empty()) return false; - struct stat st{}; - if (stat(path.data(), &st) != 0) return false; - return timestamp.tv_sec == st.st_ctim.tv_sec && timestamp.tv_nsec == st.st_ctim.tv_nsec; - } -private: - timespec timestamp{}; -}; - -static pthread_mutex_t pkg_lock = PTHREAD_MUTEX_INITIALIZER; -// pkg_lock protects all following variables -static int stub_apk_fd = -1; -static int repackaged_app_id = -1; // Only used by dyn -static string repackaged_pkg; -static Vec repackaged_cert; -static Vec trusted_cert; -static map tracked_files; -enum status { - INSTALLED, - NOT_INSTALLED, - CERT_MISMATCH, -}; - -static bool operator==(const Vec &a, const Vec &b) { - return a.size() == b.size() && memcmp(a.data(), b.data(), a.size()) == 0; -} // app_id = app_no + AID_APP_START // app_no range: [0, 9999] @@ -80,206 +37,3 @@ vector get_app_no_list() { } return list; } - -void preserve_stub_apk() { - mutex_guard g(pkg_lock); - string stub_path = get_magisk_tmp() + "/stub.apk"s; - stub_apk_fd = xopen(stub_path.data(), O_RDONLY | O_CLOEXEC); - unlink(stub_path.data()); - trusted_cert = read_certificate(stub_apk_fd, MAGISK_VER_CODE); - lseek(stub_apk_fd, 0, SEEK_SET); -} - -static void install_stub() { - if (stub_apk_fd < 0) - return; - struct stat st{}; - fstat(stub_apk_fd, &st); - char apk[] = "/data/stub.apk"; - int dfd = xopen(apk, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600); - xsendfile(dfd, stub_apk_fd, nullptr, st.st_size); - lseek(stub_apk_fd, 0, SEEK_SET); - close(dfd); - install_apk(apk); -} - -static status check_dyn(int user, string &pkg) { - struct stat st{}; - char apk[PATH_MAX]; - ssprintf(apk, sizeof(apk), - "%s/%d/%s/dyn/current.apk", APP_DATA_DIR, user, pkg.data()); - int dyn = open(apk, O_RDONLY | O_CLOEXEC); - if (dyn < 0) { - LOGW("pkg: no dyn APK, ignore\n"); - return NOT_INSTALLED; - } - auto cert = read_certificate(dyn, MAGISK_VER_CODE); - fstat(dyn, &st); - close(dyn); - - if (cert.empty() || cert != trusted_cert) { - LOGE("pkg: dyn APK signature mismatch: %s\n", apk); -#if ENFORCE_SIGNATURE - clear_pkg(pkg.data(), user); - return CERT_MISMATCH; -#endif - } - - repackaged_app_id = to_app_id(st.st_uid); - tracked_files.erase(user); - tracked_files.try_emplace(user, apk, &st); - return INSTALLED; -} - -static status check_stub(int user, string &pkg) { - struct stat st{}; - byte_array buf; - find_apk_path(pkg, buf); - string apk((const char *) buf.buf(), buf.sz()); - int fd = xopen(apk.data(), O_RDONLY | O_CLOEXEC); - if (fd < 0) - return NOT_INSTALLED; - auto cert = read_certificate(fd, -1); - fstat(fd, &st); - close(fd); - - if (cert.empty() || (pkg == repackaged_pkg && cert != repackaged_cert)) { - LOGE("pkg: repackaged APK signature invalid: %s\n", apk.data()); - uninstall_pkg(pkg.data()); - return CERT_MISMATCH; - } - - repackaged_pkg.swap(pkg); - repackaged_cert.swap(cert); - tracked_files.erase(user); - tracked_files.try_emplace(user, apk, &st); - - return INSTALLED; -} - -static status check_orig(int user) { - struct stat st{}; - byte_array buf; - find_apk_path(JAVA_PACKAGE_NAME, buf); - string apk((const char *) buf.buf(), buf.sz()); - int fd = xopen(apk.data(), O_RDONLY | O_CLOEXEC); - if (fd < 0) - return NOT_INSTALLED; - auto cert = read_certificate(fd, MAGISK_VER_CODE); - fstat(fd, &st); - close(fd); - - if (cert.empty() || cert != trusted_cert) { - LOGE("pkg: APK signature mismatch: %s\n", apk.data()); -#if ENFORCE_SIGNATURE - uninstall_pkg(JAVA_PACKAGE_NAME); - return CERT_MISMATCH; -#endif - } - - tracked_files.erase(user); - tracked_files.try_emplace(user, apk, &st); - return INSTALLED; -} - -static int get_pkg_uid(int user, const char *pkg) { - char path[PATH_MAX]; - struct stat st{}; - ssprintf(path, sizeof(path), "%s/%d/%s", APP_DATA_DIR, user, pkg); - if (stat(path, &st) == 0) { - return st.st_uid; - } - return -1; -} - -int get_manager(int user, string *pkg, bool install) { - mutex_guard g(pkg_lock); - - string db_pkg = (string) MagiskD().get_db_string(DbEntryKey::SuManager); - - // If database changed, always re-check files - if (db_pkg != repackaged_pkg) { - tracked_files.erase(user); - } - - const auto &file = tracked_files[user]; - if (file.is_same()) { - // no APK - if (file.path == "/data/system/packages.xml") { - if (install) install_stub(); - if (pkg) pkg->clear(); - return -1; - } - // dyn APK is still the same - if (file.path.starts_with(APP_DATA_DIR)) { - if (pkg) *pkg = repackaged_pkg; - return user * AID_USER_OFFSET + repackaged_app_id; - } - // stub APK is still the same - if (!repackaged_pkg.empty()) { - if (check_dyn(user, repackaged_pkg) == INSTALLED) { - if (pkg) *pkg = repackaged_pkg; - return user * AID_USER_OFFSET + repackaged_app_id; - } else { - if (pkg) pkg->clear(); - return -1; - } - } - // orig APK is still the same - int uid = get_pkg_uid(user, JAVA_PACKAGE_NAME); - if (uid < 0) { - if (pkg) pkg->clear(); - return -1; - } else { - if (pkg) *pkg = JAVA_PACKAGE_NAME; - return uid; - } - } - - if (!db_pkg.empty()) { - switch (check_stub(user, db_pkg)) { - case INSTALLED: - if (check_dyn(user, repackaged_pkg) == INSTALLED) { - if (pkg) *pkg = repackaged_pkg; - return user * AID_USER_OFFSET + repackaged_app_id; - } else { - if (pkg) pkg->clear(); - return -1; - } - case CERT_MISMATCH: - install = true; - case NOT_INSTALLED: - MagiskD().rm_db_string(DbEntryKey::SuManager); - break; - } - } - - repackaged_pkg.clear(); - repackaged_cert.clear(); - switch (check_orig(user)) { - case INSTALLED: { - int uid = get_pkg_uid(user, JAVA_PACKAGE_NAME); - if (uid < 0) { - if (pkg) pkg->clear(); - return -1; - } else { - if (pkg) *pkg = JAVA_PACKAGE_NAME; - return uid; - } - } - case CERT_MISMATCH: - install = true; - case NOT_INSTALLED: - break; - } - - auto xml = "/data/system/packages.xml"; - struct stat st{}; - stat(xml, &st); - tracked_files.erase(user); - tracked_files.try_emplace(user, xml, &st); - - if (install) install_stub(); - if (pkg) pkg->clear(); - return -1; -} diff --git a/native/src/core/package.rs b/native/src/core/package.rs new file mode 100644 index 000000000..d9263a938 --- /dev/null +++ b/native/src/core/package.rs @@ -0,0 +1,489 @@ +use crate::consts::{APP_PACKAGE_NAME, MAGISK_VER_CODE}; +use crate::daemon::{to_app_id, MagiskD, 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}; +use base::{ + cstr, error, fd_get_attr, open_fd, warn, BufReadExt, Directory, FsPath, FsPathBuf, + LoggedResult, ReadExt, ResultExt, Utf8CStrBuf, Utf8CStrBufArr, +}; +use cxx::CxxString; +use std::collections::BTreeMap; +use std::fs::File; +use std::io; +use std::io::{Cursor, Read, Seek, SeekFrom}; +use std::os::fd::AsRawFd; +use std::os::unix::fs::MetadataExt; +use std::path::{Path, PathBuf}; +use std::pin::Pin; +use std::time::Duration; + +const EOCD_MAGIC: u32 = 0x06054B50; +const APK_SIGNING_BLOCK_MAGIC: [u8; 16] = *b"APK Sig Block 42"; +const SIGNATURE_SCHEME_V2_MAGIC: u32 = 0x7109871A; + +macro_rules! bad_apk { + ($msg:literal) => { + io::Error::new(io::ErrorKind::InvalidData, concat!("cert: ", $msg)) + }; +} + +/* + * A v2/v3 signed APK has the format as following + * + * +---------------+ + * | zip content | + * +---------------+ + * | signing block | + * +---------------+ + * | central dir | + * +---------------+ + * | EOCD | + * +---------------+ + * + * Scan from end of file to find EOCD, and figure our way back to the + * offset of the signing block. Next, directly extract the certificate + * from the v2 signature block. + * + * All structures above are mostly just for documentation purpose. + * + * This method extracts the first certificate of the first signer + * 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 mut u32_val = 0u32; + let mut u64_val = 0u64; + + // Find EOCD + for i in 0u16.. { + let mut comment_sz = 0u16; + apk.seek(SeekFrom::End(-(size_of_val(&comment_sz) as i64) - i as i64))?; + apk.read_pod(&mut comment_sz)?; + + if comment_sz == i { + apk.seek(SeekFrom::Current(-22))?; + let mut magic = 0u32; + apk.read_pod(&mut magic)?; + if magic == EOCD_MAGIC { + break; + } + } + if i == 0xffff { + return Err(bad_apk!("invalid APK format")); + } + } + + // We are now at EOCD + sizeof(magic) + // Seek and read central_dir_off to find the start of the central directory + let mut central_dir_off = 0u32; + apk.seek(SeekFrom::Current(12))?; + apk.read_pod(&mut central_dir_off)?; + + // Code for parse APK comment to get version code + if version >= 0 { + let mut comment_sz = 0u16; + apk.read_pod(&mut comment_sz)?; + let mut comment = vec![0u8; comment_sz as usize]; + apk.read_exact(&mut comment)?; + let mut comment = Cursor::new(&comment); + let mut apk_ver = 0; + comment.foreach_props(|k, v| { + if k == "versionCode" { + apk_ver = v.parse::().unwrap_or(0); + false + } else { + true + } + }); + if version > apk_ver { + return Err(bad_apk!("APK version too low")); + } + } + + // Next, find the start of the APK signing block + apk.seek(SeekFrom::Start((central_dir_off - 24) as u64))?; + apk.read_pod(&mut u64_val)?; // u64_value = block_sz_ + let mut magic = [0u8; 16]; + apk.read_exact(&mut magic)?; + if magic != APK_SIGNING_BLOCK_MAGIC { + return Err(bad_apk!("invalid signing block magic")); + } + let mut signing_blk_sz = 0u64; + apk.seek(SeekFrom::Current( + -(u64_val as i64) - (size_of_val(&signing_blk_sz) as i64), + ))?; + apk.read_pod(&mut signing_blk_sz)?; + if signing_blk_sz != u64_val { + return 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; + } + + let mut id = 0u32; + apk.read_pod(&mut id)?; + if id == SIGNATURE_SCHEME_V2_MAGIC { + // Skip [signer sequence length] + [1st signer length] + [signed data length] + apk.seek(SeekFrom::Current((size_of_val(&u32_val) * 3) as i64))?; + + apk.read_pod(&mut u32_val)?; // digest sequence length + apk.seek(SeekFrom::Current(u32_val as i64))?; // skip all digests + + apk.seek(SeekFrom::Current(size_of_val(&u32_val) as i64))?; // cert sequence length + apk.read_pod(&mut u32_val)?; // 1st cert length + + let mut cert = vec![0; u32_val as usize]; + apk.read_exact(cert.as_mut())?; + return Ok(cert); + } else { + // Skip this id-value pair + apk.seek(SeekFrom::Current( + u64_val as i64 - (size_of_val(&id) as i64), + ))?; + } + } + + Err(bad_apk!("cannot find certificate")) + } + inner(apk, version).log().unwrap_or(vec![]) +} + +fn find_apk_path(pkg: &str, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { + Directory::open(cstr!("/data/app"))?.pre_order_walk(|e| { + if !e.is_dir() { + return Ok(Skip); + } + let name_bytes = e.d_name().to_bytes(); + if name_bytes.starts_with(pkg.as_bytes()) && name_bytes[pkg.len()] == b'-' { + // Found the APK path, we can abort now + e.path(buf)?; + return Ok(Abort); + } + if name_bytes.starts_with(b"~~") { + return Ok(Continue); + } + Ok(Skip) + })?; + if !buf.is_empty() { + buf.push_str("/base.apk"); + } + Ok(()) +} + +enum Status { + Installed, + NotInstalled, + CertMismatch, +} + +pub struct ManagerInfo { + stub_apk_fd: Option, + trusted_cert: Vec, + repackaged_app_id: i32, + repackaged_pkg: String, + repackaged_cert: Vec, + tracked_files: BTreeMap, +} + +impl Default for ManagerInfo { + fn default() -> Self { + ManagerInfo { + stub_apk_fd: None, + trusted_cert: Vec::new(), + repackaged_app_id: -1, + repackaged_pkg: String::new(), + repackaged_cert: Vec::new(), + tracked_files: BTreeMap::new(), + } + } +} + +#[derive(Default)] +struct TrackedFile { + path: PathBuf, + timestamp: Duration, +} + +impl TrackedFile { + fn new>(path: T) -> TrackedFile { + fn inner(path: &Path) -> TrackedFile { + let meta = match path.metadata() { + Ok(meta) => meta, + Err(_) => return TrackedFile::default(), + }; + let timestamp = Duration::new(meta.ctime() as u64, meta.ctime_nsec() as u32); + TrackedFile { + path: PathBuf::from(path), + timestamp, + } + } + inner(path.as_ref()) + } + + fn is_same(&self) -> bool { + if self.path.as_os_str().is_empty() { + return false; + } + let meta = match self.path.metadata() { + Ok(meta) => meta, + Err(_) => return false, + }; + let timestamp = Duration::new(meta.ctime() as u64, meta.ctime_nsec() as u32); + timestamp == self.timestamp + } +} + +impl ManagerInfo { + fn check_dyn(&mut self, daemon: &MagiskD, user: i32, pkg: &str) -> Status { + let mut arr = Utf8CStrBufArr::default(); + let apk = FsPathBuf::new(&mut arr) + .join(daemon.app_data_dir()) + .join_fmt(user) + .join(pkg) + .join("dyn") + .join("current.apk"); + let cert: Vec; + let uid: i32; + match apk.open(O_RDONLY | O_CLOEXEC) { + Ok(mut fd) => { + uid = fd_get_attr(fd.as_raw_fd()) + .map(|attr| attr.st.st_uid as i32) + .unwrap_or(-1); + cert = read_certificate(&mut fd, MAGISK_VER_CODE) + } + Err(_) => { + warn!("pkg: no dyn APK, ignore"); + return Status::NotInstalled; + } + } + + if cert.is_empty() || cert != self.trusted_cert { + error!("pkg: dyn APK signature mismatch: {}", apk); + #[cfg(all(feature = "check-signature", not(debug_assertions)))] + { + return Status::CertMismatch; + } + } + + self.repackaged_app_id = to_app_id(uid); + self.tracked_files.insert(user, TrackedFile::new(apk)); + Status::Installed + } + + fn check_stub(&mut self, user: i32, pkg: &str) -> Status { + let mut arr = Utf8CStrBufArr::default(); + if find_apk_path(pkg, &mut arr).is_err() { + return Status::NotInstalled; + } + let apk = FsPath::from(&arr); + + let cert: Vec; + match apk.open(O_RDONLY | O_CLOEXEC) { + Ok(mut fd) => cert = read_certificate(&mut fd, -1), + Err(_) => return Status::NotInstalled, + } + + if cert.is_empty() || (pkg == self.repackaged_pkg && cert != self.repackaged_cert) { + error!("pkg: repackaged APK signature invalid: {}", apk); + uninstall_pkg(apk); + return Status::CertMismatch; + } + + self.repackaged_pkg.clear(); + self.repackaged_pkg.push_str(pkg); + self.repackaged_cert = cert; + self.tracked_files.insert(user, TrackedFile::new(apk)); + Status::Installed + } + + fn check_orig(&mut self, user: i32) -> Status { + let mut arr = Utf8CStrBufArr::default(); + if find_apk_path(APP_PACKAGE_NAME, &mut arr).is_err() { + return Status::NotInstalled; + } + let apk = FsPath::from(&arr); + + let cert: Vec; + match apk.open(O_RDONLY | O_CLOEXEC) { + Ok(mut fd) => cert = read_certificate(&mut fd, MAGISK_VER_CODE), + Err(_) => return Status::NotInstalled, + } + + if cert.is_empty() || cert != self.trusted_cert { + error!("pkg: APK signature mismatch: {}", apk); + #[cfg(all(feature = "check-signature", not(debug_assertions)))] + { + uninstall_pkg(cstr!(APP_PACKAGE_NAME)); + return Status::CertMismatch; + } + } + + self.tracked_files.insert(user, TrackedFile::new(apk)); + Status::Installed + } + + fn install_stub(&mut self) { + if let Some(ref mut stub_fd) = self.stub_apk_fd { + // Copy the stub APK + let tmp_apk = cstr!("/data/stub.apk"); + let result: LoggedResult<()> = try { + { + let mut tmp_fd = File::from(open_fd!( + tmp_apk, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + 0o600 + )?); + io::copy(stub_fd, &mut tmp_fd)?; + } + // Seek the fd back to start + stub_fd.seek(SeekFrom::Start(0))?; + }; + if result.is_ok() { + install_apk(tmp_apk); + } + } + } + + fn get_manager(&mut self, daemon: &MagiskD, user: i32, mut install: bool) -> (i32, &str) { + let db_pkg = daemon.get_db_string(DbEntryKey::SuManager); + + // If database changed, always re-check files + if db_pkg != self.repackaged_pkg { + self.tracked_files.remove(&user); + } + + if let Some(file) = self.tracked_files.get(&user) + && file.is_same() + { + // no APK + if file.path == Path::new("/data/system/packages.xml") { + if install { + self.install_stub(); + } + return (-1, ""); + } + // dyn APK is still the same + if file.path.starts_with(daemon.app_data_dir()) { + return ( + user * AID_USER_OFFSET + self.repackaged_app_id, + &self.repackaged_pkg, + ); + } + // stub APK is still the same + if !self.repackaged_pkg.is_empty() { + return if matches!( + self.check_dyn(daemon, user, self.repackaged_pkg.clone().as_str()), + Status::Installed + ) { + ( + user * AID_USER_OFFSET + self.repackaged_app_id, + &self.repackaged_pkg, + ) + } else { + (-1, "") + }; + } + // orig APK is still the same + let uid = daemon.get_package_uid(user, APP_PACKAGE_NAME); + return if uid < 0 { + (-1, "") + } else { + (uid, APP_PACKAGE_NAME) + }; + } + + if !db_pkg.is_empty() { + match self.check_stub(user, &db_pkg) { + Status::Installed => { + return if matches!(self.check_dyn(daemon, user, &db_pkg), Status::Installed) { + ( + user * AID_USER_OFFSET + self.repackaged_app_id, + &self.repackaged_pkg, + ) + } else { + (-1, "") + } + } + Status::NotInstalled => { + daemon.rm_db_string(DbEntryKey::SuManager).ok(); + } + Status::CertMismatch => { + install = true; + daemon.rm_db_string(DbEntryKey::SuManager).ok(); + } + } + } + + self.repackaged_pkg.clear(); + self.repackaged_cert.clear(); + + match self.check_orig(user) { + Status::Installed => { + let uid = daemon.get_package_uid(user, APP_PACKAGE_NAME); + return if uid < 0 { + (-1, "") + } else { + (uid, APP_PACKAGE_NAME) + }; + } + Status::CertMismatch => install = true, + Status::NotInstalled => {} + } + + // If we cannot find any manager, track packages.xml for new package installs + self.tracked_files + .insert(user, TrackedFile::new("/data/system/packages.xml")); + + if install { + self.install_stub(); + } + (-1, "") + } +} + +impl MagiskD { + fn get_package_uid(&self, user: i32, pkg: &str) -> i32 { + let mut arr = Utf8CStrBufArr::default(); + let path = FsPathBuf::new(&mut arr) + .join(self.app_data_dir()) + .join_fmt(user) + .join(pkg); + path.get_attr() + .map(|attr| attr.st.st_uid as i32) + .unwrap_or(-1) + } + + pub fn preserve_stub_apk(&self) { + let mut info = self.manager_info.lock().unwrap(); + + let mut arr = Utf8CStrBufArr::default(); + let apk = FsPathBuf::new(&mut arr) + .join(get_magisk_tmp()) + .join("stub.apk"); + + if let Ok(mut fd) = apk.open(O_RDONLY | O_CLOEXEC) { + info.trusted_cert = read_certificate(&mut fd, MAGISK_VER_CODE); + // Seek the fd back to start + fd.seek(SeekFrom::Start(0)).log().ok(); + info.stub_apk_fd = Some(fd); + } + + apk.remove().log().ok(); + } + + 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); + if let Some(str) = ptr.as_mut() { + let mut str = Pin::new_unchecked(str); + str.as_mut().clear(); + str.push_str(pkg); + } + uid + } +} diff --git a/native/src/core/scripting.cpp b/native/src/core/scripting.cpp index c7bf7dfb9..d2ffd8ce9 100644 --- a/native/src/core/scripting.cpp +++ b/native/src/core/scripting.cpp @@ -159,10 +159,10 @@ appops set %s REQUEST_INSTALL_PACKAGES allow rm -f $APK )EOF"; -void install_apk(const char *apk) { - setfilecon(apk, MAGISK_FILE_CON); +void install_apk(rust::Utf8CStr apk) { + setfilecon(apk.c_str(), MAGISK_FILE_CON); char cmds[sizeof(install_script) + 4096]; - ssprintf(cmds, sizeof(cmds), install_script, apk, JAVA_PACKAGE_NAME); + ssprintf(cmds, sizeof(cmds), install_script, apk.c_str(), JAVA_PACKAGE_NAME); exec_command_async("/system/bin/sh", "-c", cmds); } @@ -172,9 +172,9 @@ log -t Magisk "pm_uninstall: $PKG" log -t Magisk "pm_uninstall: $(pm uninstall $PKG 2>&1)" )EOF"; -void uninstall_pkg(const char *pkg) { +void uninstall_pkg(rust::Utf8CStr pkg) { char cmds[sizeof(uninstall_script) + 256]; - ssprintf(cmds, sizeof(cmds), uninstall_script, pkg); + ssprintf(cmds, sizeof(cmds), uninstall_script, pkg.c_str()); exec_command_async("/system/bin/sh", "-c", cmds); } diff --git a/native/src/core/su/su_daemon.cpp b/native/src/core/su/su_daemon.cpp index 1ceb7f792..6c1c57fe1 100644 --- a/native/src/core/su/su_daemon.cpp +++ b/native/src/core/su/su_daemon.cpp @@ -69,7 +69,7 @@ void su_info::check_db() { // We need to check our manager if (access.policy == SuPolicy::Query || access.log || access.notify) { - mgr_uid = get_manager(to_user_id(eval_uid), &mgr_pkg, true); + mgr_uid = MagiskD().get_manager(to_user_id(eval_uid), &mgr_pkg, true); } } diff --git a/native/src/core/zygisk/entry.cpp b/native/src/core/zygisk/entry.cpp index 433308020..60ed189b7 100644 --- a/native/src/core/zygisk/entry.cpp +++ b/native/src/core/zygisk/entry.cpp @@ -132,7 +132,7 @@ static void get_process_info(int client, const sock_cred *cred) { if (is_deny_target(uid, process)) { flags |= PROCESS_ON_DENYLIST; } - if (get_manager(to_user_id(uid)) == uid) { + if (MagiskD().get_manager(to_user_id(uid), nullptr, false) == uid) { flags |= PROCESS_IS_MAGISK_APP; } if (denylist_enforced) { diff --git a/native/src/include/consts.rs b/native/src/include/consts.rs index 133540411..8c4f31663 100644 --- a/native/src/include/consts.rs +++ b/native/src/include/consts.rs @@ -8,6 +8,8 @@ mod flags; pub use flags::*; pub const MAGISK_FULL_VER: &str = concatcp!(MAGISK_VERSION, "(", MAGISK_VER_CODE, ")"); +pub const APP_PACKAGE_NAME: &str = "com.topjohnwu.magisk"; + pub const LOGFILE: &str = "/cache/magisk.log"; // data paths