mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-04-29 22:14:27 +02:00
Implement su_daemon in Rust
This commit is contained in:
parent
ab86732c89
commit
a786801141
@ -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 \
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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) }
|
||||
|
@ -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);
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
|
299
native/src/core/su/daemon.rs
Normal file
299
native/src/core/su/daemon.rs
Normal 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
141
native/src/core/su/db.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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);
|
@ -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");
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user