Implement su_daemon in Rust

This commit is contained in:
topjohnwu 2025-02-02 04:30:16 +08:00 committed by John Wu
parent ab86732c89
commit a786801141
15 changed files with 857 additions and 710 deletions

View File

@ -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 \

View File

@ -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<T: Write> fmt::Write for FmtAdaptor<'_, T> {
self.0.write_fmt(args).map_err(|_| fmt::Error)
}
}
pub struct AtomicArc<T> {
ptr: AtomicPtr<T>,
}
impl<T> AtomicArc<T> {
pub fn new(arc: Arc<T>) -> AtomicArc<T> {
let raw = Arc::into_raw(arc);
Self {
ptr: AtomicPtr::new(raw as *mut _),
}
}
pub fn load(&self) -> Arc<T> {
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<T> {
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<T>) -> Arc<T> {
let raw = Arc::into_raw(arc);
self.swap_ptr(raw)
}
pub fn store(&self, arc: Arc<T>) {
// Drop the previous value
let _ = self.swap(arc);
}
}
impl<T> Drop for AtomicArc<T> {
fn drop(&mut self) {
// Drop the internal value
let _ = self.swap_ptr(std::ptr::null());
}
}
impl<T: Default> Default for AtomicArc<T> {
fn default() -> Self {
Self::new(Default::default())
}
}

View File

@ -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();

View File

@ -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<UnixStream>, Option<UnixStream>)>,
pub zygisk_enabled: AtomicBool,
pub zygote_start_count: AtomicU32,
pub cached_su_info: AtomicArc<SuInfo>,
sdk_int: i32,
pub is_emulator: bool,
is_recovery: bool,

View File

@ -52,7 +52,6 @@ void exec_task(std::function<void()> &&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) {

View File

@ -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<ModuleInfo>);
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<u32>,
}
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<ModuleInfo>);
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<i32>;
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<ModuleInfo>);
@ -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) }

View File

@ -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);

View File

@ -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)]

View File

@ -4,8 +4,7 @@
#include <base.hpp>
#include <selinux.hpp>
#include <consts.hpp>
#include "su.hpp"
#include <core.hpp>
using namespace std;
@ -128,14 +127,15 @@ static bool check_no_error(int fd) {
}
static void exec_cmd(const char *action, vector<Extra> &data,
const shared_ptr<su_info> &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<const char *> args{ CALL_PROVIDER };
for (auto &e : data) {
e.add_bind(args);
@ -153,7 +153,7 @@ static void exec_cmd(const char *action, vector<Extra> &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<const char *> args{ START_ACTIVITY };
for (auto &e : data) {
e.add_intent(args);
@ -168,53 +168,58 @@ static void exec_cmd(const char *action, vector<Extra> &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<Extra> extras;
extras.reserve(9);
extras.emplace_back("from.uid", ctx.info->uid);
extras.emplace_back("to.uid", static_cast<int>(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<Extra> 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<Extra> 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

View File

@ -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<AccessInfo>,
}
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::<SuRequest>().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::<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);
}
// 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<SuInfo> {
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<SuInfo> {
let result: LoggedResult<Arc<SuInfo>> = 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)))
}
}

141
native/src/core/su/db.rs Normal file
View File

@ -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<i32>);
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
}
}

View File

@ -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<i32>);
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;

View File

@ -13,13 +13,24 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <algorithm>
#include <consts.hpp>
#include <base.hpp>
#include <flags.h>
#include <core.hpp>
#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<gid_t> 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<gid_t> &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");
}

View File

@ -1,69 +0,0 @@
#pragma once
#include <sys/types.h>
#include <sys/stat.h>
#include <memory>
#include <sqlite.hpp>
#include <core.hpp>
#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<gid_t> gids;
};
struct su_context {
std::shared_ptr<su_info> 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);

View File

@ -1,377 +0,0 @@
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <consts.hpp>
#include <base.hpp>
#include <selinux.hpp>
#include "su.hpp"
#include "pts.hpp"
using namespace std;
static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
static shared_ptr<su_info> 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<su_info> get_su_info(unsigned uid) {
if (uid == AID_ROOT) {
auto info = make_shared<su_info>(uid);
info->access = SILENT_ALLOW;
return info;
}
shared_ptr<su_info> info;
{
mutex_guard lock(cache_lock);
if (!cached || cached->uid != uid || !cached->is_fresh())
cached = make_shared<su_info>(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<uid_t> &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<SuPolicy>(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");
}