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(
val uid: Int,
var policy: Int = INTERACTIVE,
var policy: Int = QUERY,
var remain: Long = -1L,
var logging: Boolean = true,
var notification: Boolean = true,
) {
companion object {
const val INTERACTIVE = 0
const val QUERY = 0
const val DENY = 1
const val ALLOW = 2
const val RESTRICT = 3
}
fun toMap(): MutableMap<String, Any> {

View File

@ -184,6 +184,10 @@ int parse_int(string_view 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 ret = -1;
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> to);
uint32_t parse_uint32_hex(std::string_view s);
int parse_int(std::string_view s);
using thread_entry = void *(*)(void *);

View File

@ -88,6 +88,7 @@ pub mod ffi {
Query,
Deny,
Allow,
Restrict,
}
struct ModuleInfo {
@ -117,6 +118,7 @@ pub mod ffi {
target_pid: i32,
login: bool,
keep_env: bool,
drop_cap: bool,
shell: String,
command: 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 base::libc::{O_CLOEXEC, O_WRONLY};
use base::{Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, libc};
@ -69,6 +69,7 @@ pub(crate) fn restorecon() {
path.clear();
path.push_str(DATABIN);
restore_syscon(&mut path).log_ok();
unsafe { libc::chmod(cstr!(MAGISKDB).as_ptr(), 0o000) };
}
pub(crate) fn restore_tmpcon() -> LoggedResult<()> {

View File

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

View File

@ -9,6 +9,9 @@
#include <getopt.h>
#include <fcntl.h>
#include <pwd.h>
#include <linux/securebits.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sched.h>
#include <sys/types.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"
" -Z, --context CONTEXT Change SELinux context\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"
" -, -l, --login Pretend the shell to be a login shell\n"
" -m, -p,\n"
@ -105,6 +109,7 @@ int su_client_main(int argc, char *argv[]) {
{ "group", required_argument, nullptr, 'g' },
{ "supp-group", required_argument, nullptr, 'G' },
{ "interactive", no_argument, nullptr, 'i' },
{ "drop-cap", no_argument, nullptr, 'd' },
{ nullptr, 0, nullptr, 0 },
};
@ -121,7 +126,7 @@ int su_client_main(int argc, char *argv[]) {
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) {
case 'c': {
string command;
@ -146,6 +151,9 @@ int su_client_main(int argc, char *argv[]) {
case 'p':
req.keep_env = true;
break;
case 'd':
req.drop_cap = true;
break;
case 's':
req.shell = optarg;
break;
@ -259,11 +267,66 @@ 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)");
static void drop_caps() {
static auto last_valid_cap = []() {
uint32_t cap = CAP_WAKE_ALARM;
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;
if (!groups.empty()) {
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
sigset_t block_set;
sigemptyset(&block_set);
sigprocmask(SIG_SETMASK, &block_set, nullptr);
// Config privileges
if (!req.context.empty()) {
auto f = xopen_file("/proc/self/attr/exec", "we");
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);
// Unblock all signals
sigset_t block_set;
sigemptyset(&block_set);
sigprocmask(SIG_SETMASK, &block_set, nullptr);
execvp(req.shell.c_str(), (char **) argv);
fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno));
PLOGE("exec");

View File

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