Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
M
Magisk
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Magisk
Commits
ff2513e2
Commit
ff2513e2
authored
Apr 08, 2022
by
topjohnwu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use LD_PRELOAD to intercept sepolicy on 2SI init
parent
f24d5243
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
96 additions
and
53 deletions
+96
-53
build.py
build.py
+13
-1
build.gradle.kts
native/build.gradle.kts
+4
-2
Android.mk
native/jni/Android.mk
+9
-0
init.cpp
native/jni/init/init.cpp
+22
-2
init.hpp
native/jni/init/init.hpp
+1
-0
preload.c
native/jni/init/preload.c
+23
-0
selinux.cpp
native/jni/init/selinux.cpp
+24
-48
No files found.
build.py
View file @
ff2513e2
...
...
@@ -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
:
...
...
native/build.gradle.kts
View file @
ff2513e2
...
...
@@ -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"
)
}
}
}
...
...
native/jni/Android.mk
View file @
ff2513e2
...
...
@@ -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)
...
...
native/jni/init/init.cpp
View file @
ff2513e2
...
...
@@ -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
;
...
...
native/jni/init/init.hpp
View file @
ff2513e2
...
...
@@ -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
...
...
native/jni/init/preload.c
0 → 100644
View file @
ff2513e2
#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
;
}
native/jni/init/selinux.cpp
View file @
ff2513e2
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment