diff --git a/native/src/Android.mk b/native/src/Android.mk index a9cc53ec4..f02269e2e 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -29,7 +29,6 @@ LOCAL_SRC_FILES := \ core/su/su.cpp \ core/su/connect.cpp \ core/su/pts.cpp \ - core/su/su_daemon.cpp \ core/zygisk/entry.cpp \ core/zygisk/module.cpp \ core/zygisk/hook.cpp \ diff --git a/native/src/base/misc.rs b/native/src/base/misc.rs index 5413ffe92..e29835c00 100644 --- a/native/src/base/misc.rs +++ b/native/src/base/misc.rs @@ -1,12 +1,13 @@ -use std::fmt::Arguments; -use std::io::Write; -use std::process::exit; -use std::{fmt, io, slice, str}; - +use crate::{ffi, StrErr, Utf8CStr}; use argh::EarlyExit; use libc::c_char; - -use crate::{ffi, StrErr, Utf8CStr}; +use std::fmt::Arguments; +use std::io::Write; +use std::mem::ManuallyDrop; +use std::process::exit; +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::sync::Arc; +use std::{fmt, io, slice, str}; pub fn errno() -> &'static mut i32 { unsafe { &mut *libc::__errno() } @@ -161,3 +162,52 @@ impl fmt::Write for FmtAdaptor<'_, T> { self.0.write_fmt(args).map_err(|_| fmt::Error) } } + +pub struct AtomicArc { + ptr: AtomicPtr, +} + +impl AtomicArc { + pub fn new(arc: Arc) -> AtomicArc { + let raw = Arc::into_raw(arc); + Self { + ptr: AtomicPtr::new(raw as *mut _), + } + } + + pub fn load(&self) -> Arc { + let raw = self.ptr.load(Ordering::Acquire); + // SAFETY: the raw pointer is always created from Arc::into_raw + let arc = ManuallyDrop::new(unsafe { Arc::from_raw(raw) }); + ManuallyDrop::into_inner(arc.clone()) + } + + fn swap_ptr(&self, raw: *const T) -> Arc { + let prev = self.ptr.swap(raw as *mut _, Ordering::AcqRel); + // SAFETY: the raw pointer is always created from Arc::into_raw + unsafe { Arc::from_raw(prev) } + } + + pub fn swap(&self, arc: Arc) -> Arc { + let raw = Arc::into_raw(arc); + self.swap_ptr(raw) + } + + pub fn store(&self, arc: Arc) { + // Drop the previous value + let _ = self.swap(arc); + } +} + +impl Drop for AtomicArc { + fn drop(&mut self) { + // Drop the internal value + let _ = self.swap_ptr(std::ptr::null()); + } +} + +impl Default for AtomicArc { + fn default() -> Self { + Self::new(Default::default()) + } +} diff --git a/native/src/core/daemon.cpp b/native/src/core/daemon.cpp index 661db26cd..05bd97247 100644 --- a/native/src/core/daemon.cpp +++ b/native/src/core/daemon.cpp @@ -137,16 +137,16 @@ void MagiskD::reboot() const noexcept { } static void handle_request_async(int client, int code, const sock_cred &cred) { + auto &daemon = MagiskD::Get(); switch (code) { case +RequestCode::DENYLIST: denylist_handler(client, &cred); break; case +RequestCode::SUPERUSER: - su_daemon_handler(client, &cred); + daemon.su_daemon_handler(client, cred); break; case +RequestCode::ZYGOTE_RESTART: { LOGI("** zygote restarted\n"); - auto &daemon = MagiskD::Get(); daemon.prune_su_access(); scan_deny_apps(); daemon.zygisk_reset(false); @@ -154,7 +154,7 @@ static void handle_request_async(int client, int code, const sock_cred &cred) { break; } case +RequestCode::SQLITE_CMD: - MagiskD::Get().db_exec(client); + daemon.db_exec(client); break; case +RequestCode::REMOVE_MODULES: { int do_reboot = read_int(client); @@ -162,12 +162,12 @@ static void handle_request_async(int client, int code, const sock_cred &cred) { write_int(client, 0); close(client); if (do_reboot) { - MagiskD::Get().reboot(); + daemon.reboot(); } break; } case +RequestCode::ZYGISK: - MagiskD::Get().zygisk_handler(client); + daemon.zygisk_handler(client); break; default: __builtin_unreachable(); diff --git a/native/src/core/daemon.rs b/native/src/core/daemon.rs index ade94ab95..e74934270 100644 --- a/native/src/core/daemon.rs +++ b/native/src/core/daemon.rs @@ -8,10 +8,11 @@ use crate::get_prop; use crate::logging::{magisk_logging, setup_logfile, start_log_daemon}; use crate::mount::setup_mounts; use crate::package::ManagerInfo; +use crate::su::SuInfo; use base::libc::{O_CLOEXEC, O_RDONLY}; use base::{ - cstr, error, info, libc, open_fd, BufReadExt, FsPath, FsPathBuf, ResultExt, Utf8CStr, - Utf8CStrBufArr, + cstr, error, info, libc, open_fd, AtomicArc, BufReadExt, FsPath, FsPathBuf, ResultExt, + Utf8CStr, Utf8CStrBufArr, }; use std::fs::File; use std::io::BufReader; @@ -67,6 +68,7 @@ pub struct MagiskD { pub zygiskd_sockets: Mutex<(Option, Option)>, pub zygisk_enabled: AtomicBool, pub zygote_start_count: AtomicU32, + pub cached_su_info: AtomicArc, sdk_int: i32, pub is_emulator: bool, is_recovery: bool, diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index 2462f1c86..2942780e6 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -52,7 +52,6 @@ void exec_task(std::function &&task); // Daemon handlers void denylist_handler(int client, const sock_cred *cred); -void su_daemon_handler(int client, const sock_cred *cred); // Module stuffs void disable_modules(); @@ -76,6 +75,12 @@ bool is_deny_target(int uid, std::string_view process); void revert_unmount(int pid = -1) noexcept; void update_deny_flags(int uid, rust::Str process, uint32_t &flags); +// MagiskSU +void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode); +void app_log(const SuAppRequest &info, SuPolicy policy, bool notify); +void app_notify(const SuAppRequest &info, SuPolicy policy); +int app_request(const SuAppRequest &info); + // Rust bindings static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); } static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) { diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index b3fc8811d..1815e8dcf 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -6,12 +6,20 @@ #![feature(unix_socket_peek)] #![allow(clippy::missing_safety_doc)] -use base::Utf8CStr; +use crate::ffi::SuRequest; +use crate::socket::Encodable; +use base::{libc, Utf8CStr}; +use cxx::{type_id, ExternType}; use daemon::{daemon_entry, MagiskD}; +use derive::Decodable; use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging}; use mount::{clean_mounts, find_preinit_device, revert_unmount}; use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop}; use socket::{recv_fd, recv_fds, send_fd, send_fds}; +use std::fs::File; +use std::mem::ManuallyDrop; +use std::ops::DerefMut; +use std::os::fd::FromRawFd; use zygisk::zygisk_should_load_module; #[path = "../include/consts.rs"] @@ -53,44 +61,6 @@ pub mod ffi { END, } - extern "C++" { - include!("include/resetprop.hpp"); - - #[cxx_name = "prop_cb"] - type PropCb; - unsafe fn get_prop_rs(name: *const c_char, persist: bool) -> String; - unsafe fn prop_cb_exec( - cb: Pin<&mut PropCb>, - name: *const c_char, - value: *const c_char, - serial: u32, - ); - } - - unsafe extern "C++" { - #[namespace = "rust"] - #[cxx_name = "Utf8CStr"] - type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>; - - include!("include/core.hpp"); - - #[cxx_name = "get_magisk_tmp_rs"] - fn get_magisk_tmp() -> Utf8CStrRef<'static>; - #[cxx_name = "resolve_preinit_dir_rs"] - fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String; - fn setup_magisk_env() -> bool; - fn check_key_combo() -> bool; - fn disable_modules(); - fn exec_common_scripts(stage: Utf8CStrRef); - fn exec_module_scripts(state: Utf8CStrRef, modules: &Vec); - fn install_apk(apk: Utf8CStrRef); - fn uninstall_pkg(apk: Utf8CStrRef); - fn update_deny_flags(uid: i32, process: &str, flags: &mut u32); - fn initialize_denylist(); - fn restore_zygisk_prop(); - fn switch_mnt_ns(pid: i32) -> i32; - } - enum DbEntryKey { RootAccess, SuMultiuserMode, @@ -167,27 +137,86 @@ pub mod ffi { ProcessIsMagiskApp = 0x80000000, } + #[derive(Decodable)] + struct SuRequest { + target_uid: i32, + target_pid: i32, + login: bool, + keep_env: bool, + shell: String, + command: String, + context: String, + gids: Vec, + } + + struct SuAppRequest<'a> { + uid: i32, + pid: i32, + eval_uid: i32, + mgr_pkg: &'a str, + mgr_uid: i32, + request: &'a SuRequest, + } + + extern "C++" { + include!("include/resetprop.hpp"); + + #[cxx_name = "prop_cb"] + type PropCb; + unsafe fn get_prop_rs(name: *const c_char, persist: bool) -> String; + unsafe fn prop_cb_exec( + cb: Pin<&mut PropCb>, + name: *const c_char, + value: *const c_char, + serial: u32, + ); + } + unsafe extern "C++" { + #[namespace = "rust"] + #[cxx_name = "Utf8CStr"] + type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>; + #[cxx_name = "ucred"] + type UCred = crate::UCred; + + include!("include/core.hpp"); + + #[cxx_name = "get_magisk_tmp_rs"] + fn get_magisk_tmp() -> Utf8CStrRef<'static>; + #[cxx_name = "resolve_preinit_dir_rs"] + fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String; + fn setup_magisk_env() -> bool; + fn check_key_combo() -> bool; + fn disable_modules(); + fn exec_common_scripts(stage: Utf8CStrRef); + fn exec_module_scripts(state: Utf8CStrRef, modules: &Vec); + fn install_apk(apk: Utf8CStrRef); + fn uninstall_pkg(apk: Utf8CStrRef); + fn update_deny_flags(uid: i32, process: &str, flags: &mut u32); + fn initialize_denylist(); + fn restore_zygisk_prop(); + fn switch_mnt_ns(pid: i32) -> i32; + fn app_request(req: &SuAppRequest) -> i32; + fn app_notify(req: &SuAppRequest, policy: SuPolicy); + fn app_log(req: &SuAppRequest, policy: SuPolicy, notify: bool); + fn exec_root_shell(client: i32, pid: i32, req: &mut SuRequest, mode: MntNsMode); + include!("include/sqlite.hpp"); - fn sqlite3_errstr(code: i32) -> *const c_char; - type sqlite3; - fn open_and_init_db() -> *mut sqlite3; - type DbValues; type DbStatement; + fn sqlite3_errstr(code: i32) -> *const c_char; + fn open_and_init_db() -> *mut sqlite3; fn get_int(self: &DbValues, index: i32) -> i32; #[cxx_name = "get_str"] fn get_text(self: &DbValues, index: i32) -> &str; - fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32; fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32; } extern "Rust" { - fn rust_test_entry(); fn android_logging(); fn zygisk_logging(); fn zygisk_close_logd(); @@ -205,11 +234,27 @@ 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); #[namespace = "rust"] fn daemon_entry(); } + // Default constructors + extern "Rust" { + #[Self = DbSettings] + #[cxx_name = "New"] + fn default() -> DbSettings; + + #[Self = RootSettings] + #[cxx_name = "New"] + fn default() -> RootSettings; + + #[Self = SuRequest] + #[cxx_name = "New"] + fn default() -> SuRequest; + } + // FFI for MagiskD extern "Rust" { type MagiskD; @@ -220,6 +265,7 @@ pub mod ffi { fn zygisk_handler(&self, client: i32); fn zygisk_reset(&self, restore: bool); fn prune_su_access(&self); + fn su_daemon_handler(&self, client: i32, cred: &UCred); #[cxx_name = "get_manager"] unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32; fn set_module_list(&self, module_list: Vec); @@ -234,14 +280,6 @@ pub mod ffi { #[cxx_name = "get_root_settings"] fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool; - #[Self = DbSettings] - #[cxx_name = "New"] - fn default() -> DbSettings; - - #[Self = RootSettings] - #[cxx_name = "New"] - fn default() -> RootSettings; - #[Self = MagiskD] #[cxx_name = "Get"] fn get() -> &'static MagiskD; @@ -253,7 +291,20 @@ pub mod ffi { } } -fn rust_test_entry() {} +#[repr(transparent)] +pub struct UCred(pub libc::ucred); + +unsafe impl ExternType for UCred { + type Id = type_id!("ucred"); + type Kind = cxx::kind::Trivial; +} + +impl SuRequest { + unsafe fn write_to_fd(&self, fd: i32) { + let mut w = ManuallyDrop::new(File::from_raw_fd(fd)); + self.encode(w.deref_mut()).ok(); + } +} pub fn get_prop(name: &Utf8CStr, persist: bool) -> String { unsafe { ffi::get_prop_rs(name.as_ptr(), persist) } diff --git a/native/src/core/package.rs b/native/src/core/package.rs index 5033856b3..83cd58169 100644 --- a/native/src/core/package.rs +++ b/native/src/core/package.rs @@ -480,6 +480,12 @@ impl MagiskD { uid } + pub fn get_manager(&self, user: i32, install: bool) -> (i32, String) { + let mut info = self.manager_info.lock().unwrap(); + let (uid, pkg) = info.get_manager(self, user, install); + (uid, pkg.to_string()) + } + pub fn ensure_manager(&self) { let mut info = self.manager_info.lock().unwrap(); let _ = info.get_manager(self, 0, true); diff --git a/native/src/core/socket.rs b/native/src/core/socket.rs index 2cdc51f0e..111719e27 100644 --- a/native/src/core/socket.rs +++ b/native/src/core/socket.rs @@ -39,7 +39,7 @@ macro_rules! impl_pod_encodable { )*) } -impl_pod_encodable! { u8 i32 usize } +impl_pod_encodable! { u8 u32 i32 usize } impl Encodable for bool { #[inline(always)] diff --git a/native/src/core/su/connect.cpp b/native/src/core/su/connect.cpp index 1b5eb7974..c252bd789 100644 --- a/native/src/core/su/connect.cpp +++ b/native/src/core/su/connect.cpp @@ -4,8 +4,7 @@ #include #include #include - -#include "su.hpp" +#include using namespace std; @@ -128,14 +127,15 @@ static bool check_no_error(int fd) { } static void exec_cmd(const char *action, vector &data, - const shared_ptr &info, bool provider = true) { + const SuAppRequest &info, bool provider = true) { char target[128]; char user[4]; - ssprintf(user, sizeof(user), "%d", to_user_id(info->eval_uid)); + ssprintf(user, sizeof(user), "%d", to_user_id(info.eval_uid)); // First try content provider call method if (provider) { - ssprintf(target, sizeof(target), "content://%s.provider", info->mgr_pkg.data()); + ssprintf(target, sizeof(target), "content://%.*s.provider", + (int) info.mgr_pkg.size(), info.mgr_pkg.data()); vector args{ CALL_PROVIDER }; for (auto &e : data) { e.add_bind(args); @@ -153,7 +153,7 @@ static void exec_cmd(const char *action, vector &data, } // Then try start activity with package name - strscpy(target, info->mgr_pkg.data(), sizeof(target)); + ssprintf(target, sizeof(target), "%.*s", (int) info.mgr_pkg.size(), info.mgr_pkg.data()); vector args{ START_ACTIVITY }; for (auto &e : data) { e.add_intent(args); @@ -168,53 +168,58 @@ static void exec_cmd(const char *action, vector &data, exec_command(exec); } -void app_log(const su_context &ctx) { +void app_log(const SuAppRequest &info, SuPolicy policy, bool notify) { if (fork_dont_care() == 0) { + string context = (string) info.request.context; + string command = info.request.command.empty() + ? (string) info.request.shell + : (string) info.request.command; + vector extras; extras.reserve(9); - extras.emplace_back("from.uid", ctx.info->uid); - extras.emplace_back("to.uid", static_cast(ctx.req.uid)); - extras.emplace_back("pid", ctx.pid); - extras.emplace_back("policy", +ctx.info->access.policy); - extras.emplace_back("target", ctx.req.target); - extras.emplace_back("context", ctx.req.context.data()); - extras.emplace_back("gids", &ctx.req.gids); - extras.emplace_back("command", get_cmd(ctx.req)); - extras.emplace_back("notify", (bool) ctx.info->access.notify); + 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, ctx.info); + exec_cmd("log", extras, info); exit(0); } } -void app_notify(const su_context &ctx) { +void app_notify(const SuAppRequest &info, SuPolicy policy) { if (fork_dont_care() == 0) { vector extras; extras.reserve(3); - extras.emplace_back("from.uid", ctx.info->uid); - extras.emplace_back("pid", ctx.pid); - extras.emplace_back("policy", +ctx.info->access.policy); + extras.emplace_back("from.uid", info.uid); + extras.emplace_back("pid", info.pid); + extras.emplace_back("policy", +policy); - exec_cmd("notify", extras, ctx.info); + exec_cmd("notify", extras, info); exit(0); } } -int app_request(const su_context &ctx) { +int app_request(const SuAppRequest &info) { // Create FIFO char fifo[64]; - ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", get_magisk_tmp(), ctx.pid); + ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", get_magisk_tmp(), info.pid); mkfifo(fifo, 0600); - chown(fifo, ctx.info->mgr_uid, ctx.info->mgr_uid); + chown(fifo, info.mgr_uid, info.mgr_uid); setfilecon(fifo, MAGISK_FILE_CON); // Send request vector extras; extras.reserve(3); extras.emplace_back("fifo", fifo); - extras.emplace_back("uid", ctx.info->eval_uid); - extras.emplace_back("pid", ctx.pid); - exec_cmd("request", extras, ctx.info, false); + extras.emplace_back("uid", info.eval_uid); + extras.emplace_back("pid", info.pid); + exec_cmd("request", extras, info, false); // Wait for data input for at most 70 seconds // Open with O_RDWR to prevent FIFO open block diff --git a/native/src/core/su/daemon.rs b/native/src/core/su/daemon.rs new file mode 100644 index 000000000..0c862269e --- /dev/null +++ b/native/src/core/su/daemon.rs @@ -0,0 +1,299 @@ +use crate::daemon::{to_app_id, to_user_id, MagiskD, AID_ROOT, AID_SHELL}; +use crate::ffi::{ + app_log, app_notify, app_request, exec_root_shell, DbSettings, MultiuserMode, RootAccess, + RootSettings, SuAppRequest, SuPolicy, SuRequest, +}; +use crate::socket::IpcRead; +use crate::UCred; +use base::{debug, error, exit_on_error, libc, warn, LoggedResult, ResultExt, WriteExt}; +use std::fs::File; +use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd}; +use std::os::unix::net::UnixStream; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +const DEFAULT_SHELL: &str = "/system/bin/sh"; + +impl Default for SuRequest { + fn default() -> Self { + SuRequest { + target_uid: AID_ROOT, + target_pid: -1, + login: false, + keep_env: false, + shell: DEFAULT_SHELL.to_string(), + command: "".to_string(), + context: "".to_string(), + gids: vec![], + } + } +} + +pub struct SuInfo { + uid: i32, + eval_uid: i32, + cfg: DbSettings, + mgr_pkg: String, + mgr_uid: i32, + access: Mutex, +} + +struct AccessInfo { + settings: RootSettings, + timestamp: Instant, +} + +impl Default for SuInfo { + fn default() -> Self { + SuInfo { + uid: -1, + eval_uid: -1, + cfg: Default::default(), + mgr_pkg: Default::default(), + mgr_uid: -1, + access: Default::default(), + } + } +} + +impl Default for AccessInfo { + fn default() -> Self { + AccessInfo { + settings: Default::default(), + timestamp: Instant::now(), + } + } +} + +impl SuInfo { + fn allow(uid: i32) -> SuInfo { + let access = RootSettings { + policy: SuPolicy::Allow, + log: false, + notify: false, + }; + SuInfo { + uid, + access: Mutex::new(AccessInfo::new(access)), + ..Default::default() + } + } + + fn deny(uid: i32) -> SuInfo { + let access = RootSettings { + policy: SuPolicy::Deny, + log: false, + notify: false, + }; + SuInfo { + uid, + access: Mutex::new(AccessInfo::new(access)), + ..Default::default() + } + } +} + +impl AccessInfo { + fn new(settings: RootSettings) -> AccessInfo { + AccessInfo { + settings, + timestamp: Instant::now(), + } + } + + fn is_fresh(&self) -> bool { + self.timestamp.elapsed() < Duration::from_secs(3) + } + + fn refresh(&mut self) { + self.timestamp = Instant::now(); + } +} + +impl MagiskD { + pub fn su_daemon_handler(&self, client: i32, cred: &UCred) { + let mut client = unsafe { UnixStream::from_raw_fd(client) }; + let cred = cred.0; + debug!( + "su: request from uid=[{}], pid=[{}], client=[{}]", + cred.uid, + cred.pid, + client.as_raw_fd() + ); + + let mut req = match client.read_decodable::().log() { + Ok(req) => req, + Err(_) => { + warn!("su: remote process probably died, abort"); + client.write_pod(&SuPolicy::Deny.repr).ok(); + return; + } + }; + + let info = self.get_su_info(cred.uid as i32); + let app_req = SuAppRequest { + uid: cred.uid as i32, + pid: cred.pid as i32, + eval_uid: info.eval_uid, + mgr_pkg: &info.mgr_pkg, + mgr_uid: info.mgr_uid, + request: &req, + }; + + { + let mut access = info.access.lock().unwrap(); + + if access.settings.policy == SuPolicy::Query { + let fd = app_request(&app_req); + if fd < 0 { + access.settings.policy = SuPolicy::Deny; + } else { + let mut fd = unsafe { File::from_raw_fd(fd) }; + access.settings.policy = SuPolicy { + repr: fd + .read_decodable::() + .log() + .map(i32::from_be) + .unwrap_or(SuPolicy::Deny.repr), + }; + } + } + + if access.settings.log { + app_log(&app_req, access.settings.policy, access.settings.notify); + } else if access.settings.notify { + app_notify(&app_req, access.settings.policy); + } + + // Before unlocking, refresh the timestamp + access.refresh(); + + // Fail fast + if access.settings.policy == SuPolicy::Deny { + warn!("su: request rejected ({})", info.uid); + client.write_pod(&SuPolicy::Deny.repr).ok(); + return; + } + } + + // At this point, the root access is granted. + // Fork a child root process and monitor its exit value. + let child = unsafe { libc::fork() }; + if child == 0 { + debug!("su: fork handler"); + + // Abort upon any error occurred + exit_on_error(true); + + // ack + client.write_pod(&0).ok(); + + exec_root_shell(client.into_raw_fd(), cred.pid, &mut req, info.cfg.mnt_ns); + return; + } + if child < 0 { + error!("su: fork failed, abort"); + return; + } + + // Wait result + debug!("su: waiting child pid=[{}]", child); + let mut status = 0; + let code = unsafe { + if libc::waitpid(child, &mut status, 0) > 0 { + libc::WEXITSTATUS(status) + } else { + -1 + } + }; + debug!("su: return code=[{}]", code); + client.write_pod(&code).ok(); + } + + fn get_su_info(&self, uid: i32) -> Arc { + if uid == AID_ROOT { + return Arc::new(SuInfo::allow(AID_ROOT)); + } + + let cached = self.cached_su_info.load(); + if cached.uid == uid && cached.access.lock().unwrap().is_fresh() { + return cached; + } + + let info = self.build_su_info(uid); + self.cached_su_info.store(info.clone()); + info + } + + fn build_su_info(&self, uid: i32) -> Arc { + let result: LoggedResult> = try { + let cfg = self.get_db_settings()?; + + // Check multiuser settings + let eval_uid = match cfg.multiuser_mode { + MultiuserMode::OwnerOnly => { + if to_user_id(uid) != 0 { + return Arc::new(SuInfo::deny(uid)); + } + uid + } + MultiuserMode::OwnerManaged => to_app_id(uid), + _ => uid, + }; + + // Check su access settings + match cfg.root_access { + RootAccess::Disabled => { + warn!("Root access is disabled!"); + return Arc::new(SuInfo::deny(uid)); + } + RootAccess::AdbOnly => { + if uid != AID_SHELL { + warn!("Root access limited to ADB only!"); + return Arc::new(SuInfo::deny(uid)); + } + } + RootAccess::AppsOnly => { + if uid == AID_SHELL { + warn!("Root access is disabled for ADB!"); + return Arc::new(SuInfo::deny(uid)); + } + } + _ => {} + }; + + let mut access = RootSettings::default(); + self.get_root_settings(eval_uid, &mut access)?; + + // We need to talk to the manager, get the app info + let (mgr_uid, mgr_pkg) = + if access.policy == SuPolicy::Query || access.log || access.notify { + self.get_manager(to_user_id(eval_uid), true) + } else { + (-1, String::new()) + }; + + // If it's the manager, allow it silently + if to_app_id(uid) == to_app_id(mgr_uid) { + return Arc::new(SuInfo::allow(uid)); + } + + // If still not determined, check if manager exists + if access.policy == SuPolicy::Query && mgr_uid < 0 { + return Arc::new(SuInfo::deny(uid)); + } + + // Finally, the SuInfo + Arc::new(SuInfo { + uid, + eval_uid, + cfg, + mgr_pkg, + mgr_uid, + access: Mutex::new(AccessInfo::new(access)), + }) + }; + + result.unwrap_or(Arc::new(SuInfo::deny(uid))) + } +} diff --git a/native/src/core/su/db.rs b/native/src/core/su/db.rs new file mode 100644 index 000000000..7ed8afc73 --- /dev/null +++ b/native/src/core/su/db.rs @@ -0,0 +1,141 @@ +use crate::daemon::{ + to_app_id, to_user_id, MagiskD, AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL, +}; +use crate::db::DbArg::Integer; +use crate::db::{SqlTable, SqliteResult, SqliteReturn}; +use crate::ffi::{DbValues, MultiuserMode, RootAccess, RootSettings, SuPolicy}; +use base::ResultExt; + +impl Default for SuPolicy { + fn default() -> Self { + SuPolicy::Query + } +} + +impl Default for RootSettings { + fn default() -> Self { + RootSettings { + policy: Default::default(), + log: true, + notify: true, + } + } +} + +impl SqlTable for RootSettings { + fn on_row(&mut self, columns: &[String], values: &DbValues) { + for (i, column) in columns.iter().enumerate() { + let val = values.get_int(i as i32); + if column == "policy" { + self.policy.repr = val; + } else if column == "logging" { + self.log = val != 0; + } else if column == "notify" { + self.notify = val != 0; + } + } + } +} + +struct UidList(Vec); + +impl SqlTable for UidList { + fn on_row(&mut self, _: &[String], values: &DbValues) { + self.0.push(values.get_int(0)); + } +} + +impl MagiskD { + pub fn get_root_settings(&self, uid: i32, settings: &mut RootSettings) -> SqliteResult<()> { + self.db_exec_with_rows( + "SELECT policy, logging, notification FROM policies \ + WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))", + &[Integer(uid as i64)], + settings, + ) + .sql_result() + } + + pub fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool { + self.get_root_settings(uid, settings).log().is_ok() + } + + pub fn prune_su_access(&self) { + let mut list = UidList(Vec::new()); + if self + .db_exec_with_rows("SELECT uid FROM policies", &[], &mut list) + .sql_result() + .log() + .is_err() + { + return; + } + + let app_list = self.get_app_no_list(); + let mut rm_uids = Vec::new(); + + for uid in list.0 { + let app_id = to_app_id(uid); + if (AID_APP_START..=AID_APP_END).contains(&app_id) { + let app_no = app_id - AID_APP_START; + if !app_list.contains(app_no as usize) { + // The app_id is no longer installed + rm_uids.push(uid); + } + } + } + + for uid in rm_uids { + self.db_exec("DELETE FROM policies WHERE uid=?", &[Integer(uid as i64)]); + } + } + + pub fn uid_granted_root(&self, mut uid: i32) -> bool { + if uid == AID_ROOT { + return true; + } + + let cfg = match self.get_db_settings().log() { + Ok(cfg) => cfg, + Err(_) => return false, + }; + + // Check user root access settings + match cfg.root_access { + RootAccess::Disabled => return false, + RootAccess::AppsOnly => { + if uid == AID_SHELL { + return false; + } + } + RootAccess::AdbOnly => { + if uid != AID_SHELL { + return false; + } + } + _ => {} + } + + // Check multiuser settings + match cfg.multiuser_mode { + MultiuserMode::OwnerOnly => { + if to_user_id(uid) != 0 { + return false; + } + } + MultiuserMode::OwnerManaged => uid = to_app_id(uid), + _ => {} + } + + let mut granted = false; + let mut output_fn = + |_: &[String], values: &DbValues| granted = values.get_int(0) == SuPolicy::Allow.repr; + self.db_exec_with_rows( + "SELECT policy FROM policies WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))", + &[Integer(uid as i64)], + &mut output_fn, + ); + + granted + } +} diff --git a/native/src/core/su/mod.rs b/native/src/core/su/mod.rs index 2c7e03818..067d5b65a 100644 --- a/native/src/core/su/mod.rs +++ b/native/src/core/su/mod.rs @@ -1,141 +1,4 @@ -use crate::daemon::{ - to_app_id, to_user_id, MagiskD, AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL, -}; -use crate::db::DbArg::Integer; -use crate::db::{SqlTable, SqliteResult, SqliteReturn}; -use crate::ffi::{DbValues, MultiuserMode, RootAccess, RootSettings, SuPolicy}; -use base::ResultExt; +mod daemon; +mod db; -impl Default for SuPolicy { - fn default() -> Self { - SuPolicy::Query - } -} - -impl Default for RootSettings { - fn default() -> Self { - RootSettings { - policy: Default::default(), - log: true, - notify: true, - } - } -} - -impl SqlTable for RootSettings { - fn on_row(&mut self, columns: &[String], values: &DbValues) { - for (i, column) in columns.iter().enumerate() { - let val = values.get_int(i as i32); - if column == "policy" { - self.policy.repr = val; - } else if column == "logging" { - self.log = val != 0; - } else if column == "notify" { - self.notify = val != 0; - } - } - } -} - -struct UidList(Vec); - -impl SqlTable for UidList { - fn on_row(&mut self, _: &[String], values: &DbValues) { - self.0.push(values.get_int(0)); - } -} - -impl MagiskD { - fn get_root_settings(&self, uid: i32, settings: &mut RootSettings) -> SqliteResult<()> { - self.db_exec_with_rows( - "SELECT policy, logging, notification FROM policies \ - WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))", - &[Integer(uid as i64)], - settings, - ) - .sql_result() - } - - pub fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool { - self.get_root_settings(uid, settings).log().is_ok() - } - - pub fn prune_su_access(&self) { - let mut list = UidList(Vec::new()); - if self - .db_exec_with_rows("SELECT uid FROM policies", &[], &mut list) - .sql_result() - .log() - .is_err() - { - return; - } - - let app_list = self.get_app_no_list(); - let mut rm_uids = Vec::new(); - - for uid in list.0 { - let app_id = to_app_id(uid); - if (AID_APP_START..=AID_APP_END).contains(&app_id) { - let app_no = app_id - AID_APP_START; - if !app_list.contains(app_no as usize) { - // The app_id is no longer installed - rm_uids.push(uid); - } - } - } - - for uid in rm_uids { - self.db_exec("DELETE FROM policies WHERE uid=?", &[Integer(uid as i64)]); - } - } - - pub fn uid_granted_root(&self, mut uid: i32) -> bool { - if uid == AID_ROOT { - return true; - } - - let cfg = match self.get_db_settings().log() { - Ok(cfg) => cfg, - Err(_) => return false, - }; - - // Check user root access settings - match cfg.root_access { - RootAccess::Disabled => return false, - RootAccess::AppsOnly => { - if uid == AID_SHELL { - return false; - } - } - RootAccess::AdbOnly => { - if uid != AID_SHELL { - return false; - } - } - _ => {} - } - - // Check multiuser settings - match cfg.multiuser_mode { - MultiuserMode::OwnerOnly => { - if to_user_id(uid) != 0 { - return false; - } - } - MultiuserMode::OwnerManaged => uid = to_app_id(uid), - _ => {} - } - - let mut granted = false; - let mut output_fn = - |_: &[String], values: &DbValues| granted = values.get_int(0) == SuPolicy::Allow.repr; - self.db_exec_with_rows( - "SELECT policy FROM policies WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))", - &[Integer(uid as i64)], - &mut output_fn, - ); - - granted - } -} +pub use daemon::SuInfo; diff --git a/native/src/core/su/su.cpp b/native/src/core/su/su.cpp index 995d368f0..e85e5926c 100644 --- a/native/src/core/su/su.cpp +++ b/native/src/core/su/su.cpp @@ -13,13 +13,24 @@ #include #include +#include + #include #include #include +#include -#include "su.hpp" #include "pts.hpp" +using namespace std; + +#define DEFAULT_SHELL "/system/bin/sh" + +// Constants for atty +#define ATTY_IN (1 << 0) +#define ATTY_OUT (1 << 1) +#define ATTY_ERR (1 << 2) + int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 }; [[noreturn]] static void usage(int status) { @@ -97,7 +108,7 @@ int su_client_main(int argc, char *argv[]) { { nullptr, 0, nullptr, 0 }, }; - su_request su_req; + auto req = SuRequest::New(); for (int i = 0; i < argc; i++) { // Replace -cn and -z with -Z for backwards compatibility @@ -110,25 +121,28 @@ int su_client_main(int argc, char *argv[]) { while ((c = getopt_long(argc, argv, "c:hlmps:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) { switch (c) { - case 'c': + case 'c': { + string command; for (int i = optind - 1; i < argc; ++i) { - if (!su_req.command.empty()) - su_req.command += ' '; - su_req.command += argv[i]; + if (!command.empty()) + command += ' '; + command += argv[i]; } + req.command = command; optind = argc; break; + } case 'h': usage(EXIT_SUCCESS); case 'l': - su_req.login = true; + req.login = true; break; case 'm': case 'p': - su_req.keepenv = true; + req.keep_env = true; break; case 's': - su_req.shell = optarg; + req.shell = optarg; break; case 'V': printf("%d\n", MAGISK_VER_CODE); @@ -137,33 +151,36 @@ int su_client_main(int argc, char *argv[]) { printf("%s\n", MAGISK_VERSION ":MAGISKSU"); exit(EXIT_SUCCESS); case 'Z': - su_req.context = optarg; + req.context = optarg; break; case 'M': case 't': - if (su_req.target != -1) { + if (req.target_pid != -1) { fprintf(stderr, "Can't use -M and -t at the same time\n"); usage(EXIT_FAILURE); } if (optarg == nullptr) { - su_req.target = 0; + req.target_pid = 0; } else { - su_req.target = parse_int(optarg); - if (*optarg == '-' || su_req.target == -1) { + req.target_pid = parse_int(optarg); + if (*optarg == '-' || req.target_pid == -1) { fprintf(stderr, "Invalid PID: %s\n", optarg); usage(EXIT_FAILURE); } } break; case 'g': - case 'G': + case 'G': { + vector gids; if (int gid = parse_int(optarg); gid >= 0) { - su_req.gids.insert(c == 'g' ? su_req.gids.begin() : su_req.gids.end(), gid); + gids.insert(c == 'g' ? gids.begin() : gids.end(), gid); } else { fprintf(stderr, "Invalid GID: %s\n", optarg); usage(EXIT_FAILURE); } + std::copy(gids.begin(), gids.end(), std::back_inserter(req.gids)); break; + } default: /* Bionic getopt_long doesn't terminate its error output by newline */ fprintf(stderr, "\n"); @@ -172,7 +189,7 @@ int su_client_main(int argc, char *argv[]) { } if (optind < argc && strcmp(argv[optind], "-") == 0) { - su_req.login = true; + req.login = true; optind++; } /* username or uid */ @@ -180,9 +197,9 @@ int su_client_main(int argc, char *argv[]) { struct passwd *pw; pw = getpwnam(argv[optind]); if (pw) - su_req.uid = pw->pw_uid; + req.target_uid = pw->pw_uid; else - su_req.uid = parse_int(argv[optind]); + req.target_uid = parse_int(argv[optind]); optind++; } @@ -191,12 +208,8 @@ int su_client_main(int argc, char *argv[]) { // Connect to client fd = connect_daemon(+RequestCode::SUPERUSER); - // Send su_request - xwrite(fd, &su_req, sizeof(su_req_base)); - write_string(fd, su_req.shell); - write_string(fd, su_req.command); - write_string(fd, su_req.context); - write_vector(fd, su_req.gids); + // Send request + req.write_to_fd(fd); // Wait for ack from daemon if (read_int(fd)) { @@ -239,3 +252,162 @@ int su_client_main(int argc, char *argv[]) { return code; } + +// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root +static void set_identity(int uid, const rust::Vec &groups) { + if (seteuid(0)) { + PLOGE("seteuid (root)"); + } + gid_t gid; + if (!groups.empty()) { + if (setgroups(groups.size(), groups.data())) { + PLOGE("setgroups"); + } + gid = groups[0]; + } else { + gid = uid; + } + if (setresgid(gid, gid, gid)) { + PLOGE("setresgid (%u)", uid); + } + if (setresuid(uid, uid, uid)) { + PLOGE("setresuid (%u)", uid); + } +} + +void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode) { + // Become session leader + xsetsid(); + + // The FDs for each of the streams + int infd = recv_fd(client); + int outfd = recv_fd(client); + int errfd = recv_fd(client); + + // App need a PTY + if (read_int(client)) { + string pts; + string ptmx; + auto magiskpts = get_magisk_tmp() + "/"s SHELLPTS; + if (access(magiskpts.data(), F_OK)) { + pts = "/dev/pts"; + ptmx = "/dev/ptmx"; + } else { + pts = magiskpts; + ptmx = magiskpts + "/ptmx"; + } + int ptmx_fd = xopen(ptmx.data(), O_RDWR); + grantpt(ptmx_fd); + unlockpt(ptmx_fd); + int pty_num = get_pty_num(ptmx_fd); + if (pty_num < 0) { + // Kernel issue? Fallback to /dev/pts + close(ptmx_fd); + pts = "/dev/pts"; + ptmx_fd = xopen("/dev/ptmx", O_RDWR); + grantpt(ptmx_fd); + unlockpt(ptmx_fd); + pty_num = get_pty_num(ptmx_fd); + } + send_fd(client, ptmx_fd); + close(ptmx_fd); + + string pts_slave = pts + "/" + to_string(pty_num); + LOGD("su: pts_slave=[%s]\n", pts_slave.data()); + + // Opening the TTY has to occur after the + // fork() and setsid() so that it becomes + // our controlling TTY and not the daemon's + int ptsfd = xopen(pts_slave.data(), O_RDWR); + + if (infd < 0) + infd = ptsfd; + if (outfd < 0) + outfd = ptsfd; + if (errfd < 0) + errfd = ptsfd; + } + + // Swap out stdin, stdout, stderr + xdup2(infd, STDIN_FILENO); + xdup2(outfd, STDOUT_FILENO); + xdup2(errfd, STDERR_FILENO); + + close(infd); + close(outfd); + close(errfd); + close(client); + + // Handle namespaces + if (req.target_pid == -1) + req.target_pid = pid; + else if (req.target_pid == 0) + mode = MntNsMode::Global; + else if (mode == MntNsMode::Global) + mode = MntNsMode::Requester; + + switch (mode) { + case MntNsMode::Global: + LOGD("su: use global namespace\n"); + break; + case MntNsMode::Requester: + LOGD("su: use namespace of pid=[%d]\n", req.target_pid); + switch_mnt_ns(req.target_pid); + break; + case MntNsMode::Isolate: + LOGD("su: use new isolated namespace\n"); + switch_mnt_ns(req.target_pid); + xunshare(CLONE_NEWNS); + xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr); + break; + } + + const char *argv[4] = { nullptr }; + + argv[0] = req.login ? "-" : req.shell.c_str(); + + if (!req.command.empty()) { + argv[1] = "-c"; + argv[2] = req.command.c_str(); + } + + // Setup environment + umask(022); + char path[32]; + ssprintf(path, sizeof(path), "/proc/%d/cwd", pid); + char cwd[4096]; + if (realpath(path, cwd, sizeof(cwd)) > 0) + chdir(cwd); + ssprintf(path, sizeof(path), "/proc/%d/environ", pid); + auto env = full_read(path); + clearenv(); + for (size_t pos = 0; pos < env.size(); ++pos) { + putenv(env.data() + pos); + pos = env.find_first_of('\0', pos); + if (pos == std::string::npos) + break; + } + if (!req.keep_env) { + struct passwd *pw; + pw = getpwuid(req.target_uid); + if (pw) { + setenv("HOME", pw->pw_dir, 1); + setenv("USER", pw->pw_name, 1); + setenv("LOGNAME", pw->pw_name, 1); + setenv("SHELL", req.shell.c_str(), 1); + } + } + + // Unblock all signals + sigset_t block_set; + sigemptyset(&block_set); + sigprocmask(SIG_SETMASK, &block_set, nullptr); + if (!req.context.empty()) { + auto f = xopen_file("/proc/self/attr/exec", "we"); + if (f) fprintf(f.get(), "%s", req.context.c_str()); + } + set_identity(req.target_uid, req.gids); + execvp(req.shell.c_str(), (char **) argv); + fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno)); + PLOGE("exec"); +} diff --git a/native/src/core/su/su.hpp b/native/src/core/su/su.hpp deleted file mode 100644 index b4cbccd19..000000000 --- a/native/src/core/su/su.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -#define DEFAULT_SHELL "/system/bin/sh" - -// Constants for atty -#define ATTY_IN (1 << 0) -#define ATTY_OUT (1 << 1) -#define ATTY_ERR (1 << 2) - -#define SILENT_ALLOW { SuPolicy::Allow, false, false } -#define SILENT_DENY { SuPolicy::Deny, false, false } - -class su_info { -public: - // Unique key - const int uid; - - // These should be guarded with internal lock - int eval_uid; // The effective UID, taking multiuser settings into consideration - struct DbSettings cfg; - struct RootSettings access; - std::string mgr_pkg; - int mgr_uid; - void check_db(); - - // These should be guarded with global cache lock - bool is_fresh(); - void refresh(); - - su_info(int uid); - ~su_info(); - mutex_guard lock(); - -private: - long timestamp; - // Internal lock - pthread_mutex_t _lock; -}; - -struct su_req_base { - uid_t uid = AID_ROOT; - bool login = false; - bool keepenv = false; - pid_t target = -1; -} __attribute__((packed)); - -struct su_request : public su_req_base { - std::string shell = DEFAULT_SHELL; - std::string command; - std::string context; - std::vector gids; -}; - -struct su_context { - std::shared_ptr info; - su_request req; - int pid; -}; - -void app_log(const su_context &ctx); -void app_notify(const su_context &ctx); -int app_request(const su_context &ctx); diff --git a/native/src/core/su/su_daemon.cpp b/native/src/core/su/su_daemon.cpp deleted file mode 100644 index 7c2369ab6..000000000 --- a/native/src/core/su/su_daemon.cpp +++ /dev/null @@ -1,377 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "su.hpp" -#include "pts.hpp" - -using namespace std; - -static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER; -static shared_ptr cached; - -su_info::su_info(int uid) : -uid(uid), eval_uid(-1), cfg(DbSettings::New()), access(RootSettings::New()), -mgr_uid(-1), timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {} - -su_info::~su_info() { - pthread_mutex_destroy(&_lock); -} - -mutex_guard su_info::lock() { - return mutex_guard(_lock); -} - -bool su_info::is_fresh() { - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - long current = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; - return current - timestamp < 3000; /* 3 seconds */ -} - -void su_info::refresh() { - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - timestamp = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; -} - -void su_info::check_db() { - eval_uid = uid; - auto &daemon = MagiskD::Get(); - daemon.get_db_settings(cfg); - - // Check multiuser settings - switch (cfg.multiuser_mode) { - case MultiuserMode::OwnerOnly: - if (to_user_id(uid) != 0) { - eval_uid = -1; - access = SILENT_DENY; - } - break; - case MultiuserMode::OwnerManaged: - eval_uid = to_app_id(uid); - break; - case MultiuserMode::User: - default: - break; - } - - if (eval_uid > 0) { - if (!daemon.get_root_settings(eval_uid, access)) - return; - } - - // We need to check our manager - if (access.policy == SuPolicy::Query || access.log || access.notify) { - mgr_uid = daemon.get_manager(to_user_id(eval_uid), &mgr_pkg, true); - } -} - -static shared_ptr get_su_info(unsigned uid) { - if (uid == AID_ROOT) { - auto info = make_shared(uid); - info->access = SILENT_ALLOW; - return info; - } - - shared_ptr info; - { - mutex_guard lock(cache_lock); - if (!cached || cached->uid != uid || !cached->is_fresh()) - cached = make_shared(uid); - cached->refresh(); - info = cached; - } - - mutex_guard lock = info->lock(); - - if (info->access.policy == SuPolicy::Query) { - // Not cached, get data from database - info->check_db(); - - // If it's the manager, allow it silently - if (to_app_id(info->uid) == to_app_id(info->mgr_uid)) { - info->access = SILENT_ALLOW; - return info; - } - - // Check su access settings - switch (info->cfg.root_access) { - case RootAccess::Disabled: - LOGW("Root access is disabled!\n"); - info->access = SILENT_DENY; - break; - case RootAccess::AdbOnly: - if (info->uid != AID_SHELL) { - LOGW("Root access limited to ADB only!\n"); - info->access = SILENT_DENY; - } - break; - case RootAccess::AppsOnly: - if (info->uid == AID_SHELL) { - LOGW("Root access is disabled for ADB!\n"); - info->access = SILENT_DENY; - } - break; - case RootAccess::AppsAndAdb: - default: - break; - } - - if (info->access.policy != SuPolicy::Query) - return info; - - // If still not determined, check if manager exists - if (info->mgr_uid < 0) { - info->access = SILENT_DENY; - return info; - } - } - return info; -} - -// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root -static void set_identity(uid_t uid, const std::vector &groups) { - if (seteuid(0)) { - PLOGE("seteuid (root)"); - } - gid_t gid; - if (groups.size() > 0) { - if (setgroups(groups.size(), groups.data())) { - PLOGE("setgroups"); - } - gid = groups[0]; - } else { - gid = uid; - } - if (setresgid(gid, gid, gid)) { - PLOGE("setresgid (%u)", uid); - } - if (setresuid(uid, uid, uid)) { - PLOGE("setresuid (%u)", uid); - } -} - -void su_daemon_handler(int client, const sock_cred *cred) { - LOGD("su: request from uid=[%d], pid=[%d], client=[%d]\n", cred->uid, cred->pid, client); - - su_context ctx = { - .info = get_su_info(cred->uid), - .req = su_request(), - .pid = cred->pid - }; - - // Read su_request - if (xxread(client, &ctx.req, sizeof(su_req_base)) < 0 - || !read_string(client, ctx.req.shell) - || !read_string(client, ctx.req.command) - || !read_string(client, ctx.req.context) - || !read_vector(client, ctx.req.gids)) { - LOGW("su: remote process probably died, abort\n"); - ctx.info.reset(); - write_int(client, +SuPolicy::Deny); - close(client); - return; - } - - // If still not determined, ask manager - if (ctx.info->access.policy == SuPolicy::Query) { - int fd = app_request(ctx); - if (fd < 0) { - ctx.info->access.policy = SuPolicy::Deny; - } else { - int ret = read_int_be(fd); - ctx.info->access.policy = ret < 0 ? SuPolicy::Deny : static_cast(ret); - close(fd); - } - } - - if (ctx.info->access.log) - app_log(ctx); - else if (ctx.info->access.notify) - app_notify(ctx); - - // Fail fast - if (ctx.info->access.policy == SuPolicy::Deny) { - LOGW("su: request rejected (%u)\n", ctx.info->uid); - ctx.info.reset(); - write_int(client, +SuPolicy::Deny); - close(client); - return; - } - - // Fork a child root process - // - // The child process will need to setsid, open a pseudo-terminal - // if needed, and eventually exec shell. - // The parent process will wait for the result and - // send the return code back to our client. - - if (int child = xfork(); child) { - ctx.info.reset(); - - // Wait result - LOGD("su: waiting child pid=[%d]\n", child); - int status, code; - - if (waitpid(child, &status, 0) > 0) - code = WEXITSTATUS(status); - else - code = -1; - - LOGD("su: return code=[%d]\n", code); - write(client, &code, sizeof(code)); - close(client); - return; - } - - LOGD("su: fork handler\n"); - - // Abort upon any error occurred - exit_on_error(true); - - // ack - write_int(client, 0); - - // Become session leader - xsetsid(); - - // The FDs for each of the streams - int infd = recv_fd(client); - int outfd = recv_fd(client); - int errfd = recv_fd(client); - - // App need a PTY - if (read_int(client)) { - string pts; - string ptmx; - auto magiskpts = get_magisk_tmp() + "/"s SHELLPTS; - if (access(magiskpts.data(), F_OK)) { - pts = "/dev/pts"; - ptmx = "/dev/ptmx"; - } else { - pts = magiskpts; - ptmx = magiskpts + "/ptmx"; - } - int ptmx_fd = xopen(ptmx.data(), O_RDWR); - grantpt(ptmx_fd); - unlockpt(ptmx_fd); - int pty_num = get_pty_num(ptmx_fd); - if (pty_num < 0) { - // Kernel issue? Fallback to /dev/pts - close(ptmx_fd); - pts = "/dev/pts"; - ptmx_fd = xopen("/dev/ptmx", O_RDWR); - grantpt(ptmx_fd); - unlockpt(ptmx_fd); - pty_num = get_pty_num(ptmx_fd); - } - send_fd(client, ptmx_fd); - close(ptmx_fd); - - string pts_slave = pts + "/" + to_string(pty_num); - LOGD("su: pts_slave=[%s]\n", pts_slave.data()); - - // Opening the TTY has to occur after the - // fork() and setsid() so that it becomes - // our controlling TTY and not the daemon's - int ptsfd = xopen(pts_slave.data(), O_RDWR); - - if (infd < 0) - infd = ptsfd; - if (outfd < 0) - outfd = ptsfd; - if (errfd < 0) - errfd = ptsfd; - } - - // Swap out stdin, stdout, stderr - xdup2(infd, STDIN_FILENO); - xdup2(outfd, STDOUT_FILENO); - xdup2(errfd, STDERR_FILENO); - - close(infd); - close(outfd); - close(errfd); - close(client); - - // Handle namespaces - if (ctx.req.target == -1) - ctx.req.target = ctx.pid; - else if (ctx.req.target == 0) - ctx.info->cfg.mnt_ns = MntNsMode::Global; - else if (ctx.info->cfg.mnt_ns == MntNsMode::Global) - ctx.info->cfg.mnt_ns = MntNsMode::Requester; - switch (ctx.info->cfg.mnt_ns) { - case MntNsMode::Global: - LOGD("su: use global namespace\n"); - break; - case MntNsMode::Requester: - LOGD("su: use namespace of pid=[%d]\n", ctx.req.target); - if (switch_mnt_ns(ctx.req.target)) - LOGD("su: setns failed, fallback to global\n"); - break; - case MntNsMode::Isolate: - LOGD("su: use new isolated namespace\n"); - switch_mnt_ns(ctx.req.target); - xunshare(CLONE_NEWNS); - xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr); - break; - } - - const char *argv[4] = { nullptr }; - - argv[0] = ctx.req.login ? "-" : ctx.req.shell.data(); - - if (!ctx.req.command.empty()) { - argv[1] = "-c"; - argv[2] = ctx.req.command.data(); - } - - // Setup environment - umask(022); - char path[32]; - ssprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid); - char cwd[4096]; - if (realpath(path, cwd, sizeof(cwd)) > 0) - chdir(cwd); - ssprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid); - auto env = full_read(path); - clearenv(); - for (size_t pos = 0; pos < env.size(); ++pos) { - putenv(env.data() + pos); - pos = env.find_first_of('\0', pos); - if (pos == std::string::npos) - break; - } - if (!ctx.req.keepenv) { - struct passwd *pw; - pw = getpwuid(ctx.req.uid); - if (pw) { - setenv("HOME", pw->pw_dir, 1); - setenv("USER", pw->pw_name, 1); - setenv("LOGNAME", pw->pw_name, 1); - setenv("SHELL", ctx.req.shell.data(), 1); - } - } - - // Unblock all signals - sigset_t block_set; - sigemptyset(&block_set); - sigprocmask(SIG_SETMASK, &block_set, nullptr); - if (!ctx.req.context.empty()) { - auto f = xopen_file("/proc/self/attr/exec", "we"); - if (f) fprintf(f.get(), "%s", ctx.req.context.data()); - } - set_identity(ctx.req.uid, ctx.req.gids); - execvp(ctx.req.shell.data(), (char **) argv); - fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell.data(), strerror(errno)); - PLOGE("exec"); -}