mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-22 07:57: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():
|
def collect_binary():
|
||||||
for arch in archs:
|
for arch in archs:
|
||||||
mkdir_p(op.join('native', 'out', arch))
|
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)
|
source = op.join('native', 'libs', arch, bin)
|
||||||
target = op.join('native', 'out', arch, bin)
|
target = op.join('native', 'out', arch, bin)
|
||||||
mv(source, target)
|
mv(source, target)
|
||||||
@ -284,6 +284,11 @@ def dump_bin_header():
|
|||||||
with open(stub, 'rb') as src:
|
with open(stub, 'rb') as src:
|
||||||
text = binary_dump(src, 'manager_xz')
|
text = binary_dump(src, 'manager_xz')
|
||||||
write_if_diff(op.join(native_gen_path, 'binaries.h'), text)
|
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():
|
def dump_flag_header():
|
||||||
@ -322,6 +327,8 @@ def build_binary(args):
|
|||||||
|
|
||||||
dump_flag_header()
|
dump_flag_header()
|
||||||
|
|
||||||
|
# Build shared executables
|
||||||
|
|
||||||
flag = ''
|
flag = ''
|
||||||
|
|
||||||
if 'magisk' in args.target:
|
if 'magisk' in args.target:
|
||||||
@ -333,10 +340,15 @@ def build_binary(args):
|
|||||||
if 'test' in args.target:
|
if 'test' in args.target:
|
||||||
flag += ' B_TEST=1'
|
flag += ' B_TEST=1'
|
||||||
|
|
||||||
|
if 'magiskinit' in args.target:
|
||||||
|
flag += ' B_PRELOAD=1'
|
||||||
|
|
||||||
if flag:
|
if flag:
|
||||||
run_ndk_build(flag + ' B_SHARED=1')
|
run_ndk_build(flag + ' B_SHARED=1')
|
||||||
clean_elf()
|
clean_elf()
|
||||||
|
|
||||||
|
# Then build static executables
|
||||||
|
|
||||||
flag = ''
|
flag = ''
|
||||||
|
|
||||||
if 'magiskinit' in args.target:
|
if 'magiskinit' in args.target:
|
||||||
|
@ -15,8 +15,10 @@ android {
|
|||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
ndkBuild {
|
||||||
// Pass arguments to ndk-build.
|
// Pass arguments to ndk-build.
|
||||||
arguments("B_MAGISK=1", "B_INIT=1", "B_BOOT=1", "B_TEST=1", "B_POLICY=1",
|
arguments(
|
||||||
"MAGISK_DEBUG=1", "MAGISK_VERSION=debug", "MAGISK_VER_CODE=INT_MAX")
|
"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
|
endif
|
||||||
|
|
||||||
|
ifdef B_PRELOAD
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
LOCAL_MODULE := preload
|
||||||
|
LOCAL_SRC_FILES := init/preload.c
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
ifdef B_INIT
|
ifdef B_INIT
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
@ -8,6 +8,18 @@
|
|||||||
#include <utils.hpp>
|
#include <utils.hpp>
|
||||||
#include <binaries.h>
|
#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"
|
#include "init.hpp"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -35,16 +47,24 @@ bool unxz(int fd, const uint8_t *buf, size_t size) {
|
|||||||
return true;
|
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);
|
int fd = xopen(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return 1;
|
return 1;
|
||||||
if (!unxz(fd, manager_xz, sizeof(manager_xz)))
|
if (!unxz(fd, buf, sz))
|
||||||
return 1;
|
return 1;
|
||||||
close(fd);
|
close(fd);
|
||||||
return 0;
|
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 {
|
class RecoveryInit : public BaseInit {
|
||||||
public:
|
public:
|
||||||
using BaseInit::BaseInit;
|
using BaseInit::BaseInit;
|
||||||
|
@ -33,6 +33,7 @@ bool check_two_stage();
|
|||||||
void setup_klog();
|
void setup_klog();
|
||||||
const char *backup_init();
|
const char *backup_init();
|
||||||
int dump_manager(const char *path, mode_t mode);
|
int dump_manager(const char *path, mode_t mode);
|
||||||
|
int dump_preload(const char *path, mode_t mode);
|
||||||
|
|
||||||
/***************
|
/***************
|
||||||
* Base classes
|
* 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_COMPAT SELINUXMOCK "/compatible"
|
||||||
#define MOCK_LOAD SELINUXMOCK "/load"
|
#define MOCK_LOAD SELINUXMOCK "/load"
|
||||||
#define MOCK_BLOCKING SELINUXMOCK "/blocking"
|
#define MOCK_ENFORCE SELINUXMOCK "/enforce"
|
||||||
#define REAL_SELINUXFS SELINUXMOCK "/fs"
|
#define REAL_SELINUXFS SELINUXMOCK "/fs"
|
||||||
|
|
||||||
bool MagiskInit::hijack_sepolicy() {
|
bool MagiskInit::hijack_sepolicy() {
|
||||||
const char *blocking_target;
|
|
||||||
string actual_content;
|
|
||||||
|
|
||||||
xmkdir(SELINUXMOCK, 0);
|
xmkdir(SELINUXMOCK, 0);
|
||||||
|
|
||||||
if (access("/system/etc/selinux/apex", F_OK) == 0) {
|
if (access("/system/bin/init", F_OK) == 0) {
|
||||||
// On devices with apex sepolicy, it runs restorecon before enforcing SELinux.
|
// On 2SI devices, the 2nd stage init file is always a dynamic executable.
|
||||||
// To block control flow before that happens, we will have to hijack the
|
// This meant that instead of going through convoluted methods trying to alter
|
||||||
// plat_file_contexts file to achieve that.
|
// and block init's control flow, we can just LD_PRELOAD and replace the
|
||||||
|
// security_load_policy function with our own implementation.
|
||||||
if (access("/system/etc/selinux/plat_file_contexts", F_OK) == 0) {
|
dump_preload("/dev/preload.so", 0644);
|
||||||
blocking_target = "/system/etc/selinux/plat_file_contexts";
|
setenv("LD_PRELOAD", "/dev/preload.so", 1);
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hijack the "load" and "enforce" node in selinuxfs to manipulate
|
// Hijack the "load" and "enforce" node in selinuxfs to manipulate
|
||||||
@ -78,31 +60,28 @@ bool MagiskInit::hijack_sepolicy() {
|
|||||||
LOGD("Hijack [" SELINUX_LOAD "]\n");
|
LOGD("Hijack [" SELINUX_LOAD "]\n");
|
||||||
mkfifo(MOCK_LOAD, 0600);
|
mkfifo(MOCK_LOAD, 0600);
|
||||||
xmount(MOCK_LOAD, SELINUX_LOAD, nullptr, MS_BIND, nullptr);
|
xmount(MOCK_LOAD, SELINUX_LOAD, nullptr, MS_BIND, nullptr);
|
||||||
if (strcmp(blocking_target, SELINUX_ENFORCE) == 0) {
|
|
||||||
LOGD("Hijack [" SELINUX_ENFORCE "]\n");
|
LOGD("Hijack [" SELINUX_ENFORCE "]\n");
|
||||||
mkfifo(MOCK_BLOCKING, 0644);
|
mkfifo(MOCK_ENFORCE, 0644);
|
||||||
xmount(MOCK_BLOCKING, SELINUX_ENFORCE, nullptr, MS_BIND, nullptr);
|
xmount(MOCK_ENFORCE, SELINUX_ENFORCE, nullptr, MS_BIND, nullptr);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
string dt_compat;
|
string dt_compat;
|
||||||
if (access(SELINUX_ENFORCE, F_OK) != 0) {
|
if (access(SELINUX_ENFORCE, F_OK) != 0) {
|
||||||
// selinuxfs not mounted yet. Hijack the dt fstab nodes first
|
// 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
|
// 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];
|
char buf[4096];
|
||||||
snprintf(buf, sizeof(buf), "%s/fstab/compatible", config->dt_dir);
|
snprintf(buf, sizeof(buf), "%s/fstab/compatible", config->dt_dir);
|
||||||
dt_compat = full_read(buf);
|
dt_compat = full_read(buf);
|
||||||
|
|
||||||
if (dt_compat.empty()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remount procfs with proper options
|
||||||
|
xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009");
|
||||||
|
|
||||||
LOGD("Hijack [%s]\n", buf);
|
LOGD("Hijack [%s]\n", buf);
|
||||||
|
|
||||||
// Preserve sysfs and procfs for hijacking
|
// Preserve sysfs and procfs for hijacking
|
||||||
@ -161,31 +140,28 @@ bool MagiskInit::hijack_sepolicy() {
|
|||||||
sepol->magisk_rules();
|
sepol->magisk_rules();
|
||||||
sepol->load_rules(rules);
|
sepol->load_rules(rules);
|
||||||
|
|
||||||
// This open will block until init calls security_getenforce or selinux_android_restorecon
|
// This open will block until init calls security_getenforce
|
||||||
fd = xopen(MOCK_BLOCKING, O_WRONLY);
|
fd = xopen(MOCK_ENFORCE, O_WRONLY);
|
||||||
|
|
||||||
// Cleanup the hijacks
|
// Cleanup the hijacks
|
||||||
umount2("/init", MNT_DETACH);
|
umount2("/init", MNT_DETACH);
|
||||||
xumount2(SELINUX_LOAD, MNT_DETACH);
|
xumount2(SELINUX_LOAD, MNT_DETACH);
|
||||||
xumount2(blocking_target, MNT_DETACH);
|
xumount2(SELINUX_ENFORCE, MNT_DETACH);
|
||||||
|
|
||||||
// Load patched policy
|
// Load patched policy
|
||||||
xmkdir(REAL_SELINUXFS, 0755);
|
xmkdir(REAL_SELINUXFS, 0755);
|
||||||
xmount("selinuxfs", REAL_SELINUXFS, "selinuxfs", 0, nullptr);
|
xmount("selinuxfs", REAL_SELINUXFS, "selinuxfs", 0, nullptr);
|
||||||
sepol->to_file(REAL_SELINUXFS "/load");
|
sepol->to_file(REAL_SELINUXFS "/load");
|
||||||
|
string enforce = full_read(SELINUX_ENFORCE);
|
||||||
|
|
||||||
if (strcmp(blocking_target, SELINUX_ENFORCE) == 0) {
|
// Write to the enforce node ONLY after sepolicy is loaded. We need to make sure
|
||||||
actual_content = full_read(SELINUX_ENFORCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to mock blocking target ONLY after sepolicy is loaded. We need to make sure
|
|
||||||
// the actual init process is blocked until sepolicy is loaded, or else
|
// 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.
|
// 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
|
// We (ab)use the fact that init reads the enforce node, and because
|
||||||
// because it has been replaced with our FIFO file, init will block until we
|
// it has been replaced with our FIFO file, init will block until we
|
||||||
// write something into the pipe, effectively hijacking its control flow.
|
// 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);
|
close(fd);
|
||||||
|
|
||||||
// At this point, the init process will be unblocked
|
// At this point, the init process will be unblocked
|
||||||
|
Loading…
x
Reference in New Issue
Block a user