mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-04-19 08:01:28 +00:00
Introduce new sepolicy strategy for legacy devices
The existing sepolicy patching strategy looks like this: 1. 2SI: use LD_PRELOAD to hijack `security_load_policy` 2. Split policy: devices using split policy implies it also needs to do early mount, which means fstab is stored in device tree. So we do the following: - Hijack the fstab node in the device tree in sysfs - Wait for init to mount selinuxfs for us - Hijack selinuxfs to intercept sepolicy loading 3. Monolithic policy: directly patch `/sepolicy` Method #1 and #2 both has the magiskinit pre-init daemon handling the sepolicy patching and loading process, while method #3 gives us zero control over sepolicy loading process. Downsides: a. Pre-init daemon bypasses the need to guess which sepolicy init will load, because the original init will literally send the stock sepolicy file directly to us with this approach. b. If we want to add more features/functionalities during the sepolicy patching process, we will leave out devices using method #3 In order to solve these issues, we completely redesign the sepolicy patching strategy for non-2SI devices. Instead of limiting usage of pre-init daemon to early mount devices, we always intercept the sepolicy loading process regardless of the Android version and device setup. This will give us a unified implementation for sepolicy patching, and will make it easier to develop further new features down the line.
This commit is contained in:
parent
b6b34f7612
commit
c9eac0c438
@ -1,10 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define DEFAULT_DT_DIR "/proc/device-tree/firmware/android"
|
||||||
|
#define REDIR_PATH "/data/magiskinit"
|
||||||
|
|
||||||
|
#define PRELOAD_LIB "/dev/preload.so"
|
||||||
|
#define PRELOAD_POLICY "/dev/sepolicy"
|
||||||
|
#define PRELOAD_ACK "/dev/ack"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#include <base.hpp>
|
#include <base.hpp>
|
||||||
#include <stream.hpp>
|
#include <stream.hpp>
|
||||||
|
|
||||||
#include "init-rs.hpp"
|
#include "init-rs.hpp"
|
||||||
|
|
||||||
#define DEFAULT_DT_DIR "/proc/device-tree/firmware/android"
|
|
||||||
#define REDIR_PATH "/data/magiskinit"
|
|
||||||
|
|
||||||
int magisk_proxy_main(int, char *argv[]);
|
int magisk_proxy_main(int, char *argv[]);
|
||||||
rust::Utf8CStr backup_init();
|
rust::Utf8CStr backup_init();
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@ -16,7 +16,6 @@ mod init;
|
|||||||
mod logging;
|
mod logging;
|
||||||
mod mount;
|
mod mount;
|
||||||
mod rootdir;
|
mod rootdir;
|
||||||
mod selinux;
|
|
||||||
mod twostage;
|
mod twostage;
|
||||||
|
|
||||||
#[cxx::bridge]
|
#[cxx::bridge]
|
||||||
@ -80,7 +79,6 @@ pub mod ffi {
|
|||||||
// MagiskInit
|
// MagiskInit
|
||||||
extern "Rust" {
|
extern "Rust" {
|
||||||
type OverlayAttr;
|
type OverlayAttr;
|
||||||
fn patch_sepolicy(self: &MagiskInit, src: Utf8CStrRef, out: Utf8CStrRef);
|
|
||||||
fn parse_config_file(self: &mut MagiskInit);
|
fn parse_config_file(self: &mut MagiskInit);
|
||||||
fn mount_overlay(self: &mut MagiskInit, dest: Utf8CStrRef);
|
fn mount_overlay(self: &mut MagiskInit, dest: Utf8CStrRef);
|
||||||
fn restore_overlay_contexts(self: &MagiskInit);
|
fn restore_overlay_contexts(self: &MagiskInit);
|
||||||
@ -96,7 +94,7 @@ pub mod ffi {
|
|||||||
fn collect_devices(self: &MagiskInit);
|
fn collect_devices(self: &MagiskInit);
|
||||||
fn mount_preinit_dir(self: &MagiskInit);
|
fn mount_preinit_dir(self: &MagiskInit);
|
||||||
unsafe fn find_block(self: &MagiskInit, partname: *const c_char) -> u64;
|
unsafe fn find_block(self: &MagiskInit, partname: *const c_char) -> u64;
|
||||||
fn hijack_sepolicy(self: &mut MagiskInit) -> bool;
|
fn handle_sepolicy(self: &mut MagiskInit);
|
||||||
unsafe fn patch_fissiond(self: &mut MagiskInit, tmp_path: *const c_char);
|
unsafe fn patch_fissiond(self: &mut MagiskInit, tmp_path: *const c_char);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,29 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <dlfcn.h>
|
|
||||||
|
#include "init.hpp"
|
||||||
|
|
||||||
__attribute__((constructor))
|
__attribute__((constructor))
|
||||||
static void preload_init() {
|
static void preload_init() {
|
||||||
// Make sure our next exec won't get bugged
|
// Make sure our next exec won't get bugged
|
||||||
unsetenv("LD_PRELOAD");
|
unsetenv("LD_PRELOAD");
|
||||||
unlink("/dev/preload.so");
|
unlink(PRELOAD_LIB);
|
||||||
}
|
}
|
||||||
|
|
||||||
int security_load_policy(void *data, size_t len) {
|
int security_load_policy(void *data, size_t len) {
|
||||||
int (*load_policy)(void *, size_t) = dlsym(RTLD_NEXT, "security_load_policy");
|
int policy = open(PRELOAD_POLICY, O_WRONLY | O_CREAT, 0644);
|
||||||
// Skip checking errors, because if we cannot find the symbol, there
|
if (policy < 0) return -1;
|
||||||
// isn't much we can do other than crashing anyways.
|
|
||||||
int result = load_policy(data, len);
|
// Write the policy
|
||||||
|
write(policy, data, len);
|
||||||
|
close(policy);
|
||||||
|
|
||||||
// Wait for ack
|
// Wait for ack
|
||||||
int fd = open("/sys/fs/selinux/enforce", O_RDONLY);
|
int ack = open(PRELOAD_ACK, O_RDONLY);
|
||||||
char c;
|
char c;
|
||||||
read(fd, &c, 1);
|
read(ack, &c, 1);
|
||||||
close(fd);
|
close(ack);
|
||||||
|
|
||||||
return result;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -329,15 +329,7 @@ void MagiskInit::patch_ro_root() noexcept {
|
|||||||
// Extract overlay archives
|
// Extract overlay archives
|
||||||
extract_files(false);
|
extract_files(false);
|
||||||
|
|
||||||
// Oculus Go will use a special sepolicy if unlocked
|
handle_sepolicy();
|
||||||
if (access("/sepolicy.unlocked", F_OK) == 0) {
|
|
||||||
patch_sepolicy("/sepolicy.unlocked", ROOTOVL "/sepolicy.unlocked");
|
|
||||||
} else {
|
|
||||||
bool patch = access(SPLIT_PLAT_CIL, F_OK) != 0 && access("/sepolicy", F_OK) == 0;
|
|
||||||
if (patch || !hijack_sepolicy()) {
|
|
||||||
patch_sepolicy("/sepolicy", ROOTOVL "/sepolicy");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unlink("init-ld");
|
unlink("init-ld");
|
||||||
|
|
||||||
// Mount rootdir
|
// Mount rootdir
|
||||||
@ -368,12 +360,6 @@ void MagiskInit::patch_rw_root() noexcept {
|
|||||||
if (patch_rc_scripts("/", "/sbin", true))
|
if (patch_rc_scripts("/", "/sbin", true))
|
||||||
patch_fissiond("/sbin");
|
patch_fissiond("/sbin");
|
||||||
|
|
||||||
bool treble;
|
|
||||||
{
|
|
||||||
auto init = mmap_data("/init");
|
|
||||||
treble = init.contains(SPLIT_PLAT_CIL);
|
|
||||||
}
|
|
||||||
|
|
||||||
xmkdir(PRE_TMPSRC, 0);
|
xmkdir(PRE_TMPSRC, 0);
|
||||||
xmount("tmpfs", PRE_TMPSRC, "tmpfs", 0, "mode=755");
|
xmount("tmpfs", PRE_TMPSRC, "tmpfs", 0, "mode=755");
|
||||||
xmkdir(PRE_TMPDIR, 0);
|
xmkdir(PRE_TMPDIR, 0);
|
||||||
@ -383,10 +369,7 @@ void MagiskInit::patch_rw_root() noexcept {
|
|||||||
// Extract overlay archives
|
// Extract overlay archives
|
||||||
extract_files(true);
|
extract_files(true);
|
||||||
|
|
||||||
bool patch = !treble && access("/sepolicy", F_OK) == 0;
|
handle_sepolicy();
|
||||||
if (patch || !hijack_sepolicy()) {
|
|
||||||
patch_sepolicy("/sepolicy", "/sepolicy");
|
|
||||||
}
|
|
||||||
unlink("init-ld");
|
unlink("init-ld");
|
||||||
|
|
||||||
chdir("/");
|
chdir("/");
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include <sys/mount.h>
|
#include <sys/mount.h>
|
||||||
|
#include <sys/xattr.h>
|
||||||
|
|
||||||
#include <consts.hpp>
|
#include <consts.hpp>
|
||||||
#include <sepolicy.hpp>
|
#include <sepolicy.hpp>
|
||||||
@ -7,122 +8,235 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
#define MOCK_COMPAT SELINUXMOCK "/compatible"
|
#define POLICY_VERSION "/selinux_version"
|
||||||
|
|
||||||
|
#define MOCK_VERSION SELINUXMOCK "/version"
|
||||||
#define MOCK_LOAD SELINUXMOCK "/load"
|
#define MOCK_LOAD SELINUXMOCK "/load"
|
||||||
#define MOCK_ENFORCE SELINUXMOCK "/enforce"
|
#define MOCK_ENFORCE SELINUXMOCK "/enforce"
|
||||||
|
#define MOCK_REQPROT SELINUXMOCK "/checkreqprot"
|
||||||
|
|
||||||
bool MagiskInit::hijack_sepolicy() noexcept {
|
static void mock_fifo(const char *target, const char *mock) {
|
||||||
xmkdir(SELINUXMOCK, 0);
|
LOGD("Hijack [%s]\n", target);
|
||||||
|
mkfifo(mock, 0666);
|
||||||
|
xmount(mock, target, nullptr, MS_BIND, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
if (access("/system/bin/init", F_OK) == 0) {
|
static void mock_file(const char *target, const char *mock) {
|
||||||
// On 2SI devices, the 2nd stage init file is always a dynamic executable.
|
LOGD("Hijack [%s]\n", target);
|
||||||
// This meant that instead of going through convoluted methods trying to alter
|
close(xopen(mock, O_CREAT | O_RDONLY, 0666));
|
||||||
// and block init's control flow, we can just LD_PRELOAD and replace the
|
xmount(mock, target, nullptr, MS_BIND, nullptr);
|
||||||
// security_load_policy function with our own implementation.
|
}
|
||||||
cp_afc("init-ld", "/dev/preload.so");
|
|
||||||
setenv("LD_PRELOAD", "/dev/preload.so", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hijack the "load" and "enforce" node in selinuxfs to manipulate
|
enum SePatchStrategy {
|
||||||
// the actual sepolicy being loaded into the kernel
|
// 2SI, Android 10+
|
||||||
auto hijack = [&] {
|
// On 2SI devices, the 2nd stage init is always a dynamic executable.
|
||||||
LOGD("Hijack [" SELINUX_LOAD "]\n");
|
// This meant that instead of going through convoluted hacks, we can just
|
||||||
close(xopen(MOCK_LOAD, O_CREAT | O_RDONLY, 0600));
|
// LD_PRELOAD and replace security_load_policy with our own implementation.
|
||||||
xmount(MOCK_LOAD, SELINUX_LOAD, nullptr, MS_BIND, nullptr);
|
LD_PRELOAD,
|
||||||
LOGD("Hijack [" SELINUX_ENFORCE "]\n");
|
// Treble enabled, Android 8.0+
|
||||||
mkfifo(MOCK_ENFORCE, 0644);
|
// selinuxfs is mounted in init.cpp. Errors when mounting selinuxfs is ignored,
|
||||||
xmount(MOCK_ENFORCE, SELINUX_ENFORCE, nullptr, MS_BIND, nullptr);
|
// which means that we can directly mount selinuxfs ourselves and hijack nodes in it.
|
||||||
};
|
SELINUXFS,
|
||||||
|
// Dynamic patching, Android 6.0 - 7.1
|
||||||
|
// selinuxfs is mounted in libselinux's selinux_android_load_policy(). Errors when
|
||||||
|
// mounting selinuxfs is fatal, which means we need to block init's control flow after
|
||||||
|
// it mounted selinuxfs for us, then we can hijack nodes in it.
|
||||||
|
LEGACY,
|
||||||
|
};
|
||||||
|
|
||||||
string dt_compat;
|
void MagiskInit::handle_sepolicy() noexcept {
|
||||||
if (access(SELINUX_ENFORCE, F_OK) != 0) {
|
xmkdir(SELINUXMOCK, 0711);
|
||||||
// selinuxfs not mounted yet. Hijack the dt fstab nodes first
|
|
||||||
// and let the original init mount selinuxfs for us.
|
|
||||||
// This only happens on Android 8.0 - 9.0
|
|
||||||
|
|
||||||
char buf[4096];
|
|
||||||
ssprintf(buf, sizeof(buf), "%s/fstab/compatible", config.dt_dir.data());
|
|
||||||
dt_compat = full_read(buf);
|
|
||||||
if (dt_compat.empty()) {
|
|
||||||
// Device does not do early mount and uses monolithic policy
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remount procfs with proper options
|
|
||||||
xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009");
|
|
||||||
|
|
||||||
LOGD("Hijack [%s]\n", buf);
|
|
||||||
|
|
||||||
decltype(mount_list) new_mount_list;
|
|
||||||
// Preserve sysfs and procfs for hijacking
|
|
||||||
for (const auto &s: mount_list)
|
|
||||||
if (s != "/proc" && s != "/sys")
|
|
||||||
new_mount_list.emplace_back(s);
|
|
||||||
new_mount_list.swap(mount_list);
|
|
||||||
|
|
||||||
mkfifo(MOCK_COMPAT, 0444);
|
|
||||||
xmount(MOCK_COMPAT, buf, nullptr, MS_BIND, nullptr);
|
|
||||||
} else {
|
|
||||||
hijack();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read all custom rules into memory
|
// Read all custom rules into memory
|
||||||
string rules;
|
string rules;
|
||||||
auto rule = "/data/" PREINITMIRR "/sepolicy.rule";
|
auto rule = "/data/" PREINITMIRR "/sepolicy.rule";
|
||||||
if (xaccess(rule, R_OK) == 0) {
|
if (xaccess(rule, R_OK) == 0) {
|
||||||
LOGD("Loading custom sepolicy patch: [%s]\n", rule);
|
LOGD("Loading custom sepolicy patch: [%s]\n", rule);
|
||||||
rules = full_read(rule);
|
full_read(rule, rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 0: determine strategy
|
||||||
|
|
||||||
|
SePatchStrategy strat;
|
||||||
|
|
||||||
|
if (access("/system/bin/init", F_OK) == 0) {
|
||||||
|
strat = LD_PRELOAD;
|
||||||
|
} else {
|
||||||
|
auto init = mmap_data("/init");
|
||||||
|
if (init.contains(SPLIT_PLAT_CIL)) {
|
||||||
|
// Supports split policy
|
||||||
|
strat = SELINUXFS;
|
||||||
|
} else if (init.contains(POLICY_VERSION)) {
|
||||||
|
// Does not support split policy, hijack /selinux_version
|
||||||
|
strat = LEGACY;
|
||||||
|
} else {
|
||||||
|
LOGE("Unknown sepolicy setup, abort...\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: setup for intercepting init boot control flow
|
||||||
|
|
||||||
|
switch (strat) {
|
||||||
|
case LD_PRELOAD: {
|
||||||
|
LOGI("SePatchStrategy: LD_PRELOAD\n");
|
||||||
|
|
||||||
|
cp_afc("init-ld", PRELOAD_LIB);
|
||||||
|
setenv("LD_PRELOAD", PRELOAD_LIB, 1);
|
||||||
|
mkfifo(PRELOAD_ACK, 0666);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SELINUXFS: {
|
||||||
|
LOGI("SePatchStrategy: SELINUXFS\n");
|
||||||
|
|
||||||
|
if (access(SELINUX_ENFORCE, F_OK) != 0) {
|
||||||
|
// selinuxfs was not already mounted, mount it ourselves
|
||||||
|
|
||||||
|
// Remount procfs with proper options
|
||||||
|
xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009");
|
||||||
|
|
||||||
|
// Preserve sysfs and procfs
|
||||||
|
decltype(mount_list) new_mount_list;
|
||||||
|
std::remove_copy_if(
|
||||||
|
mount_list.begin(), mount_list.end(),
|
||||||
|
std::back_inserter(new_mount_list),
|
||||||
|
[](const auto &s) { return s == "/proc" || s == "/sys"; });
|
||||||
|
new_mount_list.swap(mount_list);
|
||||||
|
|
||||||
|
// Mount selinuxfs
|
||||||
|
xmount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_file(SELINUX_LOAD, MOCK_LOAD);
|
||||||
|
mock_fifo(SELINUX_ENFORCE, MOCK_ENFORCE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LEGACY: {
|
||||||
|
LOGI("SePatchStrategy: LEGACY\n");
|
||||||
|
|
||||||
|
if (access(POLICY_VERSION, F_OK) != 0) {
|
||||||
|
// The file does not exist, create one
|
||||||
|
close(xopen(POLICY_VERSION, O_RDONLY | O_CREAT, 0644));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The only purpose of this is to block init's control flow after it mounts selinuxfs
|
||||||
|
// and before it calls security_load_policy().
|
||||||
|
// Target: selinux_android_load_policy() -> set_policy_index() -> open(POLICY_VERSION)
|
||||||
|
mock_fifo(POLICY_VERSION, MOCK_VERSION);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new process waiting for init operations
|
// Create a new process waiting for init operations
|
||||||
if (xfork()) {
|
if (xfork()) {
|
||||||
// In parent, return and continue boot process
|
return;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dt_compat.empty()) {
|
// Step 2: wait for selinuxfs to be mounted (only for LEGACY)
|
||||||
// This open will block until init calls DoFirstStageMount
|
|
||||||
// The only purpose here is actually to wait for init to mount selinuxfs for us
|
|
||||||
int fd = xopen(MOCK_COMPAT, O_WRONLY);
|
|
||||||
|
|
||||||
char buf[4096];
|
if (strat == LEGACY) {
|
||||||
ssprintf(buf, sizeof(buf), "%s/fstab/compatible", config.dt_dir.data());
|
// Busy wait until selinuxfs is mounted
|
||||||
xumount2(buf, MNT_DETACH);
|
while (access(SELINUX_ENFORCE, F_OK)) {
|
||||||
|
// Retry every 100ms
|
||||||
|
usleep(100000);
|
||||||
|
}
|
||||||
|
|
||||||
hijack();
|
// On Android 6.0, init does not call security_getenforce() first; instead it directly
|
||||||
|
// call security_setenforce() after security_load_policy(). What's even worse, it opens the
|
||||||
|
// enforce node with O_RDWR, which will not block when opening FIFO files. As a workaround,
|
||||||
|
// we do not mock the enforce node, and block init with mock checkreqprot instead.
|
||||||
|
// Android 7.0 - 7.1 doesn't have this issue, but for simplicity, let's just use the
|
||||||
|
// same blocking strategy for both since it also works just fine.
|
||||||
|
|
||||||
xwrite(fd, dt_compat.data(), dt_compat.size());
|
mock_file(SELINUX_LOAD, MOCK_LOAD);
|
||||||
close(fd);
|
mock_fifo(SELINUX_REQPROT, MOCK_REQPROT);
|
||||||
|
|
||||||
|
// This will unblock init at selinux_android_load_policy() -> set_policy_index().
|
||||||
|
close(xopen(MOCK_VERSION, O_WRONLY));
|
||||||
|
|
||||||
|
xumount2(POLICY_VERSION, MNT_DETACH);
|
||||||
|
|
||||||
|
// libselinux does not read /selinux_version after open; instead it mmap the file,
|
||||||
|
// which can never succeed on FIFO files. This is fine as set_policy_index() will just
|
||||||
|
// fallback to the default index 0.
|
||||||
}
|
}
|
||||||
|
|
||||||
// This open will block until init calls security_getenforce
|
// Step 3: obtain sepolicy, patch, and load the patched sepolicy
|
||||||
int fd = xopen(MOCK_ENFORCE, O_WRONLY);
|
|
||||||
|
|
||||||
// Cleanup the hijacks
|
if (strat == LD_PRELOAD) {
|
||||||
umount2("/init", MNT_DETACH);
|
// This open will block until preload.so finish writing the sepolicy
|
||||||
xumount2(SELINUX_LOAD, MNT_DETACH);
|
owned_fd ack_fd = xopen(PRELOAD_ACK, O_WRONLY);
|
||||||
xumount2(SELINUX_ENFORCE, MNT_DETACH);
|
|
||||||
|
|
||||||
// Load and patch policy
|
auto sepol = SePolicy::from_file(PRELOAD_POLICY);
|
||||||
auto sepol = SePolicy::from_file(MOCK_LOAD);
|
|
||||||
sepol.magisk_rules();
|
|
||||||
sepol.load_rules(rules);
|
|
||||||
|
|
||||||
// Load patched policy into kernel
|
// Remove the files before loading the policy
|
||||||
sepol.to_file(SELINUX_LOAD);
|
unlink(PRELOAD_POLICY);
|
||||||
|
unlink(PRELOAD_ACK);
|
||||||
|
|
||||||
// restore mounted files' context after sepolicy loaded
|
sepol.magisk_rules();
|
||||||
restore_overlay_contexts();
|
sepol.load_rules(rules);
|
||||||
|
sepol.to_file(SELINUX_LOAD);
|
||||||
|
|
||||||
// Write to the enforce node ONLY after sepolicy is loaded. We need to make sure
|
// restore mounted files' context after sepolicy loaded
|
||||||
// the actual init process is blocked until sepolicy is loaded, or else
|
restore_overlay_contexts();
|
||||||
// restorecon will fail and re-exec won't change context, causing boot failure.
|
|
||||||
// We (ab)use the fact that init reads the enforce node, and because
|
|
||||||
// it has been replaced with our FIFO file, init will block until we
|
|
||||||
// write something into the pipe, effectively hijacking its control flow.
|
|
||||||
|
|
||||||
string enforce = full_read(SELINUX_ENFORCE);
|
// Write ack to restore preload.so's control flow
|
||||||
xwrite(fd, enforce.data(), enforce.length());
|
xwrite(ack_fd, &ack_fd, 1);
|
||||||
close(fd);
|
} else {
|
||||||
|
int mock_enforce = -1;
|
||||||
|
|
||||||
|
if (strat == LEGACY) {
|
||||||
|
// Busy wait until sepolicy is fully written.
|
||||||
|
struct stat st{};
|
||||||
|
decltype(st.st_size) sz;
|
||||||
|
do {
|
||||||
|
sz = st.st_size;
|
||||||
|
// Check every 100ms
|
||||||
|
usleep(100000);
|
||||||
|
xstat(MOCK_LOAD, &st);
|
||||||
|
} while (sz == 0 || sz != st.st_size);
|
||||||
|
} else {
|
||||||
|
// This open will block until init calls security_getenforce().
|
||||||
|
mock_enforce = xopen(MOCK_ENFORCE, O_WRONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup the hijacks
|
||||||
|
umount2("/init", MNT_DETACH);
|
||||||
|
xumount2(SELINUX_LOAD, MNT_DETACH);
|
||||||
|
umount2(SELINUX_ENFORCE, MNT_DETACH);
|
||||||
|
umount2(SELINUX_REQPROT, MNT_DETACH);
|
||||||
|
|
||||||
|
auto sepol = SePolicy::from_file(MOCK_LOAD);
|
||||||
|
sepol.magisk_rules();
|
||||||
|
sepol.load_rules(rules);
|
||||||
|
sepol.to_file(SELINUX_LOAD);
|
||||||
|
|
||||||
|
// For some reason, restorecon on /init won't work in some cases
|
||||||
|
setxattr("/init", XATTR_NAME_SELINUX, "u:object_r:init_exec:s0", 24, 0);
|
||||||
|
|
||||||
|
// restore mounted files' context after sepolicy loaded
|
||||||
|
restore_overlay_contexts();
|
||||||
|
|
||||||
|
// We need to make sure the actual init process is blocked until sepolicy is loaded,
|
||||||
|
// or else restorecon will fail and re-exec won't change context, causing boot failure.
|
||||||
|
// We (ab)use the fact that init either reads the enforce node, or writes the checkreqprot
|
||||||
|
// node, and because both has been replaced with FIFO files, init will block until we
|
||||||
|
// handle it, effectively hijacking its control flow until the patched sepolicy is loaded.
|
||||||
|
|
||||||
|
if (strat == LEGACY) {
|
||||||
|
// init is blocked on checkreqprot, write to the real node first, then
|
||||||
|
// unblock init by opening the mock FIFO.
|
||||||
|
owned_fd real_req = xopen(SELINUX_REQPROT, O_WRONLY);
|
||||||
|
xwrite(real_req, "0", 1);
|
||||||
|
owned_fd mock_req = xopen(MOCK_REQPROT, O_RDONLY);
|
||||||
|
full_read(mock_req);
|
||||||
|
} else {
|
||||||
|
// security_getenforce was called
|
||||||
|
string data = full_read(SELINUX_ENFORCE);
|
||||||
|
xwrite(mock_enforce, data.data(), data.length());
|
||||||
|
close(mock_enforce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// At this point, the init process will be unblocked
|
// At this point, the init process will be unblocked
|
||||||
// and continue on with restorecon + re-exec.
|
// and continue on with restorecon + re-exec.
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
use crate::ffi::MagiskInit;
|
|
||||||
use base::{debug, path, Utf8CStr};
|
|
||||||
use magiskpolicy::ffi::SePolicy;
|
|
||||||
|
|
||||||
impl MagiskInit {
|
|
||||||
pub(crate) fn patch_sepolicy(self: &MagiskInit, src: &Utf8CStr, out: &Utf8CStr) {
|
|
||||||
debug!("Patching monolithic policy");
|
|
||||||
let mut sepol = SePolicy::from_file(src);
|
|
||||||
|
|
||||||
sepol.magisk_rules();
|
|
||||||
|
|
||||||
// Custom rules
|
|
||||||
let rule = path!("/data/.magisk/preinit/sepolicy.rule");
|
|
||||||
if rule.exists() {
|
|
||||||
debug!("Loading custom sepolicy patch: [{}]", rule);
|
|
||||||
sepol.load_rule_file(rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Dumping sepolicy to: [{}]", out);
|
|
||||||
sepol.to_file(out);
|
|
||||||
|
|
||||||
// Remove OnePlus stupid debug sepolicy and use our own
|
|
||||||
let sepol_debug = path!("/sepolicy_debug");
|
|
||||||
if sepol_debug.exists() {
|
|
||||||
sepol_debug.remove().ok();
|
|
||||||
path!("/sepolicy").link_to(sepol_debug).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,3 +21,4 @@
|
|||||||
#define SELINUX_POLICY SELINUX_MNT "/policy"
|
#define SELINUX_POLICY SELINUX_MNT "/policy"
|
||||||
#define SELINUX_LOAD SELINUX_MNT "/load"
|
#define SELINUX_LOAD SELINUX_MNT "/load"
|
||||||
#define SELINUX_VERSION SELINUX_MNT "/policyvers"
|
#define SELINUX_VERSION SELINUX_MNT "/policyvers"
|
||||||
|
#define SELINUX_REQPROT SELINUX_MNT "/checkreqprot"
|
||||||
|
@ -104,8 +104,11 @@ impl SePolicy {
|
|||||||
// For tmpfs overlay on 2SI, Zygisk on lower Android versions and AVD scripts
|
// For tmpfs overlay on 2SI, Zygisk on lower Android versions and AVD scripts
|
||||||
allow(["init", "zygote", "shell"], ["tmpfs"], ["file"], all);
|
allow(["init", "zygote", "shell"], ["tmpfs"], ["file"], all);
|
||||||
|
|
||||||
|
// Allow magiskinit daemon to log to kmsg
|
||||||
|
allow(["kernel"], ["rootfs", "tmpfs"], ["chr_file"], ["write"]);
|
||||||
|
|
||||||
// Allow magiskinit daemon to handle mock selinuxfs
|
// Allow magiskinit daemon to handle mock selinuxfs
|
||||||
allow(["kernel"], ["tmpfs"], ["fifo_file"], ["write"]);
|
allow(["kernel"], ["tmpfs"], ["fifo_file"], ["open", "read", "write"]);
|
||||||
|
|
||||||
// For relabelling files
|
// For relabelling files
|
||||||
allow(["rootfs"], ["labeledfs", "tmpfs"], ["filesystem"], ["associate"]);
|
allow(["rootfs"], ["labeledfs", "tmpfs"], ["filesystem"], ["associate"]);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user