mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-05-04 00:14:26 +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/su.cpp \
|
||||||
core/su/connect.cpp \
|
core/su/connect.cpp \
|
||||||
core/su/pts.cpp \
|
core/su/pts.cpp \
|
||||||
core/su/su_daemon.cpp \
|
|
||||||
core/zygisk/entry.cpp \
|
core/zygisk/entry.cpp \
|
||||||
core/zygisk/module.cpp \
|
core/zygisk/module.cpp \
|
||||||
core/zygisk/hook.cpp \
|
core/zygisk/hook.cpp \
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use std::fmt::Arguments;
|
use crate::{ffi, StrErr, Utf8CStr};
|
||||||
use std::io::Write;
|
|
||||||
use std::process::exit;
|
|
||||||
use std::{fmt, io, slice, str};
|
|
||||||
|
|
||||||
use argh::EarlyExit;
|
use argh::EarlyExit;
|
||||||
use libc::c_char;
|
use libc::c_char;
|
||||||
|
use std::fmt::Arguments;
|
||||||
use crate::{ffi, StrErr, Utf8CStr};
|
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 {
|
pub fn errno() -> &'static mut i32 {
|
||||||
unsafe { &mut *libc::__errno() }
|
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)
|
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) {
|
static void handle_request_async(int client, int code, const sock_cred &cred) {
|
||||||
|
auto &daemon = MagiskD::Get();
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case +RequestCode::DENYLIST:
|
case +RequestCode::DENYLIST:
|
||||||
denylist_handler(client, &cred);
|
denylist_handler(client, &cred);
|
||||||
break;
|
break;
|
||||||
case +RequestCode::SUPERUSER:
|
case +RequestCode::SUPERUSER:
|
||||||
su_daemon_handler(client, &cred);
|
daemon.su_daemon_handler(client, cred);
|
||||||
break;
|
break;
|
||||||
case +RequestCode::ZYGOTE_RESTART: {
|
case +RequestCode::ZYGOTE_RESTART: {
|
||||||
LOGI("** zygote restarted\n");
|
LOGI("** zygote restarted\n");
|
||||||
auto &daemon = MagiskD::Get();
|
|
||||||
daemon.prune_su_access();
|
daemon.prune_su_access();
|
||||||
scan_deny_apps();
|
scan_deny_apps();
|
||||||
daemon.zygisk_reset(false);
|
daemon.zygisk_reset(false);
|
||||||
@ -154,7 +154,7 @@ static void handle_request_async(int client, int code, const sock_cred &cred) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case +RequestCode::SQLITE_CMD:
|
case +RequestCode::SQLITE_CMD:
|
||||||
MagiskD::Get().db_exec(client);
|
daemon.db_exec(client);
|
||||||
break;
|
break;
|
||||||
case +RequestCode::REMOVE_MODULES: {
|
case +RequestCode::REMOVE_MODULES: {
|
||||||
int do_reboot = read_int(client);
|
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);
|
write_int(client, 0);
|
||||||
close(client);
|
close(client);
|
||||||
if (do_reboot) {
|
if (do_reboot) {
|
||||||
MagiskD::Get().reboot();
|
daemon.reboot();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case +RequestCode::ZYGISK:
|
case +RequestCode::ZYGISK:
|
||||||
MagiskD::Get().zygisk_handler(client);
|
daemon.zygisk_handler(client);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
__builtin_unreachable();
|
__builtin_unreachable();
|
||||||
|
@ -8,10 +8,11 @@ use crate::get_prop;
|
|||||||
use crate::logging::{magisk_logging, setup_logfile, start_log_daemon};
|
use crate::logging::{magisk_logging, setup_logfile, start_log_daemon};
|
||||||
use crate::mount::setup_mounts;
|
use crate::mount::setup_mounts;
|
||||||
use crate::package::ManagerInfo;
|
use crate::package::ManagerInfo;
|
||||||
|
use crate::su::SuInfo;
|
||||||
use base::libc::{O_CLOEXEC, O_RDONLY};
|
use base::libc::{O_CLOEXEC, O_RDONLY};
|
||||||
use base::{
|
use base::{
|
||||||
cstr, error, info, libc, open_fd, BufReadExt, FsPath, FsPathBuf, ResultExt, Utf8CStr,
|
cstr, error, info, libc, open_fd, AtomicArc, BufReadExt, FsPath, FsPathBuf, ResultExt,
|
||||||
Utf8CStrBufArr,
|
Utf8CStr, Utf8CStrBufArr,
|
||||||
};
|
};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
@ -67,6 +68,7 @@ pub struct MagiskD {
|
|||||||
pub zygiskd_sockets: Mutex<(Option<UnixStream>, Option<UnixStream>)>,
|
pub zygiskd_sockets: Mutex<(Option<UnixStream>, Option<UnixStream>)>,
|
||||||
pub zygisk_enabled: AtomicBool,
|
pub zygisk_enabled: AtomicBool,
|
||||||
pub zygote_start_count: AtomicU32,
|
pub zygote_start_count: AtomicU32,
|
||||||
|
pub cached_su_info: AtomicArc<SuInfo>,
|
||||||
sdk_int: i32,
|
sdk_int: i32,
|
||||||
pub is_emulator: bool,
|
pub is_emulator: bool,
|
||||||
is_recovery: bool,
|
is_recovery: bool,
|
||||||
|
@ -52,7 +52,6 @@ void exec_task(std::function<void()> &&task);
|
|||||||
|
|
||||||
// Daemon handlers
|
// Daemon handlers
|
||||||
void denylist_handler(int client, const sock_cred *cred);
|
void denylist_handler(int client, const sock_cred *cred);
|
||||||
void su_daemon_handler(int client, const sock_cred *cred);
|
|
||||||
|
|
||||||
// Module stuffs
|
// Module stuffs
|
||||||
void disable_modules();
|
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 revert_unmount(int pid = -1) noexcept;
|
||||||
void update_deny_flags(int uid, rust::Str process, uint32_t &flags);
|
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
|
// Rust bindings
|
||||||
static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }
|
static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }
|
||||||
static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) {
|
static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) {
|
||||||
|
@ -6,12 +6,20 @@
|
|||||||
#![feature(unix_socket_peek)]
|
#![feature(unix_socket_peek)]
|
||||||
#![allow(clippy::missing_safety_doc)]
|
#![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 daemon::{daemon_entry, MagiskD};
|
||||||
|
use derive::Decodable;
|
||||||
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
|
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
|
||||||
use mount::{clean_mounts, find_preinit_device, revert_unmount};
|
use mount::{clean_mounts, find_preinit_device, revert_unmount};
|
||||||
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
|
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 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;
|
use zygisk::zygisk_should_load_module;
|
||||||
|
|
||||||
#[path = "../include/consts.rs"]
|
#[path = "../include/consts.rs"]
|
||||||
@ -53,44 +61,6 @@ pub mod ffi {
|
|||||||
END,
|
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 {
|
enum DbEntryKey {
|
||||||
RootAccess,
|
RootAccess,
|
||||||
SuMultiuserMode,
|
SuMultiuserMode,
|
||||||
@ -167,27 +137,86 @@ pub mod ffi {
|
|||||||
ProcessIsMagiskApp = 0x80000000,
|
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++" {
|
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");
|
include!("include/sqlite.hpp");
|
||||||
|
|
||||||
fn sqlite3_errstr(code: i32) -> *const c_char;
|
|
||||||
|
|
||||||
type sqlite3;
|
type sqlite3;
|
||||||
fn open_and_init_db() -> *mut sqlite3;
|
|
||||||
|
|
||||||
type DbValues;
|
type DbValues;
|
||||||
type DbStatement;
|
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;
|
fn get_int(self: &DbValues, index: i32) -> i32;
|
||||||
#[cxx_name = "get_str"]
|
#[cxx_name = "get_str"]
|
||||||
fn get_text(self: &DbValues, index: i32) -> &str;
|
fn get_text(self: &DbValues, index: i32) -> &str;
|
||||||
|
|
||||||
fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32;
|
fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32;
|
||||||
fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32;
|
fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Rust" {
|
extern "Rust" {
|
||||||
fn rust_test_entry();
|
|
||||||
fn android_logging();
|
fn android_logging();
|
||||||
fn zygisk_logging();
|
fn zygisk_logging();
|
||||||
fn zygisk_close_logd();
|
fn zygisk_close_logd();
|
||||||
@ -205,11 +234,27 @@ pub mod ffi {
|
|||||||
fn send_fds(socket: i32, fds: &[i32]) -> bool;
|
fn send_fds(socket: i32, fds: &[i32]) -> bool;
|
||||||
fn recv_fd(socket: i32) -> i32;
|
fn recv_fd(socket: i32) -> i32;
|
||||||
fn recv_fds(socket: i32) -> Vec<i32>;
|
fn recv_fds(socket: i32) -> Vec<i32>;
|
||||||
|
unsafe fn write_to_fd(self: &SuRequest, fd: i32);
|
||||||
|
|
||||||
#[namespace = "rust"]
|
#[namespace = "rust"]
|
||||||
fn daemon_entry();
|
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
|
// FFI for MagiskD
|
||||||
extern "Rust" {
|
extern "Rust" {
|
||||||
type MagiskD;
|
type MagiskD;
|
||||||
@ -220,6 +265,7 @@ pub mod ffi {
|
|||||||
fn zygisk_handler(&self, client: i32);
|
fn zygisk_handler(&self, client: i32);
|
||||||
fn zygisk_reset(&self, restore: bool);
|
fn zygisk_reset(&self, restore: bool);
|
||||||
fn prune_su_access(&self);
|
fn prune_su_access(&self);
|
||||||
|
fn su_daemon_handler(&self, client: i32, cred: &UCred);
|
||||||
#[cxx_name = "get_manager"]
|
#[cxx_name = "get_manager"]
|
||||||
unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32;
|
unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32;
|
||||||
fn set_module_list(&self, module_list: Vec<ModuleInfo>);
|
fn set_module_list(&self, module_list: Vec<ModuleInfo>);
|
||||||
@ -234,14 +280,6 @@ pub mod ffi {
|
|||||||
#[cxx_name = "get_root_settings"]
|
#[cxx_name = "get_root_settings"]
|
||||||
fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool;
|
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]
|
#[Self = MagiskD]
|
||||||
#[cxx_name = "Get"]
|
#[cxx_name = "Get"]
|
||||||
fn get() -> &'static MagiskD;
|
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 {
|
pub fn get_prop(name: &Utf8CStr, persist: bool) -> String {
|
||||||
unsafe { ffi::get_prop_rs(name.as_ptr(), persist) }
|
unsafe { ffi::get_prop_rs(name.as_ptr(), persist) }
|
||||||
|
@ -480,6 +480,12 @@ impl MagiskD {
|
|||||||
uid
|
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) {
|
pub fn ensure_manager(&self) {
|
||||||
let mut info = self.manager_info.lock().unwrap();
|
let mut info = self.manager_info.lock().unwrap();
|
||||||
let _ = info.get_manager(self, 0, true);
|
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 {
|
impl Encodable for bool {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -4,8 +4,7 @@
|
|||||||
#include <base.hpp>
|
#include <base.hpp>
|
||||||
#include <selinux.hpp>
|
#include <selinux.hpp>
|
||||||
#include <consts.hpp>
|
#include <consts.hpp>
|
||||||
|
#include <core.hpp>
|
||||||
#include "su.hpp"
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@ -128,14 +127,15 @@ static bool check_no_error(int fd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void exec_cmd(const char *action, vector<Extra> &data,
|
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 target[128];
|
||||||
char user[4];
|
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
|
// First try content provider call method
|
||||||
if (provider) {
|
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 };
|
vector<const char *> args{ CALL_PROVIDER };
|
||||||
for (auto &e : data) {
|
for (auto &e : data) {
|
||||||
e.add_bind(args);
|
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
|
// 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 };
|
vector<const char *> args{ START_ACTIVITY };
|
||||||
for (auto &e : data) {
|
for (auto &e : data) {
|
||||||
e.add_intent(args);
|
e.add_intent(args);
|
||||||
@ -168,53 +168,58 @@ static void exec_cmd(const char *action, vector<Extra> &data,
|
|||||||
exec_command(exec);
|
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) {
|
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;
|
vector<Extra> extras;
|
||||||
extras.reserve(9);
|
extras.reserve(9);
|
||||||
extras.emplace_back("from.uid", ctx.info->uid);
|
extras.emplace_back("from.uid", info.uid);
|
||||||
extras.emplace_back("to.uid", static_cast<int>(ctx.req.uid));
|
extras.emplace_back("to.uid", info.request.target_uid);
|
||||||
extras.emplace_back("pid", ctx.pid);
|
extras.emplace_back("pid", info.pid);
|
||||||
extras.emplace_back("policy", +ctx.info->access.policy);
|
extras.emplace_back("policy", +policy);
|
||||||
extras.emplace_back("target", ctx.req.target);
|
extras.emplace_back("target", info.request.target_pid);
|
||||||
extras.emplace_back("context", ctx.req.context.data());
|
extras.emplace_back("context", context.data());
|
||||||
extras.emplace_back("gids", &ctx.req.gids);
|
extras.emplace_back("gids", &info.request.gids);
|
||||||
extras.emplace_back("command", get_cmd(ctx.req));
|
extras.emplace_back("command", command.data());
|
||||||
extras.emplace_back("notify", (bool) ctx.info->access.notify);
|
extras.emplace_back("notify", notify);
|
||||||
|
|
||||||
exec_cmd("log", extras, ctx.info);
|
exec_cmd("log", extras, info);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void app_notify(const su_context &ctx) {
|
void app_notify(const SuAppRequest &info, SuPolicy policy) {
|
||||||
if (fork_dont_care() == 0) {
|
if (fork_dont_care() == 0) {
|
||||||
vector<Extra> extras;
|
vector<Extra> extras;
|
||||||
extras.reserve(3);
|
extras.reserve(3);
|
||||||
extras.emplace_back("from.uid", ctx.info->uid);
|
extras.emplace_back("from.uid", info.uid);
|
||||||
extras.emplace_back("pid", ctx.pid);
|
extras.emplace_back("pid", info.pid);
|
||||||
extras.emplace_back("policy", +ctx.info->access.policy);
|
extras.emplace_back("policy", +policy);
|
||||||
|
|
||||||
exec_cmd("notify", extras, ctx.info);
|
exec_cmd("notify", extras, info);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int app_request(const su_context &ctx) {
|
int app_request(const SuAppRequest &info) {
|
||||||
// Create FIFO
|
// Create FIFO
|
||||||
char fifo[64];
|
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);
|
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);
|
setfilecon(fifo, MAGISK_FILE_CON);
|
||||||
|
|
||||||
// Send request
|
// Send request
|
||||||
vector<Extra> extras;
|
vector<Extra> extras;
|
||||||
extras.reserve(3);
|
extras.reserve(3);
|
||||||
extras.emplace_back("fifo", fifo);
|
extras.emplace_back("fifo", fifo);
|
||||||
extras.emplace_back("uid", ctx.info->eval_uid);
|
extras.emplace_back("uid", info.eval_uid);
|
||||||
extras.emplace_back("pid", ctx.pid);
|
extras.emplace_back("pid", info.pid);
|
||||||
exec_cmd("request", extras, ctx.info, false);
|
exec_cmd("request", extras, info, false);
|
||||||
|
|
||||||
// Wait for data input for at most 70 seconds
|
// Wait for data input for at most 70 seconds
|
||||||
// Open with O_RDWR to prevent FIFO open block
|
// 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::{
|
mod daemon;
|
||||||
to_app_id, to_user_id, MagiskD, AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL,
|
mod db;
|
||||||
};
|
|
||||||
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 {
|
pub use daemon::SuInfo;
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -13,13 +13,24 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <consts.hpp>
|
#include <consts.hpp>
|
||||||
#include <base.hpp>
|
#include <base.hpp>
|
||||||
#include <flags.h>
|
#include <flags.h>
|
||||||
|
#include <core.hpp>
|
||||||
|
|
||||||
#include "su.hpp"
|
|
||||||
#include "pts.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 };
|
int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };
|
||||||
|
|
||||||
[[noreturn]] static void usage(int status) {
|
[[noreturn]] static void usage(int status) {
|
||||||
@ -97,7 +108,7 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
{ nullptr, 0, nullptr, 0 },
|
{ nullptr, 0, nullptr, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
su_request su_req;
|
auto req = SuRequest::New();
|
||||||
|
|
||||||
for (int i = 0; i < argc; i++) {
|
for (int i = 0; i < argc; i++) {
|
||||||
// Replace -cn and -z with -Z for backwards compatibility
|
// 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) {
|
while ((c = getopt_long(argc, argv, "c:hlmps:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'c':
|
case 'c': {
|
||||||
|
string command;
|
||||||
for (int i = optind - 1; i < argc; ++i) {
|
for (int i = optind - 1; i < argc; ++i) {
|
||||||
if (!su_req.command.empty())
|
if (!command.empty())
|
||||||
su_req.command += ' ';
|
command += ' ';
|
||||||
su_req.command += argv[i];
|
command += argv[i];
|
||||||
}
|
}
|
||||||
|
req.command = command;
|
||||||
optind = argc;
|
optind = argc;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 'h':
|
case 'h':
|
||||||
usage(EXIT_SUCCESS);
|
usage(EXIT_SUCCESS);
|
||||||
case 'l':
|
case 'l':
|
||||||
su_req.login = true;
|
req.login = true;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
case 'p':
|
case 'p':
|
||||||
su_req.keepenv = true;
|
req.keep_env = true;
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
su_req.shell = optarg;
|
req.shell = optarg;
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
printf("%d\n", MAGISK_VER_CODE);
|
printf("%d\n", MAGISK_VER_CODE);
|
||||||
@ -137,33 +151,36 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
printf("%s\n", MAGISK_VERSION ":MAGISKSU");
|
printf("%s\n", MAGISK_VERSION ":MAGISKSU");
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
case 'Z':
|
case 'Z':
|
||||||
su_req.context = optarg;
|
req.context = optarg;
|
||||||
break;
|
break;
|
||||||
case 'M':
|
case 'M':
|
||||||
case 't':
|
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");
|
fprintf(stderr, "Can't use -M and -t at the same time\n");
|
||||||
usage(EXIT_FAILURE);
|
usage(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
if (optarg == nullptr) {
|
if (optarg == nullptr) {
|
||||||
su_req.target = 0;
|
req.target_pid = 0;
|
||||||
} else {
|
} else {
|
||||||
su_req.target = parse_int(optarg);
|
req.target_pid = parse_int(optarg);
|
||||||
if (*optarg == '-' || su_req.target == -1) {
|
if (*optarg == '-' || req.target_pid == -1) {
|
||||||
fprintf(stderr, "Invalid PID: %s\n", optarg);
|
fprintf(stderr, "Invalid PID: %s\n", optarg);
|
||||||
usage(EXIT_FAILURE);
|
usage(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'g':
|
case 'g':
|
||||||
case 'G':
|
case 'G': {
|
||||||
|
vector<gid_t> gids;
|
||||||
if (int gid = parse_int(optarg); gid >= 0) {
|
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 {
|
} else {
|
||||||
fprintf(stderr, "Invalid GID: %s\n", optarg);
|
fprintf(stderr, "Invalid GID: %s\n", optarg);
|
||||||
usage(EXIT_FAILURE);
|
usage(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
std::copy(gids.begin(), gids.end(), std::back_inserter(req.gids));
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
/* Bionic getopt_long doesn't terminate its error output by newline */
|
/* Bionic getopt_long doesn't terminate its error output by newline */
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
@ -172,7 +189,7 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (optind < argc && strcmp(argv[optind], "-") == 0) {
|
if (optind < argc && strcmp(argv[optind], "-") == 0) {
|
||||||
su_req.login = true;
|
req.login = true;
|
||||||
optind++;
|
optind++;
|
||||||
}
|
}
|
||||||
/* username or uid */
|
/* username or uid */
|
||||||
@ -180,9 +197,9 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
struct passwd *pw;
|
struct passwd *pw;
|
||||||
pw = getpwnam(argv[optind]);
|
pw = getpwnam(argv[optind]);
|
||||||
if (pw)
|
if (pw)
|
||||||
su_req.uid = pw->pw_uid;
|
req.target_uid = pw->pw_uid;
|
||||||
else
|
else
|
||||||
su_req.uid = parse_int(argv[optind]);
|
req.target_uid = parse_int(argv[optind]);
|
||||||
optind++;
|
optind++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,12 +208,8 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
// Connect to client
|
// Connect to client
|
||||||
fd = connect_daemon(+RequestCode::SUPERUSER);
|
fd = connect_daemon(+RequestCode::SUPERUSER);
|
||||||
|
|
||||||
// Send su_request
|
// Send request
|
||||||
xwrite(fd, &su_req, sizeof(su_req_base));
|
req.write_to_fd(fd);
|
||||||
write_string(fd, su_req.shell);
|
|
||||||
write_string(fd, su_req.command);
|
|
||||||
write_string(fd, su_req.context);
|
|
||||||
write_vector(fd, su_req.gids);
|
|
||||||
|
|
||||||
// Wait for ack from daemon
|
// Wait for ack from daemon
|
||||||
if (read_int(fd)) {
|
if (read_int(fd)) {
|
||||||
@ -239,3 +252,162 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
return code;
|
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