From cd0eca20b0397537bd131698fdcbf10b25873740 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 8 Aug 2025 17:27:45 -0700 Subject: [PATCH] Migrate connect.cpp to Rust --- native/src/Android.mk | 1 - native/src/base/files.rs | 8 +- native/src/core/include/core.hpp | 3 - native/src/core/lib.rs | 7 +- native/src/core/su/connect.cpp | 232 ------------------------- native/src/core/su/connect.rs | 284 +++++++++++++++++++++++++++++++ native/src/core/su/daemon.rs | 17 +- native/src/core/su/mod.rs | 1 + native/src/include/consts.rs | 3 +- 9 files changed, 302 insertions(+), 254 deletions(-) delete mode 100644 native/src/core/su/connect.cpp create mode 100644 native/src/core/su/connect.rs diff --git a/native/src/Android.mk b/native/src/Android.mk index 48f93cbbf..b16b275e9 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -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 \ diff --git a/native/src/base/files.rs b/native/src/base/files.rs index c1dbf0bee..7a98f167b 100644 --- a/native/src/base/files.rs +++ b/native/src/base/files.rs @@ -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")] diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index cb18a0f8f..3421614fc 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -112,9 +112,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(); } diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 2f16e7f6e..0b86c0583 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -161,9 +161,6 @@ pub mod ffi { 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 +208,7 @@ pub mod ffi { fn send_fds(socket: i32, fds: &[i32]) -> bool; fn recv_fd(socket: i32) -> i32; fn recv_fds(socket: i32) -> Vec; - unsafe fn write_to_fd(self: &SuRequest, fd: i32); + 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; @@ -266,7 +263,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(); diff --git a/native/src/core/su/connect.cpp b/native/src/core/su/connect.cpp deleted file mode 100644 index fe1762ad9..000000000 --- a/native/src/core/su/connect.cpp +++ /dev/null @@ -1,232 +0,0 @@ -#include -#include - -#include -#include -#include - -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 *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 *v): key(k), type(INTLIST), intlist_val(v) {} - - void add_intent(vector &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 &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 &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 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 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 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 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 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; -} diff --git a/native/src/core/su/connect.rs b/native/src/core/su/connect.rs new file mode 100644 index 000000000..799ba4a55 --- /dev/null +++ b/native/src/core/su/connect.rs @@ -0,0 +1,284 @@ +use crate::consts::{INTERNAL_DIR, MAGISK_FILE_CON}; +use crate::daemon::{MagiskD, to_user_id}; +use crate::ffi::{SuAppRequest, SuPolicy, get_magisk_tmp}; +use ExtraVal::{Bool, Int, IntList, Str}; +use base::{ + BytesExt, FileAttr, LibcReturn, LoggedResult, OsError, ResultExt, cstr, fork_dont_care, info, + 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 { + key: &'static str, + value: ExtraVal, +} + +enum ExtraVal { + Int(i32), + Bool(bool), + Str(String), + IntList(Vec), +} + +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), + } + } +} + +impl MagiskD { + fn exec_cmd( + &self, + action: &'static str, + extras: &[Extra], + info: &SuAppRequest, + use_provider: bool, + ) { + let user = to_user_id(info.eval_uid); + let user = user.to_string(); + + if use_provider { + let provider = format!("content://{}.provider", 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", + 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; + } + } + } + + pub fn app_request(&self, info: &SuAppRequest) -> LoggedResult { + let mut fifo = cstr::buf::new::<64>(); + fifo.write_fmt(format_args!( + "{}/{}/su_request_{}", + get_magisk_tmp(), + INTERNAL_DIR, + info.pid + )) + .ok(); + + let fd: LoggedResult = try { + let mut attr = FileAttr::new(); + attr.st.st_mode = 0o600; + attr.st.st_uid = info.mgr_uid.as_(); + attr.st.st_gid = 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.to_string()), + }, + Extra { + key: "uid", + value: Int(info.eval_uid), + }, + Extra { + key: "pid", + value: Int(info.pid), + }, + ]; + self.exec_cmd("request", &extras, info, 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(); + fd + } + + pub fn app_notify(&self, info: &SuAppRequest, policy: SuPolicy) { + if fork_dont_care() != 0 { + return; + } + let extras = [ + Extra { + key: "from.uid", + value: Int(info.uid), + }, + Extra { + key: "pid", + value: Int(info.pid), + }, + Extra { + key: "policy", + value: Int(policy.repr), + }, + ]; + self.exec_cmd("notify", &extras, info, true); + exit(0); + } + + pub fn app_log(&self, info: &SuAppRequest, policy: SuPolicy, notify: bool) { + if fork_dont_care() != 0 { + return; + } + let command = if info.request.command.is_empty() { + &info.request.shell + } else { + &info.request.command + }; + let extras = [ + Extra { + key: "from.uid", + value: Int(info.uid), + }, + Extra { + key: "to.uid", + value: Int(info.request.target_uid), + }, + Extra { + key: "pid", + value: Int(info.pid), + }, + Extra { + key: "policy", + value: Int(policy.repr), + }, + Extra { + key: "target", + value: Int(info.request.target_pid), + }, + Extra { + key: "context", + value: Str(info.request.context.clone()), + }, + Extra { + key: "gids", + value: IntList(info.request.gids.clone()), + }, + Extra { + key: "command", + value: Str(command.clone()), + }, + Extra { + key: "notify", + value: Bool(notify), + }, + ]; + self.exec_cmd("log", &extras, info, true); + exit(0); + } +} diff --git a/native/src/core/su/daemon.rs b/native/src/core/su/daemon.rs index 9074c5840..98e30636b 100644 --- a/native/src/core/su/daemon.rs +++ b/native/src/core/su/daemon.rs @@ -1,13 +1,10 @@ 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::{SuAppRequest, 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}; @@ -145,11 +142,7 @@ impl MagiskD { 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) }; + if let Ok(mut fd) = self.app_request(&app_req) { access.settings.policy = SuPolicy { repr: fd .read_decodable::() @@ -157,13 +150,15 @@ impl MagiskD { .map(i32::from_be) .unwrap_or(SuPolicy::Deny.repr), }; + } else { + access.settings.policy = SuPolicy::Deny; } } if access.settings.log { - app_log(&app_req, access.settings.policy, access.settings.notify); + self.app_log(&app_req, access.settings.policy, access.settings.notify); } else if access.settings.notify { - app_notify(&app_req, access.settings.policy); + self.app_notify(&app_req, access.settings.policy); } // Before unlocking, refresh the timestamp diff --git a/native/src/core/su/mod.rs b/native/src/core/su/mod.rs index cea1de87e..60dcc4471 100644 --- a/native/src/core/su/mod.rs +++ b/native/src/core/su/mod.rs @@ -1,3 +1,4 @@ +mod connect; mod daemon; mod db; mod pts; diff --git a/native/src/include/consts.rs b/native/src/include/consts.rs index 690d25e76..dec2f5cb5 100644 --- a/native/src/include/consts.rs +++ b/native/src/include/consts.rs @@ -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");