mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-21 23:47:39 +00:00
Use LD_PRELOAD to intercept sepolicy on 2SI init
This commit is contained in:
parent
f24d52436b
commit
ff2513e276
14
build.py
14
build.py
@ -184,7 +184,7 @@ def load_config(args):
|
||||
def collect_binary():
|
||||
for arch in archs:
|
||||
mkdir_p(op.join('native', 'out', arch))
|
||||
for bin in support_targets:
|
||||
for bin in support_targets + ['libpreload.so']:
|
||||
source = op.join('native', 'libs', arch, bin)
|
||||
target = op.join('native', 'out', arch, bin)
|
||||
mv(source, target)
|
||||
@ -284,6 +284,11 @@ def dump_bin_header():
|
||||
with open(stub, 'rb') as src:
|
||||
text = binary_dump(src, 'manager_xz')
|
||||
write_if_diff(op.join(native_gen_path, 'binaries.h'), text)
|
||||
for arch in archs:
|
||||
preload = op.join('native', 'out', arch, 'libpreload.so')
|
||||
with open(preload, 'rb') as src:
|
||||
text = binary_dump(src, 'preload_xz')
|
||||
write_if_diff(op.join(native_gen_path, f'{arch}_binaries.h'), text)
|
||||
|
||||
|
||||
def dump_flag_header():
|
||||
@ -322,6 +327,8 @@ def build_binary(args):
|
||||
|
||||
dump_flag_header()
|
||||
|
||||
# Build shared executables
|
||||
|
||||
flag = ''
|
||||
|
||||
if 'magisk' in args.target:
|
||||
@ -333,10 +340,15 @@ def build_binary(args):
|
||||
if 'test' in args.target:
|
||||
flag += ' B_TEST=1'
|
||||
|
||||
if 'magiskinit' in args.target:
|
||||
flag += ' B_PRELOAD=1'
|
||||
|
||||
if flag:
|
||||
run_ndk_build(flag + ' B_SHARED=1')
|
||||
clean_elf()
|
||||
|
||||
# Then build static executables
|
||||
|
||||
flag = ''
|
||||
|
||||
if 'magiskinit' in args.target:
|
||||
|
@ -15,8 +15,10 @@ android {
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
// Pass arguments to ndk-build.
|
||||
arguments("B_MAGISK=1", "B_INIT=1", "B_BOOT=1", "B_TEST=1", "B_POLICY=1",
|
||||
"MAGISK_DEBUG=1", "MAGISK_VERSION=debug", "MAGISK_VER_CODE=INT_MAX")
|
||||
arguments(
|
||||
"B_MAGISK=1", "B_INIT=1", "B_BOOT=1", "B_TEST=1", "B_POLICY=1", "B_PRELOAD=1",
|
||||
"MAGISK_DEBUG=1", "MAGISK_VERSION=debug", "MAGISK_VER_CODE=INT_MAX"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,15 @@ include $(BUILD_EXECUTABLE)
|
||||
|
||||
endif
|
||||
|
||||
ifdef B_PRELOAD
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := preload
|
||||
LOCAL_SRC_FILES := init/preload.c
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
endif
|
||||
|
||||
ifdef B_INIT
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
@ -8,6 +8,18 @@
|
||||
#include <utils.hpp>
|
||||
#include <binaries.h>
|
||||
|
||||
#if defined(__arm__)
|
||||
#include <armeabi-v7a_binaries.h>
|
||||
#elif defined(__aarch64__)
|
||||
#include <arm64-v8a_binaries.h>
|
||||
#elif defined(__i386__)
|
||||
#include <x86_binaries.h>
|
||||
#elif defined(__x86_64__)
|
||||
#include <x86_64_binaries.h>
|
||||
#else
|
||||
#error Unsupported ABI
|
||||
#endif
|
||||
|
||||
#include "init.hpp"
|
||||
|
||||
using namespace std;
|
||||
@ -35,16 +47,24 @@ bool unxz(int fd, const uint8_t *buf, size_t size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int dump_manager(const char *path, mode_t mode) {
|
||||
static int dump_bin(const uint8_t *buf, size_t sz, const char *path, mode_t mode) {
|
||||
int fd = xopen(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
|
||||
if (fd < 0)
|
||||
return 1;
|
||||
if (!unxz(fd, manager_xz, sizeof(manager_xz)))
|
||||
if (!unxz(fd, buf, sz))
|
||||
return 1;
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dump_manager(const char *path, mode_t mode) {
|
||||
return dump_bin(manager_xz, sizeof(manager_xz), path, mode);
|
||||
}
|
||||
|
||||
int dump_preload(const char *path, mode_t mode) {
|
||||
return dump_bin(preload_xz, sizeof(preload_xz), path, mode);
|
||||
}
|
||||
|
||||
class RecoveryInit : public BaseInit {
|
||||
public:
|
||||
using BaseInit::BaseInit;
|
||||
|
@ -33,6 +33,7 @@ bool check_two_stage();
|
||||
void setup_klog();
|
||||
const char *backup_init();
|
||||
int dump_manager(const char *path, mode_t mode);
|
||||
int dump_preload(const char *path, mode_t mode);
|
||||
|
||||
/***************
|
||||
* Base classes
|
||||
|
23
native/jni/init/preload.c
Normal file
23
native/jni/init/preload.c
Normal file
@ -0,0 +1,23 @@
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
int security_load_policy(void *data, size_t len) {
|
||||
// Make sure our next exec won't get bugged
|
||||
unsetenv("LD_PRELOAD");
|
||||
unlink("/dev/preload.so");
|
||||
|
||||
int (*load_policy)(void *, size_t) = dlsym(RTLD_NEXT, "security_load_policy");
|
||||
// Skip checking errors, because if we cannot find the symbol, there
|
||||
// isn't much we can do other than crashing anyways.
|
||||
int result = load_policy(data, len);
|
||||
|
||||
// Wait for ack
|
||||
int fd = open("/sys/fs/selinux/enforce", O_RDONLY);
|
||||
char c;
|
||||
read(fd, &c, 1);
|
||||
close(fd);
|
||||
|
||||
return result;
|
||||
}
|
@ -39,37 +39,19 @@ void MagiskInit::patch_sepolicy(const char *file) {
|
||||
|
||||
#define MOCK_COMPAT SELINUXMOCK "/compatible"
|
||||
#define MOCK_LOAD SELINUXMOCK "/load"
|
||||
#define MOCK_BLOCKING SELINUXMOCK "/blocking"
|
||||
#define MOCK_ENFORCE SELINUXMOCK "/enforce"
|
||||
#define REAL_SELINUXFS SELINUXMOCK "/fs"
|
||||
|
||||
bool MagiskInit::hijack_sepolicy() {
|
||||
const char *blocking_target;
|
||||
string actual_content;
|
||||
|
||||
xmkdir(SELINUXMOCK, 0);
|
||||
|
||||
if (access("/system/etc/selinux/apex", F_OK) == 0) {
|
||||
// On devices with apex sepolicy, it runs restorecon before enforcing SELinux.
|
||||
// To block control flow before that happens, we will have to hijack the
|
||||
// plat_file_contexts file to achieve that.
|
||||
|
||||
if (access("/system/etc/selinux/plat_file_contexts", F_OK) == 0) {
|
||||
blocking_target = "/system/etc/selinux/plat_file_contexts";
|
||||
} else if (access("/plat_file_contexts", F_OK) == 0) {
|
||||
blocking_target = "/plat_file_contexts";
|
||||
} else {
|
||||
// Error, should never happen
|
||||
LOGE("! Cannot find plat_file_contexts\n");
|
||||
return false;
|
||||
}
|
||||
actual_content = full_read(blocking_target);
|
||||
|
||||
LOGD("Hijack [%s]\n", blocking_target);
|
||||
mkfifo(MOCK_BLOCKING, 0644);
|
||||
xmount(MOCK_BLOCKING, blocking_target, nullptr, MS_BIND, nullptr);
|
||||
} else {
|
||||
// We block using the "enforce" node
|
||||
blocking_target = SELINUX_ENFORCE;
|
||||
if (access("/system/bin/init", F_OK) == 0) {
|
||||
// On 2SI devices, the 2nd stage init file is always a dynamic executable.
|
||||
// This meant that instead of going through convoluted methods trying to alter
|
||||
// and block init's control flow, we can just LD_PRELOAD and replace the
|
||||
// security_load_policy function with our own implementation.
|
||||
dump_preload("/dev/preload.so", 0644);
|
||||
setenv("LD_PRELOAD", "/dev/preload.so", 1);
|
||||
}
|
||||
|
||||
// Hijack the "load" and "enforce" node in selinuxfs to manipulate
|
||||
@ -78,31 +60,28 @@ bool MagiskInit::hijack_sepolicy() {
|
||||
LOGD("Hijack [" SELINUX_LOAD "]\n");
|
||||
mkfifo(MOCK_LOAD, 0600);
|
||||
xmount(MOCK_LOAD, SELINUX_LOAD, nullptr, MS_BIND, nullptr);
|
||||
if (strcmp(blocking_target, SELINUX_ENFORCE) == 0) {
|
||||
LOGD("Hijack [" SELINUX_ENFORCE "]\n");
|
||||
mkfifo(MOCK_BLOCKING, 0644);
|
||||
xmount(MOCK_BLOCKING, SELINUX_ENFORCE, nullptr, MS_BIND, nullptr);
|
||||
}
|
||||
LOGD("Hijack [" SELINUX_ENFORCE "]\n");
|
||||
mkfifo(MOCK_ENFORCE, 0644);
|
||||
xmount(MOCK_ENFORCE, SELINUX_ENFORCE, nullptr, MS_BIND, nullptr);
|
||||
};
|
||||
|
||||
string dt_compat;
|
||||
if (access(SELINUX_ENFORCE, F_OK) != 0) {
|
||||
// selinuxfs not mounted yet. Hijack the dt fstab nodes first
|
||||
// and let the original init mount selinuxfs for us
|
||||
// and let the original init mount selinuxfs for us.
|
||||
// This only happens on Android 8.0 - 9.0
|
||||
|
||||
// Remount procfs with proper options
|
||||
xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009");
|
||||
|
||||
char buf[4096];
|
||||
snprintf(buf, sizeof(buf), "%s/fstab/compatible", config->dt_dir);
|
||||
dt_compat = full_read(buf);
|
||||
|
||||
if (dt_compat.empty()) {
|
||||
// Device does not do early mount and apparently use monolithic policy
|
||||
// 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);
|
||||
|
||||
// Preserve sysfs and procfs for hijacking
|
||||
@ -161,31 +140,28 @@ bool MagiskInit::hijack_sepolicy() {
|
||||
sepol->magisk_rules();
|
||||
sepol->load_rules(rules);
|
||||
|
||||
// This open will block until init calls security_getenforce or selinux_android_restorecon
|
||||
fd = xopen(MOCK_BLOCKING, O_WRONLY);
|
||||
// This open will block until init calls security_getenforce
|
||||
fd = xopen(MOCK_ENFORCE, O_WRONLY);
|
||||
|
||||
// Cleanup the hijacks
|
||||
umount2("/init", MNT_DETACH);
|
||||
xumount2(SELINUX_LOAD, MNT_DETACH);
|
||||
xumount2(blocking_target, MNT_DETACH);
|
||||
xumount2(SELINUX_ENFORCE, MNT_DETACH);
|
||||
|
||||
// Load patched policy
|
||||
xmkdir(REAL_SELINUXFS, 0755);
|
||||
xmount("selinuxfs", REAL_SELINUXFS, "selinuxfs", 0, nullptr);
|
||||
sepol->to_file(REAL_SELINUXFS "/load");
|
||||
string enforce = full_read(SELINUX_ENFORCE);
|
||||
|
||||
if (strcmp(blocking_target, SELINUX_ENFORCE) == 0) {
|
||||
actual_content = full_read(SELINUX_ENFORCE);
|
||||
}
|
||||
|
||||
// Write to mock blocking target ONLY after sepolicy is loaded. We need to make sure
|
||||
// Write to the enforce node ONLY after sepolicy is loaded. 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 reads from our blocking target, and
|
||||
// because it has been replaced with our FIFO file, init will block until we
|
||||
// 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.
|
||||
|
||||
xwrite(fd, actual_content.data(), actual_content.length());
|
||||
xwrite(fd, enforce.data(), enforce.length());
|
||||
close(fd);
|
||||
|
||||
// At this point, the init process will be unblocked
|
||||
|
Loading…
x
Reference in New Issue
Block a user