su: support drop capabilities

This commit is contained in:
vvb2060 2023-06-15 22:53:41 +08:00 committed by John Wu
parent 37a9724a54
commit ff18cb8e70
8 changed files with 97 additions and 14 deletions

View File

@ -4,15 +4,16 @@ import com.topjohnwu.magisk.core.data.magiskdb.MagiskDB
class SuPolicy( class SuPolicy(
val uid: Int, val uid: Int,
var policy: Int = INTERACTIVE, var policy: Int = QUERY,
var remain: Long = -1L, var remain: Long = -1L,
var logging: Boolean = true, var logging: Boolean = true,
var notification: Boolean = true, var notification: Boolean = true,
) { ) {
companion object { companion object {
const val INTERACTIVE = 0 const val QUERY = 0
const val DENY = 1 const val DENY = 1
const val ALLOW = 2 const val ALLOW = 2
const val RESTRICT = 3
} }
fun toMap(): MutableMap<String, Any> { fun toMap(): MutableMap<String, Any> {

View File

@ -184,6 +184,10 @@ int parse_int(string_view s) {
return parse_num<int, 10>(s); return parse_num<int, 10>(s);
} }
uint32_t parse_uint32_hex(string_view s) {
return parse_num<uint32_t, 16>(s);
}
int switch_mnt_ns(int pid) { int switch_mnt_ns(int pid) {
int ret = -1; int ret = -1;
int fd = syscall(__NR_pidfd_open, pid, 0); int fd = syscall(__NR_pidfd_open, pid, 0);

View File

@ -175,6 +175,7 @@ rust::Vec<size_t> mut_u8_patch(
rust::Slice<const uint8_t> from, rust::Slice<const uint8_t> from,
rust::Slice<const uint8_t> to); rust::Slice<const uint8_t> to);
uint32_t parse_uint32_hex(std::string_view s);
int parse_int(std::string_view s); int parse_int(std::string_view s);
using thread_entry = void *(*)(void *); using thread_entry = void *(*)(void *);

View File

@ -88,6 +88,7 @@ pub mod ffi {
Query, Query,
Deny, Deny,
Allow, Allow,
Restrict,
} }
struct ModuleInfo { struct ModuleInfo {
@ -117,6 +118,7 @@ pub mod ffi {
target_pid: i32, target_pid: i32,
login: bool, login: bool,
keep_env: bool, keep_env: bool,
drop_cap: bool,
shell: String, shell: String,
command: String, command: String,
context: String, context: String,

View File

@ -1,4 +1,4 @@
use crate::consts::{DATABIN, LOG_PIPE, MAGISK_LOG_CON, MODULEROOT, SECURE_DIR}; use crate::consts::{DATABIN, LOG_PIPE, MAGISK_LOG_CON, MAGISKDB, MODULEROOT, SECURE_DIR};
use crate::ffi::get_magisk_tmp; use crate::ffi::get_magisk_tmp;
use base::libc::{O_CLOEXEC, O_WRONLY}; use base::libc::{O_CLOEXEC, O_WRONLY};
use base::{Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, libc}; use base::{Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, libc};
@ -69,6 +69,7 @@ pub(crate) fn restorecon() {
path.clear(); path.clear();
path.push_str(DATABIN); path.push_str(DATABIN);
restore_syscon(&mut path).log_ok(); restore_syscon(&mut path).log_ok();
unsafe { libc::chmod(cstr!(MAGISKDB).as_ptr(), 0o000) };
} }
pub(crate) fn restore_tmpcon() -> LoggedResult<()> { pub(crate) fn restore_tmpcon() -> LoggedResult<()> {

View File

@ -22,6 +22,7 @@ impl Default for SuRequest {
target_pid: -1, target_pid: -1,
login: false, login: false,
keep_env: false, keep_env: false,
drop_cap: false,
shell: DEFAULT_SHELL.to_string(), shell: DEFAULT_SHELL.to_string(),
command: "".to_string(), command: "".to_string(),
context: "".to_string(), context: "".to_string(),
@ -168,7 +169,10 @@ impl MagiskD {
// Before unlocking, refresh the timestamp // Before unlocking, refresh the timestamp
access.refresh(); access.refresh();
// Fail fast if access.settings.policy == SuPolicy::Restrict {
req.drop_cap = true;
}
if access.settings.policy == SuPolicy::Deny { if access.settings.policy == SuPolicy::Deny {
warn!("su: request rejected ({})", info.uid); warn!("su: request rejected ({})", info.uid);
client.write_pod(&SuPolicy::Deny.repr).ok(); client.write_pod(&SuPolicy::Deny.repr).ok();

View File

@ -9,6 +9,9 @@
#include <getopt.h> #include <getopt.h>
#include <fcntl.h> #include <fcntl.h>
#include <pwd.h> #include <pwd.h>
#include <linux/securebits.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sched.h> #include <sched.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -46,6 +49,7 @@ int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGI
" as a primary group if the option -g is not specified\n" " as a primary group if the option -g is not specified\n"
" -Z, --context CONTEXT Change SELinux context\n" " -Z, --context CONTEXT Change SELinux context\n"
" -t, --target PID PID to take mount namespace from\n" " -t, --target PID PID to take mount namespace from\n"
" -d, --drop-cap Drop all Linux capabilities\n"
" -h, --help Display this help message and exit\n" " -h, --help Display this help message and exit\n"
" -, -l, --login Pretend the shell to be a login shell\n" " -, -l, --login Pretend the shell to be a login shell\n"
" -m, -p,\n" " -m, -p,\n"
@ -105,6 +109,7 @@ int su_client_main(int argc, char *argv[]) {
{ "group", required_argument, nullptr, 'g' }, { "group", required_argument, nullptr, 'g' },
{ "supp-group", required_argument, nullptr, 'G' }, { "supp-group", required_argument, nullptr, 'G' },
{ "interactive", no_argument, nullptr, 'i' }, { "interactive", no_argument, nullptr, 'i' },
{ "drop-cap", no_argument, nullptr, 'd' },
{ nullptr, 0, nullptr, 0 }, { nullptr, 0, nullptr, 0 },
}; };
@ -121,7 +126,7 @@ int su_client_main(int argc, char *argv[]) {
bool interactive = false; bool interactive = false;
while ((c = getopt_long(argc, argv, "c:hlimps:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) { while ((c = getopt_long(argc, argv, "c:hlimpds:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) {
switch (c) { switch (c) {
case 'c': { case 'c': {
string command; string command;
@ -146,6 +151,9 @@ int su_client_main(int argc, char *argv[]) {
case 'p': case 'p':
req.keep_env = true; req.keep_env = true;
break; break;
case 'd':
req.drop_cap = true;
break;
case 's': case 's':
req.shell = optarg; req.shell = optarg;
break; break;
@ -259,11 +267,66 @@ 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 drop_caps() {
static void set_identity(int uid, const rust::Vec<gid_t> &groups) { static auto last_valid_cap = []() {
if (seteuid(0)) { uint32_t cap = CAP_WAKE_ALARM;
PLOGE("seteuid (root)"); while (prctl(PR_CAPBSET_READ, cap) >= 0) {
cap++;
} }
return cap - 1;
}();
// Drop bounding set
for (uint32_t cap = 0; cap <= last_valid_cap; cap++) {
if (cap != CAP_SETUID) {
prctl(PR_CAPBSET_DROP, cap);
}
}
// Clean inheritable set
__user_cap_header_struct header = {.version = _LINUX_CAPABILITY_VERSION_3};
__user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3] = {};
if (capget(&header, &data[0]) == 0) {
for (size_t i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) {
data[i].inheritable = 0;
}
capset(&header, &data[0]);
}
// All capabilities will be lost after exec
prctl(PR_SET_SECUREBITS, SECBIT_NOROOT);
// Except CAP_SETUID in bounding set, it is a marker for restricted process
}
static bool proc_is_restricted(pid_t pid) {
char buf[32] = {};
auto bnd = "CapBnd:"sv;
uint32_t data[_LINUX_CAPABILITY_U32S_3] = {};
ssprintf(buf, sizeof(buf), "/proc/%d/status", pid);
file_readline(buf, [&](string_view line) -> bool {
if (line.starts_with(bnd)) {
auto p = line.begin();
advance(p, bnd.size());
while (isspace(*p)) advance(p, 1);
line.remove_prefix(distance(line.begin(), p));
for (int i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) {
auto cap = line.substr((_LINUX_CAPABILITY_U32S_3 - 1 - i) * 8, 8);
data[i] = parse_uint32_hex(cap);
}
return false;
}
return true;
});
bool equal = true;
for (int i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) {
if (i == CAP_TO_INDEX(CAP_SETUID)) {
if (data[i] != CAP_TO_MASK(CAP_SETUID)) equal = false;
} else {
if (data[i] != 0) equal = false;
}
}
return equal;
}
static void set_identity(int uid, const rust::Vec<gid_t> &groups) {
gid_t gid; gid_t gid;
if (!groups.empty()) { if (!groups.empty()) {
if (setgroups(groups.size(), groups.data())) { if (setgroups(groups.size(), groups.data())) {
@ -404,15 +467,21 @@ void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode) {
} }
} }
// Unblock all signals // Config privileges
sigset_t block_set;
sigemptyset(&block_set);
sigprocmask(SIG_SETMASK, &block_set, nullptr);
if (!req.context.empty()) { if (!req.context.empty()) {
auto f = xopen_file("/proc/self/attr/exec", "we"); auto f = xopen_file("/proc/self/attr/exec", "we");
if (f) fprintf(f.get(), "%s", req.context.c_str()); if (f) fprintf(f.get(), "%s", req.context.c_str());
} }
if (req.target_uid != AID_ROOT || req.drop_cap || proc_is_restricted(pid))
drop_caps();
if (req.target_uid != AID_ROOT || req.gids.size() > 0)
set_identity(req.target_uid, req.gids); set_identity(req.target_uid, req.gids);
// Unblock all signals
sigset_t block_set;
sigemptyset(&block_set);
sigprocmask(SIG_SETMASK, &block_set, nullptr);
execvp(req.shell.c_str(), (char **) argv); execvp(req.shell.c_str(), (char **) argv);
fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno)); fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno));
PLOGE("exec"); PLOGE("exec");

View File

@ -16,6 +16,7 @@ pub const LOGFILE: &str = "/cache/magisk.log";
pub const SECURE_DIR: &str = "/data/adb"; pub const SECURE_DIR: &str = "/data/adb";
pub const MODULEROOT: &str = concatcp!(SECURE_DIR, "/modules"); pub const MODULEROOT: &str = concatcp!(SECURE_DIR, "/modules");
pub const DATABIN: &str = concatcp!(SECURE_DIR, "/magisk"); pub const DATABIN: &str = concatcp!(SECURE_DIR, "/magisk");
pub const MAGISKDB: &str = concatcp!(SECURE_DIR, "/magisk.db");
// tmpfs paths // tmpfs paths
const INTERNAL_DIR: &str = ".magisk"; const INTERNAL_DIR: &str = ".magisk";