Migrate package detection to Rust

This commit is contained in:
topjohnwu 2025-01-09 00:14:08 +08:00 committed by John Wu
parent ee6810f417
commit 5637a258fc
12 changed files with 528 additions and 442 deletions

View File

@ -7,6 +7,10 @@ edition = "2021"
crate-type = ["staticlib"] crate-type = ["staticlib"]
path = "lib.rs" path = "lib.rs"
[features]
default = ["check-signature"]
check-signature = []
[build-dependencies] [build-dependencies]
cxx-gen = { workspace = true } cxx-gen = { workspace = true }
pb-rs = { workspace = true } pb-rs = { workspace = true }

View File

@ -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<u8> {
fn inner(apk: &mut File, version: i32) -> io::Result<Vec<u8>> {
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::<i32>().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
}

View File

@ -3,11 +3,9 @@ use crate::db::Sqlite3;
use crate::ffi::{get_magisk_tmp, RequestCode}; use crate::ffi::{get_magisk_tmp, RequestCode};
use crate::get_prop; use crate::get_prop;
use crate::logging::{magisk_logging, start_log_daemon}; use crate::logging::{magisk_logging, start_log_daemon};
use crate::package::ManagerInfo;
use base::libc::{O_CLOEXEC, O_RDONLY}; use base::libc::{O_CLOEXEC, O_RDONLY};
use base::{ use base::{cstr, info, libc, open_fd, BufReadExt, FsPath, FsPathBuf, ReadExt, Utf8CStrBufArr};
cstr, info, libc, open_fd, BufReadExt, Directory, FsPath, FsPathBuf, ReadExt, ResultExt,
Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrBufRef, WalkResult,
};
use bytemuck::bytes_of; use bytemuck::bytes_of;
use std::fs::File; use std::fs::File;
use std::io; 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)] #[derive(Default)]
pub struct MagiskD { pub struct MagiskD {
pub sql_connection: Mutex<Option<Sqlite3>>, pub sql_connection: Mutex<Option<Sqlite3>>,
pub manager_info: Mutex<ManagerInfo>,
boot_stage_lock: Mutex<BootStateFlags>, boot_stage_lock: Mutex<BootStateFlags>,
sdk_int: i32, sdk_int: i32,
pub is_emulator: bool, pub is_emulator: bool,
@ -57,6 +62,14 @@ impl MagiskD {
self.sdk_int 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) { pub fn boot_stage_handler(&self, client: i32, code: i32) {
// Make sure boot stage execution is always serialized // Make sure boot stage execution is always serialized
let mut state = self.boot_stage_lock.lock().unwrap(); let mut state = self.boot_stage_lock.lock().unwrap();
@ -183,34 +196,6 @@ pub fn get_magiskd() -> &'static MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() } 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<usize> {
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 { pub trait IpcRead {
fn ipc_read_int(&mut self) -> io::Result<i32>; fn ipc_read_int(&mut self) -> io::Result<i32>;
fn ipc_read_string(&mut self) -> io::Result<String>; fn ipc_read_string(&mut self) -> io::Result<String>;

View File

@ -61,9 +61,7 @@ void su_daemon_handler(int client, const sock_cred *cred);
void zygisk_handler(int client, const sock_cred *cred); void zygisk_handler(int client, const sock_cred *cred);
// Package // Package
void preserve_stub_apk();
std::vector<bool> get_app_no_list(); std::vector<bool> get_app_no_list();
int get_manager(int user, std::string *pkg = nullptr, bool install = false);
void prune_su_access(); void prune_su_access();
// Module stuffs // Module stuffs
@ -77,8 +75,6 @@ void exec_module_scripts(const char *stage);
void exec_script(const char *script); void exec_script(const char *script);
void exec_common_scripts(const char *stage); void exec_common_scripts(const char *stage);
void exec_module_scripts(const char *stage, const std::vector<std::string_view> &modules); void exec_module_scripts(const char *stage, const std::vector<std::string_view> &modules);
void install_apk(const char *apk);
void uninstall_pkg(const char *pkg);
void clear_pkg(const char *pkg, int user_id); void clear_pkg(const char *pkg, int user_id);
[[noreturn]] void install_module(const char *file); [[noreturn]] void install_module(const char *file);

View File

@ -3,6 +3,8 @@
#include <base.hpp> #include <base.hpp>
const char *get_magisk_tmp(); const char *get_magisk_tmp();
void install_apk(rust::Utf8CStr apk);
void uninstall_pkg(rust::Utf8CStr pkg);
// Rust bindings // Rust bindings
static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); } static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }

View File

@ -5,21 +5,20 @@
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
use base::Utf8CStr; use base::Utf8CStr;
use cert::read_certificate; use daemon::{daemon_entry, get_magiskd, MagiskD};
use daemon::{daemon_entry, find_apk_path, get_magiskd, MagiskD};
use db::get_default_db_settings; use db::get_default_db_settings;
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging}; 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, setup_mounts};
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop}; use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
use su::get_default_root_settings; use su::get_default_root_settings;
mod cert;
#[path = "../include/consts.rs"] #[path = "../include/consts.rs"]
mod consts; mod consts;
mod daemon; mod daemon;
mod db; mod db;
mod logging; mod logging;
mod mount; mod mount;
mod package;
mod resetprop; mod resetprop;
mod su; mod su;
@ -75,6 +74,8 @@ pub mod ffi {
fn get_magisk_tmp() -> Utf8CStrRef<'static>; fn get_magisk_tmp() -> Utf8CStrRef<'static>;
#[cxx_name = "resolve_preinit_dir_rs"] #[cxx_name = "resolve_preinit_dir_rs"]
fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String; 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; fn switch_mnt_ns(pid: i32) -> i32;
} }
@ -160,8 +161,6 @@ pub mod ffi {
fn zygisk_close_logd(); fn zygisk_close_logd();
fn zygisk_get_logd() -> i32; fn zygisk_get_logd() -> i32;
fn setup_logfile(); fn setup_logfile();
fn find_apk_path(pkg: Utf8CStrRef, data: &mut [u8]) -> usize;
fn read_certificate(fd: i32, version: i32) -> Vec<u8>;
fn setup_mounts(); fn setup_mounts();
fn clean_mounts(); fn clean_mounts();
fn find_preinit_device() -> String; fn find_preinit_device() -> String;
@ -181,6 +180,9 @@ pub mod ffi {
fn is_recovery(&self) -> bool; fn is_recovery(&self) -> bool;
fn sdk_int(&self) -> i32; fn sdk_int(&self) -> i32;
fn boot_stage_handler(&self, client: i32, code: 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"] #[cxx_name = "get_db_settings"]
fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool; fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool;

View File

@ -5,49 +5,6 @@
#include <flags.h> #include <flags.h>
using namespace std; 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(&timestamp, &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<uint8_t> repackaged_cert;
static Vec<uint8_t> trusted_cert;
static map<int, file_info> tracked_files;
enum status {
INSTALLED,
NOT_INSTALLED,
CERT_MISMATCH,
};
static bool operator==(const Vec<uint8_t> &a, const Vec<uint8_t> &b) {
return a.size() == b.size() && memcmp(a.data(), b.data(), a.size()) == 0;
}
// app_id = app_no + AID_APP_START // app_id = app_no + AID_APP_START
// app_no range: [0, 9999] // app_no range: [0, 9999]
@ -80,206 +37,3 @@ vector<bool> get_app_no_list() {
} }
return 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<PATH_MAX> 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<PATH_MAX> 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;
}

489
native/src/core/package.rs Normal file
View File

@ -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<u8> {
fn inner(apk: &mut File, version: i32) -> io::Result<Vec<u8>> {
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::<i32>().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<File>,
trusted_cert: Vec<u8>,
repackaged_app_id: i32,
repackaged_pkg: String,
repackaged_cert: Vec<u8>,
tracked_files: BTreeMap<i32, TrackedFile>,
}
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<T: AsRef<Path>>(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<u8>;
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<u8>;
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<u8>;
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
}
}

View File

@ -159,10 +159,10 @@ appops set %s REQUEST_INSTALL_PACKAGES allow
rm -f $APK rm -f $APK
)EOF"; )EOF";
void install_apk(const char *apk) { void install_apk(rust::Utf8CStr apk) {
setfilecon(apk, MAGISK_FILE_CON); setfilecon(apk.c_str(), MAGISK_FILE_CON);
char cmds[sizeof(install_script) + 4096]; 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); 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)" log -t Magisk "pm_uninstall: $(pm uninstall $PKG 2>&1)"
)EOF"; )EOF";
void uninstall_pkg(const char *pkg) { void uninstall_pkg(rust::Utf8CStr pkg) {
char cmds[sizeof(uninstall_script) + 256]; 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); exec_command_async("/system/bin/sh", "-c", cmds);
} }

View File

@ -69,7 +69,7 @@ void su_info::check_db() {
// We need to check our manager // We need to check our manager
if (access.policy == SuPolicy::Query || access.log || access.notify) { 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);
} }
} }

View File

@ -132,7 +132,7 @@ static void get_process_info(int client, const sock_cred *cred) {
if (is_deny_target(uid, process)) { if (is_deny_target(uid, process)) {
flags |= PROCESS_ON_DENYLIST; 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; flags |= PROCESS_IS_MAGISK_APP;
} }
if (denylist_enforced) { if (denylist_enforced) {

View File

@ -8,6 +8,8 @@ mod flags;
pub use flags::*; pub use flags::*;
pub const MAGISK_FULL_VER: &str = concatcp!(MAGISK_VERSION, "(", MAGISK_VER_CODE, ")"); 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"; pub const LOGFILE: &str = "/cache/magisk.log";
// data paths // data paths