Compare commits

...

13 Commits

Author SHA1 Message Date
Wang Han
8dc23d0ead Avoid triggering magisk --zygote-restart twice
We have already used on restart keyword to inject zygote restart, so
triggering it here on prop is not needed.
2025-08-20 12:34:39 -07:00
topjohnwu
b4287700d5 Increase timeout to 15 minutes 2025-08-20 11:23:18 -07:00
topjohnwu
8d10ab89f2 Set zygisk properties in Rust 2025-08-20 11:23:18 -07:00
topjohnwu
49fdc1addb Prevent setting zygisk prop twice 2025-08-20 11:23:18 -07:00
topjohnwu
1333d3b986 Fix canary emulator 2025-08-18 11:25:47 -07:00
残页
335146a6a2 Update supported API levels 2025-08-17 23:58:43 -07:00
topjohnwu
eaf9527971 Use AOSP ATD for API 36
[skip ci]
2025-08-15 17:25:41 -07:00
LoveSy
da937a88c8 if !restore { set_zygisk_prop(); } 2025-08-15 16:45:01 -07:00
topjohnwu
9476e7282d More borrowing, less copying 2025-08-08 21:06:41 -07:00
topjohnwu
251c3c3e0e Remove old ffi data structure 2025-08-08 21:06:41 -07:00
topjohnwu
cd0eca20b0 Migrate connect.cpp to Rust 2025-08-08 21:06:41 -07:00
topjohnwu
6839cb9ab2 Keep /system/xbin/su on emulators 2025-08-08 21:06:41 -07:00
topjohnwu
d11a3397d8 Reduce verbose logging in Zygisk 2025-08-08 21:06:41 -07:00
22 changed files with 608 additions and 546 deletions

View File

@@ -105,7 +105,7 @@ jobs:
sudo udevadm trigger --name-match=kvm
- name: Run AVD test
timeout-minutes: 10
timeout-minutes: 15
env:
AVD_TEST_LOG: 1
run: scripts/avd.sh test ${{ matrix.version }} ${{ matrix.type }}
@@ -146,7 +146,7 @@ jobs:
sudo udevadm trigger --name-match=kvm
- name: Run AVD test
timeout-minutes: 10
timeout-minutes: 15
env:
FORCE_32_BIT: 1
AVD_TEST_LOG: 1
@@ -191,7 +191,7 @@ jobs:
scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}
- name: Run Cuttlefish test
timeout-minutes: 10
timeout-minutes: 15
run: sudo -E -u $USER scripts/cuttlefish.sh test
- name: Upload logs on error

View File

@@ -24,7 +24,6 @@ LOCAL_SRC_FILES := \
core/core-rs.cpp \
core/resetprop/resetprop.cpp \
core/su/su.cpp \
core/su/connect.cpp \
core/zygisk/entry.cpp \
core/zygisk/module.cpp \
core/zygisk/hook.cpp \

View File

@@ -140,8 +140,14 @@ pub struct FileAttr {
pub con: crate::Utf8CStrBufArr<128>,
}
impl Default for FileAttr {
fn default() -> Self {
Self::new()
}
}
impl FileAttr {
fn new() -> Self {
pub fn new() -> Self {
FileAttr {
st: unsafe { mem::zeroed() },
#[cfg(feature = "selinux")]

View File

@@ -218,7 +218,7 @@ static void handle_request_sync(int client, int code) {
denylist_handler(-1, nullptr);
// Restore native bridge property
restore_zygisk_prop();
MagiskD::Get().restore_zygisk_prop();
write_int(client, 0);

View File

@@ -10,15 +10,15 @@ use crate::mount::{clean_mounts, setup_preinit_dir};
use crate::package::ManagerInfo;
use crate::selinux::restore_tmpcon;
use crate::su::SuInfo;
use crate::zygisk::ZygiskState;
use base::libc::{O_APPEND, O_CLOEXEC, O_RDONLY, O_WRONLY};
use base::{
AtomicArc, BufReadExt, FsPathBuilder, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, error, info, libc,
};
use std::fmt::Write as FmtWrite;
use std::io::{BufReader, Write};
use std::os::unix::net::UnixStream;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};
// Global magiskd singleton
@@ -66,9 +66,8 @@ pub struct MagiskD {
pub manager_info: Mutex<ManagerInfo>,
boot_stage_lock: Mutex<BootStateFlags>,
pub module_list: OnceLock<Vec<ModuleInfo>>,
pub zygiskd_sockets: Mutex<(Option<UnixStream>, Option<UnixStream>)>,
pub zygisk_enabled: AtomicBool,
pub zygote_start_count: AtomicU32,
pub zygisk: Mutex<ZygiskState>,
pub cached_su_info: AtomicArc<SuInfo>,
sdk_int: i32,
pub is_emulator: bool,
@@ -80,10 +79,6 @@ impl MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() }
}
pub fn zygisk_enabled(&self) -> bool {
self.zygisk_enabled.load(Ordering::Acquire)
}
pub fn sdk_int(&self) -> i32 {
self.sdk_int
}
@@ -175,7 +170,7 @@ impl MagiskD {
setup_preinit_dir();
self.ensure_manager();
self.zygisk_reset(true)
self.zygisk.lock().unwrap().reset(true);
}
pub fn boot_stage_handler(&self, client: i32, code: i32) {
@@ -319,7 +314,6 @@ pub fn daemon_entry() {
sdk_int,
is_emulator,
is_recovery,
zygote_start_count: AtomicU32::new(1),
..Default::default()
};
MAGISKD.set(magiskd).ok();

View File

@@ -35,11 +35,6 @@ void unlock_blocks();
bool setup_magisk_env();
bool check_key_combo();
// Zygisk daemon
rust::Str get_zygisk_lib_name();
void set_zygisk_prop();
void restore_zygisk_prop();
// Sockets
struct sock_cred : public ucred {
std::string context;
@@ -112,9 +107,6 @@ 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(); }

View File

@@ -3,6 +3,7 @@
#![feature(fn_traits)]
#![feature(unix_socket_ancillary_data)]
#![feature(unix_socket_peek)]
#![feature(default_field_values)]
#![allow(clippy::missing_safety_doc)]
use crate::ffi::SuRequest;
@@ -125,15 +126,6 @@ pub mod ffi {
gids: Vec<u32>,
}
struct SuAppRequest<'a> {
uid: i32,
pid: i32,
eval_uid: i32,
mgr_pkg: &'a str,
mgr_uid: i32,
request: &'a SuRequest,
}
unsafe extern "C++" {
#[namespace = "rust"]
#[cxx_name = "Utf8CStr"]
@@ -157,13 +149,7 @@ pub mod ffi {
fn uninstall_pkg(apk: Utf8CStrRef);
fn update_deny_flags(uid: i32, process: &str, flags: &mut u32);
fn initialize_denylist();
fn get_zygisk_lib_name() -> &'static str;
fn set_zygisk_prop();
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);
include!("include/sqlite.hpp");
@@ -211,7 +197,7 @@ pub mod ffi {
fn send_fds(socket: i32, fds: &[i32]) -> bool;
fn recv_fd(socket: i32) -> i32;
fn recv_fds(socket: i32) -> Vec<i32>;
unsafe fn write_to_fd(self: &SuRequest, fd: i32);
fn write_to_fd(self: &SuRequest, fd: i32);
fn pump_tty(infd: i32, outfd: i32);
fn get_pty_num(fd: i32) -> i32;
fn restore_stdin() -> bool;
@@ -240,6 +226,7 @@ pub mod ffi {
fn boot_stage_handler(&self, client: i32, code: i32);
fn zygisk_handler(&self, client: i32);
fn zygisk_reset(&self, restore: bool);
fn restore_zygisk_prop(&self);
fn prune_su_access(&self);
fn su_daemon_handler(&self, client: i32, cred: &UCred);
#[cxx_name = "get_manager"]
@@ -266,7 +253,7 @@ unsafe impl ExternType for UCred {
}
impl SuRequest {
unsafe fn write_to_fd(&self, fd: i32) {
fn write_to_fd(&self, fd: i32) {
unsafe {
let mut w = ManuallyDrop::new(File::from_raw_fd(fd));
self.encode(w.deref_mut()).ok();

View File

@@ -1,9 +1,6 @@
use crate::consts::{MODULEMNT, MODULEROOT, MODULEUPGRADE, WORKERDIR};
use crate::daemon::MagiskD;
use crate::ffi::{
ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, get_zygisk_lib_name,
load_prop_file, set_zygisk_prop,
};
use crate::ffi::{ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, load_prop_file};
use crate::mount::setup_module_mount;
use base::{
DirEntry, Directory, FsPathBuilder, LibcReturn, LoggedResult, OsResultStatic, ResultExt,
@@ -376,7 +373,7 @@ fn get_path_env() -> String {
.unwrap_or_default()
}
fn inject_magisk_bins(system: &mut FsNode) {
fn inject_magisk_bins(system: &mut FsNode, is_emulator: bool) {
fn inject(children: &mut FsNodeMap) {
let mut path = cstr::buf::default().join_path(get_magisk_tmp());
@@ -418,7 +415,7 @@ fn inject_magisk_bins(system: &mut FsNode) {
let mut candidates = vec![];
for orig_item in path_env.split(':') {
// Filter not suitbale paths
// Filter non-suitable paths
if !MAGISK_BIN_INJECT_PARTITIONS
.iter()
.any(|p| orig_item.starts_with(p.as_str()))
@@ -430,6 +427,11 @@ fn inject_magisk_bins(system: &mut FsNode) {
continue;
}
// We want to keep /system/xbin/su on emulators (for debugging)
if is_emulator && orig_item.starts_with("/system/xbin") {
continue;
}
// Override existing su first
let su_path = Utf8CString::from(format!("{orig_item}/su"));
if su_path.exists() {
@@ -497,9 +499,7 @@ fn inject_magisk_bins(system: &mut FsNode) {
}
}
fn inject_zygisk_bins(system: &mut FsNode) {
let name = get_zygisk_lib_name();
fn inject_zygisk_bins(name: &str, system: &mut FsNode) {
#[cfg(target_pointer_width = "64")]
let has_32_bit = cstr!("/system/bin/linker").exists();
@@ -555,114 +555,6 @@ fn inject_zygisk_bins(system: &mut FsNode) {
}
}
fn apply_modules(zygisk: bool, module_list: &[ModuleInfo]) {
let mut system = FsNode::new_dir();
// Build all the base "prefix" paths
let mut root = cstr::buf::default().join_path("/");
let mut module_dir = cstr::buf::default().join_path(MODULEROOT);
let mut module_mnt = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(MODULEMNT);
let mut worker = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(WORKERDIR);
// Create a collection of all relevant paths
let mut root_paths = FilePaths {
real: PathTracker::from(&mut root),
worker: PathTracker::from(&mut worker),
module_mnt: PathTracker::from(&mut module_mnt),
module_root: PathTracker::from(&mut module_dir),
};
// Step 1: Create virtual filesystem tree
//
// In this step, there is zero logic applied during tree construction; we simply collect and
// record the union of all module filesystem trees under each of their /system directory.
for info in module_list {
let mut module_paths = root_paths.append(&info.name);
{
// Read props
let prop = module_paths.append("system.prop");
if prop.module().exists() {
// Do NOT go through property service as it could cause boot lock
load_prop_file(prop.module(), true);
}
}
{
// Check whether skip mounting
let skip = module_paths.append("skip_mount");
if skip.module().exists() {
continue;
}
}
{
// Double check whether the system folder exists
let sys = module_paths.append("system");
if sys.module().exists() {
info!("{}: loading module files", &info.name);
system.collect(sys).log_ok();
}
}
}
// Step 2: Inject custom files
//
// Magisk provides some built-in functionality that requires augmenting the filesystem.
// We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library
// has to also be added into the default LD_LIBRARY_PATH for code injection.
// We directly inject file nodes into the virtual filesystem tree we built in the previous
// step, treating Magisk just like a special "module".
if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") {
inject_magisk_bins(&mut system);
}
if zygisk {
inject_zygisk_bins(&mut system);
}
// Step 3: Extract all supported read-only partition roots
//
// For simplicity and backwards compatibility on older Android versions, when constructing
// Magisk modules, we always assume that there is only a single read-only partition mounted
// at /system. However, on modern Android there are actually multiple read-only partitions
// mounted at their respective paths. We need to extract these subtrees out of the main
// tree and treat them as individual trees.
let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */
if let FsNode::Directory { children } = &mut system {
for dir in SECONDARY_READ_ONLY_PARTITIONS {
// Only treat these nodes as root iff it is actually a directory in rootdir
if let Ok(attr) = dir.get_attr()
&& attr.is_dir()
{
let name = dir.trim_start_matches('/');
if let Some(root) = children.remove(name) {
roots.insert(name, root);
}
}
}
}
roots.insert("system", system);
for (dir, mut root) in roots {
// Step 4: Convert virtual filesystem tree into concrete operations
//
// Compare the virtual filesystem tree we constructed against the real filesystem
// structure on-device to generate a series of "operations".
// The "core" of the logic is to decide which directories need to be rebuilt in the
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
let path = root_paths.append(dir);
root.commit(path, true).log_ok();
}
}
fn upgrade_modules() -> LoggedResult<()> {
let mut upgrade = Directory::open(cstr!(MODULEUPGRADE))?;
let ufd = upgrade.as_raw_fd();
@@ -872,11 +764,120 @@ impl MagiskD {
// Recollect modules (module scripts could remove itself)
let modules = collect_modules(zygisk, true);
if zygisk {
set_zygisk_prop();
}
apply_modules(zygisk, &modules);
self.apply_modules(&modules);
self.module_list.set(modules).ok();
}
fn apply_modules(&self, module_list: &[ModuleInfo]) {
let mut system = FsNode::new_dir();
// Build all the base "prefix" paths
let mut root = cstr::buf::default().join_path("/");
let mut module_dir = cstr::buf::default().join_path(MODULEROOT);
let mut module_mnt = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(MODULEMNT);
let mut worker = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(WORKERDIR);
// Create a collection of all relevant paths
let mut root_paths = FilePaths {
real: PathTracker::from(&mut root),
worker: PathTracker::from(&mut worker),
module_mnt: PathTracker::from(&mut module_mnt),
module_root: PathTracker::from(&mut module_dir),
};
// Step 1: Create virtual filesystem tree
//
// In this step, there is zero logic applied during tree construction; we simply collect and
// record the union of all module filesystem trees under each of their /system directory.
for info in module_list {
let mut module_paths = root_paths.append(&info.name);
{
// Read props
let prop = module_paths.append("system.prop");
if prop.module().exists() {
// Do NOT go through property service as it could cause boot lock
load_prop_file(prop.module(), true);
}
}
{
// Check whether skip mounting
let skip = module_paths.append("skip_mount");
if skip.module().exists() {
continue;
}
}
{
// Double check whether the system folder exists
let sys = module_paths.append("system");
if sys.module().exists() {
info!("{}: loading module files", &info.name);
system.collect(sys).log_ok();
}
}
}
// Step 2: Inject custom files
//
// Magisk provides some built-in functionality that requires augmenting the filesystem.
// We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library
// has to also be added into the default LD_LIBRARY_PATH for code injection.
// We directly inject file nodes into the virtual filesystem tree we built in the previous
// step, treating Magisk just like a special "module".
if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") {
inject_magisk_bins(&mut system, self.is_emulator);
}
// Handle zygisk
if self.zygisk_enabled.load(Ordering::Acquire) {
let mut zygisk = self.zygisk.lock().unwrap();
zygisk.set_prop();
inject_zygisk_bins(&zygisk.lib_name, &mut system);
}
// Step 3: Extract all supported read-only partition roots
//
// For simplicity and backwards compatibility on older Android versions, when constructing
// Magisk modules, we always assume that there is only a single read-only partition mounted
// at /system. However, on modern Android there are actually multiple read-only partitions
// mounted at their respective paths. We need to extract these subtrees out of the main
// tree and treat them as individual trees.
let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */
if let FsNode::Directory { children } = &mut system {
for dir in SECONDARY_READ_ONLY_PARTITIONS {
// Only treat these nodes as root iff it is actually a directory in rootdir
if let Ok(attr) = dir.get_attr()
&& attr.is_dir()
{
let name = dir.trim_start_matches('/');
if let Some(root) = children.remove(name) {
roots.insert(name, root);
}
}
}
}
roots.insert("system", system);
for (dir, mut root) in roots {
// Step 4: Convert virtual filesystem tree into concrete operations
//
// Compare the virtual filesystem tree we constructed against the real filesystem
// structure on-device to generate a series of "operations".
// The "core" of the logic is to decide which directories need to be rebuilt in the
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
let path = root_paths.append(dir);
root.commit(path, true).log_ok();
}
}
}

View File

@@ -1,232 +0,0 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <base.hpp>
#include <consts.hpp>
#include <core.hpp>
using namespace std;
#define CALL_PROVIDER \
"/system/bin/app_process", "/system/bin", "com.android.commands.content.Content", \
"call", "--uri", target, "--user", user, "--method", action
#define START_ACTIVITY \
"/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \
"start", "-p", target, "--user", user, "-a", "android.intent.action.VIEW", \
"-f", "0x18800020", "--es", "action", action
// 0x18800020 = FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|
// FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|FLAG_INCLUDE_STOPPED_PACKAGES
class Extra {
const char *key;
enum {
INT,
BOOL,
STRING,
INTLIST,
} type;
union {
int int_val;
bool bool_val;
const char *str_val;
const vector<uint32_t> *intlist_val;
};
string str;
public:
Extra(const char *k, int v): key(k), type(INT), int_val(v) {}
Extra(const char *k, bool v): key(k), type(BOOL), bool_val(v) {}
Extra(const char *k, const char *v): key(k), type(STRING), str_val(v) {}
Extra(const char *k, const vector<uint32_t> *v): key(k), type(INTLIST), intlist_val(v) {}
void add_intent(vector<const char *> &vec) {
const char *val;
switch (type) {
case INT:
vec.push_back("--ei");
str = to_string(int_val);
val = str.data();
break;
case BOOL:
vec.push_back("--ez");
val = bool_val ? "true" : "false";
break;
case STRING:
vec.push_back("--es");
val = str_val;
break;
case INTLIST:
vec.push_back("--es");
for (auto i : *intlist_val) {
str += to_string(i);
str += ",";
}
if (!str.empty()) str.pop_back();
val = str.data();
break;
}
vec.push_back(key);
vec.push_back(val);
}
void add_bind(vector<const char *> &vec) {
char buf[32];
str = key;
switch (type) {
case INT:
str += ":i:";
ssprintf(buf, sizeof(buf), "%d", int_val);
str += buf;
break;
case BOOL:
str += ":b:";
str += bool_val ? "true" : "false";
break;
case STRING:
str += ":s:";
if (SDK_INT >= 30) {
string tmp = str_val;
replace_all(tmp, "\\", "\\\\");
replace_all(tmp, ":", "\\:");
str += tmp;
} else {
str += str_val;
}
break;
case INTLIST:
str += ":s:";
for (auto i : *intlist_val) {
str += to_string(i);
str += ",";
}
if (str.back() == ',') str.pop_back();
break;
}
vec.push_back("--extra");
vec.push_back(str.data());
}
};
static bool check_no_error(int fd) {
char buf[1024];
auto out = xopen_file(fd, "r");
while (fgets(buf, sizeof(buf), out.get())) {
if (strncasecmp(buf, "Error", 5) == 0) {
LOGD("exec_cmd: %s\n", buf);
return false;
}
}
return true;
}
static void exec_cmd(const char *action, vector<Extra> &data,
const SuAppRequest &info, bool provider = true) {
char target[128];
char user[4];
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",
(int) info.mgr_pkg.size(), info.mgr_pkg.data());
vector<const char *> args{ CALL_PROVIDER };
for (auto &e : data) {
e.add_bind(args);
}
args.push_back(nullptr);
exec_t exec {
.err = true,
.fd = -1,
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/content.jar", 1); },
.argv = args.data()
};
exec_command_sync(exec);
if (check_no_error(exec.fd))
return;
}
// Then try start activity with package name
ssprintf(target, sizeof(target), "%.*s", (int) info.mgr_pkg.size(), info.mgr_pkg.data());
vector<const char *> args{ START_ACTIVITY };
for (auto &e : data) {
e.add_intent(args);
}
args.push_back(nullptr);
exec_t exec {
.fd = -2,
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/am.jar", 1); },
.fork = fork_dont_care,
.argv = args.data()
};
exec_command(exec);
}
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<Extra> extras;
extras.reserve(9);
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 SuAppRequest &info, SuPolicy policy) {
if (fork_dont_care() == 0) {
vector<Extra> extras;
extras.reserve(3);
extras.emplace_back("from.uid", info.uid);
extras.emplace_back("pid", info.pid);
extras.emplace_back("policy", +policy);
exec_cmd("notify", extras, info);
exit(0);
}
}
int app_request(const SuAppRequest &info) {
// Create FIFO
char fifo[64];
ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", get_magisk_tmp(), info.pid);
mkfifo(fifo, 0600);
chown(fifo, info.mgr_uid, info.mgr_uid);
setfilecon(fifo, MAGISK_FILE_CON);
// Send request
vector<Extra> extras;
extras.reserve(3);
extras.emplace_back("fifo", fifo);
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
int fd = xopen(fifo, O_RDWR | O_CLOEXEC);
struct pollfd pfd = {
.fd = fd,
.events = POLLIN
};
if (xpoll(&pfd, 1, 70 * 1000) <= 0) {
close(fd);
fd = -1;
}
unlink(fifo);
return fd;
}

View File

@@ -0,0 +1,315 @@
use super::SuInfo;
use super::db::RootSettings;
use crate::consts::{INTERNAL_DIR, MAGISK_FILE_CON};
use crate::daemon::to_user_id;
use crate::ffi::{SuPolicy, SuRequest, get_magisk_tmp};
use crate::socket::IpcRead;
use ExtraVal::{Bool, Int, IntList, Str};
use base::{
BytesExt, FileAttr, LibcReturn, LoggedResult, OsError, ResultExt, cstr, fork_dont_care, libc,
};
use libc::pollfd as PollFd;
use num_traits::AsPrimitive;
use std::{fmt::Write, fs::File, os::fd::AsRawFd, process::Command, process::exit};
struct Extra<'a> {
key: &'static str,
value: ExtraVal<'a>,
}
enum ExtraVal<'a> {
Int(i32),
Bool(bool),
Str(&'a str),
IntList(&'a [u32]),
}
impl Extra<'_> {
fn add_intent(&self, cmd: &mut Command) {
match self.value {
Int(i) => {
cmd.args(["--ei", self.key, &i.to_string()]);
}
Bool(b) => {
cmd.args(["--ez", self.key, &b.to_string()]);
}
Str(s) => {
cmd.args(["--es", self.key, s]);
}
IntList(list) => {
cmd.args(["--es", self.key]);
let mut tmp = String::new();
list.iter().for_each(|i| write!(&mut tmp, "{i},").unwrap());
tmp.pop();
cmd.arg(&tmp);
}
}
}
fn add_bind(&self, cmd: &mut Command) {
let mut tmp: String;
match self.value {
Int(i) => {
tmp = format!("{}:i:{}", self.key, i);
}
Bool(b) => {
tmp = format!("{}:b:{}", self.key, b);
}
Str(s) => {
let s = s.replace("\\", "\\\\").replace(":", "\\:");
tmp = format!("{}:s:{}", self.key, s);
}
IntList(list) => {
tmp = format!("{}:s:", self.key);
if !list.is_empty() {
list.iter().for_each(|i| write!(&mut tmp, "{i},").unwrap());
tmp.pop();
}
}
}
cmd.args(["--extra", &tmp]);
}
fn add_bind_legacy(&self, cmd: &mut Command) {
match self.value {
Str(s) => {
let tmp = format!("{}:s:{}", self.key, s);
cmd.args(["--extra", &tmp]);
}
_ => self.add_bind(cmd),
}
}
}
pub(super) struct SuAppContext<'a> {
pub(super) cred: libc::ucred,
pub(super) request: &'a SuRequest,
pub(super) info: &'a SuInfo,
pub(super) settings: &'a mut RootSettings,
pub(super) sdk_int: i32,
}
impl SuAppContext<'_> {
fn exec_cmd(&self, action: &'static str, extras: &[Extra], use_provider: bool) {
let user = to_user_id(self.info.eval_uid);
let user = user.to_string();
if use_provider {
let provider = format!("content://{}.provider", self.info.mgr_pkg);
let mut cmd = Command::new("/system/bin/app_process");
cmd.args([
"/system/bin",
"com.android.commands.content.Content",
"call",
"--uri",
&provider,
"--user",
&user,
"--method",
action,
]);
if self.sdk_int >= 30 {
extras.iter().for_each(|e| e.add_bind(&mut cmd))
} else {
extras.iter().for_each(|e| e.add_bind_legacy(&mut cmd))
}
cmd.env("CLASSPATH", "/system/framework/content.jar");
if let Ok(output) = cmd.output()
&& !output.stderr.contains(b"Error")
&& !output.stdout.contains(b"Error")
{
// The provider call succeed
return;
}
}
let mut cmd = Command::new("/system/bin/app_process");
cmd.args([
"/system/bin",
"com.android.commands.am.Am",
"start",
"-p",
&self.info.mgr_pkg,
"--user",
&user,
"-a",
"android.intent.action.VIEW",
"-f",
// FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|
// FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|FLAG_INCLUDE_STOPPED_PACKAGES
"0x18800020",
"--es",
"action",
action,
]);
extras.iter().for_each(|e| e.add_intent(&mut cmd));
cmd.env("CLASSPATH", "/system/framework/am.jar");
// Sometimes `am start` will fail, keep trying until it works
loop {
if let Ok(output) = cmd.output()
&& !output.stdout.is_empty()
{
break;
}
}
}
fn app_request(&mut self) {
let mut fifo = cstr::buf::new::<64>();
fifo.write_fmt(format_args!(
"{}/{}/su_request_{}",
get_magisk_tmp(),
INTERNAL_DIR,
self.cred.pid
))
.ok();
let fd: LoggedResult<File> = try {
let mut attr = FileAttr::new();
attr.st.st_mode = 0o600;
attr.st.st_uid = self.info.mgr_uid.as_();
attr.st.st_gid = self.info.mgr_uid.as_();
attr.con.write_str(MAGISK_FILE_CON).ok();
fifo.mkfifo(0o600)?;
fifo.set_attr(&attr)?;
let extras = [
Extra {
key: "fifo",
value: Str(&fifo),
},
Extra {
key: "uid",
value: Int(self.info.eval_uid),
},
Extra {
key: "pid",
value: Int(self.cred.pid),
},
];
self.exec_cmd("request", &extras, false);
// Open with O_RDWR to prevent FIFO open block
let fd = fifo.open(libc::O_RDWR | libc::O_CLOEXEC)?;
// Wait for data input for at most 70 seconds
let mut pfd = PollFd {
fd: fd.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
if unsafe { libc::poll(&mut pfd, 1, 70 * 1000).as_os_result("poll", None, None)? } == 0
{
Err(OsError::with_os_error(libc::ETIMEDOUT, "poll", None, None))?;
}
fd
};
fifo.remove().log_ok();
if let Ok(mut fd) = fd {
self.settings.policy = SuPolicy {
repr: fd
.read_decodable::<i32>()
.log()
.map(i32::from_be)
.unwrap_or(SuPolicy::Deny.repr),
};
} else {
self.settings.policy = SuPolicy::Deny;
};
}
fn app_notify(&self) {
let extras = [
Extra {
key: "from.uid",
value: Int(self.cred.uid.as_()),
},
Extra {
key: "pid",
value: Int(self.cred.pid.as_()),
},
Extra {
key: "policy",
value: Int(self.settings.policy.repr),
},
];
self.exec_cmd("notify", &extras, true);
}
fn app_log(&self) {
let command = if self.request.command.is_empty() {
&self.request.shell
} else {
&self.request.command
};
let extras = [
Extra {
key: "from.uid",
value: Int(self.cred.uid.as_()),
},
Extra {
key: "to.uid",
value: Int(self.request.target_uid),
},
Extra {
key: "pid",
value: Int(self.cred.pid.as_()),
},
Extra {
key: "policy",
value: Int(self.settings.policy.repr),
},
Extra {
key: "target",
value: Int(self.request.target_pid),
},
Extra {
key: "context",
value: Str(&self.request.context),
},
Extra {
key: "gids",
value: IntList(&self.request.gids),
},
Extra {
key: "command",
value: Str(command),
},
Extra {
key: "notify",
value: Bool(self.settings.notify),
},
];
self.exec_cmd("log", &extras, true);
}
pub(super) fn connect_app(&mut self) {
// If policy is undetermined, show dialog for user consent
if self.settings.policy == SuPolicy::Query {
self.app_request();
}
if !self.settings.log && !self.settings.notify {
return;
}
if fork_dont_care() != 0 {
return;
}
// Notify su usage to application
if self.settings.log {
self.app_log();
} else if self.settings.notify {
self.app_notify();
}
exit(0);
}
}

View File

@@ -1,13 +1,11 @@
use super::connect::SuAppContext;
use super::db::RootSettings;
use crate::UCred;
use crate::daemon::{AID_ROOT, AID_SHELL, MagiskD, to_app_id, to_user_id};
use crate::db::{DbSettings, MultiuserMode, RootAccess};
use crate::ffi::{
SuAppRequest, SuPolicy, SuRequest, app_log, app_notify, app_request, exec_root_shell,
};
use crate::ffi::{SuPolicy, SuRequest, exec_root_shell};
use crate::socket::IpcRead;
use crate::su::db::RootSettings;
use base::{LoggedResult, ResultExt, WriteExt, debug, error, exit_on_error, libc, warn};
use std::fs::File;
use std::os::fd::{FromRawFd, IntoRawFd};
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
@@ -32,11 +30,11 @@ impl Default for SuRequest {
}
pub struct SuInfo {
uid: i32,
eval_uid: i32,
pub(super) uid: i32,
pub(super) eval_uid: i32,
pub(super) mgr_pkg: String,
pub(super) mgr_uid: i32,
cfg: DbSettings,
mgr_pkg: String,
mgr_uid: i32,
access: Mutex<AccessInfo>,
}
@@ -132,39 +130,18 @@ impl MagiskD {
};
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::<i32>()
.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);
}
// Talk to su manager
let mut app = SuAppContext {
cred,
request: &req,
info: &info,
settings: &mut access.settings,
sdk_int: self.sdk_int(),
};
app.connect_app();
// Before unlocking, refresh the timestamp
access.refresh();
@@ -291,9 +268,9 @@ impl MagiskD {
Arc::new(SuInfo {
uid,
eval_uid,
cfg,
mgr_pkg,
mgr_uid,
cfg,
access: Mutex::new(AccessInfo::new(access)),
})
};

View File

@@ -1,3 +1,4 @@
mod connect;
mod daemon;
mod db;
mod pts;

View File

@@ -1,12 +1,12 @@
use crate::consts::MODULEROOT;
use crate::daemon::{MagiskD, to_user_id};
use crate::ffi::{
ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, restore_zygisk_prop, update_deny_flags,
ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, get_prop, set_prop, update_deny_flags,
};
use crate::socket::{IpcRead, UnixSocketExt};
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, STDOUT_FILENO};
use base::{
Directory, FsPathBuilder, LoggedError, LoggedResult, ResultExt, WriteExt, cstr, error,
Directory, FsPathBuilder, LoggedError, LoggedResult, ResultExt, Utf8CStr, WriteExt, cstr,
fork_dont_care, libc, raw_cstr, warn,
};
use std::fmt::Write;
@@ -15,6 +15,8 @@ use std::os::unix::net::UnixStream;
use std::ptr;
use std::sync::atomic::Ordering;
const NBPROP: &Utf8CStr = cstr!("ro.dalvik.vm.native.bridge");
const ZYGISKLDR: &str = "libzygisk.so";
const UNMOUNT_MASK: u32 =
ZygiskStateFlags::ProcessOnDenyList.repr | ZygiskStateFlags::DenyListEnforced.repr;
@@ -56,6 +58,105 @@ fn exec_zygiskd(is_64_bit: bool, remote: UnixStream) {
}
}
#[derive(Default)]
pub struct ZygiskState {
pub lib_name: String,
sockets: (Option<UnixStream>, Option<UnixStream>),
start_count: u32 = 1,
}
impl ZygiskState {
fn connect_zygiskd(&mut self, mut client: UnixStream, daemon: &MagiskD) -> LoggedResult<()> {
let is_64_bit: bool = client.read_decodable()?;
let socket = if is_64_bit {
&mut self.sockets.1
} else {
&mut self.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) = daemon.get_module_fds(is_64_bit) {
local.send_fds(&module_fds)?;
}
if local.read_decodable::<i32>()? != 0 {
Err(LoggedError::default())?;
}
local
};
socket.send_fds(&[client.as_raw_fd()])?;
Ok(())
}
pub fn reset(&mut self, mut restore: bool) {
if restore {
self.start_count = 1;
} else {
self.sockets = (None, None);
self.start_count += 1;
if self.start_count > 3 {
warn!("zygote crashed too many times, rolling-back");
restore = true;
}
}
if restore {
self.restore_prop();
} else {
self.set_prop();
}
}
pub fn set_prop(&mut self) {
if !self.lib_name.is_empty() {
return;
}
let orig = get_prop(NBPROP, false);
self.lib_name = if orig.is_empty() || orig == "0" {
ZYGISKLDR.to_string()
} else {
orig + ZYGISKLDR
};
set_prop(NBPROP, Utf8CStr::from_string(&mut self.lib_name), false);
// Whether Huawei's Maple compiler is enabled.
// If so, system server will be created by a special Zygote which ignores the native bridge
// and make system server out of our control. Avoid it by disabling.
if get_prop(cstr!("ro.maple.enable"), false) == "1" {
set_prop(cstr!("ro.maple.enable"), cstr!("0"), false);
}
}
fn restore_prop(&mut self) {
let mut orig = "0".to_string();
if self.lib_name.len() > ZYGISKLDR.len() {
orig = self.lib_name[ZYGISKLDR.len()..].to_string();
}
set_prop(NBPROP, Utf8CStr::from_string(&mut orig), false);
self.lib_name.clear();
}
}
impl MagiskD {
pub fn zygisk_handler(&self, client: i32) {
let mut client = unsafe { UnixStream::from_raw_fd(client) };
@@ -65,33 +166,18 @@ impl MagiskD {
};
match code {
ZygiskRequest::GetInfo => self.get_process_info(client)?,
ZygiskRequest::ConnectCompanion => self.connect_zygiskd(client),
ZygiskRequest::ConnectCompanion => self
.zygisk
.lock()
.unwrap()
.connect_zygiskd(client, self)
.log_with_msg(|w| w.write_str("zygiskd startup error"))?,
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<Vec<RawFd>> {
self.module_list.get().map(|module_list| {
module_list
@@ -105,54 +191,6 @@ impl MagiskD {
})
}
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::<i32>()? != 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()?;
@@ -212,3 +250,18 @@ impl MagiskD {
Ok(())
}
}
// FFI to C++
impl MagiskD {
pub fn zygisk_enabled(&self) -> bool {
self.zygisk_enabled.load(Ordering::Acquire)
}
pub fn zygisk_reset(&self, restore: bool) {
self.zygisk.lock().unwrap().reset(restore);
}
pub fn restore_zygisk_prop(&self) {
self.zygisk.lock().unwrap().restore_prop();
}
}

View File

@@ -10,8 +10,6 @@
using namespace std;
static string zygisk_lib_name = "0";
static void zygiskd(int socket) {
if (getuid() != 0 || fcntl(socket, F_GETFD) < 0)
exit(-1);
@@ -109,30 +107,3 @@ extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf {
return false;
},
};
rust::Str get_zygisk_lib_name() {
return zygisk_lib_name;
}
void set_zygisk_prop() {
string native_bridge_orig = get_prop(NBPROP);
if (native_bridge_orig.empty()) {
native_bridge_orig = "0";
}
zygisk_lib_name = native_bridge_orig == "0" ? ZYGISKLDR : ZYGISKLDR + native_bridge_orig;
set_prop(NBPROP, zygisk_lib_name.data());
// Whether Huawei's Maple compiler is enabled.
// If so, system server will be created by a special Zygote which ignores the native bridge
// and make system server out of our control. Avoid it by disabling.
if (get_prop("ro.maple.enable") == "1") {
set_prop("ro.maple.enable", "0");
}
}
void restore_zygisk_prop() {
string native_bridge_orig = "0";
if (zygisk_lib_name.length() > strlen(ZYGISKLDR)) {
native_bridge_orig = zygisk_lib_name.substr(strlen(ZYGISKLDR));
}
set_prop(NBPROP, native_bridge_orig.data());
}

View File

@@ -496,7 +496,7 @@ void HookContext::hook_jni_methods(JNIEnv *env, const char *clz, JNIMethods meth
auto &new_method = new_methods[i];
if (new_method.fnPtr == method.fnPtr) {
auto &old_method = old_methods[i];
ZLOGD("replace %s#%s%s %p -> %p\n", clz, method.name, method.signature, old_method.fnPtr, method.fnPtr);
ZLOGV("replace %s#%s%s %p -> %p\n", clz, method.name, method.signature, old_method.fnPtr, method.fnPtr);
method.fnPtr = old_method.fnPtr;
break;
}

View File

@@ -1,3 +1,3 @@
mod daemon;
pub use daemon::zygisk_should_load_module;
pub use daemon::{ZygiskState, zygisk_should_load_module};

View File

@@ -19,8 +19,8 @@
#endif
// Extreme verbose logging
#define ZLOGV(...) ZLOGD(__VA_ARGS__)
//#define ZLOGV(...) (void*)0
//#define ZLOGV(...) ZLOGD(__VA_ARGS__)
#define ZLOGV(...) (void*)0
void hook_entry();
void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods);

View File

@@ -20,7 +20,7 @@ pub const DATABIN: &str = concatcp!(SECURE_DIR, "/magisk");
pub const MAGISKDB: &str = concatcp!(SECURE_DIR, "/magisk.db");
// tmpfs paths
const INTERNAL_DIR: &str = ".magisk";
pub const INTERNAL_DIR: &str = ".magisk";
pub const MAIN_CONFIG: &str = concatcp!(INTERNAL_DIR, "/config");
pub const PREINITMIRR: &str = concatcp!(INTERNAL_DIR, "/preinit");
pub const MODULEMNT: &str = concatcp!(INTERNAL_DIR, "/modules");
@@ -37,6 +37,7 @@ pub const SEPOL_PROC_DOMAIN: &str = "magisk";
pub const MAGISK_PROC_CON: &str = concatcp!("u:r:", SEPOL_PROC_DOMAIN, ":s0");
// Unconstrained file type that anyone can access
pub const SEPOL_FILE_TYPE: &str = "magisk_file";
pub const MAGISK_FILE_CON: &str = concatcp!("u:object_r:", SEPOL_FILE_TYPE, ":s0");
// Log pipe that only root and zygote can open
pub const SEPOL_LOG_TYPE: &str = "magisk_log_file";
pub const MAGISK_LOG_CON: &str = concatcp!("u:object_r:", SEPOL_LOG_TYPE, ":s0");

View File

@@ -32,9 +32,6 @@ on nonencrypted
on property:sys.boot_completed=1
exec {0} 0 0 -- {1}/magisk --boot-complete
on property:init.svc.zygote=stopped
exec {0} 0 0 -- {1}/magisk --zygote-restart
"#,
"u:r:magisk:s0", tmp_dir
)

View File

@@ -13,7 +13,7 @@ emu_args=
emu_pid=
atd_min_api=30
atd_max_api=35
atd_max_api=36
huge_ram_min_api=26
case $(uname -m) in
@@ -94,7 +94,7 @@ resolve_vars() {
UpsideDownCakePrivacySandbox) api=34 ;;
VanillaIceCream) api=35 ;;
Baklava) api=36 ;;
36*CANARY) api=36 ;;
36*CANARY) api=10000 ;;
*)
print_error "! Unknown system image version '$ver'"
exit 1

View File

@@ -2,7 +2,7 @@
# AVD MagiskInit Setup
#####################################################################
#
# Support API level: 23 - 35
# Support API level: 23 - 36
#
# With an emulator booted and accessible via ADB, usage:
# ./build.py avd_patch path/to/booted/avd-image/ramdisk.img

View File

@@ -2,7 +2,7 @@
# AVD Magisk Setup
#####################################################################
#
# Support API level: 23 - 35
# Support API level: 23 - 36
#
# For developing Magisk, just use:
# ./build.py emulator