Compare commits
1 commit
main
...
v3.2.0-sta
| Author | SHA1 | Date | |
|---|---|---|---|
| b7554a5383 |
260 changed files with 14125 additions and 25095 deletions
2
README.md
Normal file
2
README.md
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# sukisu
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ Generally need to modify the `do_execve` and `compat_do_execve` methods in `fs/e
|
||||||
.ptr.compat = __envp,
|
.ptr.compat = __envp,
|
||||||
};
|
};
|
||||||
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||||
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0); // 32-bit su and 32-on-64 support
|
+ trace_ksu_trace_execveat_sucompat_hook((int *)AT_FDCWD, &filename, NULL, NULL, NULL); /* 32-bit su */
|
||||||
+#endif
|
+#endif
|
||||||
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
|
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -237,3 +237,34 @@ Need to modify the `input_event` method in `drivers/input/input.c`, not `input_h
|
||||||
|
|
||||||
spin_lock_irqsave(&dev->event_lock, flags);
|
spin_lock_irqsave(&dev->event_lock, flags);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### devpts Hook (`pty.c`)
|
||||||
|
|
||||||
|
Need to modify the `pts_unix98_lookup` method in `drivers/tty/pty.c`
|
||||||
|
|
||||||
|
```patch
|
||||||
|
--- a/drivers/tty/pty.c
|
||||||
|
+++ b/drivers/tty/pty.c
|
||||||
|
@@ -31,6 +31,10 @@
|
||||||
|
#include <linux/compat.h>
|
||||||
|
#include "tty.h"
|
||||||
|
|
||||||
|
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||||
|
+#include <../../drivers/kernelsu/ksu_trace.h>
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
#undef TTY_DEBUG_HANGUP
|
||||||
|
#ifdef TTY_DEBUG_HANGUP
|
||||||
|
# define tty_debug_hangup(tty, f, args...) tty_debug(tty, f, ##args)
|
||||||
|
@@ -707,6 +711,10 @@ static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
|
||||||
|
{
|
||||||
|
struct tty_struct *tty;
|
||||||
|
|
||||||
|
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||||
|
+ trace_ksu_trace_devpts_hook((struct inode *)file->f_path.dentry->d_inode);
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
mutex_lock(&devpts_mutex);
|
||||||
|
tty = devpts_get_priv(file->f_path.dentry);
|
||||||
|
mutex_unlock(&devpts_mutex);
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
.ptr.compat = __envp,
|
.ptr.compat = __envp,
|
||||||
};
|
};
|
||||||
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||||
+ trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0)); // 32-bit su and 32-on-64 support
|
+ trace_ksu_trace_execveat_sucompat_hook((int *)AT_FDCWD, &filename, NULL, NULL, NULL); /* 32-bit su */
|
||||||
+#endif
|
+#endif
|
||||||
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
|
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -237,3 +237,34 @@
|
||||||
|
|
||||||
spin_lock_irqsave(&dev->event_lock, flags);
|
spin_lock_irqsave(&dev->event_lock, flags);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### devpts 钩子 (`pty.c`)
|
||||||
|
|
||||||
|
需要修改 `drivers/tty/pty.c` 的 `pts_unix98_lookup` 方法
|
||||||
|
|
||||||
|
```patch
|
||||||
|
--- a/drivers/tty/pty.c
|
||||||
|
+++ b/drivers/tty/pty.c
|
||||||
|
@@ -31,6 +31,10 @@
|
||||||
|
#include <linux/compat.h>
|
||||||
|
#include "tty.h"
|
||||||
|
|
||||||
|
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||||
|
+#include <../../drivers/kernelsu/ksu_trace.h>
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
#undef TTY_DEBUG_HANGUP
|
||||||
|
#ifdef TTY_DEBUG_HANGUP
|
||||||
|
# define tty_debug_hangup(tty, f, args...) tty_debug(tty, f, ##args)
|
||||||
|
@@ -707,6 +711,10 @@ static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
|
||||||
|
{
|
||||||
|
struct tty_struct *tty;
|
||||||
|
|
||||||
|
+#if defined(CONFIG_KSU) && defined(CONFIG_KSU_TRACEPOINT_HOOK)
|
||||||
|
+ trace_ksu_trace_devpts_hook((struct inode *)file->f_path.dentry->d_inode);
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
mutex_lock(&devpts_mutex);
|
||||||
|
tty = devpts_get_priv(file->f_path.dentry);
|
||||||
|
mutex_unlock(&devpts_mutex);
|
||||||
|
```
|
||||||
|
|
|
||||||
BIN
icon/logo.png
Normal file
BIN
icon/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 342 B |
|
|
@ -56,8 +56,8 @@ ColumnLimit: 80
|
||||||
CommentPragmas: '^ IWYU pragma:'
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
#CompactNamespaces: false # Unknown to clang-format-4.0
|
#CompactNamespaces: false # Unknown to clang-format-4.0
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
ConstructorInitializerIndentWidth: 4
|
ConstructorInitializerIndentWidth: 8
|
||||||
ContinuationIndentWidth: 4
|
ContinuationIndentWidth: 8
|
||||||
Cpp11BracedListStyle: false
|
Cpp11BracedListStyle: false
|
||||||
DerivePointerAlignment: false
|
DerivePointerAlignment: false
|
||||||
DisableFormat: false
|
DisableFormat: false
|
||||||
|
|
@ -501,7 +501,7 @@ IncludeCategories:
|
||||||
IncludeIsMainRegex: '(Test)?$'
|
IncludeIsMainRegex: '(Test)?$'
|
||||||
IndentCaseLabels: false
|
IndentCaseLabels: false
|
||||||
#IndentPPDirectives: None # Unknown to clang-format-5.0
|
#IndentPPDirectives: None # Unknown to clang-format-5.0
|
||||||
IndentWidth: 4
|
IndentWidth: 8
|
||||||
IndentWrappedFunctionNames: false
|
IndentWrappedFunctionNames: false
|
||||||
JavaScriptQuotes: Leave
|
JavaScriptQuotes: Leave
|
||||||
JavaScriptWrapImports: true
|
JavaScriptWrapImports: true
|
||||||
|
|
@ -511,7 +511,7 @@ MacroBlockEnd: ''
|
||||||
MaxEmptyLinesToKeep: 1
|
MaxEmptyLinesToKeep: 1
|
||||||
NamespaceIndentation: None
|
NamespaceIndentation: None
|
||||||
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
|
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
|
||||||
ObjCBlockIndentWidth: 4
|
ObjCBlockIndentWidth: 8
|
||||||
ObjCSpaceAfterProperty: true
|
ObjCSpaceAfterProperty: true
|
||||||
ObjCSpaceBeforeProtocolList: true
|
ObjCSpaceBeforeProtocolList: true
|
||||||
|
|
||||||
|
|
@ -543,6 +543,6 @@ SpacesInCStyleCastParentheses: false
|
||||||
SpacesInParentheses: false
|
SpacesInParentheses: false
|
||||||
SpacesInSquareBrackets: false
|
SpacesInSquareBrackets: false
|
||||||
Standard: Cpp03
|
Standard: Cpp03
|
||||||
TabWidth: 4
|
TabWidth: 8
|
||||||
UseTab: Never
|
UseTab: Always
|
||||||
...
|
...
|
||||||
|
|
|
||||||
22
kernel/.gitignore
vendored
22
kernel/.gitignore
vendored
|
|
@ -1,22 +0,0 @@
|
||||||
.cache/
|
|
||||||
.thinlto-cache/
|
|
||||||
compile_commands.json
|
|
||||||
*.ko
|
|
||||||
*.o
|
|
||||||
*.mod
|
|
||||||
*.lds
|
|
||||||
*.mod.o
|
|
||||||
.*.o*
|
|
||||||
.*.mod*
|
|
||||||
*.ko*
|
|
||||||
*.mod.c
|
|
||||||
*.symvers*
|
|
||||||
*.order
|
|
||||||
.*.ko.cmd
|
|
||||||
.tmp_versions/
|
|
||||||
libs/
|
|
||||||
obj/
|
|
||||||
|
|
||||||
CLAUDE.md
|
|
||||||
.ddk-version
|
|
||||||
.vscode/settings.json
|
|
||||||
11
kernel/.vscode/c_cpp_properties.json
vendored
11
kernel/.vscode/c_cpp_properties.json
vendored
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Linux",
|
|
||||||
"cStandard": "c11",
|
|
||||||
"intelliSenseMode": "gcc-arm64",
|
|
||||||
"compileCommands": "${workspaceFolder}/compile_commands.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version": 4
|
|
||||||
}
|
|
||||||
92
kernel/.vscode/generate_compdb.py
vendored
92
kernel/.vscode/generate_compdb.py
vendored
|
|
@ -1,92 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import print_function, division
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import fnmatch
|
|
||||||
import functools
|
|
||||||
import json
|
|
||||||
import math
|
|
||||||
import multiprocessing
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
CMD_VAR_RE = re.compile(r'^\s*(?:saved)?cmd_(\S+)\s*:=\s*(.+)\s*$', re.MULTILINE)
|
|
||||||
SOURCE_VAR_RE = re.compile(r'^\s*source_(\S+)\s*:=\s*(.+)\s*$', re.MULTILINE)
|
|
||||||
|
|
||||||
|
|
||||||
def print_progress_bar(progress):
|
|
||||||
progress_bar = '[' + '|' * int(50 * progress) + '-' * int(50 * (1.0 - progress)) + ']'
|
|
||||||
print('\r', progress_bar, "{0:.1%}".format(progress), end='\r', file=sys.stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_cmd_file(out_dir, cmdfile_path):
|
|
||||||
with open(cmdfile_path, 'r') as cmdfile:
|
|
||||||
cmdfile_content = cmdfile.read()
|
|
||||||
|
|
||||||
commands = { match.group(1): match.group(2) for match in CMD_VAR_RE.finditer(cmdfile_content) }
|
|
||||||
sources = { match.group(1): match.group(2) for match in SOURCE_VAR_RE.finditer(cmdfile_content) }
|
|
||||||
|
|
||||||
return [{
|
|
||||||
'directory': out_dir,
|
|
||||||
'command': commands[o_file_name],
|
|
||||||
'file': source,
|
|
||||||
'output': o_file_name
|
|
||||||
} for o_file_name, source in sources.items()]
|
|
||||||
|
|
||||||
|
|
||||||
def gen_compile_commands(cmd_file_search_path, out_dir):
|
|
||||||
print("Building *.o.cmd file list...", file=sys.stderr)
|
|
||||||
|
|
||||||
out_dir = os.path.abspath(out_dir)
|
|
||||||
|
|
||||||
if not cmd_file_search_path:
|
|
||||||
cmd_file_search_path = [out_dir]
|
|
||||||
|
|
||||||
cmd_files = []
|
|
||||||
for search_path in cmd_file_search_path:
|
|
||||||
if (os.path.isdir(search_path)):
|
|
||||||
for cur_dir, subdir, files in os.walk(search_path):
|
|
||||||
cmd_files.extend(os.path.join(cur_dir, cmdfile_name) for cmdfile_name in fnmatch.filter(files, '*.o.cmd'))
|
|
||||||
else:
|
|
||||||
cmd_files.extend(search_path)
|
|
||||||
|
|
||||||
if not cmd_files:
|
|
||||||
print("No *.o.cmd files found in", ", ".join(cmd_file_search_path), file=sys.stderr)
|
|
||||||
return
|
|
||||||
|
|
||||||
print("Parsing *.o.cmd files...", file=sys.stderr)
|
|
||||||
|
|
||||||
n_processed = 0
|
|
||||||
print_progress_bar(0)
|
|
||||||
|
|
||||||
compdb = []
|
|
||||||
pool = multiprocessing.Pool()
|
|
||||||
try:
|
|
||||||
for compdb_chunk in pool.imap_unordered(functools.partial(parse_cmd_file, out_dir), cmd_files, chunksize=int(math.sqrt(len(cmd_files)))):
|
|
||||||
compdb.extend(compdb_chunk)
|
|
||||||
n_processed += 1
|
|
||||||
print_progress_bar(n_processed / len(cmd_files))
|
|
||||||
|
|
||||||
finally:
|
|
||||||
pool.terminate()
|
|
||||||
pool.join()
|
|
||||||
|
|
||||||
print(file=sys.stderr)
|
|
||||||
print("Writing compile_commands.json...", file=sys.stderr)
|
|
||||||
|
|
||||||
with open('compile_commands.json', 'w') as compdb_file:
|
|
||||||
json.dump(compdb, compdb_file, indent=1)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
cmd_parser = argparse.ArgumentParser()
|
|
||||||
cmd_parser.add_argument('-O', '--out-dir', type=str, default=os.getcwd(), help="Build output directory")
|
|
||||||
cmd_parser.add_argument('cmd_file_search_path', nargs='*', help="*.cmd file search path")
|
|
||||||
gen_compile_commands(**vars(cmd_parser.parse_args()))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
16
kernel/.vscode/tasks.json
vendored
16
kernel/.vscode/tasks.json
vendored
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
|
||||||
// for the documentation about the tasks.json format
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "Generate compile_commands.json",
|
|
||||||
"type": "process",
|
|
||||||
"command": "python",
|
|
||||||
"args": [
|
|
||||||
"${workspaceRoot}/.vscode/generate_compdb.py"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -2,6 +2,7 @@ menu "KernelSU"
|
||||||
|
|
||||||
config KSU
|
config KSU
|
||||||
tristate "KernelSU function support"
|
tristate "KernelSU function support"
|
||||||
|
depends on OVERLAY_FS
|
||||||
default y
|
default y
|
||||||
help
|
help
|
||||||
Enable kernel-level root privileges on Android System.
|
Enable kernel-level root privileges on Android System.
|
||||||
|
|
@ -15,13 +16,6 @@ config KSU_DEBUG
|
||||||
help
|
help
|
||||||
Enable KernelSU debug mode.
|
Enable KernelSU debug mode.
|
||||||
|
|
||||||
config KSU_MANUAL_SU
|
|
||||||
bool "Use manual su"
|
|
||||||
depends on KSU
|
|
||||||
default y
|
|
||||||
help
|
|
||||||
Use manual su and authorize the corresponding command line and application via prctl
|
|
||||||
|
|
||||||
config KPM
|
config KPM
|
||||||
bool "Enable SukiSU KPM"
|
bool "Enable SukiSU KPM"
|
||||||
depends on KSU && 64BIT
|
depends on KSU && 64BIT
|
||||||
|
|
@ -33,10 +27,31 @@ config KPM
|
||||||
select KALLSYMS
|
select KALLSYMS
|
||||||
select KALLSYMS_ALL
|
select KALLSYMS_ALL
|
||||||
|
|
||||||
|
choice
|
||||||
|
prompt "KernelSU hook type"
|
||||||
|
depends on KSU
|
||||||
|
default KSU_KPROBES_HOOK
|
||||||
|
help
|
||||||
|
Hook type for KernelSU
|
||||||
|
|
||||||
|
config KSU_KPROBES_HOOK
|
||||||
|
bool "Hook KernelSU with Kprobes"
|
||||||
|
depends on KPROBES
|
||||||
|
help
|
||||||
|
If enabled, Hook required KernelSU syscalls with Kernel-probe.
|
||||||
|
|
||||||
|
config KSU_TRACEPOINT_HOOK
|
||||||
|
bool "Hook KernelSU with Tracepoint"
|
||||||
|
depends on TRACEPOINTS
|
||||||
|
help
|
||||||
|
If enabled, Hook required KernelSU syscalls with Tracepoint.
|
||||||
|
|
||||||
config KSU_MANUAL_HOOK
|
config KSU_MANUAL_HOOK
|
||||||
bool "Hook KernelSU manually"
|
bool "Hook KernelSU manually"
|
||||||
depends on KSU != m
|
depends on KSU != m
|
||||||
help
|
help
|
||||||
If enabled, Hook required KernelSU syscalls with manually-patched function.
|
If enabled, Hook required KernelSU syscalls with manually-patched function.
|
||||||
|
|
||||||
|
endchoice
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,17 @@
|
||||||
kernelsu-objs := ksu.o
|
kernelsu-objs := ksu.o
|
||||||
kernelsu-objs += allowlist.o
|
kernelsu-objs += allowlist.o
|
||||||
kernelsu-objs += app_profile.o
|
|
||||||
kernelsu-objs += dynamic_manager.o
|
kernelsu-objs += dynamic_manager.o
|
||||||
kernelsu-objs += apk_sign.o
|
kernelsu-objs += apk_sign.o
|
||||||
kernelsu-objs += sucompat.o
|
kernelsu-objs += sucompat.o
|
||||||
kernelsu-objs += syscall_hook_manager.o
|
|
||||||
kernelsu-objs += throne_tracker.o
|
kernelsu-objs += throne_tracker.o
|
||||||
kernelsu-objs += pkg_observer.o
|
kernelsu-objs += core_hook.o
|
||||||
kernelsu-objs += throne_tracker.o
|
|
||||||
kernelsu-objs += umount_manager.o
|
|
||||||
kernelsu-objs += setuid_hook.o
|
|
||||||
kernelsu-objs += kernel_umount.o
|
|
||||||
kernelsu-objs += supercalls.o
|
|
||||||
kernelsu-objs += feature.o
|
|
||||||
kernelsu-objs += ksud.o
|
kernelsu-objs += ksud.o
|
||||||
kernelsu-objs += embed_ksud.o
|
kernelsu-objs += embed_ksud.o
|
||||||
kernelsu-objs += seccomp_cache.o
|
kernelsu-objs += kernel_compat.o
|
||||||
kernelsu-objs += file_wrapper.o
|
|
||||||
kernelsu-objs += throne_comm.o
|
kernelsu-objs += throne_comm.o
|
||||||
kernelsu-objs += sulog.o
|
|
||||||
|
|
||||||
ifeq ($(CONFIG_KSU_MANUAL_SU), y)
|
ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y)
|
||||||
ccflags-y += -DCONFIG_KSU_MANUAL_SU
|
kernelsu-objs += ksu_trace.o
|
||||||
kernelsu-objs += manual_su.o
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
kernelsu-objs += selinux/selinux.o
|
kernelsu-objs += selinux/selinux.o
|
||||||
|
|
@ -32,62 +21,46 @@ ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
|
||||||
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
|
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
|
||||||
|
|
||||||
obj-$(CONFIG_KSU) += kernelsu.o
|
obj-$(CONFIG_KSU) += kernelsu.o
|
||||||
|
obj-$(CONFIG_KSU_TRACEPOINT_HOOK) += ksu_trace_export.o
|
||||||
|
|
||||||
obj-$(CONFIG_KPM) += kpm/
|
obj-$(CONFIG_KPM) += kpm/
|
||||||
|
|
||||||
|
|
||||||
REPO_OWNER := SukiSU-Ultra
|
REPO_OWNER := SukiSU-Ultra
|
||||||
REPO_NAME := SukiSU-Ultra
|
REPO_NAME := SukiSU-Ultra
|
||||||
REPO_BRANCH := main
|
REPO_BRANCH := main
|
||||||
KSU_VERSION_API := 4.0.0
|
KSU_VERSION_API := 3.2.0
|
||||||
|
|
||||||
GIT_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git
|
GIT_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git
|
||||||
CURL_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin curl
|
CURL_BIN := /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin curl
|
||||||
|
|
||||||
KDIR := $(KDIR)
|
|
||||||
MDIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
|
|
||||||
|
|
||||||
ifneq ($(KDIR),)
|
|
||||||
$(info -- KDIR: $(KDIR))
|
|
||||||
$(info -- MDIR: $(MDIR))
|
|
||||||
endif
|
|
||||||
|
|
||||||
KSU_GITHUB_VERSION := $(shell $(CURL_BIN) -s "https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
|
KSU_GITHUB_VERSION := $(shell $(CURL_BIN) -s "https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
|
||||||
KSU_GITHUB_VERSION_COMMIT := $(shell $(CURL_BIN) -sI "https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/commits?sha=$(REPO_BRANCH)&per_page=1" | grep -i "link:" | sed -n 's/.*page=\([0-9]*\)>; rel="last".*/\1/p')
|
KSU_GITHUB_VERSION_COMMIT := $(shell $(CURL_BIN) -sI "https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/commits?sha=$(REPO_BRANCH)&per_page=1" | grep -i "link:" | sed -n 's/.*page=\([0-9]*\)>; rel="last".*/\1/p')
|
||||||
|
|
||||||
ifeq ($(findstring $(srctree),$(src)),$(srctree))
|
LOCAL_GIT_EXISTS := $(shell test -e $(srctree)/$(src)/../.git && echo 1 || echo 0)
|
||||||
KSU_SRC := $(src)
|
|
||||||
else
|
|
||||||
KSU_SRC := $(srctree)/$(src)
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifneq ($(shell test -e $(KSU_SRC)/../.git && echo "in-tree"),in-tree)
|
|
||||||
KSU_SRC := $(MDIR)
|
|
||||||
endif
|
|
||||||
|
|
||||||
LOCAL_GIT_EXISTS := $(shell test -e $(KSU_SRC)/../.git && echo 1 || echo 0)
|
|
||||||
|
|
||||||
define get_ksu_version_full
|
define get_ksu_version_full
|
||||||
v$1-$(shell cd $(KSU_SRC); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(KSU_SRC); $(GIT_BIN) rev-parse --abbrev-ref HEAD)
|
v$1-$(shell cd $(srctree)/$(src); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(srctree)/$(src); $(GIT_BIN) rev-parse --abbrev-ref HEAD)
|
||||||
endef
|
endef
|
||||||
|
|
||||||
ifeq ($(KSU_GITHUB_VERSION_COMMIT),)
|
ifeq ($(KSU_GITHUB_VERSION_COMMIT),)
|
||||||
ifeq ($(LOCAL_GIT_EXISTS),1)
|
ifeq ($(LOCAL_GIT_EXISTS),1)
|
||||||
$(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||||
KSU_LOCAL_VERSION := $(shell cd $(KSU_SRC); $(GIT_BIN) rev-list --count $(REPO_BRANCH))
|
KSU_LOCAL_VERSION := $(shell cd $(srctree)/$(src); $(GIT_BIN) rev-list --count $(REPO_BRANCH))
|
||||||
KSU_VERSION := $(shell expr 40000 + $(KSU_LOCAL_VERSION) - 2815)
|
KSU_VERSION := $(shell expr 10000 + $(KSU_LOCAL_VERSION) + 700)
|
||||||
$(info -- $(REPO_NAME) version (local .git): $(KSU_VERSION))
|
$(info -- $(REPO_NAME) version (local .git): $(KSU_VERSION))
|
||||||
else
|
else
|
||||||
KSU_VERSION := 13000
|
KSU_VERSION := 13000
|
||||||
$(warning -- Could not fetch version online or via local .git! Using fallback version: $(KSU_VERSION))
|
$(warning -- Could not fetch version online or via local .git! Using fallback version: $(KSU_VERSION))
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
KSU_VERSION := $(shell expr 40000 + $(KSU_GITHUB_VERSION_COMMIT) - 2815)
|
KSU_VERSION := $(shell expr 10000 + $(KSU_GITHUB_VERSION_COMMIT) + 700)
|
||||||
$(info -- $(REPO_NAME) version (GitHub): $(KSU_VERSION))
|
$(info -- $(REPO_NAME) version (GitHub): $(KSU_VERSION))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(KSU_GITHUB_VERSION),)
|
ifeq ($(KSU_GITHUB_VERSION),)
|
||||||
ifeq ($(LOCAL_GIT_EXISTS),1)
|
ifeq ($(LOCAL_GIT_EXISTS),1)
|
||||||
$(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||||
KSU_VERSION_FULL := $(call get_ksu_version_full,$(KSU_VERSION_API))
|
KSU_VERSION_FULL := $(call get_ksu_version_full,$(KSU_VERSION_API))
|
||||||
$(info -- $(REPO_NAME) version (local .git): $(KSU_VERSION_FULL))
|
$(info -- $(REPO_NAME) version (local .git): $(KSU_VERSION_FULL))
|
||||||
$(info -- $(REPO_NAME) Formatted version (local .git): $(KSU_VERSION))
|
$(info -- $(REPO_NAME) Formatted version (local .git): $(KSU_VERSION))
|
||||||
|
|
@ -96,7 +69,7 @@ ifeq ($(KSU_GITHUB_VERSION),)
|
||||||
$(warning -- $(REPO_NAME) version: $(KSU_VERSION_FULL))
|
$(warning -- $(REPO_NAME) version: $(KSU_VERSION_FULL))
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
$(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
$(shell cd $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
|
||||||
KSU_VERSION_FULL := $(call get_ksu_version_full,$(KSU_GITHUB_VERSION))
|
KSU_VERSION_FULL := $(call get_ksu_version_full,$(KSU_GITHUB_VERSION))
|
||||||
$(info -- $(REPO_NAME) version (Github): $(KSU_VERSION_FULL))
|
$(info -- $(REPO_NAME) version (Github): $(KSU_VERSION_FULL))
|
||||||
endif
|
endif
|
||||||
|
|
@ -120,13 +93,14 @@ ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
|
||||||
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(CONFIG_KSU_MANUAL_HOOK), y)
|
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
|
||||||
ccflags-y += -DKSU_MANUAL_HOOK
|
|
||||||
$(info -- SukiSU: KSU_MANUAL_HOOK Temporarily discontinued))
|
ifeq ($(CONFIG_KSU_KPROBES_HOOK), y)
|
||||||
else
|
$(info -- SukiSU: CONFIG_KSU_KPROBES_HOOK)
|
||||||
ccflags-y += -DKSU_KPROBES_HOOK
|
else ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y)
|
||||||
ccflags-y += -DKSU_TP_HOOK
|
$(info -- SukiSU: CONFIG_KSU_TRACEPOINT_HOOK)
|
||||||
$(info -- SukiSU: KSU_TRACEPOINT_HOOK)
|
else ifeq ($(CONFIG_KSU_MANUAL_HOOK), y)
|
||||||
|
$(info -- SukiSU: CONFIG_KSU_MANUAL_HOOK)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
|
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
|
||||||
|
|
@ -143,30 +117,14 @@ endif
|
||||||
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||||
$(info -- KERNEL_TYPE: $(KERNEL_TYPE))
|
$(info -- KERNEL_TYPE: $(KERNEL_TYPE))
|
||||||
|
|
||||||
|
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||||
ifeq ($(CONFIG_KPM), y)
|
ifeq ($(CONFIG_KPM), y)
|
||||||
$(info -- KPM is enabled)
|
$(info -- KPM is enabled)
|
||||||
else
|
else
|
||||||
$(info -- KPM is disabled)
|
$(info -- KPM is disabled)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Check new vfs_getattr()
|
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
|
||||||
ifeq ($(shell grep -A1 "^int vfs_getattr" $(srctree)/fs/stat.c | grep -q "query_flags" ; echo $$?),0)
|
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
|
||||||
ccflags-y += -DKSU_HAS_NEW_VFS_GETATTR
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Function proc_ops check
|
|
||||||
ifeq ($(shell grep -q "struct proc_ops " $(srctree)/include/linux/proc_fs.h; echo $$?),0)
|
|
||||||
ccflags-y += -DKSU_COMPAT_HAS_PROC_OPS
|
|
||||||
endif
|
|
||||||
|
|
||||||
ccflags-y += -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat -Wno-missing-prototypes
|
|
||||||
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function -Wno-unused-variable
|
|
||||||
|
|
||||||
all:
|
|
||||||
make -C $(KDIR) M=$(MDIR) modules
|
|
||||||
compdb:
|
|
||||||
python3 $(MDIR)/.vscode/generate_compdb.py -O $(KDIR) $(MDIR)
|
|
||||||
clean:
|
|
||||||
make -C $(KDIR) M=$(MDIR) clean
|
|
||||||
|
|
||||||
# Keep a new line here!! Because someone may append config
|
# Keep a new line here!! Because someone may append config
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
#include <linux/mutex.h>
|
|
||||||
#include <linux/task_work.h>
|
|
||||||
#include <linux/capability.h>
|
#include <linux/capability.h>
|
||||||
#include <linux/compiler.h>
|
#include <linux/compiler.h>
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
|
|
@ -10,16 +8,14 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/version.h>
|
#include <linux/version.h>
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
|
||||||
#include <linux/compiler_types.h>
|
#include <linux/compiler_types.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#include "ksu.h"
|
||||||
#include "klog.h" // IWYU pragma: keep
|
#include "klog.h" // IWYU pragma: keep
|
||||||
#include "ksud.h"
|
|
||||||
#include "selinux/selinux.h"
|
#include "selinux/selinux.h"
|
||||||
|
#include "kernel_compat.h"
|
||||||
#include "allowlist.h"
|
#include "allowlist.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "syscall_hook_manager.h"
|
|
||||||
|
|
||||||
#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32
|
#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32
|
||||||
#define FILE_FORMAT_VERSION 3 // u32
|
#define FILE_FORMAT_VERSION 3 // u32
|
||||||
|
|
@ -33,10 +29,7 @@ static DEFINE_MUTEX(allowlist_mutex);
|
||||||
static struct root_profile default_root_profile;
|
static struct root_profile default_root_profile;
|
||||||
static struct non_root_profile default_non_root_profile;
|
static struct non_root_profile default_non_root_profile;
|
||||||
|
|
||||||
void persistent_allow_list(void);
|
static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly __aligned(PAGE_SIZE);
|
||||||
|
|
||||||
static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly
|
|
||||||
__aligned(PAGE_SIZE);
|
|
||||||
static int allow_list_pointer __read_mostly = 0;
|
static int allow_list_pointer __read_mostly = 0;
|
||||||
|
|
||||||
static void remove_uid_from_arr(uid_t uid)
|
static void remove_uid_from_arr(uid_t uid)
|
||||||
|
|
@ -47,7 +40,7 @@ static void remove_uid_from_arr(uid_t uid)
|
||||||
if (allow_list_pointer == 0)
|
if (allow_list_pointer == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
temp_arr = kzalloc(sizeof(allow_list_arr), GFP_KERNEL);
|
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
|
||||||
if (temp_arr == NULL) {
|
if (temp_arr == NULL) {
|
||||||
pr_err("%s: unable to allocate memory\n", __func__);
|
pr_err("%s: unable to allocate memory\n", __func__);
|
||||||
return;
|
return;
|
||||||
|
|
@ -68,7 +61,7 @@ static void remove_uid_from_arr(uid_t uid)
|
||||||
kfree(temp_arr);
|
kfree(temp_arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init_default_profiles(void)
|
static void init_default_profiles()
|
||||||
{
|
{
|
||||||
kernel_cap_t full_cap = CAP_FULL_SET;
|
kernel_cap_t full_cap = CAP_FULL_SET;
|
||||||
|
|
||||||
|
|
@ -97,6 +90,11 @@ static uint8_t allow_list_bitmap[PAGE_SIZE] __read_mostly __aligned(PAGE_SIZE);
|
||||||
|
|
||||||
#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist"
|
#define KERNEL_SU_ALLOWLIST "/data/adb/ksu/.allowlist"
|
||||||
|
|
||||||
|
static struct work_struct ksu_save_work;
|
||||||
|
static struct work_struct ksu_load_work;
|
||||||
|
|
||||||
|
bool persistent_allow_list(void);
|
||||||
|
|
||||||
void ksu_show_allow_list(void)
|
void ksu_show_allow_list(void)
|
||||||
{
|
{
|
||||||
struct perm_data *p = NULL;
|
struct perm_data *p = NULL;
|
||||||
|
|
@ -110,7 +108,7 @@ void ksu_show_allow_list(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_KSU_DEBUG
|
#ifdef CONFIG_KSU_DEBUG
|
||||||
static void ksu_grant_root_to_shell(void)
|
static void ksu_grant_root_to_shell()
|
||||||
{
|
{
|
||||||
struct app_profile profile = {
|
struct app_profile profile = {
|
||||||
.version = KSU_APP_PROFILE_VER,
|
.version = KSU_APP_PROFILE_VER,
|
||||||
|
|
@ -118,8 +116,7 @@ static void ksu_grant_root_to_shell(void)
|
||||||
.current_uid = 2000,
|
.current_uid = 2000,
|
||||||
};
|
};
|
||||||
strcpy(profile.key, "com.android.shell");
|
strcpy(profile.key, "com.android.shell");
|
||||||
strcpy(profile.rp_config.profile.selinux_domain,
|
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
||||||
KSU_DEFAULT_SELINUX_DOMAIN);
|
|
||||||
ksu_set_app_profile(&profile, false);
|
ksu_set_app_profile(&profile, false);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -145,8 +142,7 @@ exit:
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool forbid_system_uid(uid_t uid)
|
static inline bool forbid_system_uid(uid_t uid) {
|
||||||
{
|
|
||||||
#define SHELL_UID 2000
|
#define SHELL_UID 2000
|
||||||
#define SYSTEM_UID 1000
|
#define SYSTEM_UID 1000
|
||||||
return uid < SHELL_UID && uid != SYSTEM_UID;
|
return uid < SHELL_UID && uid != SYSTEM_UID;
|
||||||
|
|
@ -200,7 +196,7 @@ bool ksu_set_app_profile(struct app_profile *profile, bool persist)
|
||||||
}
|
}
|
||||||
|
|
||||||
// not found, alloc a new node!
|
// not found, alloc a new node!
|
||||||
p = (struct perm_data *)kzalloc(sizeof(struct perm_data), GFP_KERNEL);
|
p = (struct perm_data *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
|
||||||
if (!p) {
|
if (!p) {
|
||||||
pr_err("ksu_set_app_profile alloc failed\n");
|
pr_err("ksu_set_app_profile alloc failed\n");
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -222,11 +218,9 @@ bool ksu_set_app_profile(struct app_profile *profile, bool persist)
|
||||||
out:
|
out:
|
||||||
if (profile->current_uid <= BITMAP_UID_MAX) {
|
if (profile->current_uid <= BITMAP_UID_MAX) {
|
||||||
if (profile->allow_su)
|
if (profile->allow_su)
|
||||||
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |=
|
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE);
|
||||||
1 << (profile->current_uid % BITS_PER_BYTE);
|
|
||||||
else
|
else
|
||||||
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &=
|
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE));
|
||||||
~(1 << (profile->current_uid % BITS_PER_BYTE));
|
|
||||||
} else {
|
} else {
|
||||||
if (profile->allow_su) {
|
if (profile->allow_su) {
|
||||||
/*
|
/*
|
||||||
|
|
@ -258,11 +252,8 @@ out:
|
||||||
sizeof(default_root_profile));
|
sizeof(default_root_profile));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (persist) {
|
if (persist)
|
||||||
persistent_allow_list();
|
persistent_allow_list();
|
||||||
// FIXME: use a new flag
|
|
||||||
ksu_mark_running_process();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -271,20 +262,23 @@ bool __ksu_is_allow_uid(uid_t uid)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if (unlikely(uid == 0)) {
|
||||||
|
// already root, but only allow our domain.
|
||||||
|
return is_ksu_domain();
|
||||||
|
}
|
||||||
|
|
||||||
if (forbid_system_uid(uid)) {
|
if (forbid_system_uid(uid)) {
|
||||||
// do not bother going through the list if it's system
|
// do not bother going through the list if it's system
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (likely(ksu_is_manager_uid_valid()) &&
|
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
|
||||||
unlikely(ksu_get_manager_uid() == uid)) {
|
|
||||||
// manager is always allowed!
|
// manager is always allowed!
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (likely(uid <= BITMAP_UID_MAX)) {
|
if (likely(uid <= BITMAP_UID_MAX)) {
|
||||||
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] &
|
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE)));
|
||||||
(1 << (uid % BITS_PER_BYTE)));
|
|
||||||
} else {
|
} else {
|
||||||
for (i = 0; i < allow_list_pointer; i++) {
|
for (i = 0; i < allow_list_pointer; i++) {
|
||||||
if (allow_list_arr[i] == uid)
|
if (allow_list_arr[i] == uid)
|
||||||
|
|
@ -295,20 +289,10 @@ bool __ksu_is_allow_uid(uid_t uid)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool __ksu_is_allow_uid_for_current(uid_t uid)
|
|
||||||
{
|
|
||||||
if (unlikely(uid == 0)) {
|
|
||||||
// already root, but only allow our domain.
|
|
||||||
return is_ksu_domain();
|
|
||||||
}
|
|
||||||
return __ksu_is_allow_uid(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ksu_uid_should_umount(uid_t uid)
|
bool ksu_uid_should_umount(uid_t uid)
|
||||||
{
|
{
|
||||||
struct app_profile profile = { .current_uid = uid };
|
struct app_profile profile = { .current_uid = uid };
|
||||||
if (likely(ksu_is_manager_uid_valid()) &&
|
if (likely(ksu_is_manager_uid_valid()) && unlikely(ksu_get_manager_uid() == uid)) {
|
||||||
unlikely(ksu_get_manager_uid() == uid)) {
|
|
||||||
// we should not umount on manager!
|
// we should not umount on manager!
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -365,7 +349,7 @@ bool ksu_get_allow_list(int *array, int *length, bool allow)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void do_persistent_allow_list(struct callback_head *_cb)
|
void do_save_allow_list(struct work_struct *work)
|
||||||
{
|
{
|
||||||
u32 magic = FILE_MAGIC;
|
u32 magic = FILE_MAGIC;
|
||||||
u32 version = FILE_FORMAT_VERSION;
|
u32 version = FILE_FORMAT_VERSION;
|
||||||
|
|
@ -373,64 +357,41 @@ static void do_persistent_allow_list(struct callback_head *_cb)
|
||||||
struct list_head *pos = NULL;
|
struct list_head *pos = NULL;
|
||||||
loff_t off = 0;
|
loff_t off = 0;
|
||||||
|
|
||||||
mutex_lock(&allowlist_mutex);
|
|
||||||
struct file *fp =
|
struct file *fp =
|
||||||
filp_open(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||||
if (IS_ERR(fp)) {
|
if (IS_ERR(fp)) {
|
||||||
pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
|
pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
|
||||||
goto unlock;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// store magic and version
|
// store magic and version
|
||||||
if (kernel_write(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) !=
|
||||||
|
sizeof(magic)) {
|
||||||
pr_err("save_allow_list write magic failed.\n");
|
pr_err("save_allow_list write magic failed.\n");
|
||||||
goto close_file;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kernel_write(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) !=
|
||||||
|
sizeof(version)) {
|
||||||
pr_err("save_allow_list write version failed.\n");
|
pr_err("save_allow_list write version failed.\n");
|
||||||
goto close_file;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
list_for_each (pos, &allow_list) {
|
list_for_each (pos, &allow_list) {
|
||||||
p = list_entry(pos, struct perm_data, list);
|
p = list_entry(pos, struct perm_data, list);
|
||||||
pr_info("save allow list, name: %s uid :%d, allow: %d\n",
|
pr_info("save allow list, name: %s uid :%d, allow: %d\n",
|
||||||
p->profile.key, p->profile.current_uid, p->profile.allow_su);
|
p->profile.key, p->profile.current_uid,
|
||||||
|
p->profile.allow_su);
|
||||||
|
|
||||||
kernel_write(fp, &p->profile, sizeof(p->profile), &off);
|
ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile),
|
||||||
|
&off);
|
||||||
}
|
}
|
||||||
|
|
||||||
close_file:
|
exit:
|
||||||
filp_close(fp, 0);
|
filp_close(fp, 0);
|
||||||
unlock:
|
|
||||||
mutex_unlock(&allowlist_mutex);
|
|
||||||
kfree(_cb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void persistent_allow_list()
|
void do_load_allow_list(struct work_struct *work)
|
||||||
{
|
|
||||||
struct task_struct *tsk;
|
|
||||||
|
|
||||||
tsk = get_pid_task(find_vpid(1), PIDTYPE_PID);
|
|
||||||
if (!tsk) {
|
|
||||||
pr_err("save_allow_list find init task err\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct callback_head *cb =
|
|
||||||
kzalloc(sizeof(struct callback_head), GFP_KERNEL);
|
|
||||||
if (!cb) {
|
|
||||||
pr_err("save_allow_list alloc cb err\b");
|
|
||||||
goto put_task;
|
|
||||||
}
|
|
||||||
cb->func = do_persistent_allow_list;
|
|
||||||
task_work_add(tsk, cb, TWA_RESUME);
|
|
||||||
|
|
||||||
put_task:
|
|
||||||
put_task_struct(tsk);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_load_allow_list()
|
|
||||||
{
|
{
|
||||||
loff_t off = 0;
|
loff_t off = 0;
|
||||||
ssize_t ret = 0;
|
ssize_t ret = 0;
|
||||||
|
|
@ -444,20 +405,22 @@ void ksu_load_allow_list()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// load allowlist now!
|
// load allowlist now!
|
||||||
fp = filp_open(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
|
fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
|
||||||
if (IS_ERR(fp)) {
|
if (IS_ERR(fp)) {
|
||||||
pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
|
pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify magic
|
// verify magic
|
||||||
if (kernel_read(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
|
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) !=
|
||||||
|
sizeof(magic) ||
|
||||||
magic != FILE_MAGIC) {
|
magic != FILE_MAGIC) {
|
||||||
pr_err("allowlist file invalid: %d!\n", magic);
|
pr_err("allowlist file invalid: %d!\n", magic);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kernel_read(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) !=
|
||||||
|
sizeof(version)) {
|
||||||
pr_err("allowlist read version: %d failed\n", version);
|
pr_err("allowlist read version: %d failed\n", version);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
@ -467,15 +430,16 @@ void ksu_load_allow_list()
|
||||||
while (true) {
|
while (true) {
|
||||||
struct app_profile profile;
|
struct app_profile profile;
|
||||||
|
|
||||||
ret = kernel_read(fp, &profile, sizeof(profile), &off);
|
ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile),
|
||||||
|
&off);
|
||||||
|
|
||||||
if (ret <= 0) {
|
if (ret <= 0) {
|
||||||
pr_info("load_allow_list read err: %zd\n", ret);
|
pr_info("load_allow_list read err: %zd\n", ret);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n", profile.key,
|
pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n",
|
||||||
profile.current_uid, profile.allow_su);
|
profile.key, profile.current_uid, profile.allow_su);
|
||||||
ksu_set_app_profile(&profile, false);
|
ksu_set_app_profile(&profile, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -484,17 +448,11 @@ exit:
|
||||||
filp_close(fp, 0);
|
filp_close(fp, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *),
|
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data)
|
||||||
void *data)
|
|
||||||
{
|
{
|
||||||
struct perm_data *np = NULL;
|
struct perm_data *np = NULL;
|
||||||
struct perm_data *n = NULL;
|
struct perm_data *n = NULL;
|
||||||
|
|
||||||
if (!ksu_boot_completed) {
|
|
||||||
pr_info("boot not completed, skip prune\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
// TODO: use RCU!
|
// TODO: use RCU!
|
||||||
mutex_lock(&allowlist_mutex);
|
mutex_lock(&allowlist_mutex);
|
||||||
|
|
@ -508,8 +466,7 @@ void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *),
|
||||||
pr_info("prune uid: %d, package: %s\n", uid, package);
|
pr_info("prune uid: %d, package: %s\n", uid, package);
|
||||||
list_del(&np->list);
|
list_del(&np->list);
|
||||||
if (likely(uid <= BITMAP_UID_MAX)) {
|
if (likely(uid <= BITMAP_UID_MAX)) {
|
||||||
allow_list_bitmap[uid / BITS_PER_BYTE] &=
|
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
|
||||||
~(1 << (uid % BITS_PER_BYTE));
|
|
||||||
}
|
}
|
||||||
remove_uid_from_arr(uid);
|
remove_uid_from_arr(uid);
|
||||||
smp_mb();
|
smp_mb();
|
||||||
|
|
@ -523,6 +480,17 @@ void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure allow list works cross boot
|
||||||
|
bool persistent_allow_list(void)
|
||||||
|
{
|
||||||
|
return ksu_queue_work(&ksu_save_work);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ksu_load_allow_list(void)
|
||||||
|
{
|
||||||
|
return ksu_queue_work(&ksu_load_work);
|
||||||
|
}
|
||||||
|
|
||||||
void ksu_allowlist_init(void)
|
void ksu_allowlist_init(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
@ -535,6 +503,9 @@ void ksu_allowlist_init(void)
|
||||||
|
|
||||||
INIT_LIST_HEAD(&allow_list);
|
INIT_LIST_HEAD(&allow_list);
|
||||||
|
|
||||||
|
INIT_WORK(&ksu_save_work, do_save_allow_list);
|
||||||
|
INIT_WORK(&ksu_load_work, do_load_allow_list);
|
||||||
|
|
||||||
init_default_profiles();
|
init_default_profiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -543,6 +514,8 @@ void ksu_allowlist_exit(void)
|
||||||
struct perm_data *np = NULL;
|
struct perm_data *np = NULL;
|
||||||
struct perm_data *n = NULL;
|
struct perm_data *n = NULL;
|
||||||
|
|
||||||
|
do_save_allow_list(NULL);
|
||||||
|
|
||||||
// free allowlist
|
// free allowlist
|
||||||
mutex_lock(&allowlist_mutex);
|
mutex_lock(&allowlist_mutex);
|
||||||
list_for_each_entry_safe (np, n, &allow_list, list) {
|
list_for_each_entry_safe (np, n, &allow_list, list) {
|
||||||
|
|
@ -551,81 +524,3 @@ void ksu_allowlist_exit(void)
|
||||||
}
|
}
|
||||||
mutex_unlock(&allowlist_mutex);
|
mutex_unlock(&allowlist_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
|
||||||
bool ksu_temp_grant_root_once(uid_t uid)
|
|
||||||
{
|
|
||||||
struct app_profile profile = {
|
|
||||||
.version = KSU_APP_PROFILE_VER,
|
|
||||||
.allow_su = true,
|
|
||||||
.current_uid = uid,
|
|
||||||
};
|
|
||||||
|
|
||||||
const char *default_key = "com.temp.once";
|
|
||||||
|
|
||||||
struct perm_data *p = NULL;
|
|
||||||
struct list_head *pos = NULL;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
list_for_each (pos, &allow_list) {
|
|
||||||
p = list_entry(pos, struct perm_data, list);
|
|
||||||
if (p->profile.current_uid == uid) {
|
|
||||||
strcpy(profile.key, p->profile.key);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
strcpy(profile.key, default_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
profile.rp_config.profile.uid = default_root_profile.uid;
|
|
||||||
profile.rp_config.profile.gid = default_root_profile.gid;
|
|
||||||
profile.rp_config.profile.groups_count = default_root_profile.groups_count;
|
|
||||||
memcpy(profile.rp_config.profile.groups, default_root_profile.groups, sizeof(default_root_profile.groups));
|
|
||||||
memcpy(&profile.rp_config.profile.capabilities, &default_root_profile.capabilities, sizeof(default_root_profile.capabilities));
|
|
||||||
profile.rp_config.profile.namespaces = default_root_profile.namespaces;
|
|
||||||
strcpy(profile.rp_config.profile.selinux_domain, default_root_profile.selinux_domain);
|
|
||||||
|
|
||||||
bool ok = ksu_set_app_profile(&profile, false);
|
|
||||||
if (ok)
|
|
||||||
pr_info("pending_root: UID=%d granted and persisted\n", uid);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_temp_revoke_root_once(uid_t uid)
|
|
||||||
{
|
|
||||||
struct app_profile profile = {
|
|
||||||
.version = KSU_APP_PROFILE_VER,
|
|
||||||
.allow_su = false,
|
|
||||||
.current_uid = uid,
|
|
||||||
};
|
|
||||||
|
|
||||||
const char *default_key = "com.temp.once";
|
|
||||||
|
|
||||||
struct perm_data *p = NULL;
|
|
||||||
struct list_head *pos = NULL;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
list_for_each (pos, &allow_list) {
|
|
||||||
p = list_entry(pos, struct perm_data, list);
|
|
||||||
if (p->profile.current_uid == uid) {
|
|
||||||
strcpy(profile.key, p->profile.key);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
strcpy(profile.key, default_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
profile.nrp_config.profile.umount_modules = default_non_root_profile.umount_modules;
|
|
||||||
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
|
|
||||||
|
|
||||||
ksu_set_app_profile(&profile, false);
|
|
||||||
persistent_allow_list();
|
|
||||||
pr_info("pending_root: UID=%d removed and persist updated\n", uid);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -2,29 +2,19 @@
|
||||||
#define __KSU_H_ALLOWLIST
|
#define __KSU_H_ALLOWLIST
|
||||||
|
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/uidgid.h>
|
#include "ksu.h"
|
||||||
#include "app_profile.h"
|
|
||||||
|
|
||||||
#define PER_USER_RANGE 100000
|
|
||||||
#define FIRST_APPLICATION_UID 10000
|
|
||||||
#define LAST_APPLICATION_UID 19999
|
|
||||||
|
|
||||||
void ksu_allowlist_init(void);
|
void ksu_allowlist_init(void);
|
||||||
|
|
||||||
void ksu_allowlist_exit(void);
|
void ksu_allowlist_exit(void);
|
||||||
|
|
||||||
void ksu_load_allow_list(void);
|
bool ksu_load_allow_list(void);
|
||||||
|
|
||||||
void ksu_show_allow_list(void);
|
void ksu_show_allow_list(void);
|
||||||
|
|
||||||
// Check if the uid is in allow list
|
|
||||||
bool __ksu_is_allow_uid(uid_t uid);
|
bool __ksu_is_allow_uid(uid_t uid);
|
||||||
#define ksu_is_allow_uid(uid) unlikely(__ksu_is_allow_uid(uid))
|
#define ksu_is_allow_uid(uid) unlikely(__ksu_is_allow_uid(uid))
|
||||||
|
|
||||||
// Check if the uid is in allow list, or current is ksu domain root
|
|
||||||
bool __ksu_is_allow_uid_for_current(uid_t uid);
|
|
||||||
#define ksu_is_allow_uid_for_current(uid) unlikely(__ksu_is_allow_uid_for_current(uid))
|
|
||||||
|
|
||||||
bool ksu_get_allow_list(int *array, int *length, bool allow);
|
bool ksu_get_allow_list(int *array, int *length, bool allow);
|
||||||
|
|
||||||
void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, char *, void *), void *data);
|
void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, char *, void *), void *data);
|
||||||
|
|
@ -34,16 +24,4 @@ bool ksu_set_app_profile(struct app_profile *, bool persist);
|
||||||
|
|
||||||
bool ksu_uid_should_umount(uid_t uid);
|
bool ksu_uid_should_umount(uid_t uid);
|
||||||
struct root_profile *ksu_get_root_profile(uid_t uid);
|
struct root_profile *ksu_get_root_profile(uid_t uid);
|
||||||
|
|
||||||
static inline bool is_appuid(uid_t uid)
|
|
||||||
{
|
|
||||||
uid_t appid = uid % PER_USER_RANGE;
|
|
||||||
return appid >= FIRST_APPLICATION_UID && appid <= LAST_APPLICATION_UID;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
|
||||||
bool ksu_temp_grant_root_once(uid_t uid);
|
|
||||||
void ksu_temp_revoke_root_once(uid_t uid);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include "apk_sign.h"
|
#include "apk_sign.h"
|
||||||
#include "dynamic_manager.h"
|
#include "dynamic_manager.h"
|
||||||
#include "klog.h" // IWYU pragma: keep
|
#include "klog.h" // IWYU pragma: keep
|
||||||
|
#include "kernel_compat.h"
|
||||||
#include "manager_sign.h"
|
#include "manager_sign.h"
|
||||||
|
|
||||||
struct sdesc {
|
struct sdesc {
|
||||||
|
|
@ -24,7 +25,10 @@ struct sdesc {
|
||||||
char ctx[];
|
char ctx[];
|
||||||
};
|
};
|
||||||
|
|
||||||
static apk_sign_key_t apk_sign_keys[] = {
|
static struct apk_sign_key {
|
||||||
|
unsigned size;
|
||||||
|
const char *sha256;
|
||||||
|
} apk_sign_keys[] = {
|
||||||
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
|
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
|
||||||
#ifdef EXPECTED_SIZE
|
#ifdef EXPECTED_SIZE
|
||||||
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom
|
{EXPECTED_SIZE, EXPECTED_HASH}, // Custom
|
||||||
|
|
@ -37,7 +41,7 @@ static struct sdesc *init_sdesc(struct crypto_shash *alg)
|
||||||
int size;
|
int size;
|
||||||
|
|
||||||
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
|
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
|
||||||
sdesc = kzalloc(size, GFP_KERNEL);
|
sdesc = kmalloc(size, GFP_KERNEL);
|
||||||
if (!sdesc)
|
if (!sdesc)
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
sdesc->shash.tfm = alg;
|
sdesc->shash.tfm = alg;
|
||||||
|
|
@ -101,7 +105,7 @@ static bool check_dynamic_sign(struct file *fp, u32 size4, loff_t *pos, int *mat
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel_read(fp, cert, size4, pos);
|
ksu_kernel_read_compat(fp, cert, size4, pos);
|
||||||
|
|
||||||
unsigned char digest[SHA256_DIGEST_SIZE];
|
unsigned char digest[SHA256_DIGEST_SIZE];
|
||||||
if (ksu_sha256(cert, size4, digest) < 0) {
|
if (ksu_sha256(cert, size4, digest) < 0) {
|
||||||
|
|
@ -128,22 +132,22 @@ static bool check_dynamic_sign(struct file *fp, u32 size4, loff_t *pos, int *mat
|
||||||
static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, int *matched_index)
|
static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, int *matched_index)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
apk_sign_key_t sign_key;
|
struct apk_sign_key sign_key;
|
||||||
bool signature_valid = false;
|
bool signature_valid = false;
|
||||||
|
|
||||||
kernel_read(fp, size4, 0x4, pos); // signer-sequence length
|
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length
|
||||||
kernel_read(fp, size4, 0x4, pos); // signer length
|
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length
|
||||||
kernel_read(fp, size4, 0x4, pos); // signed data length
|
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length
|
||||||
|
|
||||||
*offset += 0x4 * 3;
|
*offset += 0x4 * 3;
|
||||||
|
|
||||||
kernel_read(fp, size4, 0x4, pos); // digests-sequence length
|
ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length
|
||||||
|
|
||||||
*pos += *size4;
|
*pos += *size4;
|
||||||
*offset += 0x4 + *size4;
|
*offset += 0x4 + *size4;
|
||||||
|
|
||||||
kernel_read(fp, size4, 0x4, pos); // certificates length
|
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length
|
||||||
kernel_read(fp, size4, 0x4, pos); // certificate length
|
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length
|
||||||
*offset += 0x4 * 2;
|
*offset += 0x4 * 2;
|
||||||
|
|
||||||
if (ksu_is_dynamic_manager_enabled()) {
|
if (ksu_is_dynamic_manager_enabled()) {
|
||||||
|
|
@ -168,9 +172,9 @@ static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, i
|
||||||
pr_info("cert length overlimit\n");
|
pr_info("cert length overlimit\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
kernel_read(fp, cert, *size4, pos);
|
ksu_kernel_read_compat(fp, cert, *size4, pos);
|
||||||
unsigned char digest[SHA256_DIGEST_SIZE];
|
unsigned char digest[SHA256_DIGEST_SIZE];
|
||||||
if (ksu_sha256(cert, *size4, digest) < 0 ) {
|
if (IS_ERR(ksu_sha256(cert, *size4, digest))) {
|
||||||
pr_info("sha256 error\n");
|
pr_info("sha256 error\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +218,7 @@ static bool has_v1_signature_file(struct file *fp)
|
||||||
|
|
||||||
loff_t pos = 0;
|
loff_t pos = 0;
|
||||||
|
|
||||||
while (kernel_read(fp, &header,
|
while (ksu_kernel_read_compat(fp, &header,
|
||||||
sizeof(struct zip_entry_header), &pos) ==
|
sizeof(struct zip_entry_header), &pos) ==
|
||||||
sizeof(struct zip_entry_header)) {
|
sizeof(struct zip_entry_header)) {
|
||||||
if (header.signature != 0x04034b50) {
|
if (header.signature != 0x04034b50) {
|
||||||
|
|
@ -224,13 +228,12 @@ static bool has_v1_signature_file(struct file *fp)
|
||||||
// Read the entry file name
|
// Read the entry file name
|
||||||
if (header.file_name_length == sizeof(MANIFEST) - 1) {
|
if (header.file_name_length == sizeof(MANIFEST) - 1) {
|
||||||
char fileName[sizeof(MANIFEST)];
|
char fileName[sizeof(MANIFEST)];
|
||||||
kernel_read(fp, fileName,
|
ksu_kernel_read_compat(fp, fileName,
|
||||||
header.file_name_length, &pos);
|
header.file_name_length, &pos);
|
||||||
fileName[header.file_name_length] = '\0';
|
fileName[header.file_name_length] = '\0';
|
||||||
|
|
||||||
// Check if the entry matches META-INF/MANIFEST.MF
|
// Check if the entry matches META-INF/MANIFEST.MF
|
||||||
if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) ==
|
if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) == 0) {
|
||||||
0) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -250,16 +253,14 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana
|
||||||
unsigned char buffer[0x11] = { 0 };
|
unsigned char buffer[0x11] = { 0 };
|
||||||
u32 size4;
|
u32 size4;
|
||||||
u64 size8, size_of_block;
|
u64 size8, size_of_block;
|
||||||
|
|
||||||
loff_t pos;
|
loff_t pos;
|
||||||
|
|
||||||
bool v2_signing_valid = false;
|
bool v2_signing_valid = false;
|
||||||
int v2_signing_blocks = 0;
|
int v2_signing_blocks = 0;
|
||||||
bool v3_signing_exist = false;
|
bool v3_signing_exist = false;
|
||||||
bool v3_1_signing_exist = false;
|
bool v3_1_signing_exist = false;
|
||||||
int matched_index = -1;
|
int matched_index = -1;
|
||||||
int i;
|
int i;
|
||||||
struct file *fp = filp_open(path, O_RDONLY, 0);
|
struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0);
|
||||||
if (IS_ERR(fp)) {
|
if (IS_ERR(fp)) {
|
||||||
pr_err("open %s error.\n", path);
|
pr_err("open %s error.\n", path);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -278,10 +279,10 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana
|
||||||
for (i = 0;; ++i) {
|
for (i = 0;; ++i) {
|
||||||
unsigned short n;
|
unsigned short n;
|
||||||
pos = generic_file_llseek(fp, -i - 2, SEEK_END);
|
pos = generic_file_llseek(fp, -i - 2, SEEK_END);
|
||||||
kernel_read(fp, &n, 2, &pos);
|
ksu_kernel_read_compat(fp, &n, 2, &pos);
|
||||||
if (n == i) {
|
if (n == i) {
|
||||||
pos -= 22;
|
pos -= 22;
|
||||||
kernel_read(fp, &size4, 4, &pos);
|
ksu_kernel_read_compat(fp, &size4, 4, &pos);
|
||||||
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
|
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -294,17 +295,17 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana
|
||||||
|
|
||||||
pos += 12;
|
pos += 12;
|
||||||
// offset
|
// offset
|
||||||
kernel_read(fp, &size4, 0x4, &pos);
|
ksu_kernel_read_compat(fp, &size4, 0x4, &pos);
|
||||||
pos = size4 - 0x18;
|
pos = size4 - 0x18;
|
||||||
|
|
||||||
kernel_read(fp, &size8, 0x8, &pos);
|
ksu_kernel_read_compat(fp, &size8, 0x8, &pos);
|
||||||
kernel_read(fp, buffer, 0x10, &pos);
|
ksu_kernel_read_compat(fp, buffer, 0x10, &pos);
|
||||||
if (strcmp((char *)buffer, "APK Sig Block 42")) {
|
if (strcmp((char *)buffer, "APK Sig Block 42")) {
|
||||||
goto clean;
|
goto clean;
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = size4 - (size8 + 0x8);
|
pos = size4 - (size8 + 0x8);
|
||||||
kernel_read(fp, &size_of_block, 0x8, &pos);
|
ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos);
|
||||||
if (size_of_block != size8) {
|
if (size_of_block != size8) {
|
||||||
goto clean;
|
goto clean;
|
||||||
}
|
}
|
||||||
|
|
@ -313,12 +314,12 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana
|
||||||
while (loop_count++ < 10) {
|
while (loop_count++ < 10) {
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
kernel_read(fp, &size8, 0x8,
|
ksu_kernel_read_compat(fp, &size8, 0x8,
|
||||||
&pos); // sequence length
|
&pos); // sequence length
|
||||||
if (size8 == size_of_block) {
|
if (size8 == size_of_block) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
kernel_read(fp, &id, 0x4, &pos); // id
|
ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id
|
||||||
offset = 4;
|
offset = 4;
|
||||||
if (id == 0x7109871au) {
|
if (id == 0x7109871au) {
|
||||||
v2_signing_blocks++;
|
v2_signing_blocks++;
|
||||||
|
|
|
||||||
|
|
@ -1,303 +0,0 @@
|
||||||
#include <linux/capability.h>
|
|
||||||
#include <linux/cred.h>
|
|
||||||
#include <linux/sched.h>
|
|
||||||
#include <linux/sched/signal.h>
|
|
||||||
#include <linux/seccomp.h>
|
|
||||||
#include <linux/thread_info.h>
|
|
||||||
#include <linux/uidgid.h>
|
|
||||||
#include <linux/version.h>
|
|
||||||
#include "objsec.h"
|
|
||||||
|
|
||||||
#include "allowlist.h"
|
|
||||||
#include "app_profile.h"
|
|
||||||
#include "klog.h" // IWYU pragma: keep
|
|
||||||
#include "selinux/selinux.h"
|
|
||||||
#include "syscall_hook_manager.h"
|
|
||||||
#include "sucompat.h"
|
|
||||||
|
|
||||||
#include "sulog.h"
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 7, 0)
|
|
||||||
static struct group_info root_groups = { .usage = REFCOUNT_INIT(2), };
|
|
||||||
#else
|
|
||||||
static struct group_info root_groups = { .usage = ATOMIC_INIT(2) };
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void setup_groups(struct root_profile *profile, struct cred *cred)
|
|
||||||
{
|
|
||||||
if (profile->groups_count > KSU_MAX_GROUPS) {
|
|
||||||
pr_warn("Failed to setgroups, too large group: %d!\n",
|
|
||||||
profile->uid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (profile->groups_count == 1 && profile->groups[0] == 0) {
|
|
||||||
// setgroup to root and return early.
|
|
||||||
if (cred->group_info)
|
|
||||||
put_group_info(cred->group_info);
|
|
||||||
cred->group_info = get_group_info(&root_groups);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 ngroups = profile->groups_count;
|
|
||||||
struct group_info *group_info = groups_alloc(ngroups);
|
|
||||||
if (!group_info) {
|
|
||||||
pr_warn("Failed to setgroups, ENOMEM for: %d\n", profile->uid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < ngroups; i++) {
|
|
||||||
gid_t gid = profile->groups[i];
|
|
||||||
kgid_t kgid = make_kgid(current_user_ns(), gid);
|
|
||||||
if (!gid_valid(kgid)) {
|
|
||||||
pr_warn("Failed to setgroups, invalid gid: %d\n", gid);
|
|
||||||
put_group_info(group_info);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
group_info->gid[i] = kgid;
|
|
||||||
}
|
|
||||||
|
|
||||||
groups_sort(group_info);
|
|
||||||
set_groups(cred, group_info);
|
|
||||||
put_group_info(group_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
void disable_seccomp(void)
|
|
||||||
{
|
|
||||||
assert_spin_locked(¤t->sighand->siglock);
|
|
||||||
// disable seccomp
|
|
||||||
#if defined(CONFIG_GENERIC_ENTRY) && \
|
|
||||||
LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
|
||||||
clear_syscall_work(SECCOMP);
|
|
||||||
#else
|
|
||||||
clear_thread_flag(TIF_SECCOMP);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_SECCOMP
|
|
||||||
current->seccomp.mode = 0;
|
|
||||||
current->seccomp.filter = NULL;
|
|
||||||
atomic_set(¤t->seccomp.filter_count, 0);
|
|
||||||
#else
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void escape_with_root_profile(void)
|
|
||||||
{
|
|
||||||
struct cred *cred;
|
|
||||||
struct task_struct *p = current;
|
|
||||||
struct task_struct *t;
|
|
||||||
|
|
||||||
cred = prepare_creds();
|
|
||||||
if (!cred) {
|
|
||||||
pr_warn("prepare_creds failed!\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cred->euid.val == 0) {
|
|
||||||
pr_warn("Already root, don't escape!\n");
|
|
||||||
#if __SULOG_GATE
|
|
||||||
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root_failed");
|
|
||||||
#endif
|
|
||||||
abort_creds(cred);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct root_profile *profile = ksu_get_root_profile(cred->uid.val);
|
|
||||||
|
|
||||||
cred->uid.val = profile->uid;
|
|
||||||
cred->suid.val = profile->uid;
|
|
||||||
cred->euid.val = profile->uid;
|
|
||||||
cred->fsuid.val = profile->uid;
|
|
||||||
|
|
||||||
cred->gid.val = profile->gid;
|
|
||||||
cred->fsgid.val = profile->gid;
|
|
||||||
cred->sgid.val = profile->gid;
|
|
||||||
cred->egid.val = profile->gid;
|
|
||||||
cred->securebits = 0;
|
|
||||||
|
|
||||||
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
|
|
||||||
sizeof(kernel_cap_t));
|
|
||||||
|
|
||||||
// setup capabilities
|
|
||||||
// we need CAP_DAC_READ_SEARCH becuase `/data/adb/ksud` is not accessible for non root process
|
|
||||||
// we add it here but don't add it to cap_inhertiable, it would be dropped automaticly after exec!
|
|
||||||
u64 cap_for_ksud =
|
|
||||||
profile->capabilities.effective | CAP_DAC_READ_SEARCH;
|
|
||||||
memcpy(&cred->cap_effective, &cap_for_ksud,
|
|
||||||
sizeof(cred->cap_effective));
|
|
||||||
memcpy(&cred->cap_permitted, &profile->capabilities.effective,
|
|
||||||
sizeof(cred->cap_permitted));
|
|
||||||
memcpy(&cred->cap_bset, &profile->capabilities.effective,
|
|
||||||
sizeof(cred->cap_bset));
|
|
||||||
|
|
||||||
setup_groups(profile, cred);
|
|
||||||
|
|
||||||
commit_creds(cred);
|
|
||||||
|
|
||||||
// Refer to kernel/seccomp.c: seccomp_set_mode_strict
|
|
||||||
// When disabling Seccomp, ensure that current->sighand->siglock is held during the operation.
|
|
||||||
spin_lock_irq(¤t->sighand->siglock);
|
|
||||||
disable_seccomp();
|
|
||||||
spin_unlock_irq(¤t->sighand->siglock);
|
|
||||||
|
|
||||||
setup_selinux(profile->selinux_domain);
|
|
||||||
#if __SULOG_GATE
|
|
||||||
ksu_sulog_report_su_grant(current_euid().val, NULL, "escape_to_root");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for_each_thread (p, t) {
|
|
||||||
ksu_set_task_tracepoint_flag(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
|
||||||
|
|
||||||
#include "ksud.h"
|
|
||||||
|
|
||||||
#ifndef DEVPTS_SUPER_MAGIC
|
|
||||||
#define DEVPTS_SUPER_MAGIC 0x1cd1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int __manual_su_handle_devpts(struct inode *inode)
|
|
||||||
{
|
|
||||||
if (!current->mm) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uid_t uid = current_uid().val;
|
|
||||||
if (uid % 100000 < 10000) {
|
|
||||||
// not untrusted_app, ignore it
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (likely(!ksu_is_allow_uid_for_current(uid)))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) || defined(KSU_OPTIONAL_SELINUX_INODE)
|
|
||||||
struct inode_security_struct *sec = selinux_inode(inode);
|
|
||||||
#else
|
|
||||||
struct inode_security_struct *sec =
|
|
||||||
(struct inode_security_struct *)inode->i_security;
|
|
||||||
#endif
|
|
||||||
if (ksu_file_sid && sec)
|
|
||||||
sec->sid = ksu_file_sid;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void disable_seccomp_for_task(struct task_struct *tsk)
|
|
||||||
{
|
|
||||||
assert_spin_locked(&tsk->sighand->siglock);
|
|
||||||
#ifdef CONFIG_SECCOMP
|
|
||||||
if (tsk->seccomp.mode == SECCOMP_MODE_DISABLED && !tsk->seccomp.filter)
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
clear_tsk_thread_flag(tsk, TIF_SECCOMP);
|
|
||||||
#ifdef CONFIG_SECCOMP
|
|
||||||
tsk->seccomp.mode = SECCOMP_MODE_DISABLED;
|
|
||||||
if (tsk->seccomp.filter) {
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
|
|
||||||
seccomp_filter_release(tsk);
|
|
||||||
#else
|
|
||||||
put_seccomp_filter(tsk);
|
|
||||||
tsk->seccomp.filter = NULL;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid)
|
|
||||||
{
|
|
||||||
struct cred *newcreds;
|
|
||||||
struct task_struct *target_task;
|
|
||||||
unsigned long flags;
|
|
||||||
struct task_struct *p = current;
|
|
||||||
struct task_struct *t;
|
|
||||||
|
|
||||||
pr_info("cmd_su: escape_to_root_for_cmd_su called for UID: %d, PID: %d\n", target_uid, target_pid);
|
|
||||||
|
|
||||||
// Find target task by PID
|
|
||||||
rcu_read_lock();
|
|
||||||
target_task = pid_task(find_vpid(target_pid), PIDTYPE_PID);
|
|
||||||
if (!target_task) {
|
|
||||||
rcu_read_unlock();
|
|
||||||
pr_err("cmd_su: target task not found for PID: %d\n", target_pid);
|
|
||||||
#if __SULOG_GATE
|
|
||||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "target_not_found");
|
|
||||||
#endif
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
get_task_struct(target_task);
|
|
||||||
rcu_read_unlock();
|
|
||||||
|
|
||||||
if (task_uid(target_task).val == 0) {
|
|
||||||
pr_warn("cmd_su: target task is already root, PID: %d\n", target_pid);
|
|
||||||
put_task_struct(target_task);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
newcreds = prepare_kernel_cred(target_task);
|
|
||||||
if (newcreds == NULL) {
|
|
||||||
pr_err("cmd_su: failed to allocate new cred for PID: %d\n", target_pid);
|
|
||||||
#if __SULOG_GATE
|
|
||||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "cred_alloc_failed");
|
|
||||||
#endif
|
|
||||||
put_task_struct(target_task);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct root_profile *profile = ksu_get_root_profile(target_uid);
|
|
||||||
|
|
||||||
newcreds->uid.val = profile->uid;
|
|
||||||
newcreds->suid.val = profile->uid;
|
|
||||||
newcreds->euid.val = profile->uid;
|
|
||||||
newcreds->fsuid.val = profile->uid;
|
|
||||||
|
|
||||||
newcreds->gid.val = profile->gid;
|
|
||||||
newcreds->fsgid.val = profile->gid;
|
|
||||||
newcreds->sgid.val = profile->gid;
|
|
||||||
newcreds->egid.val = profile->gid;
|
|
||||||
newcreds->securebits = 0;
|
|
||||||
|
|
||||||
u64 cap_for_cmd_su = profile->capabilities.effective | CAP_DAC_READ_SEARCH | CAP_SETUID | CAP_SETGID;
|
|
||||||
memcpy(&newcreds->cap_effective, &cap_for_cmd_su, sizeof(newcreds->cap_effective));
|
|
||||||
memcpy(&newcreds->cap_permitted, &profile->capabilities.effective, sizeof(newcreds->cap_permitted));
|
|
||||||
memcpy(&newcreds->cap_bset, &profile->capabilities.effective, sizeof(newcreds->cap_bset));
|
|
||||||
|
|
||||||
setup_groups(profile, newcreds);
|
|
||||||
task_lock(target_task);
|
|
||||||
|
|
||||||
const struct cred *old_creds = get_task_cred(target_task);
|
|
||||||
|
|
||||||
rcu_assign_pointer(target_task->real_cred, newcreds);
|
|
||||||
rcu_assign_pointer(target_task->cred, get_cred(newcreds));
|
|
||||||
task_unlock(target_task);
|
|
||||||
|
|
||||||
if (target_task->sighand) {
|
|
||||||
spin_lock_irqsave(&target_task->sighand->siglock, flags);
|
|
||||||
disable_seccomp_for_task(target_task);
|
|
||||||
spin_unlock_irqrestore(&target_task->sighand->siglock, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
setup_selinux(profile->selinux_domain);
|
|
||||||
put_cred(old_creds);
|
|
||||||
wake_up_process(target_task);
|
|
||||||
|
|
||||||
if (target_task->signal->tty) {
|
|
||||||
struct inode *inode = target_task->signal->tty->driver_data;
|
|
||||||
if (inode && inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC) {
|
|
||||||
__manual_su_handle_devpts(inode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
put_task_struct(target_task);
|
|
||||||
#if __SULOG_GATE
|
|
||||||
ksu_sulog_report_su_grant(target_uid, "cmd_su", "manual_escalation");
|
|
||||||
#endif
|
|
||||||
for_each_thread (p, t) {
|
|
||||||
ksu_set_task_tracepoint_flag(t);
|
|
||||||
}
|
|
||||||
pr_info("cmd_su: privilege escalation completed for UID: %d, PID: %d\n", target_uid, target_pid);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
#ifndef __KSU_H_APP_PROFILE
|
|
||||||
#define __KSU_H_APP_PROFILE
|
|
||||||
|
|
||||||
#include <linux/types.h>
|
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
struct cred;
|
|
||||||
|
|
||||||
#define KSU_APP_PROFILE_VER 2
|
|
||||||
#define KSU_MAX_PACKAGE_NAME 256
|
|
||||||
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
|
||||||
#define KSU_MAX_GROUPS 32
|
|
||||||
#define KSU_SELINUX_DOMAIN 64
|
|
||||||
|
|
||||||
struct root_profile {
|
|
||||||
int32_t uid;
|
|
||||||
int32_t gid;
|
|
||||||
|
|
||||||
int32_t groups_count;
|
|
||||||
int32_t groups[KSU_MAX_GROUPS];
|
|
||||||
|
|
||||||
// kernel_cap_t is u32[2] for capabilities v3
|
|
||||||
struct {
|
|
||||||
u64 effective;
|
|
||||||
u64 permitted;
|
|
||||||
u64 inheritable;
|
|
||||||
} capabilities;
|
|
||||||
|
|
||||||
char selinux_domain[KSU_SELINUX_DOMAIN];
|
|
||||||
|
|
||||||
int32_t namespaces;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct non_root_profile {
|
|
||||||
bool umount_modules;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct app_profile {
|
|
||||||
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
|
||||||
u32 version;
|
|
||||||
|
|
||||||
// this is usually the package of the app, but can be other value for special apps
|
|
||||||
char key[KSU_MAX_PACKAGE_NAME];
|
|
||||||
int32_t current_uid;
|
|
||||||
bool allow_su;
|
|
||||||
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
bool use_default;
|
|
||||||
char template_name[KSU_MAX_PACKAGE_NAME];
|
|
||||||
|
|
||||||
struct root_profile profile;
|
|
||||||
} rp_config;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
bool use_default;
|
|
||||||
|
|
||||||
struct non_root_profile profile;
|
|
||||||
} nrp_config;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Escalate current process to root with the appropriate profile
|
|
||||||
void escape_with_root_profile(void);
|
|
||||||
|
|
||||||
void escape_to_root_for_cmd_su(uid_t target_uid, pid_t target_pid);
|
|
||||||
|
|
||||||
void disable_seccomp(void);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -18,8 +18,10 @@
|
||||||
#define __PT_SP_REG sp
|
#define __PT_SP_REG sp
|
||||||
#define __PT_IP_REG pc
|
#define __PT_IP_REG pc
|
||||||
|
|
||||||
#define REBOOT_SYMBOL "__arm64_sys_reboot"
|
#define PRCTL_SYMBOL "__arm64_sys_prctl"
|
||||||
#define SYS_READ_SYMBOL "__arm64_sys_read"
|
#define SYS_READ_SYMBOL "__arm64_sys_read"
|
||||||
|
#define SYS_NEWFSTATAT_SYMBOL "__arm64_sys_newfstatat"
|
||||||
|
#define SYS_FACCESSAT_SYMBOL "__arm64_sys_faccessat"
|
||||||
#define SYS_EXECVE_SYMBOL "__arm64_sys_execve"
|
#define SYS_EXECVE_SYMBOL "__arm64_sys_execve"
|
||||||
|
|
||||||
#elif defined(__x86_64__)
|
#elif defined(__x86_64__)
|
||||||
|
|
@ -37,8 +39,10 @@
|
||||||
#define __PT_RC_REG ax
|
#define __PT_RC_REG ax
|
||||||
#define __PT_SP_REG sp
|
#define __PT_SP_REG sp
|
||||||
#define __PT_IP_REG ip
|
#define __PT_IP_REG ip
|
||||||
#define REBOOT_SYMBOL "__x64_sys_reboot"
|
#define PRCTL_SYMBOL "__x64_sys_prctl"
|
||||||
#define SYS_READ_SYMBOL "__x64_sys_read"
|
#define SYS_READ_SYMBOL "__x64_sys_read"
|
||||||
|
#define SYS_NEWFSTATAT_SYMBOL "__x64_sys_newfstatat"
|
||||||
|
#define SYS_FACCESSAT_SYMBOL "__x64_sys_faccessat"
|
||||||
#define SYS_EXECVE_SYMBOL "__x64_sys_execve"
|
#define SYS_EXECVE_SYMBOL "__x64_sys_execve"
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
|
||||||
1092
kernel/core_hook.c
Normal file
1092
kernel/core_hook.c
Normal file
File diff suppressed because it is too large
Load diff
10
kernel/core_hook.h
Normal file
10
kernel/core_hook.h
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#ifndef __KSU_H_KSU_CORE
|
||||||
|
#define __KSU_H_KSU_CORE
|
||||||
|
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include "apk_sign.h"
|
||||||
|
|
||||||
|
void __init ksu_core_init(void);
|
||||||
|
void ksu_core_exit(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include "dynamic_manager.h"
|
#include "dynamic_manager.h"
|
||||||
#include "klog.h" // IWYU pragma: keep
|
#include "klog.h" // IWYU pragma: keep
|
||||||
|
#include "kernel_compat.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
#define MAX_MANAGERS 2
|
#define MAX_MANAGERS 2
|
||||||
|
|
@ -232,23 +233,23 @@ static void do_save_dynamic_manager(struct work_struct *work)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fp = filp_open(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||||
if (IS_ERR(fp)) {
|
if (IS_ERR(fp)) {
|
||||||
pr_err("save_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
|
pr_err("save_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kernel_write(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
|
||||||
pr_err("save_dynamic_manager write magic failed.\n");
|
pr_err("save_dynamic_manager write magic failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kernel_write(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||||
pr_err("save_dynamic_manager write version failed.\n");
|
pr_err("save_dynamic_manager write version failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kernel_write(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
|
if (ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
|
||||||
pr_err("save_dynamic_manager write config failed.\n");
|
pr_err("save_dynamic_manager write config failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +271,7 @@ static void do_load_dynamic_manager(struct work_struct *work)
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
fp = filp_open(KERNEL_SU_DYNAMIC_MANAGER, O_RDONLY, 0);
|
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_RDONLY, 0);
|
||||||
if (IS_ERR(fp)) {
|
if (IS_ERR(fp)) {
|
||||||
if (PTR_ERR(fp) == -ENOENT) {
|
if (PTR_ERR(fp) == -ENOENT) {
|
||||||
pr_info("No saved dynamic manager config found\n");
|
pr_info("No saved dynamic manager config found\n");
|
||||||
|
|
@ -280,20 +281,20 @@ static void do_load_dynamic_manager(struct work_struct *work)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kernel_read(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
|
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
|
||||||
magic != DYNAMIC_MANAGER_FILE_MAGIC) {
|
magic != DYNAMIC_MANAGER_FILE_MAGIC) {
|
||||||
pr_err("dynamic manager file invalid magic: %x!\n", magic);
|
pr_err("dynamic manager file invalid magic: %x!\n", magic);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kernel_read(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
|
||||||
pr_err("dynamic manager read version failed\n");
|
pr_err("dynamic manager read version failed\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
pr_info("dynamic manager file version: %d\n", version);
|
pr_info("dynamic manager file version: %d\n", version);
|
||||||
|
|
||||||
ret = kernel_read(fp, &loaded_config, sizeof(loaded_config), &off);
|
ret = ksu_kernel_read_compat(fp, &loaded_config, sizeof(loaded_config), &off);
|
||||||
if (ret <= 0) {
|
if (ret <= 0) {
|
||||||
pr_info("load_dynamic_manager read err: %zd\n", ret);
|
pr_info("load_dynamic_manager read err: %zd\n", ret);
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
@ -347,14 +348,14 @@ static void do_clear_dynamic_manager(struct work_struct *work)
|
||||||
|
|
||||||
memset(zero_buffer, 0, sizeof(zero_buffer));
|
memset(zero_buffer, 0, sizeof(zero_buffer));
|
||||||
|
|
||||||
fp = filp_open(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||||
if (IS_ERR(fp)) {
|
if (IS_ERR(fp)) {
|
||||||
pr_err("clear_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
|
pr_err("clear_dynamic_manager create file failed: %ld\n", PTR_ERR(fp));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write null bytes to overwrite the file content
|
// Write null bytes to overwrite the file content
|
||||||
if (kernel_write(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) {
|
if (ksu_kernel_write_compat(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) {
|
||||||
pr_err("clear_dynamic_manager write null bytes failed.\n");
|
pr_err("clear_dynamic_manager write null bytes failed.\n");
|
||||||
} else {
|
} else {
|
||||||
pr_info("Dynamic sign config file cleared successfully\n");
|
pr_info("Dynamic sign config file cleared successfully\n");
|
||||||
|
|
|
||||||
173
kernel/feature.c
173
kernel/feature.c
|
|
@ -1,173 +0,0 @@
|
||||||
#include "feature.h"
|
|
||||||
#include "klog.h" // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include <linux/mutex.h>
|
|
||||||
|
|
||||||
static const struct ksu_feature_handler *feature_handlers[KSU_FEATURE_MAX];
|
|
||||||
|
|
||||||
static DEFINE_MUTEX(feature_mutex);
|
|
||||||
|
|
||||||
int ksu_register_feature_handler(const struct ksu_feature_handler *handler)
|
|
||||||
{
|
|
||||||
if (!handler) {
|
|
||||||
pr_err("feature: register handler is NULL\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handler->feature_id >= KSU_FEATURE_MAX) {
|
|
||||||
pr_err("feature: invalid feature_id %u\n", handler->feature_id);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handler->get_handler && !handler->set_handler) {
|
|
||||||
pr_err("feature: no handler provided for feature %u\n", handler->feature_id);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_lock(&feature_mutex);
|
|
||||||
|
|
||||||
if (feature_handlers[handler->feature_id]) {
|
|
||||||
pr_warn("feature: handler for %u already registered, overwriting\n",
|
|
||||||
handler->feature_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
feature_handlers[handler->feature_id] = handler;
|
|
||||||
|
|
||||||
pr_info("feature: registered handler for %s (id=%u)\n",
|
|
||||||
handler->name ? handler->name : "unknown", handler->feature_id);
|
|
||||||
|
|
||||||
mutex_unlock(&feature_mutex);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_unregister_feature_handler(u32 feature_id)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
if (feature_id >= KSU_FEATURE_MAX) {
|
|
||||||
pr_err("feature: invalid feature_id %u\n", feature_id);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_lock(&feature_mutex);
|
|
||||||
|
|
||||||
if (!feature_handlers[feature_id]) {
|
|
||||||
pr_warn("feature: no handler registered for %u\n", feature_id);
|
|
||||||
ret = -ENOENT;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
feature_handlers[feature_id] = NULL;
|
|
||||||
|
|
||||||
pr_info("feature: unregistered handler for id=%u\n", feature_id);
|
|
||||||
|
|
||||||
out:
|
|
||||||
mutex_unlock(&feature_mutex);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_get_feature(u32 feature_id, u64 *value, bool *supported)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
const struct ksu_feature_handler *handler;
|
|
||||||
|
|
||||||
if (feature_id >= KSU_FEATURE_MAX) {
|
|
||||||
pr_err("feature: invalid feature_id %u\n", feature_id);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!value || !supported) {
|
|
||||||
pr_err("feature: invalid parameters\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_lock(&feature_mutex);
|
|
||||||
|
|
||||||
handler = feature_handlers[feature_id];
|
|
||||||
|
|
||||||
if (!handler) {
|
|
||||||
*supported = false;
|
|
||||||
*value = 0;
|
|
||||||
pr_debug("feature: feature %u not supported\n", feature_id);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
*supported = true;
|
|
||||||
|
|
||||||
if (!handler->get_handler) {
|
|
||||||
pr_warn("feature: no get_handler for feature %u\n", feature_id);
|
|
||||||
ret = -EOPNOTSUPP;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = handler->get_handler(value);
|
|
||||||
if (ret) {
|
|
||||||
pr_err("feature: get_handler for %u failed: %d\n", feature_id, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
mutex_unlock(&feature_mutex);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_set_feature(u32 feature_id, u64 value)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
const struct ksu_feature_handler *handler;
|
|
||||||
|
|
||||||
if (feature_id >= KSU_FEATURE_MAX) {
|
|
||||||
pr_err("feature: invalid feature_id %u\n", feature_id);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_lock(&feature_mutex);
|
|
||||||
|
|
||||||
handler = feature_handlers[feature_id];
|
|
||||||
|
|
||||||
if (!handler) {
|
|
||||||
pr_err("feature: feature %u not registered\n", feature_id);
|
|
||||||
ret = -EOPNOTSUPP;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handler->set_handler) {
|
|
||||||
pr_warn("feature: no set_handler for feature %u\n", feature_id);
|
|
||||||
ret = -EOPNOTSUPP;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = handler->set_handler(value);
|
|
||||||
if (ret) {
|
|
||||||
pr_err("feature: set_handler for %u failed: %d\n", feature_id, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
mutex_unlock(&feature_mutex);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_feature_init(void)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < KSU_FEATURE_MAX; i++) {
|
|
||||||
feature_handlers[i] = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
pr_info("feature: feature management initialized\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_feature_exit(void)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
mutex_lock(&feature_mutex);
|
|
||||||
|
|
||||||
for (i = 0; i < KSU_FEATURE_MAX; i++) {
|
|
||||||
feature_handlers[i] = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_unlock(&feature_mutex);
|
|
||||||
|
|
||||||
pr_info("feature: feature management cleaned up\n");
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
#ifndef __KSU_H_FEATURE
|
|
||||||
#define __KSU_H_FEATURE
|
|
||||||
|
|
||||||
#include <linux/types.h>
|
|
||||||
|
|
||||||
enum ksu_feature_id {
|
|
||||||
KSU_FEATURE_SU_COMPAT = 0,
|
|
||||||
KSU_FEATURE_KERNEL_UMOUNT = 1,
|
|
||||||
KSU_FEATURE_ENHANCED_SECURITY = 2,
|
|
||||||
KSU_FEATURE_SULOG = 3,
|
|
||||||
|
|
||||||
KSU_FEATURE_MAX
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef int (*ksu_feature_get_t)(u64 *value);
|
|
||||||
typedef int (*ksu_feature_set_t)(u64 value);
|
|
||||||
|
|
||||||
struct ksu_feature_handler {
|
|
||||||
u32 feature_id;
|
|
||||||
const char *name;
|
|
||||||
ksu_feature_get_t get_handler;
|
|
||||||
ksu_feature_set_t set_handler;
|
|
||||||
};
|
|
||||||
|
|
||||||
int ksu_register_feature_handler(const struct ksu_feature_handler *handler);
|
|
||||||
|
|
||||||
int ksu_unregister_feature_handler(u32 feature_id);
|
|
||||||
|
|
||||||
int ksu_get_feature(u32 feature_id, u64 *value, bool *supported);
|
|
||||||
|
|
||||||
int ksu_set_feature(u32 feature_id, u64 value);
|
|
||||||
|
|
||||||
void ksu_feature_init(void);
|
|
||||||
|
|
||||||
void ksu_feature_exit(void);
|
|
||||||
|
|
||||||
#endif // __KSU_H_FEATURE
|
|
||||||
|
|
@ -1,341 +0,0 @@
|
||||||
#include <linux/export.h>
|
|
||||||
#include <linux/anon_inodes.h>
|
|
||||||
#include <linux/capability.h>
|
|
||||||
#include <linux/cred.h>
|
|
||||||
#include <linux/err.h>
|
|
||||||
#include <linux/file.h>
|
|
||||||
#include <linux/fs.h>
|
|
||||||
#include <linux/seq_file.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/uaccess.h>
|
|
||||||
#include <linux/version.h>
|
|
||||||
|
|
||||||
#include "klog.h" // IWYU pragma: keep
|
|
||||||
#include "selinux/selinux.h"
|
|
||||||
|
|
||||||
#include "file_wrapper.h"
|
|
||||||
|
|
||||||
static loff_t ksu_wrapper_llseek(struct file *fp, loff_t off, int flags) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->llseek(data->orig, off, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ksu_wrapper_read(struct file *fp, char __user *ptr, size_t sz, loff_t *off) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->read(orig, ptr, sz, off);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ksu_wrapper_write(struct file *fp, const char __user *ptr, size_t sz, loff_t *off) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->write(orig, ptr, sz, off);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ksu_wrapper_read_iter(struct kiocb *iocb, struct iov_iter *iovi) {
|
|
||||||
struct ksu_file_wrapper* data = iocb->ki_filp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
iocb->ki_filp = orig;
|
|
||||||
return orig->f_op->read_iter(iocb, iovi);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ksu_wrapper_write_iter(struct kiocb *iocb, struct iov_iter *iovi) {
|
|
||||||
struct ksu_file_wrapper* data = iocb->ki_filp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
iocb->ki_filp = orig;
|
|
||||||
return orig->f_op->write_iter(iocb, iovi);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
|
|
||||||
static int ksu_wrapper_iopoll(struct kiocb *kiocb, struct io_comp_batch* icb, unsigned int v) {
|
|
||||||
struct ksu_file_wrapper* data = kiocb->ki_filp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
kiocb->ki_filp = orig;
|
|
||||||
return orig->f_op->iopoll(kiocb, icb, v);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static int ksu_wrapper_iopoll(struct kiocb *kiocb, bool spin) {
|
|
||||||
struct ksu_file_wrapper* data = kiocb->ki_filp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
kiocb->ki_filp = orig;
|
|
||||||
return orig->f_op->iopoll(kiocb, spin);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
|
||||||
static int ksu_wrapper_iterate (struct file *fp, struct dir_context *dc) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->iterate(orig, dc);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int ksu_wrapper_iterate_shared(struct file *fp, struct dir_context *dc) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->iterate_shared(orig, dc);
|
|
||||||
}
|
|
||||||
|
|
||||||
static __poll_t ksu_wrapper_poll(struct file *fp, struct poll_table_struct *pts) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->poll(orig, pts);
|
|
||||||
}
|
|
||||||
|
|
||||||
static long ksu_wrapper_unlocked_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->unlocked_ioctl(orig, cmd, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static long ksu_wrapper_compat_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->compat_ioctl(orig, cmd, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ksu_wrapper_mmap(struct file *fp, struct vm_area_struct * vma) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->mmap(orig, vma);
|
|
||||||
}
|
|
||||||
|
|
||||||
// static unsigned long mmap_supported_flags {}
|
|
||||||
|
|
||||||
static int ksu_wrapper_open(struct inode *ino, struct file *fp) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
struct inode *orig_ino = file_inode(orig);
|
|
||||||
return orig->f_op->open(orig_ino, orig);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ksu_wrapper_flush(struct file *fp, fl_owner_t id) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->flush(orig, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int ksu_wrapper_fsync(struct file *fp, loff_t off1, loff_t off2, int datasync) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->fsync(orig, off1, off2, datasync);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ksu_wrapper_fasync(int arg, struct file *fp, int arg2) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->fasync(arg, orig, arg2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ksu_wrapper_lock(struct file *fp, int arg1, struct file_lock *fl) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
return orig->f_op->lock(orig, arg1, fl);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
|
||||||
static ssize_t ksu_wrapper_sendpage(struct file *fp, struct page *pg, int arg1, size_t sz, loff_t *off, int arg2) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->sendpage) {
|
|
||||||
return orig->f_op->sendpage(orig, pg, arg1, sz, off, arg2);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static unsigned long ksu_wrapper_get_unmapped_area(struct file *fp, unsigned long arg1, unsigned long arg2, unsigned long arg3, unsigned long arg4) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->get_unmapped_area) {
|
|
||||||
return orig->f_op->get_unmapped_area(orig, arg1, arg2, arg3, arg4);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// static int ksu_wrapper_check_flags(int arg) {}
|
|
||||||
|
|
||||||
static int ksu_wrapper_flock(struct file *fp, int arg1, struct file_lock *fl) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->flock) {
|
|
||||||
return orig->f_op->flock(orig, arg1, fl);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ksu_wrapper_splice_write(struct pipe_inode_info * pii, struct file *fp, loff_t *off, size_t sz, unsigned int arg1) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->splice_write) {
|
|
||||||
return orig->f_op->splice_write(pii, orig, off, sz, arg1);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ksu_wrapper_splice_read(struct file *fp, loff_t *off, struct pipe_inode_info *pii, size_t sz, unsigned int arg1) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->splice_read) {
|
|
||||||
return orig->f_op->splice_read(orig, off, pii, sz, arg1);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
|
|
||||||
void ksu_wrapper_splice_eof(struct file *fp) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->splice_eof) {
|
|
||||||
return orig->f_op->splice_eof(orig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
|
||||||
static int ksu_wrapper_setlease(struct file *fp, int arg1, struct file_lease **fl, void **p) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->setlease) {
|
|
||||||
return orig->f_op->setlease(orig, arg1, fl, p);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
|
|
||||||
static int ksu_wrapper_setlease(struct file *fp, int arg1, struct file_lock **fl, void **p) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->setlease) {
|
|
||||||
return orig->f_op->setlease(orig, arg1, fl, p);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static int ksu_wrapper_setlease(struct file *fp, long arg1, struct file_lock **fl, void **p) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->setlease) {
|
|
||||||
return orig->f_op->setlease(orig, arg1, fl, p);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static long ksu_wrapper_fallocate(struct file *fp, int mode, loff_t offset, loff_t len) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->fallocate) {
|
|
||||||
return orig->f_op->fallocate(orig, mode, offset, len);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ksu_wrapper_show_fdinfo(struct seq_file *m, struct file *f) {
|
|
||||||
struct ksu_file_wrapper* data = f->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->show_fdinfo) {
|
|
||||||
orig->f_op->show_fdinfo(m, orig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t ksu_wrapper_copy_file_range(struct file *f1, loff_t off1, struct file *f2,
|
|
||||||
loff_t off2, size_t sz, unsigned int flags) {
|
|
||||||
// TODO: determine which file to use
|
|
||||||
struct ksu_file_wrapper* data = f1->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->copy_file_range) {
|
|
||||||
return orig->f_op->copy_file_range(orig, off1, f2, off2, sz, flags);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static loff_t ksu_wrapper_remap_file_range(struct file *file_in, loff_t pos_in,
|
|
||||||
struct file *file_out, loff_t pos_out,
|
|
||||||
loff_t len, unsigned int remap_flags) {
|
|
||||||
// TODO: determine which file to use
|
|
||||||
struct ksu_file_wrapper* data = file_in->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->remap_file_range) {
|
|
||||||
return orig->f_op->remap_file_range(orig, pos_in, file_out, pos_out, len, remap_flags);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ksu_wrapper_fadvise(struct file *fp, loff_t off1, loff_t off2, int flags) {
|
|
||||||
struct ksu_file_wrapper* data = fp->private_data;
|
|
||||||
struct file* orig = data->orig;
|
|
||||||
if (orig->f_op->fadvise) {
|
|
||||||
return orig->f_op->fadvise(orig, off1, off2, flags);
|
|
||||||
}
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ksu_wrapper_release(struct inode *inode, struct file *filp) {
|
|
||||||
ksu_delete_file_wrapper(filp->private_data);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ksu_file_wrapper* ksu_create_file_wrapper(struct file* fp) {
|
|
||||||
struct ksu_file_wrapper* p = kcalloc(sizeof(struct ksu_file_wrapper), 1, GFP_KERNEL);
|
|
||||||
if (!p) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_file(fp);
|
|
||||||
|
|
||||||
p->orig = fp;
|
|
||||||
p->ops.owner = THIS_MODULE;
|
|
||||||
p->ops.llseek = fp->f_op->llseek ? ksu_wrapper_llseek : NULL;
|
|
||||||
p->ops.read = fp->f_op->read ? ksu_wrapper_read : NULL;
|
|
||||||
p->ops.write = fp->f_op->write ? ksu_wrapper_write : NULL;
|
|
||||||
p->ops.read_iter = fp->f_op->read_iter ? ksu_wrapper_read_iter : NULL;
|
|
||||||
p->ops.write_iter = fp->f_op->write_iter ? ksu_wrapper_write_iter : NULL;
|
|
||||||
p->ops.iopoll = fp->f_op->iopoll ? ksu_wrapper_iopoll : NULL;
|
|
||||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
|
||||||
p->ops.iterate = fp->f_op->iterate ? ksu_wrapper_iterate : NULL;
|
|
||||||
#endif
|
|
||||||
p->ops.iterate_shared = fp->f_op->iterate_shared ? ksu_wrapper_iterate_shared : NULL;
|
|
||||||
p->ops.poll = fp->f_op->poll ? ksu_wrapper_poll : NULL;
|
|
||||||
p->ops.unlocked_ioctl = fp->f_op->unlocked_ioctl ? ksu_wrapper_unlocked_ioctl : NULL;
|
|
||||||
p->ops.compat_ioctl = fp->f_op->compat_ioctl ? ksu_wrapper_compat_ioctl : NULL;
|
|
||||||
p->ops.mmap = fp->f_op->mmap ? ksu_wrapper_mmap : NULL;
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
|
||||||
p->ops.fop_flags = fp->f_op->fop_flags;
|
|
||||||
#else
|
|
||||||
p->ops.mmap_supported_flags = fp->f_op->mmap_supported_flags;
|
|
||||||
#endif
|
|
||||||
p->ops.open = fp->f_op->open ? ksu_wrapper_open : NULL;
|
|
||||||
p->ops.flush = fp->f_op->flush ? ksu_wrapper_flush : NULL;
|
|
||||||
p->ops.release = ksu_wrapper_release;
|
|
||||||
p->ops.fsync = fp->f_op->fsync ? ksu_wrapper_fsync : NULL;
|
|
||||||
p->ops.fasync = fp->f_op->fasync ? ksu_wrapper_fasync : NULL;
|
|
||||||
p->ops.lock = fp->f_op->lock ? ksu_wrapper_lock : NULL;
|
|
||||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
|
||||||
p->ops.sendpage = fp->f_op->sendpage ? ksu_wrapper_sendpage : NULL;
|
|
||||||
#endif
|
|
||||||
p->ops.get_unmapped_area = fp->f_op->get_unmapped_area ? ksu_wrapper_get_unmapped_area : NULL;
|
|
||||||
p->ops.check_flags = fp->f_op->check_flags;
|
|
||||||
p->ops.flock = fp->f_op->flock ? ksu_wrapper_flock : NULL;
|
|
||||||
p->ops.splice_write = fp->f_op->splice_write ? ksu_wrapper_splice_write : NULL;
|
|
||||||
p->ops.splice_read = fp->f_op->splice_read ? ksu_wrapper_splice_read : NULL;
|
|
||||||
p->ops.setlease = fp->f_op->setlease ? ksu_wrapper_setlease : NULL;
|
|
||||||
p->ops.fallocate = fp->f_op->fallocate ? ksu_wrapper_fallocate : NULL;
|
|
||||||
p->ops.show_fdinfo = fp->f_op->show_fdinfo ? ksu_wrapper_show_fdinfo : NULL;
|
|
||||||
p->ops.copy_file_range = fp->f_op->copy_file_range ? ksu_wrapper_copy_file_range : NULL;
|
|
||||||
p->ops.remap_file_range = fp->f_op->remap_file_range ? ksu_wrapper_remap_file_range : NULL;
|
|
||||||
p->ops.fadvise = fp->f_op->fadvise ? ksu_wrapper_fadvise : NULL;
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
|
|
||||||
p->ops.splice_eof = fp->f_op->splice_eof ? ksu_wrapper_splice_eof : NULL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_delete_file_wrapper(struct ksu_file_wrapper* data) {
|
|
||||||
fput((struct file*) data->orig);
|
|
||||||
kfree(data);
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#ifndef KSU_FILE_WRAPPER_H
|
|
||||||
#define KSU_FILE_WRAPPER_H
|
|
||||||
|
|
||||||
#include <linux/file.h>
|
|
||||||
#include <linux/fs.h>
|
|
||||||
|
|
||||||
struct ksu_file_wrapper {
|
|
||||||
struct file* orig;
|
|
||||||
struct file_operations ops;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_file_wrapper* ksu_create_file_wrapper(struct file* fp);
|
|
||||||
void ksu_delete_file_wrapper(struct ksu_file_wrapper* data);
|
|
||||||
#endif // KSU_FILE_WRAPPER_H
|
|
||||||
28
kernel/include/ksu_hook.h
Normal file
28
kernel/include/ksu_hook.h
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef __KSU_H_KSHOOK
|
||||||
|
#define __KSU_H_KSHOOK
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
// For sucompat
|
||||||
|
|
||||||
|
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||||
|
int *flags);
|
||||||
|
|
||||||
|
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
||||||
|
|
||||||
|
// For ksud
|
||||||
|
|
||||||
|
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||||
|
size_t *count_ptr, loff_t **pos);
|
||||||
|
|
||||||
|
// For ksud and sucompat
|
||||||
|
|
||||||
|
int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
|
||||||
|
void *envp, int *flags);
|
||||||
|
|
||||||
|
// For volume button
|
||||||
|
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||||
|
int *value);
|
||||||
|
|
||||||
|
#endif
|
||||||
94
kernel/kernel_compat.c
Normal file
94
kernel/kernel_compat.c
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
#include <linux/version.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/nsproxy.h>
|
||||||
|
#include <linux/sched/task.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
#include "klog.h" // IWYU pragma: keep
|
||||||
|
#include "kernel_compat.h"
|
||||||
|
|
||||||
|
extern struct task_struct init_task;
|
||||||
|
|
||||||
|
// mnt_ns context switch for environment that android_init->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns, such as WSA
|
||||||
|
struct ksu_ns_fs_saved {
|
||||||
|
struct nsproxy *ns;
|
||||||
|
struct fs_struct *fs;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void ksu_save_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
|
||||||
|
{
|
||||||
|
ns_fs_saved->ns = current->nsproxy;
|
||||||
|
ns_fs_saved->fs = current->fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ksu_load_ns_fs(struct ksu_ns_fs_saved *ns_fs_saved)
|
||||||
|
{
|
||||||
|
current->nsproxy = ns_fs_saved->ns;
|
||||||
|
current->fs = ns_fs_saved->fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool android_context_saved_checked = false;
|
||||||
|
static bool android_context_saved_enabled = false;
|
||||||
|
static struct ksu_ns_fs_saved android_context_saved;
|
||||||
|
|
||||||
|
void ksu_android_ns_fs_check()
|
||||||
|
{
|
||||||
|
if (android_context_saved_checked)
|
||||||
|
return;
|
||||||
|
android_context_saved_checked = true;
|
||||||
|
task_lock(current);
|
||||||
|
if (current->nsproxy && current->fs &&
|
||||||
|
current->nsproxy->mnt_ns != init_task.nsproxy->mnt_ns) {
|
||||||
|
android_context_saved_enabled = true;
|
||||||
|
#ifdef CONFIG_KSU_DEBUG
|
||||||
|
pr_info("android context saved enabled due to init mnt_ns(%p) != android mnt_ns(%p)\n",
|
||||||
|
current->nsproxy->mnt_ns, init_task.nsproxy->mnt_ns);
|
||||||
|
#endif
|
||||||
|
ksu_save_ns_fs(&android_context_saved);
|
||||||
|
} else {
|
||||||
|
pr_info("android context saved disabled\n");
|
||||||
|
}
|
||||||
|
task_unlock(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct file *ksu_filp_open_compat(const char *filename, int flags, umode_t mode)
|
||||||
|
{
|
||||||
|
// switch mnt_ns even if current is not wq_worker, to ensure what we open is the correct file in android mnt_ns, rather than user created mnt_ns
|
||||||
|
struct ksu_ns_fs_saved saved;
|
||||||
|
if (android_context_saved_enabled) {
|
||||||
|
#ifdef CONFIG_KSU_DEBUG
|
||||||
|
pr_info("start switch current nsproxy and fs to android context\n");
|
||||||
|
#endif
|
||||||
|
task_lock(current);
|
||||||
|
ksu_save_ns_fs(&saved);
|
||||||
|
ksu_load_ns_fs(&android_context_saved);
|
||||||
|
task_unlock(current);
|
||||||
|
}
|
||||||
|
struct file *fp = filp_open(filename, flags, mode);
|
||||||
|
if (android_context_saved_enabled) {
|
||||||
|
task_lock(current);
|
||||||
|
ksu_load_ns_fs(&saved);
|
||||||
|
task_unlock(current);
|
||||||
|
#ifdef CONFIG_KSU_DEBUG
|
||||||
|
pr_info("switch current nsproxy and fs back to saved successfully\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
|
||||||
|
loff_t *pos)
|
||||||
|
{
|
||||||
|
return kernel_read(p, buf, count, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count,
|
||||||
|
loff_t *pos)
|
||||||
|
{
|
||||||
|
return kernel_write(p, buf, count, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
|
||||||
|
long count)
|
||||||
|
{
|
||||||
|
return strncpy_from_user_nofault(dst, unsafe_addr, count);
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,63 @@
|
||||||
|
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <linux/version.h>
|
#include <linux/version.h>
|
||||||
|
#include "ss/policydb.h"
|
||||||
|
#include "linux/key.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* list_count_nodes - count the number of nodes in a list
|
||||||
|
* @head: the head of the list
|
||||||
|
*
|
||||||
|
* This function iterates over the list starting from @head and counts
|
||||||
|
* the number of nodes in the list. It does not modify the list.
|
||||||
|
*
|
||||||
|
* Context: Any context. The function is safe to call in any context,
|
||||||
|
* including interrupt context, as it does not sleep or allocate
|
||||||
|
* memory.
|
||||||
|
*
|
||||||
|
* Return: the number of nodes in the list (excluding the head)
|
||||||
|
*/
|
||||||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||||
|
static inline __maybe_unused size_t list_count_nodes(const struct list_head *head)
|
||||||
|
{
|
||||||
|
const struct list_head *pos;
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
if (!head)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
list_for_each(pos, head)
|
||||||
|
count++;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adapt to Huawei HISI kernel without affecting other kernels ,
|
||||||
|
* Huawei Hisi Kernel EBITMAP Enable or Disable Flag ,
|
||||||
|
* From ss/ebitmap.h
|
||||||
|
*/
|
||||||
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) && \
|
||||||
|
(LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) || \
|
||||||
|
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)) && \
|
||||||
|
(LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0))
|
||||||
|
#ifdef HISI_SELINUX_EBITMAP_RO
|
||||||
|
#define CONFIG_IS_HW_HISI
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern long ksu_strncpy_from_user_nofault(char *dst,
|
||||||
|
const void __user *unsafe_addr,
|
||||||
|
long count);
|
||||||
|
|
||||||
|
extern void ksu_android_ns_fs_check();
|
||||||
|
extern struct file *ksu_filp_open_compat(const char *filename, int flags,
|
||||||
|
umode_t mode);
|
||||||
|
extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
|
||||||
|
loff_t *pos);
|
||||||
|
extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf,
|
||||||
|
size_t count, loff_t *pos);
|
||||||
/*
|
/*
|
||||||
* ksu_copy_from_user_retry
|
* ksu_copy_from_user_retry
|
||||||
* try nofault copy first, if it fails, try with plain
|
* try nofault copy first, if it fails, try with plain
|
||||||
|
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
#include <linux/sched.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/task_work.h>
|
|
||||||
#include <linux/cred.h>
|
|
||||||
#include <linux/fs.h>
|
|
||||||
#include <linux/mount.h>
|
|
||||||
#include <linux/namei.h>
|
|
||||||
#include <linux/nsproxy.h>
|
|
||||||
#include <linux/path.h>
|
|
||||||
#include <linux/printk.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
|
|
||||||
#include "kernel_umount.h"
|
|
||||||
#include "klog.h" // IWYU pragma: keep
|
|
||||||
#include "allowlist.h"
|
|
||||||
#include "selinux/selinux.h"
|
|
||||||
#include "feature.h"
|
|
||||||
#include "ksud.h"
|
|
||||||
|
|
||||||
#include "umount_manager.h"
|
|
||||||
#include "sulog.h"
|
|
||||||
|
|
||||||
static bool ksu_kernel_umount_enabled = true;
|
|
||||||
|
|
||||||
static int kernel_umount_feature_get(u64 *value)
|
|
||||||
{
|
|
||||||
*value = ksu_kernel_umount_enabled ? 1 : 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int kernel_umount_feature_set(u64 value)
|
|
||||||
{
|
|
||||||
bool enable = value != 0;
|
|
||||||
ksu_kernel_umount_enabled = enable;
|
|
||||||
pr_info("kernel_umount: set to %d\n", enable);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct ksu_feature_handler kernel_umount_handler = {
|
|
||||||
.feature_id = KSU_FEATURE_KERNEL_UMOUNT,
|
|
||||||
.name = "kernel_umount",
|
|
||||||
.get_handler = kernel_umount_feature_get,
|
|
||||||
.set_handler = kernel_umount_feature_set,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern int path_umount(struct path *path, int flags);
|
|
||||||
|
|
||||||
static void ksu_umount_mnt(struct path *path, int flags)
|
|
||||||
{
|
|
||||||
int err = path_umount(path, flags);
|
|
||||||
if (err) {
|
|
||||||
pr_info("umount %s failed: %d\n", path->dentry->d_iname, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void try_umount(const char *mnt, int flags)
|
|
||||||
{
|
|
||||||
struct path path;
|
|
||||||
int err = kern_path(mnt, 0, &path);
|
|
||||||
if (err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.dentry != path.mnt->mnt_root) {
|
|
||||||
// it is not root mountpoint, maybe umounted by others already.
|
|
||||||
path_put(&path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ksu_umount_mnt(&path, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct umount_tw {
|
|
||||||
struct callback_head cb;
|
|
||||||
const struct cred *old_cred;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void umount_tw_func(struct callback_head *cb)
|
|
||||||
{
|
|
||||||
struct umount_tw *tw = container_of(cb, struct umount_tw, cb);
|
|
||||||
const struct cred *saved = NULL;
|
|
||||||
if (tw->old_cred) {
|
|
||||||
saved = override_creds(tw->old_cred);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct mount_entry *entry;
|
|
||||||
down_read(&mount_list_lock);
|
|
||||||
list_for_each_entry(entry, &mount_list, list) {
|
|
||||||
pr_info("%s: unmounting: %s flags 0x%x\n", __func__, entry->umountable, entry->flags);
|
|
||||||
try_umount(entry->umountable, entry->flags);
|
|
||||||
}
|
|
||||||
up_read(&mount_list_lock);
|
|
||||||
|
|
||||||
ksu_umount_manager_execute_all(tw->old_cred);
|
|
||||||
|
|
||||||
if (saved)
|
|
||||||
revert_creds(saved);
|
|
||||||
|
|
||||||
if (tw->old_cred)
|
|
||||||
put_cred(tw->old_cred);
|
|
||||||
|
|
||||||
kfree(tw);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_handle_umount(uid_t old_uid, uid_t new_uid)
|
|
||||||
{
|
|
||||||
struct umount_tw *tw;
|
|
||||||
|
|
||||||
// this hook is used for umounting overlayfs for some uid, if there isn't any module mounted, just ignore it!
|
|
||||||
if (!ksu_module_mounted) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ksu_kernel_umount_enabled) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: isolated process which directly forks from zygote is not handled
|
|
||||||
if (!is_appuid(new_uid)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ksu_uid_should_umount(new_uid)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check old process's selinux context, if it is not zygote, ignore it!
|
|
||||||
// because some su apps may setuid to untrusted_app but they are in global mount namespace
|
|
||||||
// when we umount for such process, that is a disaster!
|
|
||||||
bool is_zygote_child = is_zygote(get_current_cred());
|
|
||||||
if (!is_zygote_child) {
|
|
||||||
pr_info("handle umount ignore non zygote child: %d\n", current->pid);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#if __SULOG_GATE
|
|
||||||
ksu_sulog_report_syscall(new_uid, NULL, "setuid", NULL);
|
|
||||||
#endif
|
|
||||||
// umount the target mnt
|
|
||||||
pr_info("handle umount for uid: %d, pid: %d\n", new_uid, current->pid);
|
|
||||||
|
|
||||||
tw = kzalloc(sizeof(*tw), GFP_ATOMIC);
|
|
||||||
if (!tw)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
tw->old_cred = get_current_cred();
|
|
||||||
tw->cb.func = umount_tw_func;
|
|
||||||
|
|
||||||
int err = task_work_add(current, &tw->cb, TWA_RESUME);
|
|
||||||
if (err) {
|
|
||||||
if (tw->old_cred) {
|
|
||||||
put_cred(tw->old_cred);
|
|
||||||
}
|
|
||||||
kfree(tw);
|
|
||||||
pr_warn("unmount add task_work failed\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_kernel_umount_init(void)
|
|
||||||
{
|
|
||||||
int rc = 0;
|
|
||||||
rc = ksu_umount_manager_init();
|
|
||||||
if (rc) {
|
|
||||||
pr_err("Failed to initialize umount manager: %d\n", rc);
|
|
||||||
}
|
|
||||||
if (ksu_register_feature_handler(&kernel_umount_handler)) {
|
|
||||||
pr_err("Failed to register kernel_umount feature handler\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_kernel_umount_exit(void)
|
|
||||||
{
|
|
||||||
ksu_unregister_feature_handler(KSU_FEATURE_KERNEL_UMOUNT);
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
#ifndef __KSU_H_KERNEL_UMOUNT
|
|
||||||
#define __KSU_H_KERNEL_UMOUNT
|
|
||||||
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/list.h>
|
|
||||||
#include <linux/rwsem.h>
|
|
||||||
|
|
||||||
void ksu_kernel_umount_init(void);
|
|
||||||
void ksu_kernel_umount_exit(void);
|
|
||||||
|
|
||||||
void try_umount(const char *mnt, int flags);
|
|
||||||
|
|
||||||
// Handler function to be called from setresuid hook
|
|
||||||
int ksu_handle_umount(uid_t old_uid, uid_t new_uid);
|
|
||||||
|
|
||||||
// for the umount list
|
|
||||||
struct mount_entry {
|
|
||||||
char *umountable;
|
|
||||||
unsigned int flags;
|
|
||||||
struct list_head list;
|
|
||||||
};
|
|
||||||
extern struct list_head mount_list;
|
|
||||||
extern struct rw_semaphore mount_list_lock;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
static int sukisu_is_su_allow_uid(uid_t uid)
|
static int sukisu_is_su_allow_uid(uid_t uid)
|
||||||
{
|
{
|
||||||
return ksu_is_allow_uid_for_current(uid) ? 1 : 0;
|
return ksu_is_allow_uid(uid) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sukisu_get_ap_mod_exclude(uid_t uid)
|
static int sukisu_get_ap_mod_exclude(uid_t uid)
|
||||||
|
|
|
||||||
279
kernel/kpm/kpm.c
279
kernel/kpm/kpm.c
|
|
@ -9,10 +9,13 @@
|
||||||
* 并参照KernelPatch的标准KPM格式实现加载和控制
|
* 并参照KernelPatch的标准KPM格式实现加载和控制
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/export.h>
|
||||||
|
#include <linux/module.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <linux/kernfs.h>
|
#include <linux/kernfs.h>
|
||||||
#include <linux/file.h>
|
#include <linux/file.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
#include <linux/vmalloc.h>
|
#include <linux/vmalloc.h>
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
#include <linux/elf.h>
|
#include <linux/elf.h>
|
||||||
|
|
@ -22,25 +25,26 @@
|
||||||
#include <linux/spinlock.h>
|
#include <linux/spinlock.h>
|
||||||
#include <linux/rcupdate.h>
|
#include <linux/rcupdate.h>
|
||||||
#include <asm/elf.h>
|
#include <asm/elf.h>
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
#include <asm/cacheflush.h>
|
#include <asm/cacheflush.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
#include <linux/set_memory.h>
|
#include <linux/set_memory.h>
|
||||||
|
#include <linux/version.h>
|
||||||
#include <linux/export.h>
|
#include <linux/export.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <asm/insn.h>
|
#include <asm/insn.h>
|
||||||
#include <linux/kprobes.h>
|
#include <linux/kprobes.h>
|
||||||
#include <linux/stacktrace.h>
|
#include <linux/stacktrace.h>
|
||||||
|
#include <linux/kallsyms.h>
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0) && defined(CONFIG_MODULES)
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0) && defined(CONFIG_MODULES)
|
||||||
#include <linux/moduleloader.h>
|
#include <linux/moduleloader.h>
|
||||||
#endif
|
#endif
|
||||||
#include "kpm.h"
|
#include "kpm.h"
|
||||||
#include "compact.h"
|
#include "compact.h"
|
||||||
|
|
||||||
#define KPM_NAME_LEN 32
|
|
||||||
#define KPM_ARGS_LEN 1024
|
|
||||||
|
|
||||||
#ifndef NO_OPTIMIZE
|
#ifndef NO_OPTIMIZE
|
||||||
#if defined(__GNUC__) && !defined(__clang__)
|
#if defined(__GNUC__) && !defined(__clang__)
|
||||||
#define NO_OPTIMIZE __attribute__((optimize("O0")))
|
#define NO_OPTIMIZE __attribute__((optimize("O0")))
|
||||||
|
|
@ -52,231 +56,156 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
noinline NO_OPTIMIZE void sukisu_kpm_load_module_path(const char *path,
|
noinline NO_OPTIMIZE void sukisu_kpm_load_module_path(const char *path,
|
||||||
const char *args, void *ptr, int *result)
|
const char *args, void *ptr, void __user *result)
|
||||||
{
|
{
|
||||||
pr_info("kpm: Stub function called (sukisu_kpm_load_module_path). "
|
int res = -1;
|
||||||
|
|
||||||
|
printk("KPM: Stub function called (sukisu_kpm_load_module_path). "
|
||||||
"path=%s args=%s ptr=%p\n", path, args, ptr);
|
"path=%s args=%s ptr=%p\n", path, args, ptr);
|
||||||
|
|
||||||
__asm__ volatile("nop");
|
__asm__ volatile("nop");
|
||||||
|
|
||||||
|
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||||
|
printk("KPM: Copy to user failed.");
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(sukisu_kpm_load_module_path);
|
EXPORT_SYMBOL(sukisu_kpm_load_module_path);
|
||||||
|
|
||||||
noinline NO_OPTIMIZE void sukisu_kpm_unload_module(const char *name,
|
noinline NO_OPTIMIZE void sukisu_kpm_unload_module(const char *name,
|
||||||
void *ptr, int *result)
|
void *ptr, void __user *result)
|
||||||
{
|
{
|
||||||
pr_info("kpm: Stub function called (sukisu_kpm_unload_module). "
|
int res = -1;
|
||||||
|
|
||||||
|
printk("KPM: Stub function called (sukisu_kpm_unload_module). "
|
||||||
"name=%s ptr=%p\n", name, ptr);
|
"name=%s ptr=%p\n", name, ptr);
|
||||||
|
|
||||||
__asm__ volatile("nop");
|
__asm__ volatile("nop");
|
||||||
|
|
||||||
|
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||||
|
printk("KPM: Copy to user failed.");
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(sukisu_kpm_unload_module);
|
EXPORT_SYMBOL(sukisu_kpm_unload_module);
|
||||||
|
|
||||||
noinline NO_OPTIMIZE void sukisu_kpm_num(int *result)
|
noinline NO_OPTIMIZE void sukisu_kpm_num(void __user *result)
|
||||||
{
|
{
|
||||||
pr_info("kpm: Stub function called (sukisu_kpm_num).\n");
|
int res = 0;
|
||||||
|
|
||||||
|
printk("KPM: Stub function called (sukisu_kpm_num).\n");
|
||||||
|
|
||||||
__asm__ volatile("nop");
|
__asm__ volatile("nop");
|
||||||
|
|
||||||
|
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||||
|
printk("KPM: Copy to user failed.");
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(sukisu_kpm_num);
|
EXPORT_SYMBOL(sukisu_kpm_num);
|
||||||
|
|
||||||
noinline NO_OPTIMIZE void sukisu_kpm_info(const char *name, char *buf, int bufferSize,
|
noinline NO_OPTIMIZE void sukisu_kpm_info(const char *name, void __user *out,
|
||||||
int *size)
|
void __user *result)
|
||||||
{
|
{
|
||||||
pr_info("kpm: Stub function called (sukisu_kpm_info). "
|
int res = -1;
|
||||||
"name=%s buffer=%p\n", name, buf);
|
|
||||||
|
printk("KPM: Stub function called (sukisu_kpm_info). "
|
||||||
|
"name=%s buffer=%p\n", name, out);
|
||||||
|
|
||||||
__asm__ volatile("nop");
|
__asm__ volatile("nop");
|
||||||
|
|
||||||
|
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||||
|
printk("KPM: Copy to user failed.");
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(sukisu_kpm_info);
|
EXPORT_SYMBOL(sukisu_kpm_info);
|
||||||
|
|
||||||
noinline NO_OPTIMIZE void sukisu_kpm_list(void *out, int bufferSize,
|
noinline NO_OPTIMIZE void sukisu_kpm_list(void __user *out, unsigned int bufferSize,
|
||||||
int *result)
|
void __user *result)
|
||||||
{
|
{
|
||||||
pr_info("kpm: Stub function called (sukisu_kpm_list). "
|
int res = -1;
|
||||||
|
|
||||||
|
printk("KPM: Stub function called (sukisu_kpm_list). "
|
||||||
"buffer=%p size=%d\n", out, bufferSize);
|
"buffer=%p size=%d\n", out, bufferSize);
|
||||||
|
|
||||||
|
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||||
|
printk("KPM: Copy to user failed.");
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(sukisu_kpm_list);
|
EXPORT_SYMBOL(sukisu_kpm_list);
|
||||||
|
|
||||||
noinline NO_OPTIMIZE void sukisu_kpm_control(const char *name, const char *args, long arg_len,
|
noinline NO_OPTIMIZE void sukisu_kpm_control(void __user *name, void __user *args,
|
||||||
int *result)
|
void __user *result)
|
||||||
{
|
{
|
||||||
pr_info("kpm: Stub function called (sukisu_kpm_control). "
|
int res = -1;
|
||||||
"name=%p args=%p arg_len=%ld\n", name, args, arg_len);
|
|
||||||
|
printk("KPM: Stub function called (sukisu_kpm_control). "
|
||||||
|
"name=%p args=%p\n", name, args);
|
||||||
|
|
||||||
__asm__ volatile("nop");
|
__asm__ volatile("nop");
|
||||||
|
|
||||||
|
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||||
|
printk("KPM: Copy to user failed.");
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(sukisu_kpm_control);
|
EXPORT_SYMBOL(sukisu_kpm_control);
|
||||||
|
|
||||||
noinline NO_OPTIMIZE void sukisu_kpm_version(char *buf, int bufferSize)
|
noinline NO_OPTIMIZE void sukisu_kpm_version(void __user *out, unsigned int bufferSize,
|
||||||
|
void __user *result)
|
||||||
{
|
{
|
||||||
pr_info("kpm: Stub function called (sukisu_kpm_version). "
|
int res = -1;
|
||||||
"buffer=%p\n", buf);
|
|
||||||
|
printk("KPM: Stub function called (sukisu_kpm_version). "
|
||||||
|
"buffer=%p size=%d\n", out, bufferSize);
|
||||||
|
|
||||||
|
if (copy_to_user(result, &res, sizeof(res)) < 1)
|
||||||
|
printk("KPM: Copy to user failed.");
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(sukisu_kpm_version);
|
EXPORT_SYMBOL(sukisu_kpm_version);
|
||||||
|
|
||||||
noinline int sukisu_handle_kpm(unsigned long control_code, unsigned long arg1, unsigned long arg2,
|
noinline int sukisu_handle_kpm(unsigned long arg2, unsigned long arg3, unsigned long arg4,
|
||||||
unsigned long result_code)
|
unsigned long arg5)
|
||||||
{
|
{
|
||||||
int res = -1;
|
if (arg2 == SUKISU_KPM_LOAD) {
|
||||||
if (control_code == SUKISU_KPM_LOAD) {
|
char kernel_load_path[256] = { 0 };
|
||||||
char kernel_load_path[256];
|
char kernel_args_buffer[256] = { 0 };
|
||||||
char kernel_args_buffer[256];
|
|
||||||
|
|
||||||
if (arg1 == 0) {
|
if (arg3 == 0)
|
||||||
res = -EINVAL;
|
return -1;
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!access_ok(arg1, 255)) {
|
strncpy_from_user((char *)&kernel_load_path, (const char __user *)arg3, 255);
|
||||||
goto invalid_arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy_from_user((char *)&kernel_load_path, (const char *)arg1, 255);
|
if (arg4 != 0)
|
||||||
|
strncpy_from_user((char *)&kernel_args_buffer, (const char __user *)arg4, 255);
|
||||||
if (arg2 != 0) {
|
|
||||||
if (!access_ok(arg2, 255)) {
|
|
||||||
goto invalid_arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy_from_user((char *)&kernel_args_buffer, (const char *)arg2, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
sukisu_kpm_load_module_path((const char *)&kernel_load_path,
|
sukisu_kpm_load_module_path((const char *)&kernel_load_path,
|
||||||
(const char *)&kernel_args_buffer, NULL, &res);
|
(const char *)&kernel_args_buffer, NULL, (void __user *)arg5);
|
||||||
} else if (control_code == SUKISU_KPM_UNLOAD) {
|
} else if (arg2 == SUKISU_KPM_UNLOAD) {
|
||||||
char kernel_name_buffer[256];
|
char kernel_name_buffer[256] = { 0 };
|
||||||
|
|
||||||
if (arg1 == 0) {
|
if (arg3 == 0)
|
||||||
res = -EINVAL;
|
return -1;
|
||||||
goto exit;
|
|
||||||
|
strncpy_from_user((char *)&kernel_name_buffer, (const char __user *)arg3, 255);
|
||||||
|
|
||||||
|
sukisu_kpm_unload_module((const char *)&kernel_name_buffer, NULL,
|
||||||
|
(void __user *)arg5);
|
||||||
|
} else if (arg2 == SUKISU_KPM_NUM) {
|
||||||
|
sukisu_kpm_num((void __user *)arg5);
|
||||||
|
} else if (arg2 == SUKISU_KPM_INFO) {
|
||||||
|
char kernel_name_buffer[256] = { 0 };
|
||||||
|
|
||||||
|
if (arg3 == 0 || arg4 == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
strncpy_from_user((char *)&kernel_name_buffer, (const char __user *)arg3, 255);
|
||||||
|
|
||||||
|
sukisu_kpm_info((const char *)&kernel_name_buffer, (char __user *)arg4,
|
||||||
|
(void __user *)arg5);
|
||||||
|
} else if (arg2 == SUKISU_KPM_LIST) {
|
||||||
|
sukisu_kpm_list((char __user *)arg3, (unsigned int)arg4, (void __user *)arg5);
|
||||||
|
} else if (arg2 == SUKISU_KPM_CONTROL) {
|
||||||
|
sukisu_kpm_control((char __user *)arg3, (char __user *)arg4, (void __user *)arg5);
|
||||||
|
} else if (arg2 == SUKISU_KPM_VERSION) {
|
||||||
|
sukisu_kpm_version((char __user *)arg3, (unsigned int)arg4, (void __user *)arg5);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!access_ok(arg1, sizeof(kernel_name_buffer))) {
|
|
||||||
goto invalid_arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy_from_user((char *)&kernel_name_buffer, (const char *)arg1, sizeof(kernel_name_buffer));
|
|
||||||
|
|
||||||
sukisu_kpm_unload_module((const char *)&kernel_name_buffer, NULL, &res);
|
|
||||||
} else if (control_code == SUKISU_KPM_NUM) {
|
|
||||||
sukisu_kpm_num(&res);
|
|
||||||
} else if (control_code == SUKISU_KPM_INFO) {
|
|
||||||
char kernel_name_buffer[256];
|
|
||||||
char buf[256];
|
|
||||||
int size;
|
|
||||||
|
|
||||||
if (arg1 == 0 || arg2 == 0) {
|
|
||||||
res = -EINVAL;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!access_ok(arg1, sizeof(kernel_name_buffer))) {
|
|
||||||
goto invalid_arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy_from_user((char *)&kernel_name_buffer, (const char __user *)arg1, sizeof(kernel_name_buffer));
|
|
||||||
|
|
||||||
sukisu_kpm_info((const char *)&kernel_name_buffer, (char *)&buf, sizeof(buf), &size);
|
|
||||||
|
|
||||||
if (!access_ok(arg2, size)) {
|
|
||||||
goto invalid_arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = copy_to_user(arg2, &buf, size);
|
|
||||||
|
|
||||||
} else if (control_code == SUKISU_KPM_LIST) {
|
|
||||||
char buf[1024];
|
|
||||||
int len = (int) arg2;
|
|
||||||
|
|
||||||
if (len <= 0) {
|
|
||||||
res = -EINVAL;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!access_ok(arg2, len)) {
|
|
||||||
goto invalid_arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
sukisu_kpm_list((char *)&buf, sizeof(buf), &res);
|
|
||||||
|
|
||||||
if (res > len) {
|
|
||||||
res = -ENOBUFS;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (copy_to_user(arg1, &buf, len) != 0)
|
|
||||||
pr_info("kpm: Copy to user failed.");
|
|
||||||
|
|
||||||
} else if (control_code == SUKISU_KPM_CONTROL) {
|
|
||||||
char kpm_name[KPM_NAME_LEN] = { 0 };
|
|
||||||
char kpm_args[KPM_ARGS_LEN] = { 0 };
|
|
||||||
|
|
||||||
if (!access_ok(arg1, sizeof(kpm_name))) {
|
|
||||||
goto invalid_arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!access_ok(arg2, sizeof(kpm_args))) {
|
|
||||||
goto invalid_arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
long name_len = strncpy_from_user((char *)&kpm_name, (const char __user *)arg1, sizeof(kpm_name));
|
|
||||||
if (name_len <= 0) {
|
|
||||||
res = -EINVAL;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
long arg_len = strncpy_from_user((char *)&kpm_args, (const char __user *)arg2, sizeof(kpm_args));
|
|
||||||
|
|
||||||
sukisu_kpm_control((const char *)&kpm_name, (const char *)&kpm_args, arg_len, &res);
|
|
||||||
|
|
||||||
} else if (control_code == SUKISU_KPM_VERSION) {
|
|
||||||
char buffer[256] = {0};
|
|
||||||
|
|
||||||
sukisu_kpm_version((char*) &buffer, sizeof(buffer));
|
|
||||||
|
|
||||||
unsigned int outlen = (unsigned int) arg2;
|
|
||||||
int len = strlen(buffer);
|
|
||||||
if (len >= outlen) len = outlen - 1;
|
|
||||||
|
|
||||||
res = copy_to_user(arg1, &buffer, len + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
exit:
|
|
||||||
if (copy_to_user(result_code, &res, sizeof(res)) != 0)
|
|
||||||
pr_info("kpm: Copy to user failed.");
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
invalid_arg:
|
|
||||||
pr_err("kpm: invalid pointer detected! arg1: %px arg2: %px\n", (void *)arg1, (void *)arg2);
|
|
||||||
res = -EFAULT;
|
|
||||||
goto exit;
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(sukisu_handle_kpm);
|
EXPORT_SYMBOL(sukisu_handle_kpm);
|
||||||
|
|
||||||
int sukisu_is_kpm_control_code(unsigned long control_code) {
|
int sukisu_is_kpm_control_code(unsigned long arg2) {
|
||||||
return (control_code >= CMD_KPM_CONTROL &&
|
return (arg2 >= CMD_KPM_CONTROL &&
|
||||||
control_code <= CMD_KPM_CONTROL_MAX) ? 1 : 0;
|
arg2 <= CMD_KPM_CONTROL_MAX) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int do_kpm(void __user *arg)
|
|
||||||
{
|
|
||||||
struct ksu_kpm_cmd cmd;
|
|
||||||
|
|
||||||
if (copy_from_user(&cmd, arg, sizeof(cmd))) {
|
|
||||||
pr_err("kpm: copy_from_user failed\n");
|
|
||||||
return -EFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!access_ok(cmd.control_code, sizeof(int))) {
|
|
||||||
pr_err("kpm: invalid control_code pointer %px\n", (void *)cmd.control_code);
|
|
||||||
return -EFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!access_ok(cmd.result_code, sizeof(int))) {
|
|
||||||
pr_err("kpm: invalid result_code pointer %px\n", (void *)cmd.result_code);
|
|
||||||
return -EFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sukisu_handle_kpm(cmd.control_code, cmd.arg1, cmd.arg2, cmd.result_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,70 +1,58 @@
|
||||||
#ifndef __SUKISU_KPM_H
|
#ifndef __SUKISU_KPM_H
|
||||||
#define __SUKISU_KPM_H
|
#define __SUKISU_KPM_H
|
||||||
|
|
||||||
#include <linux/types.h>
|
extern int sukisu_handle_kpm(unsigned long arg2, unsigned long arg3, unsigned long arg4,
|
||||||
#include <linux/ioctl.h>
|
unsigned long arg5);
|
||||||
|
extern int sukisu_is_kpm_control_code(unsigned long arg2);
|
||||||
struct ksu_kpm_cmd {
|
|
||||||
__aligned_u64 __user control_code;
|
|
||||||
__aligned_u64 __user arg1;
|
|
||||||
__aligned_u64 __user arg2;
|
|
||||||
__aligned_u64 __user result_code;
|
|
||||||
};
|
|
||||||
|
|
||||||
int sukisu_handle_kpm(unsigned long control_code, unsigned long arg3, unsigned long arg4, unsigned long result_code);
|
|
||||||
int sukisu_is_kpm_control_code(unsigned long control_code);
|
|
||||||
int do_kpm(void __user *arg);
|
|
||||||
|
|
||||||
#define KSU_IOCTL_KPM _IOC(_IOC_READ|_IOC_WRITE, 'K', 200, 0)
|
|
||||||
|
|
||||||
/* KPM Control Code */
|
/* KPM Control Code */
|
||||||
#define CMD_KPM_CONTROL 1
|
#define CMD_KPM_CONTROL 28
|
||||||
#define CMD_KPM_CONTROL_MAX 10
|
#define CMD_KPM_CONTROL_MAX 35
|
||||||
|
|
||||||
/* Control Code */
|
/* Control Code */
|
||||||
/*
|
/*
|
||||||
* prctl(xxx, 1, "PATH", "ARGS")
|
* prctl(xxx, 28, "PATH", "ARGS")
|
||||||
* success return 0, error return -N
|
* success return 0, error return -N
|
||||||
*/
|
*/
|
||||||
#define SUKISU_KPM_LOAD 1
|
#define SUKISU_KPM_LOAD 28
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* prctl(xxx, 2, "NAME")
|
* prctl(xxx, 29, "NAME")
|
||||||
* success return 0, error return -N
|
* success return 0, error return -N
|
||||||
*/
|
*/
|
||||||
#define SUKISU_KPM_UNLOAD 2
|
#define SUKISU_KPM_UNLOAD 29
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* num = prctl(xxx, 3)
|
* num = prctl(xxx, 30)
|
||||||
* error return -N
|
* error return -N
|
||||||
* success return +num or 0
|
* success return +num or 0
|
||||||
*/
|
*/
|
||||||
#define SUKISU_KPM_NUM 3
|
#define SUKISU_KPM_NUM 30
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* prctl(xxx, 4, Buffer, BufferSize)
|
* prctl(xxx, 31, Buffer, BufferSize)
|
||||||
* success return +out, error return -N
|
* success return +out, error return -N
|
||||||
*/
|
*/
|
||||||
#define SUKISU_KPM_LIST 4
|
#define SUKISU_KPM_LIST 31
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* prctl(xxx, 5, "NAME", Buffer[256])
|
* prctl(xxx, 32, "NAME", Buffer[256])
|
||||||
* success return +out, error return -N
|
* success return +out, error return -N
|
||||||
*/
|
*/
|
||||||
#define SUKISU_KPM_INFO 5
|
#define SUKISU_KPM_INFO 32
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* prctl(xxx, 6, "NAME", "ARGS")
|
* prctl(xxx, 33, "NAME", "ARGS")
|
||||||
* success return KPM's result value
|
* success return KPM's result value
|
||||||
* error return -N
|
* error return -N
|
||||||
*/
|
*/
|
||||||
#define SUKISU_KPM_CONTROL 6
|
#define SUKISU_KPM_CONTROL 33
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* prctl(xxx, 7, buffer, bufferSize)
|
* prctl(xxx, 34, buffer, bufferSize)
|
||||||
* success return KPM's result value
|
* success return KPM's result value
|
||||||
* error return -N
|
* error return -N
|
||||||
*/
|
*/
|
||||||
#define SUKISU_KPM_VERSION 7
|
#define SUKISU_KPM_VERSION 34
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
69
kernel/ksu.c
69
kernel/ksu.c
|
|
@ -3,19 +3,13 @@
|
||||||
#include <linux/kobject.h>
|
#include <linux/kobject.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/workqueue.h>
|
#include <linux/workqueue.h>
|
||||||
#include <linux/version.h>
|
|
||||||
|
|
||||||
#include "allowlist.h"
|
#include "allowlist.h"
|
||||||
#include "feature.h"
|
#include "arch.h"
|
||||||
|
#include "core_hook.h"
|
||||||
#include "klog.h" // IWYU pragma: keep
|
#include "klog.h" // IWYU pragma: keep
|
||||||
|
#include "ksu.h"
|
||||||
#include "throne_tracker.h"
|
#include "throne_tracker.h"
|
||||||
#include "syscall_hook_manager.h"
|
|
||||||
#include "ksud.h"
|
|
||||||
#include "supercalls.h"
|
|
||||||
|
|
||||||
#include "sulog.h"
|
|
||||||
#include "throne_comm.h"
|
|
||||||
#include "dynamic_manager.h"
|
|
||||||
|
|
||||||
static struct workqueue_struct *ksu_workqueue;
|
static struct workqueue_struct *ksu_workqueue;
|
||||||
|
|
||||||
|
|
@ -24,19 +18,17 @@ bool ksu_queue_work(struct work_struct *work)
|
||||||
return queue_work(ksu_workqueue, work);
|
return queue_work(ksu_workqueue, work);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sukisu_custom_config_init(void)
|
extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||||
{
|
void *argv, void *envp, int *flags);
|
||||||
}
|
|
||||||
|
|
||||||
void sukisu_custom_config_exit(void)
|
extern void ksu_sucompat_init();
|
||||||
{
|
extern void ksu_sucompat_exit();
|
||||||
ksu_uid_exit();
|
extern void ksu_ksud_init();
|
||||||
ksu_throne_comm_exit();
|
extern void ksu_ksud_exit();
|
||||||
ksu_dynamic_manager_exit();
|
#ifdef CONFIG_KSU_TRACEPOINT_HOOK
|
||||||
#if __SULOG_GATE
|
extern void ksu_trace_register();
|
||||||
ksu_sulog_exit();
|
extern void ksu_trace_unregister();
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
int __init kernelsu_init(void)
|
int __init kernelsu_init(void)
|
||||||
{
|
{
|
||||||
|
|
@ -50,26 +42,24 @@ int __init kernelsu_init(void)
|
||||||
pr_alert("*************************************************************");
|
pr_alert("*************************************************************");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ksu_feature_init();
|
ksu_core_init();
|
||||||
|
|
||||||
ksu_supercalls_init();
|
|
||||||
|
|
||||||
sukisu_custom_config_init();
|
|
||||||
|
|
||||||
ksu_syscall_hook_manager_init();
|
|
||||||
|
|
||||||
ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);
|
ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);
|
||||||
|
|
||||||
ksu_allowlist_init();
|
ksu_allowlist_init();
|
||||||
|
|
||||||
ksu_throne_tracker_init();
|
ksu_throne_tracker_init();
|
||||||
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
#ifdef KSU_KPROBES_HOOK
|
ksu_sucompat_init();
|
||||||
ksu_ksud_init();
|
ksu_ksud_init();
|
||||||
#else
|
#else
|
||||||
pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html");
|
pr_alert("KPROBES is disabled, KernelSU may not work, please check https://kernelsu.org/guide/how-to-integrate-for-non-gki.html");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_KSU_TRACEPOINT_HOOK
|
||||||
|
ksu_trace_register();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef MODULE
|
#ifdef MODULE
|
||||||
#ifndef CONFIG_KSU_DEBUG
|
#ifndef CONFIG_KSU_DEBUG
|
||||||
kobject_del(&THIS_MODULE->mkobj.kobj);
|
kobject_del(&THIS_MODULE->mkobj.kobj);
|
||||||
|
|
@ -78,28 +68,24 @@ int __init kernelsu_init(void)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void ksu_observer_exit(void);
|
|
||||||
void kernelsu_exit(void)
|
void kernelsu_exit(void)
|
||||||
{
|
{
|
||||||
ksu_allowlist_exit();
|
ksu_allowlist_exit();
|
||||||
|
|
||||||
ksu_observer_exit();
|
|
||||||
|
|
||||||
ksu_throne_tracker_exit();
|
ksu_throne_tracker_exit();
|
||||||
|
|
||||||
destroy_workqueue(ksu_workqueue);
|
destroy_workqueue(ksu_workqueue);
|
||||||
|
|
||||||
#ifdef KSU_KPROBES_HOOK
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
ksu_ksud_exit();
|
ksu_ksud_exit();
|
||||||
|
ksu_sucompat_exit();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ksu_syscall_hook_manager_exit();
|
#ifdef CONFIG_KSU_TRACEPOINT_HOOK
|
||||||
|
ksu_trace_unregister();
|
||||||
|
#endif
|
||||||
|
|
||||||
sukisu_custom_config_exit();
|
ksu_core_exit();
|
||||||
|
|
||||||
ksu_supercalls_exit();
|
|
||||||
|
|
||||||
ksu_feature_exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(kernelsu_init);
|
module_init(kernelsu_init);
|
||||||
|
|
@ -108,9 +94,4 @@ module_exit(kernelsu_exit);
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
MODULE_AUTHOR("weishu");
|
MODULE_AUTHOR("weishu");
|
||||||
MODULE_DESCRIPTION("Android KernelSU");
|
MODULE_DESCRIPTION("Android KernelSU");
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0)
|
|
||||||
MODULE_IMPORT_NS("VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver");
|
|
||||||
#else
|
|
||||||
MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
|
MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
|
||||||
#endif
|
|
||||||
|
|
|
||||||
84
kernel/ksu.h
84
kernel/ksu.h
|
|
@ -7,12 +7,40 @@
|
||||||
#define KERNEL_SU_VERSION KSU_VERSION
|
#define KERNEL_SU_VERSION KSU_VERSION
|
||||||
#define KERNEL_SU_OPTION 0xDEADBEEF
|
#define KERNEL_SU_OPTION 0xDEADBEEF
|
||||||
|
|
||||||
extern bool ksu_uid_scanner_enabled;
|
#define CMD_GRANT_ROOT 0
|
||||||
|
#define CMD_BECOME_MANAGER 1
|
||||||
|
#define CMD_GET_VERSION 2
|
||||||
|
#define CMD_ALLOW_SU 3
|
||||||
|
#define CMD_DENY_SU 4
|
||||||
|
#define CMD_GET_ALLOW_LIST 5
|
||||||
|
#define CMD_GET_DENY_LIST 6
|
||||||
|
#define CMD_REPORT_EVENT 7
|
||||||
|
#define CMD_SET_SEPOLICY 8
|
||||||
|
#define CMD_CHECK_SAFEMODE 9
|
||||||
|
#define CMD_GET_APP_PROFILE 10
|
||||||
|
#define CMD_SET_APP_PROFILE 11
|
||||||
|
#define CMD_UID_GRANTED_ROOT 12
|
||||||
|
#define CMD_UID_SHOULD_UMOUNT 13
|
||||||
|
#define CMD_IS_SU_ENABLED 14
|
||||||
|
#define CMD_ENABLE_SU 15
|
||||||
|
|
||||||
|
#define CMD_GET_FULL_VERSION 0xC0FFEE1A
|
||||||
|
|
||||||
|
#define CMD_ENABLE_KPM 100
|
||||||
|
#define CMD_HOOK_TYPE 101
|
||||||
|
#define CMD_DYNAMIC_MANAGER 103
|
||||||
|
#define CMD_GET_MANAGERS 104
|
||||||
|
|
||||||
#define EVENT_POST_FS_DATA 1
|
#define EVENT_POST_FS_DATA 1
|
||||||
#define EVENT_BOOT_COMPLETED 2
|
#define EVENT_BOOT_COMPLETED 2
|
||||||
#define EVENT_MODULE_MOUNTED 3
|
#define EVENT_MODULE_MOUNTED 3
|
||||||
|
|
||||||
|
#define KSU_APP_PROFILE_VER 2
|
||||||
|
#define KSU_MAX_PACKAGE_NAME 256
|
||||||
|
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
||||||
|
#define KSU_MAX_GROUPS 32
|
||||||
|
#define KSU_SELINUX_DOMAIN 64
|
||||||
|
|
||||||
// SukiSU Ultra kernel su version full strings
|
// SukiSU Ultra kernel su version full strings
|
||||||
#ifndef KSU_VERSION_FULL
|
#ifndef KSU_VERSION_FULL
|
||||||
#define KSU_VERSION_FULL "v3.x-00000000@unknown"
|
#define KSU_VERSION_FULL "v3.x-00000000@unknown"
|
||||||
|
|
@ -23,10 +51,6 @@ extern bool ksu_uid_scanner_enabled;
|
||||||
#define DYNAMIC_MANAGER_OP_GET 1
|
#define DYNAMIC_MANAGER_OP_GET 1
|
||||||
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
||||||
|
|
||||||
#define UID_SCANNER_OP_GET_STATUS 0
|
|
||||||
#define UID_SCANNER_OP_TOGGLE 1
|
|
||||||
#define UID_SCANNER_OP_CLEAR_ENV 2
|
|
||||||
|
|
||||||
struct dynamic_manager_user_config {
|
struct dynamic_manager_user_config {
|
||||||
unsigned int operation;
|
unsigned int operation;
|
||||||
unsigned int size;
|
unsigned int size;
|
||||||
|
|
@ -41,9 +65,56 @@ struct manager_list_info {
|
||||||
} managers[2];
|
} managers[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct root_profile {
|
||||||
|
int32_t uid;
|
||||||
|
int32_t gid;
|
||||||
|
|
||||||
|
int32_t groups_count;
|
||||||
|
int32_t groups[KSU_MAX_GROUPS];
|
||||||
|
|
||||||
|
// kernel_cap_t is u32[2] for capabilities v3
|
||||||
|
struct {
|
||||||
|
u64 effective;
|
||||||
|
u64 permitted;
|
||||||
|
u64 inheritable;
|
||||||
|
} capabilities;
|
||||||
|
|
||||||
|
char selinux_domain[KSU_SELINUX_DOMAIN];
|
||||||
|
|
||||||
|
int32_t namespaces;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct non_root_profile {
|
||||||
|
bool umount_modules;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct app_profile {
|
||||||
|
// It may be utilized for backward compatibility, although we have never explicitly made any promises regarding this.
|
||||||
|
u32 version;
|
||||||
|
|
||||||
|
// this is usually the package of the app, but can be other value for special apps
|
||||||
|
char key[KSU_MAX_PACKAGE_NAME];
|
||||||
|
int32_t current_uid;
|
||||||
|
bool allow_su;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
bool use_default;
|
||||||
|
char template_name[KSU_MAX_PACKAGE_NAME];
|
||||||
|
|
||||||
|
struct root_profile profile;
|
||||||
|
} rp_config;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool use_default;
|
||||||
|
|
||||||
|
struct non_root_profile profile;
|
||||||
|
} nrp_config;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
bool ksu_queue_work(struct work_struct *work);
|
bool ksu_queue_work(struct work_struct *work);
|
||||||
|
|
||||||
#if 0
|
|
||||||
static inline int startswith(char *s, char *prefix)
|
static inline int startswith(char *s, char *prefix)
|
||||||
{
|
{
|
||||||
return strncmp(s, prefix, strlen(prefix));
|
return strncmp(s, prefix, strlen(prefix));
|
||||||
|
|
@ -57,6 +128,5 @@ static inline int endswith(const char *s, const char *t)
|
||||||
return 1;
|
return 1;
|
||||||
return strcmp(s + slen - tlen, t);
|
return strcmp(s + slen - tlen, t);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
69
kernel/ksu_trace.c
Normal file
69
kernel/ksu_trace.c
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#include "ksu_trace.h"
|
||||||
|
|
||||||
|
|
||||||
|
// extern kernelsu functions
|
||||||
|
extern bool ksu_vfs_read_hook __read_mostly;
|
||||||
|
extern bool ksu_input_hook __read_mostly;
|
||||||
|
extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, void *argv, void *envp, int *flags);
|
||||||
|
extern int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, int *flags);
|
||||||
|
extern int ksu_handle_sys_read(unsigned int fd, char __user **buf_ptr, size_t *count_ptr);
|
||||||
|
extern int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
||||||
|
extern int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code, int *value);
|
||||||
|
// end kernelsu functions
|
||||||
|
|
||||||
|
|
||||||
|
// tracepoint callback functions
|
||||||
|
void ksu_trace_execveat_sucompat_hook_callback(void *data, int *fd, struct filename **filename_ptr,
|
||||||
|
void *argv, void *envp, int *flags)
|
||||||
|
{
|
||||||
|
ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ksu_trace_faccessat_hook_callback(void *data, int *dfd, const char __user **filename_user,
|
||||||
|
int *mode, int *flags)
|
||||||
|
{
|
||||||
|
ksu_handle_faccessat(dfd, filename_user, mode, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ksu_trace_sys_read_hook_callback(void *data, unsigned int fd, char __user **buf_ptr,
|
||||||
|
size_t *count_ptr)
|
||||||
|
{
|
||||||
|
if (unlikely(ksu_vfs_read_hook))
|
||||||
|
ksu_handle_sys_read(fd, buf_ptr, count_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ksu_trace_stat_hook_callback(void *data, int *dfd, const char __user **filename_user,
|
||||||
|
int *flags)
|
||||||
|
{
|
||||||
|
ksu_handle_stat(dfd, filename_user, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ksu_trace_input_hook_callback(void *data, unsigned int *type, unsigned int *code,
|
||||||
|
int *value)
|
||||||
|
{
|
||||||
|
if (unlikely(ksu_input_hook))
|
||||||
|
ksu_handle_input_handle_event(type, code, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// end tracepoint callback functions
|
||||||
|
|
||||||
|
|
||||||
|
// register tracepoint callback functions
|
||||||
|
void ksu_trace_register(void)
|
||||||
|
{
|
||||||
|
register_trace_ksu_trace_execveat_sucompat_hook(ksu_trace_execveat_sucompat_hook_callback, NULL);
|
||||||
|
register_trace_ksu_trace_faccessat_hook(ksu_trace_faccessat_hook_callback, NULL);
|
||||||
|
register_trace_ksu_trace_sys_read_hook(ksu_trace_sys_read_hook_callback, NULL);
|
||||||
|
register_trace_ksu_trace_stat_hook(ksu_trace_stat_hook_callback, NULL);
|
||||||
|
register_trace_ksu_trace_input_hook(ksu_trace_input_hook_callback, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unregister tracepoint callback functions
|
||||||
|
void ksu_trace_unregister(void)
|
||||||
|
{
|
||||||
|
unregister_trace_ksu_trace_execveat_sucompat_hook(ksu_trace_execveat_sucompat_hook_callback, NULL);
|
||||||
|
unregister_trace_ksu_trace_faccessat_hook(ksu_trace_faccessat_hook_callback, NULL);
|
||||||
|
unregister_trace_ksu_trace_sys_read_hook(ksu_trace_sys_read_hook_callback, NULL);
|
||||||
|
unregister_trace_ksu_trace_stat_hook(ksu_trace_stat_hook_callback, NULL);
|
||||||
|
unregister_trace_ksu_trace_input_hook(ksu_trace_input_hook_callback, NULL);
|
||||||
|
}
|
||||||
37
kernel/ksu_trace.h
Normal file
37
kernel/ksu_trace.h
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#undef TRACE_SYSTEM
|
||||||
|
#define TRACE_SYSTEM ksu_trace
|
||||||
|
|
||||||
|
#if !defined(_KSU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||||
|
#define _KSU_TRACE_H
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/tracepoint.h>
|
||||||
|
|
||||||
|
DECLARE_TRACE(ksu_trace_execveat_sucompat_hook,
|
||||||
|
TP_PROTO(int *fd, struct filename **filename_ptr, void *argv, void *envp, int *flags),
|
||||||
|
TP_ARGS(fd, filename_ptr, argv, envp, flags));
|
||||||
|
|
||||||
|
DECLARE_TRACE(ksu_trace_faccessat_hook,
|
||||||
|
TP_PROTO(int *dfd, const char __user **filename_user, int *mode, int *flags),
|
||||||
|
TP_ARGS(dfd, filename_user, mode, flags));
|
||||||
|
|
||||||
|
DECLARE_TRACE(ksu_trace_sys_read_hook,
|
||||||
|
TP_PROTO(unsigned int fd, char __user **buf_ptr, size_t *count_ptr),
|
||||||
|
TP_ARGS(fd, buf_ptr, count_ptr));
|
||||||
|
|
||||||
|
DECLARE_TRACE(ksu_trace_stat_hook,
|
||||||
|
TP_PROTO(int *dfd, const char __user **filename_user, int *flags),
|
||||||
|
TP_ARGS(dfd, filename_user, flags));
|
||||||
|
|
||||||
|
DECLARE_TRACE(ksu_trace_input_hook,
|
||||||
|
TP_PROTO(unsigned int *type, unsigned int *code, int *value),
|
||||||
|
TP_ARGS(type, code, value));
|
||||||
|
|
||||||
|
#endif /* _KSU_TRACE_H */
|
||||||
|
|
||||||
|
#undef TRACE_INCLUDE_PATH
|
||||||
|
#define TRACE_INCLUDE_PATH .
|
||||||
|
#undef TRACE_INCLUDE_FILE
|
||||||
|
#define TRACE_INCLUDE_FILE ksu_trace
|
||||||
|
|
||||||
|
#include <trace/define_trace.h>
|
||||||
8
kernel/ksu_trace_export.c
Normal file
8
kernel/ksu_trace_export.c
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#define CREATE_TRACE_POINTS
|
||||||
|
#include "ksu_trace.h"
|
||||||
|
|
||||||
|
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_execveat_sucompat_hook);
|
||||||
|
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_faccessat_hook);
|
||||||
|
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_sys_read_hook);
|
||||||
|
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_stat_hook);
|
||||||
|
EXPORT_TRACEPOINT_SYMBOL_GPL(ksu_trace_input_hook);
|
||||||
506
kernel/ksud.c
506
kernel/ksud.c
|
|
@ -1,6 +1,3 @@
|
||||||
#include <linux/rcupdate.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/task_work.h>
|
|
||||||
#include <asm/current.h>
|
#include <asm/current.h>
|
||||||
#include <linux/compat.h>
|
#include <linux/compat.h>
|
||||||
#include <linux/cred.h>
|
#include <linux/cred.h>
|
||||||
|
|
@ -14,19 +11,15 @@
|
||||||
#include <linux/printk.h>
|
#include <linux/printk.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
#include <linux/namei.h>
|
|
||||||
#include <linux/workqueue.h>
|
#include <linux/workqueue.h>
|
||||||
|
|
||||||
#include "manager.h"
|
|
||||||
#include "allowlist.h"
|
#include "allowlist.h"
|
||||||
#include "arch.h"
|
#include "arch.h"
|
||||||
#include "klog.h" // IWYU pragma: keep
|
#include "klog.h" // IWYU pragma: keep
|
||||||
#include "ksud.h"
|
#include "ksud.h"
|
||||||
|
#include "kernel_compat.h"
|
||||||
#include "selinux/selinux.h"
|
#include "selinux/selinux.h"
|
||||||
#include "throne_tracker.h"
|
|
||||||
|
|
||||||
bool ksu_module_mounted __read_mostly = false;
|
|
||||||
bool ksu_boot_completed __read_mostly = false;
|
|
||||||
|
|
||||||
static const char KERNEL_SU_RC[] =
|
static const char KERNEL_SU_RC[] =
|
||||||
"\n"
|
"\n"
|
||||||
|
|
@ -55,17 +48,17 @@ static void stop_vfs_read_hook();
|
||||||
static void stop_execve_hook();
|
static void stop_execve_hook();
|
||||||
static void stop_input_hook();
|
static void stop_input_hook();
|
||||||
|
|
||||||
#ifdef KSU_KPROBES_HOOK
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
static struct work_struct stop_vfs_read_work;
|
static struct work_struct stop_vfs_read_work;
|
||||||
static struct work_struct stop_execve_hook_work;
|
static struct work_struct stop_execve_hook_work;
|
||||||
static struct work_struct stop_input_hook_work;
|
static struct work_struct stop_input_hook_work;
|
||||||
#else
|
#else
|
||||||
bool ksu_vfs_read_hook __read_mostly = true;
|
bool ksu_vfs_read_hook __read_mostly = true;
|
||||||
bool ksu_execveat_hook __read_mostly = true;
|
|
||||||
bool ksu_input_hook __read_mostly = true;
|
bool ksu_input_hook __read_mostly = true;
|
||||||
#endif
|
#endif
|
||||||
|
bool ksu_execveat_hook __read_mostly = true;
|
||||||
|
|
||||||
u32 ksu_file_sid;
|
u32 ksu_devpts_sid;
|
||||||
|
|
||||||
// Detect whether it is on or not
|
// Detect whether it is on or not
|
||||||
static bool is_boot_phase = true;
|
static bool is_boot_phase = true;
|
||||||
|
|
@ -80,142 +73,21 @@ void on_post_fs_data(void)
|
||||||
done = true;
|
done = true;
|
||||||
pr_info("on_post_fs_data!\n");
|
pr_info("on_post_fs_data!\n");
|
||||||
ksu_load_allow_list();
|
ksu_load_allow_list();
|
||||||
ksu_observer_init();
|
|
||||||
// sanity check, this may influence the performance
|
// sanity check, this may influence the performance
|
||||||
stop_input_hook();
|
stop_input_hook();
|
||||||
|
|
||||||
|
ksu_devpts_sid = ksu_get_devpts_sid();
|
||||||
|
pr_info("devpts sid: %d\n", ksu_devpts_sid);
|
||||||
|
|
||||||
// End of boot state
|
// End of boot state
|
||||||
is_boot_phase = false;
|
is_boot_phase = false;
|
||||||
|
|
||||||
ksu_file_sid = ksu_get_ksu_file_sid();
|
|
||||||
pr_info("ksu_file sid: %d\n", ksu_file_sid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void ext4_unregister_sysfs(struct super_block *sb);
|
// since _ksud handler only uses argv and envp for comparisons
|
||||||
int nuke_ext4_sysfs(const char* mnt)
|
// this can probably work
|
||||||
|
// adapted from ksu_handle_execveat_ksud
|
||||||
|
static int ksu_handle_bprm_ksud(const char *filename, const char *argv1, const char *envp, size_t envp_len)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_EXT4_FS
|
|
||||||
struct path path;
|
|
||||||
int err = kern_path(mnt, 0, &path);
|
|
||||||
if (err) {
|
|
||||||
pr_err("nuke path err: %d\n", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct super_block *sb = path.dentry->d_inode->i_sb;
|
|
||||||
const char *name = sb->s_type->name;
|
|
||||||
if (strcmp(name, "ext4") != 0) {
|
|
||||||
pr_info("nuke but module aren't mounted\n");
|
|
||||||
path_put(&path);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ext4_unregister_sysfs(sb);
|
|
||||||
path_put(&path);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_module_mounted(void){
|
|
||||||
pr_info("on_module_mounted!\n");
|
|
||||||
ksu_module_mounted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_boot_completed(void){
|
|
||||||
ksu_boot_completed = true;
|
|
||||||
pr_info("on_boot_completed!\n");
|
|
||||||
track_throne(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define MAX_ARG_STRINGS 0x7FFFFFFF
|
|
||||||
struct user_arg_ptr {
|
|
||||||
#ifdef CONFIG_COMPAT
|
|
||||||
bool is_compat;
|
|
||||||
#endif
|
|
||||||
union {
|
|
||||||
const char __user *const __user *native;
|
|
||||||
#ifdef CONFIG_COMPAT
|
|
||||||
const compat_uptr_t __user *compat;
|
|
||||||
#endif
|
|
||||||
} ptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
|
|
||||||
{
|
|
||||||
const char __user *native;
|
|
||||||
|
|
||||||
#ifdef CONFIG_COMPAT
|
|
||||||
if (unlikely(argv.is_compat)) {
|
|
||||||
compat_uptr_t compat;
|
|
||||||
|
|
||||||
if (get_user(compat, argv.ptr.compat + nr))
|
|
||||||
return ERR_PTR(-EFAULT);
|
|
||||||
|
|
||||||
return compat_ptr(compat);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (get_user(native, argv.ptr.native + nr))
|
|
||||||
return ERR_PTR(-EFAULT);
|
|
||||||
|
|
||||||
return native;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* count() counts the number of strings in array ARGV.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Make sure old GCC compiler can use __maybe_unused,
|
|
||||||
* Test passed in 4.4.x ~ 4.9.x when use GCC.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int __maybe_unused count(struct user_arg_ptr argv, int max)
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
if (argv.ptr.native != NULL) {
|
|
||||||
for (;;) {
|
|
||||||
const char __user *p = get_user_arg_ptr(argv, i);
|
|
||||||
|
|
||||||
if (!p)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (IS_ERR(p))
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
if (i >= max)
|
|
||||||
return -E2BIG;
|
|
||||||
++i;
|
|
||||||
|
|
||||||
if (fatal_signal_pending(current))
|
|
||||||
return -ERESTARTNOHAND;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void on_post_fs_data_cbfun(struct callback_head *cb)
|
|
||||||
{
|
|
||||||
on_post_fs_data();
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct callback_head on_post_fs_data_cb = { .func =
|
|
||||||
on_post_fs_data_cbfun };
|
|
||||||
|
|
||||||
// IMPORTANT NOTE: the call from execve_handler_pre WON'T provided correct value for envp and flags in GKI version
|
|
||||||
int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
|
||||||
struct user_arg_ptr *argv,
|
|
||||||
struct user_arg_ptr *envp, int *flags)
|
|
||||||
{
|
|
||||||
#ifndef KSU_KPROBES_HOOK
|
|
||||||
if (!ksu_execveat_hook) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
struct filename *filename;
|
|
||||||
|
|
||||||
static const char app_process[] = "/system/bin/app_process";
|
static const char app_process[] = "/system/bin/app_process";
|
||||||
static bool first_app_process = true;
|
static bool first_app_process = true;
|
||||||
|
|
||||||
|
|
@ -225,110 +97,142 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
|
||||||
static const char old_system_init[] = "/init";
|
static const char old_system_init[] = "/init";
|
||||||
static bool init_second_stage_executed = false;
|
static bool init_second_stage_executed = false;
|
||||||
|
|
||||||
if (!filename_ptr)
|
// return early when disabled
|
||||||
|
if (!ksu_execveat_hook)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
filename = *filename_ptr;
|
if (!filename)
|
||||||
if (IS_ERR(filename)) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (unlikely(!memcmp(filename->name, system_bin_init,
|
// debug! remove me!
|
||||||
sizeof(system_bin_init) - 1) &&
|
pr_info("%s: filename: %s argv1: %s envp_len: %zu\n", __func__, filename, argv1, envp_len);
|
||||||
argv)) {
|
|
||||||
// /system/bin/init executed
|
#ifdef CONFIG_KSU_DEBUG
|
||||||
int argc = count(*argv, MAX_ARG_STRINGS);
|
const char *envp_n = envp;
|
||||||
pr_info("/system/bin/init argc: %d\n", argc);
|
unsigned int envc = 1;
|
||||||
if (argc > 1 && !init_second_stage_executed) {
|
do {
|
||||||
const char __user *p = get_user_arg_ptr(*argv, 1);
|
pr_info("%s: envp[%d]: %s\n", __func__, envc, envp_n);
|
||||||
if (p && !IS_ERR(p)) {
|
envp_n += strlen(envp_n) + 1;
|
||||||
char first_arg[16];
|
envc++;
|
||||||
strncpy_from_user_nofault(first_arg, p, sizeof(first_arg));
|
} while (envp_n < envp + 256);
|
||||||
pr_info("/system/bin/init first arg: %s\n", first_arg);
|
#endif
|
||||||
if (!strcmp(first_arg, "second_stage")) {
|
|
||||||
pr_info("/system/bin/init second_stage executed\n");
|
if (init_second_stage_executed)
|
||||||
|
goto first_app_process;
|
||||||
|
|
||||||
|
// /system/bin/init with argv1
|
||||||
|
if (!init_second_stage_executed
|
||||||
|
&& (!memcmp(filename, system_bin_init, sizeof(system_bin_init) - 1))) {
|
||||||
|
if (argv1 && !strcmp(argv1, "second_stage")) {
|
||||||
|
pr_info("%s: /system/bin/init second_stage executed\n", __func__);
|
||||||
apply_kernelsu_rules();
|
apply_kernelsu_rules();
|
||||||
init_second_stage_executed = true;
|
init_second_stage_executed = true;
|
||||||
}
|
ksu_android_ns_fs_check();
|
||||||
} else {
|
|
||||||
pr_err("/system/bin/init parse args err!\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (unlikely(!memcmp(filename->name, old_system_init,
|
|
||||||
sizeof(old_system_init) - 1) &&
|
|
||||||
argv)) {
|
|
||||||
// /init executed
|
|
||||||
int argc = count(*argv, MAX_ARG_STRINGS);
|
|
||||||
pr_info("/init argc: %d\n", argc);
|
|
||||||
if (argc > 1 && !init_second_stage_executed) {
|
|
||||||
/* This applies to versions between Android 6 ~ 7 */
|
|
||||||
const char __user *p = get_user_arg_ptr(*argv, 1);
|
|
||||||
if (p && !IS_ERR(p)) {
|
|
||||||
char first_arg[16];
|
|
||||||
strncpy_from_user_nofault(first_arg, p, sizeof(first_arg));
|
|
||||||
pr_info("/init first arg: %s\n", first_arg);
|
|
||||||
if (!strcmp(first_arg, "--second-stage")) {
|
|
||||||
pr_info("/init second_stage executed\n");
|
|
||||||
apply_kernelsu_rules();
|
|
||||||
init_second_stage_executed = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pr_err("/init parse args err!\n");
|
|
||||||
}
|
|
||||||
} else if (argc == 1 && !init_second_stage_executed && envp) {
|
|
||||||
/* This applies to versions between Android 8 ~ 9 */
|
|
||||||
int envc = count(*envp, MAX_ARG_STRINGS);
|
|
||||||
if (envc > 0) {
|
|
||||||
int n;
|
|
||||||
for (n = 1; n <= envc; n++) {
|
|
||||||
const char __user *p = get_user_arg_ptr(*envp, n);
|
|
||||||
if (!p || IS_ERR(p)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
char env[256];
|
|
||||||
// Reading environment variable strings from user space
|
|
||||||
if (strncpy_from_user_nofault(env, p, sizeof(env)) < 0)
|
|
||||||
continue;
|
|
||||||
// Parsing environment variable names and values
|
|
||||||
char *env_name = env;
|
|
||||||
char *env_value = strchr(env, '=');
|
|
||||||
if (env_value == NULL)
|
|
||||||
continue;
|
|
||||||
// Replace equal sign with string terminator
|
|
||||||
*env_value = '\0';
|
|
||||||
env_value++;
|
|
||||||
// Check if the environment variable name and value are matching
|
|
||||||
if (!strcmp(env_name, "INIT_SECOND_STAGE") &&
|
|
||||||
(!strcmp(env_value, "1") ||
|
|
||||||
!strcmp(env_value, "true"))) {
|
|
||||||
pr_info("/init second_stage executed\n");
|
|
||||||
apply_kernelsu_rules();
|
|
||||||
init_second_stage_executed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(first_app_process && !memcmp(filename->name, app_process,
|
// /init with argv1
|
||||||
sizeof(app_process) - 1))) {
|
if (!init_second_stage_executed
|
||||||
|
&& (!memcmp(filename, old_system_init, sizeof(old_system_init) - 1))) {
|
||||||
|
if (argv1 && !strcmp(argv1, "--second-stage")) {
|
||||||
|
pr_info("%s: /init --second-stage executed\n", __func__);
|
||||||
|
apply_kernelsu_rules();
|
||||||
|
init_second_stage_executed = true;
|
||||||
|
ksu_android_ns_fs_check();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!envp || !envp_len)
|
||||||
|
goto first_app_process;
|
||||||
|
|
||||||
|
// /init without argv1/useless-argv1 but usable envp
|
||||||
|
// untested! TODO: test and debug me!
|
||||||
|
if (!init_second_stage_executed && (!memcmp(filename, old_system_init, sizeof(old_system_init) - 1))) {
|
||||||
|
|
||||||
|
// we hunt for "INIT_SECOND_STAGE"
|
||||||
|
const char *envp_n = envp;
|
||||||
|
unsigned int envc = 1;
|
||||||
|
do {
|
||||||
|
if (strstarts(envp_n, "INIT_SECOND_STAGE"))
|
||||||
|
break;
|
||||||
|
envp_n += strlen(envp_n) + 1;
|
||||||
|
envc++;
|
||||||
|
} while (envp_n < envp + envp_len);
|
||||||
|
pr_info("%s: envp[%d]: %s\n", __func__, envc, envp_n);
|
||||||
|
|
||||||
|
if (!strcmp(envp_n, "INIT_SECOND_STAGE=1")
|
||||||
|
|| !strcmp(envp_n, "INIT_SECOND_STAGE=true") ) {
|
||||||
|
pr_info("%s: /init +envp: INIT_SECOND_STAGE executed\n", __func__);
|
||||||
|
apply_kernelsu_rules();
|
||||||
|
init_second_stage_executed = true;
|
||||||
|
ksu_android_ns_fs_check();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
first_app_process:
|
||||||
|
if (first_app_process && !memcmp(filename, app_process, sizeof(app_process) - 1)) {
|
||||||
first_app_process = false;
|
first_app_process = false;
|
||||||
pr_info("exec app_process, /data prepared, second_stage: %d\n",
|
pr_info("%s: exec app_process, /data prepared, second_stage: %d\n", __func__, init_second_stage_executed);
|
||||||
init_second_stage_executed);
|
on_post_fs_data();
|
||||||
struct task_struct *init_task;
|
|
||||||
rcu_read_lock();
|
|
||||||
init_task = rcu_dereference(current->real_parent);
|
|
||||||
if (init_task) {
|
|
||||||
task_work_add(init_task, &on_post_fs_data_cb, TWA_RESUME);
|
|
||||||
}
|
|
||||||
rcu_read_unlock();
|
|
||||||
|
|
||||||
stop_execve_hook();
|
stop_execve_hook();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ksu_handle_pre_ksud(const char *filename)
|
||||||
|
{
|
||||||
|
if (likely(!ksu_execveat_hook))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// not /system/bin/init, not /init, not /system/bin/app_process (64/32 thingy)
|
||||||
|
// return 0;
|
||||||
|
if (likely(strcmp(filename, "/system/bin/init") && strcmp(filename, "/init")
|
||||||
|
&& !strstarts(filename, "/system/bin/app_process") ))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!current || !current->mm)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// https://elixir.bootlin.com/linux/v4.14.1/source/include/linux/mm_types.h#L429
|
||||||
|
// unsigned long arg_start, arg_end, env_start, env_end;
|
||||||
|
unsigned long arg_start = current->mm->arg_start;
|
||||||
|
unsigned long arg_end = current->mm->arg_end;
|
||||||
|
unsigned long env_start = current->mm->env_start;
|
||||||
|
unsigned long env_end = current->mm->env_end;
|
||||||
|
|
||||||
|
size_t arg_len = arg_end - arg_start;
|
||||||
|
size_t envp_len = env_end - env_start;
|
||||||
|
|
||||||
|
if (arg_len <= 0 || envp_len <= 0) // this wont make sense, filter it
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
#define ARGV_MAX 32 // this is enough for argv1
|
||||||
|
#define ENVP_MAX 256 // this is enough for INIT_SECOND_STAGE
|
||||||
|
char args[ARGV_MAX];
|
||||||
|
size_t argv_copy_len = (arg_len > ARGV_MAX) ? ARGV_MAX : arg_len;
|
||||||
|
char envp[ENVP_MAX];
|
||||||
|
size_t envp_copy_len = (envp_len > ENVP_MAX) ? ENVP_MAX : envp_len;
|
||||||
|
|
||||||
|
// we cant use strncpy on here, else it will truncate once it sees \0
|
||||||
|
if (ksu_copy_from_user_retry(args, (void __user *)arg_start, argv_copy_len))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (ksu_copy_from_user_retry(envp, (void __user *)env_start, envp_copy_len))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
args[ARGV_MAX - 1] = '\0';
|
||||||
|
envp[ENVP_MAX - 1] = '\0';
|
||||||
|
|
||||||
|
// we only need argv1 !
|
||||||
|
// abuse strlen here since it only gets length up to \0
|
||||||
|
char *argv1 = args + strlen(args) + 1;
|
||||||
|
if (argv1 >= args + argv_copy_len) // out of bounds!
|
||||||
|
argv1 = "";
|
||||||
|
|
||||||
|
return ksu_handle_bprm_ksud(filename, argv1, envp, envp_copy_len);
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t (*orig_read)(struct file *, char __user *, size_t, loff_t *);
|
static ssize_t (*orig_read)(struct file *, char __user *, size_t, loff_t *);
|
||||||
static ssize_t (*orig_read_iter)(struct kiocb *, struct iov_iter *);
|
static ssize_t (*orig_read_iter)(struct kiocb *, struct iov_iter *);
|
||||||
static struct file_operations fops_proxy;
|
static struct file_operations fops_proxy;
|
||||||
|
|
@ -340,7 +244,8 @@ static ssize_t read_proxy(struct file *file, char __user *buf, size_t count,
|
||||||
bool first_read = file->f_pos == 0;
|
bool first_read = file->f_pos == 0;
|
||||||
ssize_t ret = orig_read(file, buf, count, pos);
|
ssize_t ret = orig_read(file, buf, count, pos);
|
||||||
if (first_read) {
|
if (first_read) {
|
||||||
pr_info("read_proxy append %ld + %ld\n", ret, read_count_append);
|
pr_info("read_proxy append %ld + %ld\n", ret,
|
||||||
|
read_count_append);
|
||||||
ret += read_count_append;
|
ret += read_count_append;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -351,16 +256,17 @@ static ssize_t read_iter_proxy(struct kiocb *iocb, struct iov_iter *to)
|
||||||
bool first_read = iocb->ki_pos == 0;
|
bool first_read = iocb->ki_pos == 0;
|
||||||
ssize_t ret = orig_read_iter(iocb, to);
|
ssize_t ret = orig_read_iter(iocb, to);
|
||||||
if (first_read) {
|
if (first_read) {
|
||||||
pr_info("read_iter_proxy append %ld + %ld\n", ret, read_count_append);
|
pr_info("read_iter_proxy append %ld + %ld\n", ret,
|
||||||
|
read_count_append);
|
||||||
ret += read_count_append;
|
ret += read_count_append;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr,
|
||||||
size_t *count_ptr, loff_t **pos)
|
size_t *count_ptr, loff_t **pos)
|
||||||
{
|
{
|
||||||
#ifndef KSU_KPROBES_HOOK
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
if (!ksu_vfs_read_hook) {
|
if (!ksu_vfs_read_hook) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -473,7 +379,7 @@ static bool is_volumedown_enough(unsigned int count)
|
||||||
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
|
||||||
int *value)
|
int *value)
|
||||||
{
|
{
|
||||||
#ifndef KSU_KPROBES_HOOK
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
if (!ksu_input_hook) {
|
if (!ksu_input_hook) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -515,28 +421,124 @@ bool ksu_is_safe_mode()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef KSU_KPROBES_HOOK
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
|
||||||
static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
static int sys_execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
asmlinkage int sys_execve(const char __user *filenamei,
|
||||||
|
const char __user *const __user *argv,
|
||||||
|
const char __user *const __user *envp, struct pt_regs *regs)
|
||||||
|
*/
|
||||||
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||||
const char __user **filename_user =
|
const char __user *filename_user = (const char __user *)PT_REGS_PARM1(real_regs);
|
||||||
(const char **)&PT_REGS_PARM1(real_regs);
|
const char __user *const __user *__argv = (const char __user *const __user *)PT_REGS_PARM2(real_regs);
|
||||||
const char __user *const __user *__argv =
|
const char __user *const __user *__envp = (const char __user *const __user *)PT_REGS_PARM3(real_regs);
|
||||||
(const char __user *const __user *)PT_REGS_PARM2(real_regs);
|
|
||||||
struct user_arg_ptr argv = { .ptr.native = __argv };
|
|
||||||
struct filename filename_in, *filename_p;
|
|
||||||
char path[32];
|
char path[32];
|
||||||
|
|
||||||
if (!filename_user)
|
if (!filename_user)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
memset(path, 0, sizeof(path));
|
// filename stage
|
||||||
strncpy_from_user_nofault(path, *filename_user, 32);
|
if (ksu_copy_from_user_retry(path, filename_user, sizeof(path)))
|
||||||
filename_in.name = path;
|
return 0;
|
||||||
|
|
||||||
filename_p = &filename_in;
|
path[sizeof(path) - 1] = '\0';
|
||||||
return ksu_handle_execveat_ksud(AT_FDCWD, &filename_p, &argv, NULL, NULL);
|
|
||||||
|
// not /system/bin/init, not /init, not /system/bin/app_process (64/32 thingy)
|
||||||
|
// we dont care !!
|
||||||
|
if (likely(strcmp(path, "/system/bin/init") && strcmp(path, "/init")
|
||||||
|
&& !strstarts(path, "/system/bin/app_process") ))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// argv stage
|
||||||
|
char argv1[32] = {0};
|
||||||
|
// memzero_explicit(argv1, 32);
|
||||||
|
if (__argv) {
|
||||||
|
const char __user *arg1_user = NULL;
|
||||||
|
// grab argv[1] pointer
|
||||||
|
// this looks like
|
||||||
|
/*
|
||||||
|
* 0x1000 ./program << this is __argv
|
||||||
|
* 0x1001 -o
|
||||||
|
* 0x1002 arg
|
||||||
|
*/
|
||||||
|
if (ksu_copy_from_user_retry(&arg1_user, __argv + 1, sizeof(arg1_user)))
|
||||||
|
goto no_argv1; // copy argv[1] pointer fail, probably no argv1 !!
|
||||||
|
|
||||||
|
if (arg1_user)
|
||||||
|
ksu_copy_from_user_retry(argv1, arg1_user, sizeof(argv1));
|
||||||
|
}
|
||||||
|
|
||||||
|
no_argv1:
|
||||||
|
argv1[sizeof(argv1) - 1] = '\0';
|
||||||
|
|
||||||
|
// envp stage
|
||||||
|
#define ENVP_MAX 256
|
||||||
|
char envp[ENVP_MAX] = {0};
|
||||||
|
char *dst = envp;
|
||||||
|
size_t envp_len = 0;
|
||||||
|
int i = 0; // to track user pointer offset from __envp
|
||||||
|
|
||||||
|
// memzero_explicit(envp, ENVP_MAX);
|
||||||
|
|
||||||
|
if (__envp) {
|
||||||
|
do {
|
||||||
|
const char __user *env_entry_user = NULL;
|
||||||
|
// this is also like argv above
|
||||||
|
/*
|
||||||
|
* 0x1001 PATH=/bin
|
||||||
|
* 0x1002 VARIABLE=value
|
||||||
|
* 0x1002 some_more_env_var=1
|
||||||
|
*/
|
||||||
|
|
||||||
|
// check if pointer exists
|
||||||
|
if (ksu_copy_from_user_retry(&env_entry_user, __envp + i, sizeof(env_entry_user)))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// check if no more env entry
|
||||||
|
if (!env_entry_user)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// probably redundant to while condition but ok
|
||||||
|
if (envp_len >= ENVP_MAX - 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// copy strings from env_entry_user pointer that we collected
|
||||||
|
// also break if failed
|
||||||
|
if (ksu_copy_from_user_retry(dst, env_entry_user, ENVP_MAX - envp_len))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// get the length of that new copy above
|
||||||
|
// get lngth of dst as far as ENVP_MAX - current collected envp_len
|
||||||
|
size_t len = strnlen(dst, ENVP_MAX - envp_len);
|
||||||
|
if (envp_len + len + 1 > ENVP_MAX)
|
||||||
|
break; // if more than 255 bytes, bail
|
||||||
|
|
||||||
|
dst[len] = '\0';
|
||||||
|
// collect total number of copied strings
|
||||||
|
envp_len = envp_len + len + 1;
|
||||||
|
// increment dst address since we need to put something on next iter
|
||||||
|
dst = dst + len + 1;
|
||||||
|
// pointer walk, __envp + i
|
||||||
|
i++;
|
||||||
|
} while (envp_len < ENVP_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
at this point, we shoul've collected envp from
|
||||||
|
* 0x1001 PATH=/bin
|
||||||
|
* 0x1002 VARIABLE=value
|
||||||
|
* 0x1002 some_more_env_var=1
|
||||||
|
to
|
||||||
|
* 0x1234 PATH=/bin\0VARIABLE=value\0some_more_env_var=1\0\0\0\0
|
||||||
|
*/
|
||||||
|
|
||||||
|
envp[ENVP_MAX - 1] = '\0';
|
||||||
|
|
||||||
|
#ifdef CONFIG_KSU_DEBUG
|
||||||
|
pr_info("%s: filename: %s argv[1]:%s envp_len: %zu\n", __func__, path, argv1, envp_len);
|
||||||
|
#endif
|
||||||
|
return ksu_handle_bprm_ksud(path, argv1, envp, envp_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sys_read_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
static int sys_read_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
|
|
@ -573,6 +575,7 @@ static struct kprobe input_event_kp = {
|
||||||
.pre_handler = input_handle_event_handler_pre,
|
.pre_handler = input_handle_event_handler_pre,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static void do_stop_vfs_read_hook(struct work_struct *work)
|
static void do_stop_vfs_read_hook(struct work_struct *work)
|
||||||
{
|
{
|
||||||
unregister_kprobe(&vfs_read_kp);
|
unregister_kprobe(&vfs_read_kp);
|
||||||
|
|
@ -591,7 +594,7 @@ static void do_stop_input_hook(struct work_struct *work)
|
||||||
|
|
||||||
static void stop_vfs_read_hook()
|
static void stop_vfs_read_hook()
|
||||||
{
|
{
|
||||||
#ifdef KSU_KPROBES_HOOK
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
bool ret = schedule_work(&stop_vfs_read_work);
|
bool ret = schedule_work(&stop_vfs_read_work);
|
||||||
pr_info("unregister vfs_read kprobe: %d!\n", ret);
|
pr_info("unregister vfs_read kprobe: %d!\n", ret);
|
||||||
#else
|
#else
|
||||||
|
|
@ -602,13 +605,13 @@ static void stop_vfs_read_hook()
|
||||||
|
|
||||||
static void stop_execve_hook()
|
static void stop_execve_hook()
|
||||||
{
|
{
|
||||||
#ifdef KSU_KPROBES_HOOK
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
bool ret = schedule_work(&stop_execve_hook_work);
|
bool ret = schedule_work(&stop_execve_hook_work);
|
||||||
pr_info("unregister execve kprobe: %d!\n", ret);
|
pr_info("unregister execve kprobe: %d!\n", ret);
|
||||||
#else
|
#else
|
||||||
ksu_execveat_hook = false;
|
|
||||||
pr_info("stop execve_hook\n");
|
pr_info("stop execve_hook\n");
|
||||||
#endif
|
#endif
|
||||||
|
ksu_execveat_hook = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stop_input_hook()
|
static void stop_input_hook()
|
||||||
|
|
@ -618,7 +621,7 @@ static void stop_input_hook()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
input_hook_stopped = true;
|
input_hook_stopped = true;
|
||||||
#ifdef KSU_KPROBES_HOOK
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
bool ret = schedule_work(&stop_input_hook_work);
|
bool ret = schedule_work(&stop_input_hook_work);
|
||||||
pr_info("unregister input kprobe: %d!\n", ret);
|
pr_info("unregister input kprobe: %d!\n", ret);
|
||||||
#else
|
#else
|
||||||
|
|
@ -630,7 +633,7 @@ static void stop_input_hook()
|
||||||
// ksud: module support
|
// ksud: module support
|
||||||
void ksu_ksud_init()
|
void ksu_ksud_init()
|
||||||
{
|
{
|
||||||
#ifdef KSU_KPROBES_HOOK
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = register_kprobe(&execve_kp);
|
ret = register_kprobe(&execve_kp);
|
||||||
|
|
@ -650,11 +653,12 @@ void ksu_ksud_init()
|
||||||
|
|
||||||
void ksu_ksud_exit()
|
void ksu_ksud_exit()
|
||||||
{
|
{
|
||||||
#ifdef KSU_KPROBES_HOOK
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
unregister_kprobe(&execve_kp);
|
unregister_kprobe(&execve_kp);
|
||||||
// this should be done before unregister vfs_read_kp
|
// this should be done before unregister vfs_read_kp
|
||||||
// unregister_kprobe(&vfs_read_kp);
|
// unregister_kprobe(&vfs_read_kp);
|
||||||
unregister_kprobe(&input_event_kp);
|
unregister_kprobe(&input_event_kp);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
is_boot_phase = false;
|
is_boot_phase = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,13 @@
|
||||||
|
|
||||||
#define KSUD_PATH "/data/adb/ksud"
|
#define KSUD_PATH "/data/adb/ksud"
|
||||||
|
|
||||||
void ksu_ksud_init();
|
|
||||||
void ksu_ksud_exit();
|
|
||||||
|
|
||||||
void on_post_fs_data(void);
|
void on_post_fs_data(void);
|
||||||
void on_module_mounted(void);
|
|
||||||
void on_boot_completed(void);
|
|
||||||
|
|
||||||
bool ksu_is_safe_mode(void);
|
bool ksu_is_safe_mode(void);
|
||||||
|
|
||||||
int nuke_ext4_sysfs(const char* mnt);
|
extern u32 ksu_devpts_sid;
|
||||||
|
|
||||||
extern u32 ksu_file_sid;
|
extern bool ksu_execveat_hook __read_mostly;
|
||||||
extern bool ksu_module_mounted;
|
extern int ksu_handle_pre_ksud(const char *filename);
|
||||||
extern bool ksu_boot_completed;
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,18 @@ extern void ksu_add_manager(uid_t uid, int signature_index);
|
||||||
extern void ksu_remove_manager(uid_t uid);
|
extern void ksu_remove_manager(uid_t uid);
|
||||||
extern int ksu_get_manager_signature_index(uid_t uid);
|
extern int ksu_get_manager_signature_index(uid_t uid);
|
||||||
|
|
||||||
static inline bool ksu_is_manager_uid_valid(void)
|
static inline bool ksu_is_manager_uid_valid()
|
||||||
{
|
{
|
||||||
return ksu_manager_uid != KSU_INVALID_UID;
|
return ksu_manager_uid != KSU_INVALID_UID;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool is_manager(void)
|
static inline bool is_manager()
|
||||||
{
|
{
|
||||||
return unlikely(ksu_is_any_manager(current_uid().val) ||
|
return unlikely(ksu_is_any_manager(current_uid().val) ||
|
||||||
(ksu_manager_uid != KSU_INVALID_UID && ksu_manager_uid == current_uid().val));
|
(ksu_manager_uid != KSU_INVALID_UID && ksu_manager_uid == current_uid().val));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uid_t ksu_get_manager_uid(void)
|
static inline uid_t ksu_get_manager_uid()
|
||||||
{
|
{
|
||||||
return ksu_manager_uid;
|
return ksu_manager_uid;
|
||||||
}
|
}
|
||||||
|
|
@ -34,10 +34,9 @@ static inline void ksu_set_manager_uid(uid_t uid)
|
||||||
ksu_manager_uid = uid;
|
ksu_manager_uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void ksu_invalidate_manager_uid(void)
|
static inline void ksu_invalidate_manager_uid()
|
||||||
{
|
{
|
||||||
ksu_manager_uid = KSU_INVALID_UID;
|
ksu_manager_uid = KSU_INVALID_UID;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ksu_observer_init(void);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -9,9 +9,5 @@
|
||||||
#define EXPECTED_SIZE_OTHER 0x300
|
#define EXPECTED_SIZE_OTHER 0x300
|
||||||
#define EXPECTED_HASH_OTHER "0000000000000000000000000000000000000000000000000000000000000000"
|
#define EXPECTED_HASH_OTHER "0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
unsigned size;
|
|
||||||
const char *sha256;
|
|
||||||
} apk_sign_key_t;
|
|
||||||
|
|
||||||
#endif /* MANAGER_SIGN_H */
|
#endif /* MANAGER_SIGN_H */
|
||||||
|
|
@ -1,357 +0,0 @@
|
||||||
#include <linux/string.h>
|
|
||||||
#include <linux/uaccess.h>
|
|
||||||
#include <linux/printk.h>
|
|
||||||
#include <linux/cred.h>
|
|
||||||
#include <linux/fs.h>
|
|
||||||
#include <linux/uaccess.h>
|
|
||||||
#include <linux/file.h>
|
|
||||||
#include <linux/mm.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/binfmts.h>
|
|
||||||
|
|
||||||
#include "manual_su.h"
|
|
||||||
#include "ksu.h"
|
|
||||||
#include "allowlist.h"
|
|
||||||
#include "manager.h"
|
|
||||||
#include "app_profile.h"
|
|
||||||
|
|
||||||
static bool current_verified = false;
|
|
||||||
static void ksu_cleanup_expired_tokens(void);
|
|
||||||
static bool is_current_verified(void);
|
|
||||||
static void add_pending_root(uid_t uid);
|
|
||||||
|
|
||||||
static struct pending_uid pending_uids[MAX_PENDING] = {0};
|
|
||||||
static int pending_cnt = 0;
|
|
||||||
static struct ksu_token_entry auth_tokens[MAX_TOKENS] = {0};
|
|
||||||
static int token_count = 0;
|
|
||||||
static DEFINE_SPINLOCK(token_lock);
|
|
||||||
|
|
||||||
static char* get_token_from_envp(void)
|
|
||||||
{
|
|
||||||
struct mm_struct *mm;
|
|
||||||
char *envp_start, *envp_end;
|
|
||||||
char *env_ptr, *token = NULL;
|
|
||||||
unsigned long env_len;
|
|
||||||
char *env_copy = NULL;
|
|
||||||
|
|
||||||
if (!current->mm)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
mm = current->mm;
|
|
||||||
|
|
||||||
down_read(&mm->mmap_lock);
|
|
||||||
|
|
||||||
envp_start = (char *)mm->env_start;
|
|
||||||
envp_end = (char *)mm->env_end;
|
|
||||||
env_len = envp_end - envp_start;
|
|
||||||
|
|
||||||
if (env_len <= 0 || env_len > PAGE_SIZE * 32) {
|
|
||||||
up_read(&mm->mmap_lock);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
env_copy = kzalloc(env_len + 1, GFP_KERNEL);
|
|
||||||
if (!env_copy) {
|
|
||||||
up_read(&mm->mmap_lock);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (copy_from_user(env_copy, envp_start, env_len)) {
|
|
||||||
kfree(env_copy);
|
|
||||||
up_read(&mm->mmap_lock);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
up_read(&mm->mmap_lock);
|
|
||||||
|
|
||||||
env_copy[env_len] = '\0';
|
|
||||||
env_ptr = env_copy;
|
|
||||||
|
|
||||||
while (env_ptr < env_copy + env_len) {
|
|
||||||
if (strncmp(env_ptr, KSU_TOKEN_ENV_NAME "=", strlen(KSU_TOKEN_ENV_NAME) + 1) == 0) {
|
|
||||||
char *token_start = env_ptr + strlen(KSU_TOKEN_ENV_NAME) + 1;
|
|
||||||
char *token_end = strchr(token_start, '\0');
|
|
||||||
|
|
||||||
if (token_end && (token_end - token_start) == KSU_TOKEN_LENGTH) {
|
|
||||||
token = kzalloc(KSU_TOKEN_LENGTH + 1, GFP_KERNEL);
|
|
||||||
if (token) {
|
|
||||||
memcpy(token, token_start, KSU_TOKEN_LENGTH);
|
|
||||||
token[KSU_TOKEN_LENGTH] = '\0';
|
|
||||||
pr_info("manual_su: found auth token in environment\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
env_ptr += strlen(env_ptr) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
kfree(env_copy);
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char* ksu_generate_auth_token(void)
|
|
||||||
{
|
|
||||||
static char token_buffer[KSU_TOKEN_LENGTH + 1];
|
|
||||||
unsigned long flags;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
ksu_cleanup_expired_tokens();
|
|
||||||
|
|
||||||
spin_lock_irqsave(&token_lock, flags);
|
|
||||||
|
|
||||||
if (token_count >= MAX_TOKENS) {
|
|
||||||
for (i = 0; i < MAX_TOKENS - 1; i++) {
|
|
||||||
auth_tokens[i] = auth_tokens[i + 1];
|
|
||||||
}
|
|
||||||
token_count = MAX_TOKENS - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < KSU_TOKEN_LENGTH; i++) {
|
|
||||||
u8 rand_byte;
|
|
||||||
get_random_bytes(&rand_byte, 1);
|
|
||||||
int char_type = rand_byte % 3;
|
|
||||||
if (char_type == 0) {
|
|
||||||
token_buffer[i] = 'A' + (rand_byte % 26);
|
|
||||||
} else if (char_type == 1) {
|
|
||||||
token_buffer[i] = 'a' + (rand_byte % 26);
|
|
||||||
} else {
|
|
||||||
token_buffer[i] = '0' + (rand_byte % 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
|
||||||
strscpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
|
|
||||||
#else
|
|
||||||
strlcpy(auth_tokens[token_count].token, token_buffer, KSU_TOKEN_LENGTH + 1);
|
|
||||||
#endif
|
|
||||||
auth_tokens[token_count].expire_time = jiffies + KSU_TOKEN_EXPIRE_TIME * HZ;
|
|
||||||
auth_tokens[token_count].used = false;
|
|
||||||
token_count++;
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&token_lock, flags);
|
|
||||||
|
|
||||||
pr_info("manual_su: generated new auth token (expires in %d seconds)\n", KSU_TOKEN_EXPIRE_TIME);
|
|
||||||
return token_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ksu_verify_auth_token(const char *token)
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
bool valid = false;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (!token || strlen(token) != KSU_TOKEN_LENGTH) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock_irqsave(&token_lock, flags);
|
|
||||||
|
|
||||||
for (i = 0; i < token_count; i++) {
|
|
||||||
if (!auth_tokens[i].used &&
|
|
||||||
time_before(jiffies, auth_tokens[i].expire_time) &&
|
|
||||||
strcmp(auth_tokens[i].token, token) == 0) {
|
|
||||||
|
|
||||||
auth_tokens[i].used = true;
|
|
||||||
valid = true;
|
|
||||||
pr_info("manual_su: auth token verified successfully\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&token_lock, flags);
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
pr_warn("manual_su: invalid or expired auth token\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ksu_cleanup_expired_tokens(void)
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
int i, j;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&token_lock, flags);
|
|
||||||
|
|
||||||
for (i = 0; i < token_count; ) {
|
|
||||||
if (time_after(jiffies, auth_tokens[i].expire_time) || auth_tokens[i].used) {
|
|
||||||
for (j = i; j < token_count - 1; j++) {
|
|
||||||
auth_tokens[j] = auth_tokens[j + 1];
|
|
||||||
}
|
|
||||||
token_count--;
|
|
||||||
pr_debug("manual_su: cleaned up expired/used token\n");
|
|
||||||
} else {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&token_lock, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int handle_token_generation(struct manual_su_request *request)
|
|
||||||
{
|
|
||||||
if (current_uid().val > 2000) {
|
|
||||||
pr_warn("manual_su: token generation denied for app UID %d\n", current_uid().val);
|
|
||||||
return -EPERM;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *new_token = ksu_generate_auth_token();
|
|
||||||
if (!new_token) {
|
|
||||||
pr_err("manual_su: failed to generate token\n");
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
|
||||||
strscpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
|
||||||
#else
|
|
||||||
strlcpy(request->token_buffer, new_token, KSU_TOKEN_LENGTH + 1);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
pr_info("manual_su: auth token generated successfully\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int handle_escalation_request(struct manual_su_request *request)
|
|
||||||
{
|
|
||||||
uid_t target_uid = request->target_uid;
|
|
||||||
pid_t target_pid = request->target_pid;
|
|
||||||
struct task_struct *tsk;
|
|
||||||
|
|
||||||
rcu_read_lock();
|
|
||||||
tsk = pid_task(find_vpid(target_pid), PIDTYPE_PID);
|
|
||||||
if (!tsk || ksu_task_is_dead(tsk)) {
|
|
||||||
rcu_read_unlock();
|
|
||||||
pr_err("cmd_su: PID %d is invalid or dead\n", target_pid);
|
|
||||||
return -ESRCH;
|
|
||||||
}
|
|
||||||
rcu_read_unlock();
|
|
||||||
|
|
||||||
if (current_uid().val == 0 || is_manager() || ksu_is_allow_uid_for_current(current_uid().val))
|
|
||||||
goto allowed;
|
|
||||||
|
|
||||||
char *env_token = get_token_from_envp();
|
|
||||||
if (!env_token) {
|
|
||||||
pr_warn("manual_su: no auth token found in environment\n");
|
|
||||||
return -EACCES;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool token_valid = ksu_verify_auth_token(env_token);
|
|
||||||
kfree(env_token);
|
|
||||||
|
|
||||||
if (!token_valid) {
|
|
||||||
pr_warn("manual_su: token verification failed\n");
|
|
||||||
return -EACCES;
|
|
||||||
}
|
|
||||||
|
|
||||||
allowed:
|
|
||||||
current_verified = true;
|
|
||||||
escape_to_root_for_cmd_su(target_uid, target_pid);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int handle_add_pending_request(struct manual_su_request *request)
|
|
||||||
{
|
|
||||||
uid_t target_uid = request->target_uid;
|
|
||||||
|
|
||||||
if (!is_current_verified()) {
|
|
||||||
pr_warn("manual_su: add_pending denied, not verified\n");
|
|
||||||
return -EPERM;
|
|
||||||
}
|
|
||||||
|
|
||||||
add_pending_root(target_uid);
|
|
||||||
current_verified = false;
|
|
||||||
pr_info("manual_su: pending root added for UID %d\n", target_uid);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_handle_manual_su_request(int option, struct manual_su_request *request)
|
|
||||||
{
|
|
||||||
if (!request) {
|
|
||||||
pr_err("manual_su: invalid request pointer\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (option) {
|
|
||||||
case MANUAL_SU_OP_GENERATE_TOKEN:
|
|
||||||
pr_info("manual_su: handling token generation request\n");
|
|
||||||
return handle_token_generation(request);
|
|
||||||
|
|
||||||
case MANUAL_SU_OP_ESCALATE:
|
|
||||||
pr_info("manual_su: handling escalation request for UID %d, PID %d\n",
|
|
||||||
request->target_uid, request->target_pid);
|
|
||||||
return handle_escalation_request(request);
|
|
||||||
|
|
||||||
case MANUAL_SU_OP_ADD_PENDING:
|
|
||||||
pr_info("manual_su: handling add pending request for UID %d\n", request->target_uid);
|
|
||||||
return handle_add_pending_request(request);
|
|
||||||
|
|
||||||
default:
|
|
||||||
pr_err("manual_su: unknown option %d\n", option);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_current_verified(void)
|
|
||||||
{
|
|
||||||
return current_verified;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_pending_root(uid_t uid)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < pending_cnt; i++) {
|
|
||||||
if (pending_uids[i].uid == uid) {
|
|
||||||
pending_uids[i].use_count++;
|
|
||||||
pending_uids[i].remove_calls++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove_pending_root(uid_t uid)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < pending_cnt; i++) {
|
|
||||||
if (pending_uids[i].uid == uid) {
|
|
||||||
pending_uids[i].remove_calls++;
|
|
||||||
|
|
||||||
if (pending_uids[i].remove_calls >= REMOVE_DELAY_CALLS) {
|
|
||||||
pending_uids[i] = pending_uids[--pending_cnt];
|
|
||||||
pr_info("pending_root: removed UID %d after %d calls\n", uid, REMOVE_DELAY_CALLS);
|
|
||||||
ksu_temp_revoke_root_once(uid);
|
|
||||||
} else {
|
|
||||||
pr_info("pending_root: UID %d remove_call=%d (<%d)\n",
|
|
||||||
uid, pending_uids[i].remove_calls, REMOVE_DELAY_CALLS);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_pending_root(uid_t uid)
|
|
||||||
{
|
|
||||||
if (pending_cnt >= MAX_PENDING) {
|
|
||||||
pr_warn("pending_root: cache full\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < pending_cnt; i++) {
|
|
||||||
if (pending_uids[i].uid == uid) {
|
|
||||||
pending_uids[i].use_count = 0;
|
|
||||||
pending_uids[i].remove_calls = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pending_uids[pending_cnt++] = (struct pending_uid){uid, 0};
|
|
||||||
ksu_temp_grant_root_once(uid);
|
|
||||||
pr_info("pending_root: cached UID %d\n", uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_try_escalate_for_uid(uid_t uid)
|
|
||||||
{
|
|
||||||
if (!is_pending_root(uid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
pr_info("pending_root: UID=%d temporarily allowed\n", uid);
|
|
||||||
remove_pending_root(uid);
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
#ifndef __KSU_MANUAL_SU_H
|
|
||||||
#define __KSU_MANUAL_SU_H
|
|
||||||
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/sched.h>
|
|
||||||
#include <linux/version.h>
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
|
|
||||||
#define mmap_lock mmap_sem
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define ksu_task_is_dead(t) ((t)->exit_state != 0)
|
|
||||||
|
|
||||||
#define MAX_PENDING 16
|
|
||||||
#define REMOVE_DELAY_CALLS 150
|
|
||||||
#define MAX_TOKENS 10
|
|
||||||
|
|
||||||
#define KSU_SU_VERIFIED_BIT (1UL << 0)
|
|
||||||
#define KSU_TOKEN_LENGTH 32
|
|
||||||
#define KSU_TOKEN_ENV_NAME "KSU_AUTH_TOKEN"
|
|
||||||
#define KSU_TOKEN_EXPIRE_TIME 150
|
|
||||||
|
|
||||||
#define MANUAL_SU_OP_GENERATE_TOKEN 0
|
|
||||||
#define MANUAL_SU_OP_ESCALATE 1
|
|
||||||
#define MANUAL_SU_OP_ADD_PENDING 2
|
|
||||||
|
|
||||||
struct pending_uid {
|
|
||||||
uid_t uid;
|
|
||||||
int use_count;
|
|
||||||
int remove_calls;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct manual_su_request {
|
|
||||||
uid_t target_uid;
|
|
||||||
pid_t target_pid;
|
|
||||||
char token_buffer[KSU_TOKEN_LENGTH + 1];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_token_entry {
|
|
||||||
char token[KSU_TOKEN_LENGTH + 1];
|
|
||||||
unsigned long expire_time;
|
|
||||||
bool used;
|
|
||||||
};
|
|
||||||
|
|
||||||
int ksu_handle_manual_su_request(int option, struct manual_su_request *request);
|
|
||||||
bool is_pending_root(uid_t uid);
|
|
||||||
void remove_pending_root(uid_t uid);
|
|
||||||
void ksu_try_escalate_for_uid(uid_t uid);
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/fs.h>
|
|
||||||
#include <linux/namei.h>
|
|
||||||
#include <linux/fsnotify_backend.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/rculist.h>
|
|
||||||
#include <linux/version.h>
|
|
||||||
#include "klog.h" // IWYU pragma: keep
|
|
||||||
#include "ksu.h"
|
|
||||||
#include "throne_tracker.h"
|
|
||||||
#include "throne_comm.h"
|
|
||||||
|
|
||||||
#define MASK_SYSTEM (FS_CREATE | FS_MOVE | FS_EVENT_ON_CHILD)
|
|
||||||
|
|
||||||
struct watch_dir {
|
|
||||||
const char *path;
|
|
||||||
u32 mask;
|
|
||||||
struct path kpath;
|
|
||||||
struct inode *inode;
|
|
||||||
struct fsnotify_mark *mark;
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct fsnotify_group *g;
|
|
||||||
|
|
||||||
static int ksu_handle_inode_event(struct fsnotify_mark *mark, u32 mask,
|
|
||||||
struct inode *inode, struct inode *dir,
|
|
||||||
const struct qstr *file_name, u32 cookie)
|
|
||||||
{
|
|
||||||
if (!file_name)
|
|
||||||
return 0;
|
|
||||||
if (mask & FS_ISDIR)
|
|
||||||
return 0;
|
|
||||||
if (file_name->len == 13 &&
|
|
||||||
!memcmp(file_name->name, "packages.list", 13)) {
|
|
||||||
pr_info("packages.list detected: %d\n", mask);
|
|
||||||
if (ksu_uid_scanner_enabled) {
|
|
||||||
ksu_request_userspace_scan();
|
|
||||||
}
|
|
||||||
track_throne(false);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct fsnotify_ops ksu_ops = {
|
|
||||||
.handle_inode_event = ksu_handle_inode_event,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int add_mark_on_inode(struct inode *inode, u32 mask,
|
|
||||||
struct fsnotify_mark **out)
|
|
||||||
{
|
|
||||||
struct fsnotify_mark *m;
|
|
||||||
|
|
||||||
m = kzalloc(sizeof(*m), GFP_KERNEL);
|
|
||||||
if (!m)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
fsnotify_init_mark(m, g);
|
|
||||||
m->mask = mask;
|
|
||||||
|
|
||||||
if (fsnotify_add_inode_mark(m, inode, 0)) {
|
|
||||||
fsnotify_put_mark(m);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
*out = m;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int watch_one_dir(struct watch_dir *wd)
|
|
||||||
{
|
|
||||||
int ret = kern_path(wd->path, LOOKUP_FOLLOW, &wd->kpath);
|
|
||||||
if (ret) {
|
|
||||||
pr_info("path not ready: %s (%d)\n", wd->path, ret);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
wd->inode = d_inode(wd->kpath.dentry);
|
|
||||||
ihold(wd->inode);
|
|
||||||
|
|
||||||
ret = add_mark_on_inode(wd->inode, wd->mask, &wd->mark);
|
|
||||||
if (ret) {
|
|
||||||
pr_err("Add mark failed for %s (%d)\n", wd->path, ret);
|
|
||||||
path_put(&wd->kpath);
|
|
||||||
iput(wd->inode);
|
|
||||||
wd->inode = NULL;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
pr_info("watching %s\n", wd->path);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unwatch_one_dir(struct watch_dir *wd)
|
|
||||||
{
|
|
||||||
if (wd->mark) {
|
|
||||||
fsnotify_destroy_mark(wd->mark, g);
|
|
||||||
fsnotify_put_mark(wd->mark);
|
|
||||||
wd->mark = NULL;
|
|
||||||
}
|
|
||||||
if (wd->inode) {
|
|
||||||
iput(wd->inode);
|
|
||||||
wd->inode = NULL;
|
|
||||||
}
|
|
||||||
if (wd->kpath.dentry) {
|
|
||||||
path_put(&wd->kpath);
|
|
||||||
memset(&wd->kpath, 0, sizeof(wd->kpath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct watch_dir g_watch = { .path = "/data/system",
|
|
||||||
.mask = MASK_SYSTEM };
|
|
||||||
|
|
||||||
int ksu_observer_init(void)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)
|
|
||||||
g = fsnotify_alloc_group(&ksu_ops, 0);
|
|
||||||
#else
|
|
||||||
g = fsnotify_alloc_group(&ksu_ops);
|
|
||||||
#endif
|
|
||||||
if (IS_ERR(g))
|
|
||||||
return PTR_ERR(g);
|
|
||||||
|
|
||||||
ret = watch_one_dir(&g_watch);
|
|
||||||
pr_info("observer init done\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_observer_exit(void)
|
|
||||||
{
|
|
||||||
unwatch_one_dir(&g_watch);
|
|
||||||
fsnotify_put_group(g);
|
|
||||||
pr_info("observer exit done\n");
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
#include <linux/version.h>
|
|
||||||
#include <linux/fs.h>
|
|
||||||
#include <linux/nsproxy.h>
|
|
||||||
#include <linux/sched/task.h>
|
|
||||||
#include <linux/uaccess.h>
|
|
||||||
#include <linux/filter.h>
|
|
||||||
#include <linux/seccomp.h>
|
|
||||||
#include "klog.h" // IWYU pragma: keep
|
|
||||||
#include "seccomp_cache.h"
|
|
||||||
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2
|
|
||||||
struct action_cache {
|
|
||||||
DECLARE_BITMAP(allow_native, SECCOMP_ARCH_NATIVE_NR);
|
|
||||||
#ifdef SECCOMP_ARCH_COMPAT
|
|
||||||
DECLARE_BITMAP(allow_compat, SECCOMP_ARCH_COMPAT_NR);
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
struct seccomp_filter {
|
|
||||||
refcount_t refs;
|
|
||||||
refcount_t users;
|
|
||||||
bool log;
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0)
|
|
||||||
bool wait_killable_recv;
|
|
||||||
#endif
|
|
||||||
struct action_cache cache;
|
|
||||||
struct seccomp_filter *prev;
|
|
||||||
struct bpf_prog *prog;
|
|
||||||
struct notification *notif;
|
|
||||||
struct mutex notify_lock;
|
|
||||||
wait_queue_head_t wqh;
|
|
||||||
};
|
|
||||||
|
|
||||||
void ksu_seccomp_clear_cache(struct seccomp_filter *filter, int nr)
|
|
||||||
{
|
|
||||||
if (!filter) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nr >= 0 && nr < SECCOMP_ARCH_NATIVE_NR) {
|
|
||||||
clear_bit(nr, filter->cache.allow_native);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SECCOMP_ARCH_COMPAT
|
|
||||||
if (nr >= 0 && nr < SECCOMP_ARCH_COMPAT_NR) {
|
|
||||||
clear_bit(nr, filter->cache.allow_compat);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_seccomp_allow_cache(struct seccomp_filter *filter, int nr)
|
|
||||||
{
|
|
||||||
if (!filter) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nr >= 0 && nr < SECCOMP_ARCH_NATIVE_NR) {
|
|
||||||
set_bit(nr, filter->cache.allow_native);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SECCOMP_ARCH_COMPAT
|
|
||||||
if (nr >= 0 && nr < SECCOMP_ARCH_COMPAT_NR) {
|
|
||||||
set_bit(nr, filter->cache.allow_compat);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
#ifndef __KSU_H_SECCOMP_CACHE
|
|
||||||
#define __KSU_H_SECCOMP_CACHE
|
|
||||||
|
|
||||||
#include <linux/fs.h>
|
|
||||||
#include <linux/version.h>
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 2) // Android backport this feature in 5.10.2
|
|
||||||
extern void ksu_seccomp_clear_cache(struct seccomp_filter *filter, int nr);
|
|
||||||
extern void ksu_seccomp_allow_cache(struct seccomp_filter *filter, int nr);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -2,7 +2,15 @@ obj-y += selinux.o
|
||||||
obj-y += sepolicy.o
|
obj-y += sepolicy.o
|
||||||
obj-y += rules.o
|
obj-y += rules.o
|
||||||
|
|
||||||
ccflags-y += -Wno-strict-prototypes -Wno-int-conversion
|
ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0)
|
||||||
|
ccflags-y += -DKSU_COMPAT_HAS_CURRENT_SID
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(shell grep -q "struct selinux_state " $(srctree)/security/selinux/include/security.h; echo $$?),0)
|
||||||
|
ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE
|
||||||
|
endif
|
||||||
|
|
||||||
|
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion
|
||||||
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
|
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function
|
||||||
ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
|
ccflags-y += -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
|
||||||
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
|
ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#include "selinux.h"
|
#include "selinux.h"
|
||||||
#include "sepolicy.h"
|
#include "sepolicy.h"
|
||||||
#include "ss/services.h"
|
#include "ss/services.h"
|
||||||
#include "linux/lsm_audit.h" // IWYU pragma: keep
|
#include "linux/lsm_audit.h"
|
||||||
#include "xfrm.h"
|
#include "xfrm.h"
|
||||||
|
|
||||||
#define SELINUX_POLICY_INSTEAD_SELINUX_SS
|
#define SELINUX_POLICY_INSTEAD_SELINUX_SS
|
||||||
|
|
@ -145,17 +145,45 @@ void apply_kernelsu_rules()
|
||||||
#define CMD_TYPE_CHANGE 8
|
#define CMD_TYPE_CHANGE 8
|
||||||
#define CMD_GENFSCON 9
|
#define CMD_GENFSCON 9
|
||||||
|
|
||||||
|
#ifdef CONFIG_64BIT
|
||||||
struct sepol_data {
|
struct sepol_data {
|
||||||
u32 cmd;
|
u32 cmd;
|
||||||
u32 subcmd;
|
u32 subcmd;
|
||||||
char __user *sepol1;
|
u64 field_sepol1;
|
||||||
char __user *sepol2;
|
u64 field_sepol2;
|
||||||
char __user *sepol3;
|
u64 field_sepol3;
|
||||||
char __user *sepol4;
|
u64 field_sepol4;
|
||||||
char __user *sepol5;
|
u64 field_sepol5;
|
||||||
char __user *sepol6;
|
u64 field_sepol6;
|
||||||
char __user *sepol7;
|
u64 field_sepol7;
|
||||||
};
|
};
|
||||||
|
#ifdef CONFIG_COMPAT
|
||||||
|
extern bool ksu_is_compat __read_mostly;
|
||||||
|
struct sepol_compat_data {
|
||||||
|
u32 cmd;
|
||||||
|
u32 subcmd;
|
||||||
|
u32 field_sepol1;
|
||||||
|
u32 field_sepol2;
|
||||||
|
u32 field_sepol3;
|
||||||
|
u32 field_sepol4;
|
||||||
|
u32 field_sepol5;
|
||||||
|
u32 field_sepol6;
|
||||||
|
u32 field_sepol7;
|
||||||
|
};
|
||||||
|
#endif // CONFIG_COMPAT
|
||||||
|
#else
|
||||||
|
struct sepol_data {
|
||||||
|
u32 cmd;
|
||||||
|
u32 subcmd;
|
||||||
|
u32 field_sepol1;
|
||||||
|
u32 field_sepol2;
|
||||||
|
u32 field_sepol3;
|
||||||
|
u32 field_sepol4;
|
||||||
|
u32 field_sepol5;
|
||||||
|
u32 field_sepol6;
|
||||||
|
u32 field_sepol7;
|
||||||
|
};
|
||||||
|
#endif // CONFIG_64BIT
|
||||||
|
|
||||||
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||||
char **object)
|
char **object)
|
||||||
|
|
@ -166,18 +194,14 @@ static int get_object(char *buf, char __user *user_object, size_t buf_sz,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strncpy_from_user(buf, user_object, buf_sz) < 0) {
|
if (strncpy_from_user(buf, user_object, buf_sz) < 0) {
|
||||||
return -EINVAL;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
*object = buf;
|
*object = buf;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0))
|
|
||||||
extern int avc_ss_reset(u32 seqno);
|
|
||||||
#else
|
|
||||||
extern int avc_ss_reset(struct selinux_avc *avc, u32 seqno);
|
|
||||||
#endif
|
|
||||||
// reset avc cache table, otherwise the new rules will not take effect if already denied
|
// reset avc cache table, otherwise the new rules will not take effect if already denied
|
||||||
static void reset_avc_cache()
|
static void reset_avc_cache()
|
||||||
{
|
{
|
||||||
|
|
@ -199,27 +223,71 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
struct policydb *db;
|
struct policydb *db;
|
||||||
|
|
||||||
if (!arg4) {
|
if (!arg4) {
|
||||||
return -EINVAL;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getenforce()) {
|
if (!getenforce()) {
|
||||||
pr_info("SELinux permissive or disabled when handle policy!\n");
|
pr_info("SELinux permissive or disabled when handle policy!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 cmd, subcmd;
|
||||||
|
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7;
|
||||||
|
|
||||||
|
#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
|
||||||
|
if (unlikely(ksu_is_compat)) {
|
||||||
|
struct sepol_compat_data compat_data;
|
||||||
|
if (copy_from_user(&compat_data, arg4, sizeof(struct sepol_compat_data))) {
|
||||||
|
pr_err("sepol: copy sepol_data failed.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
sepol1 = compat_ptr(compat_data.field_sepol1);
|
||||||
|
sepol2 = compat_ptr(compat_data.field_sepol2);
|
||||||
|
sepol3 = compat_ptr(compat_data.field_sepol3);
|
||||||
|
sepol4 = compat_ptr(compat_data.field_sepol4);
|
||||||
|
sepol5 = compat_ptr(compat_data.field_sepol5);
|
||||||
|
sepol6 = compat_ptr(compat_data.field_sepol6);
|
||||||
|
sepol7 = compat_ptr(compat_data.field_sepol7);
|
||||||
|
cmd = compat_data.cmd;
|
||||||
|
subcmd = compat_data.subcmd;
|
||||||
|
} else {
|
||||||
struct sepol_data data;
|
struct sepol_data data;
|
||||||
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||||
pr_err("sepol: copy sepol_data failed.\n");
|
pr_err("sepol: copy sepol_data failed.\n");
|
||||||
return -EINVAL;
|
return -1;
|
||||||
}
|
}
|
||||||
|
sepol1 = data.field_sepol1;
|
||||||
u32 cmd = data.cmd;
|
sepol2 = data.field_sepol2;
|
||||||
u32 subcmd = data.subcmd;
|
sepol3 = data.field_sepol3;
|
||||||
|
sepol4 = data.field_sepol4;
|
||||||
|
sepol5 = data.field_sepol5;
|
||||||
|
sepol6 = data.field_sepol6;
|
||||||
|
sepol7 = data.field_sepol7;
|
||||||
|
cmd = data.cmd;
|
||||||
|
subcmd = data.subcmd;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// basically for full native, say (64BIT=y COMPAT=n) || (64BIT=n)
|
||||||
|
struct sepol_data data;
|
||||||
|
if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
|
||||||
|
pr_err("sepol: copy sepol_data failed.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
sepol1 = data.field_sepol1;
|
||||||
|
sepol2 = data.field_sepol2;
|
||||||
|
sepol3 = data.field_sepol3;
|
||||||
|
sepol4 = data.field_sepol4;
|
||||||
|
sepol5 = data.field_sepol5;
|
||||||
|
sepol6 = data.field_sepol6;
|
||||||
|
sepol7 = data.field_sepol7;
|
||||||
|
cmd = data.cmd;
|
||||||
|
subcmd = data.subcmd;
|
||||||
|
#endif
|
||||||
|
|
||||||
mutex_lock(&ksu_rules);
|
mutex_lock(&ksu_rules);
|
||||||
|
|
||||||
db = get_policydb();
|
db = get_policydb();
|
||||||
|
|
||||||
int ret = -EINVAL;
|
int ret = -1;
|
||||||
if (cmd == CMD_NORMAL_PERM) {
|
if (cmd == CMD_NORMAL_PERM) {
|
||||||
char src_buf[MAX_SEPOL_LEN];
|
char src_buf[MAX_SEPOL_LEN];
|
||||||
char tgt_buf[MAX_SEPOL_LEN];
|
char tgt_buf[MAX_SEPOL_LEN];
|
||||||
|
|
@ -227,22 +295,22 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
char perm_buf[MAX_SEPOL_LEN];
|
char perm_buf[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
char *s, *t, *c, *p;
|
char *s, *t, *c, *p;
|
||||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||||
pr_err("sepol: copy tgt failed.\n");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||||
pr_err("sepol: copy cls failed.\n");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_object(perm_buf, data.sepol4, sizeof(perm_buf), &p) <
|
if (get_object(perm_buf, sepol4, sizeof(perm_buf), &p) <
|
||||||
0) {
|
0) {
|
||||||
pr_err("sepol: copy perm failed.\n");
|
pr_err("sepol: copy perm failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
@ -260,7 +328,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
} else {
|
} else {
|
||||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||||
}
|
}
|
||||||
ret = success ? 0 : -EINVAL;
|
ret = success ? 0 : -1;
|
||||||
|
|
||||||
} else if (cmd == CMD_XPERM) {
|
} else if (cmd == CMD_XPERM) {
|
||||||
char src_buf[MAX_SEPOL_LEN];
|
char src_buf[MAX_SEPOL_LEN];
|
||||||
|
|
@ -272,24 +340,24 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
char perm_set[MAX_SEPOL_LEN];
|
char perm_set[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
char *s, *t, *c;
|
char *s, *t, *c;
|
||||||
if (get_object(src_buf, data.sepol1, sizeof(src_buf), &s) < 0) {
|
if (get_object(src_buf, sepol1, sizeof(src_buf), &s) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (get_object(tgt_buf, data.sepol2, sizeof(tgt_buf), &t) < 0) {
|
if (get_object(tgt_buf, sepol2, sizeof(tgt_buf), &t) < 0) {
|
||||||
pr_err("sepol: copy tgt failed.\n");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (get_object(cls_buf, data.sepol3, sizeof(cls_buf), &c) < 0) {
|
if (get_object(cls_buf, sepol3, sizeof(cls_buf), &c) < 0) {
|
||||||
pr_err("sepol: copy cls failed.\n");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(operation, data.sepol4,
|
if (strncpy_from_user(operation, sepol4,
|
||||||
sizeof(operation)) < 0) {
|
sizeof(operation)) < 0) {
|
||||||
pr_err("sepol: copy operation failed.\n");
|
pr_err("sepol: copy operation failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(perm_set, data.sepol5, sizeof(perm_set)) <
|
if (strncpy_from_user(perm_set, sepol5, sizeof(perm_set)) <
|
||||||
0) {
|
0) {
|
||||||
pr_err("sepol: copy perm_set failed.\n");
|
pr_err("sepol: copy perm_set failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
@ -305,11 +373,11 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
} else {
|
} else {
|
||||||
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
pr_err("sepol: unknown subcmd: %d\n", subcmd);
|
||||||
}
|
}
|
||||||
ret = success ? 0 : -EINVAL;
|
ret = success ? 0 : -1;
|
||||||
} else if (cmd == CMD_TYPE_STATE) {
|
} else if (cmd == CMD_TYPE_STATE) {
|
||||||
char src[MAX_SEPOL_LEN];
|
char src[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
@ -329,11 +397,11 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
char type[MAX_SEPOL_LEN];
|
char type[MAX_SEPOL_LEN];
|
||||||
char attr[MAX_SEPOL_LEN];
|
char attr[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(type, data.sepol1, sizeof(type)) < 0) {
|
if (strncpy_from_user(type, sepol1, sizeof(type)) < 0) {
|
||||||
pr_err("sepol: copy type failed.\n");
|
pr_err("sepol: copy type failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(attr, data.sepol2, sizeof(attr)) < 0) {
|
if (strncpy_from_user(attr, sepol2, sizeof(attr)) < 0) {
|
||||||
pr_err("sepol: copy attr failed.\n");
|
pr_err("sepol: copy attr failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
@ -353,7 +421,7 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
} else if (cmd == CMD_ATTR) {
|
} else if (cmd == CMD_ATTR) {
|
||||||
char attr[MAX_SEPOL_LEN];
|
char attr[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(attr, data.sepol1, sizeof(attr)) < 0) {
|
if (strncpy_from_user(attr, sepol1, sizeof(attr)) < 0) {
|
||||||
pr_err("sepol: copy attr failed.\n");
|
pr_err("sepol: copy attr failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
@ -370,28 +438,28 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
char default_type[MAX_SEPOL_LEN];
|
char default_type[MAX_SEPOL_LEN];
|
||||||
char object[MAX_SEPOL_LEN];
|
char object[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||||
pr_err("sepol: copy tgt failed.\n");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||||
pr_err("sepol: copy cls failed.\n");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(default_type, data.sepol4,
|
if (strncpy_from_user(default_type, sepol4,
|
||||||
sizeof(default_type)) < 0) {
|
sizeof(default_type)) < 0) {
|
||||||
pr_err("sepol: copy default_type failed.\n");
|
pr_err("sepol: copy default_type failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
char *real_object;
|
char *real_object;
|
||||||
if (data.sepol5 == NULL) {
|
if (sepol5 == NULL) {
|
||||||
real_object = NULL;
|
real_object = NULL;
|
||||||
} else {
|
} else {
|
||||||
if (strncpy_from_user(object, data.sepol5,
|
if (strncpy_from_user(object, sepol5,
|
||||||
sizeof(object)) < 0) {
|
sizeof(object)) < 0) {
|
||||||
pr_err("sepol: copy object failed.\n");
|
pr_err("sepol: copy object failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
@ -410,19 +478,19 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
char cls[MAX_SEPOL_LEN];
|
char cls[MAX_SEPOL_LEN];
|
||||||
char default_type[MAX_SEPOL_LEN];
|
char default_type[MAX_SEPOL_LEN];
|
||||||
|
|
||||||
if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
|
if (strncpy_from_user(src, sepol1, sizeof(src)) < 0) {
|
||||||
pr_err("sepol: copy src failed.\n");
|
pr_err("sepol: copy src failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(tgt, data.sepol2, sizeof(tgt)) < 0) {
|
if (strncpy_from_user(tgt, sepol2, sizeof(tgt)) < 0) {
|
||||||
pr_err("sepol: copy tgt failed.\n");
|
pr_err("sepol: copy tgt failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(cls, data.sepol3, sizeof(cls)) < 0) {
|
if (strncpy_from_user(cls, sepol3, sizeof(cls)) < 0) {
|
||||||
pr_err("sepol: copy cls failed.\n");
|
pr_err("sepol: copy cls failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(default_type, data.sepol4,
|
if (strncpy_from_user(default_type, sepol4,
|
||||||
sizeof(default_type)) < 0) {
|
sizeof(default_type)) < 0) {
|
||||||
pr_err("sepol: copy default_type failed.\n");
|
pr_err("sepol: copy default_type failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
@ -443,15 +511,15 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
|
||||||
char name[MAX_SEPOL_LEN];
|
char name[MAX_SEPOL_LEN];
|
||||||
char path[MAX_SEPOL_LEN];
|
char path[MAX_SEPOL_LEN];
|
||||||
char context[MAX_SEPOL_LEN];
|
char context[MAX_SEPOL_LEN];
|
||||||
if (strncpy_from_user(name, data.sepol1, sizeof(name)) < 0) {
|
if (strncpy_from_user(name, sepol1, sizeof(name)) < 0) {
|
||||||
pr_err("sepol: copy name failed.\n");
|
pr_err("sepol: copy name failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(path, data.sepol2, sizeof(path)) < 0) {
|
if (strncpy_from_user(path, sepol2, sizeof(path)) < 0) {
|
||||||
pr_err("sepol: copy path failed.\n");
|
pr_err("sepol: copy path failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (strncpy_from_user(context, data.sepol3, sizeof(context)) <
|
if (strncpy_from_user(context, sepol3, sizeof(context)) <
|
||||||
0) {
|
0) {
|
||||||
pr_err("sepol: copy context failed.\n");
|
pr_err("sepol: copy context failed.\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
#include "selinux.h"
|
#include "selinux.h"
|
||||||
#include "linux/cred.h"
|
|
||||||
#include "linux/sched.h"
|
|
||||||
#include "objsec.h"
|
#include "objsec.h"
|
||||||
#include "linux/version.h"
|
#include "linux/version.h"
|
||||||
#include "../klog.h" // IWYU pragma: keep
|
#include "../klog.h" // IWYU pragma: keep
|
||||||
|
|
@ -42,6 +40,13 @@ void setup_selinux(const char *domain)
|
||||||
pr_err("transive domain failed.\n");
|
pr_err("transive domain failed.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* we didn't need this now, we have change selinux rules when boot!
|
||||||
|
if (!is_domain_permissive) {
|
||||||
|
if (set_domain_permissive() == 0) {
|
||||||
|
is_domain_permissive = true;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void setenforce(bool enforce)
|
void setenforce(bool enforce)
|
||||||
|
|
@ -66,89 +71,60 @@ bool getenforce()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 14, 0)
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) && \
|
||||||
struct lsm_context {
|
!defined(KSU_COMPAT_HAS_CURRENT_SID)
|
||||||
char *context;
|
/*
|
||||||
u32 len;
|
* get the subjective security ID of the current task
|
||||||
};
|
*/
|
||||||
|
static inline u32 current_sid(void)
|
||||||
|
{
|
||||||
|
const struct task_security_struct *tsec = current_security();
|
||||||
|
|
||||||
static int __security_secid_to_secctx(u32 secid, struct lsm_context *cp)
|
return tsec->sid;
|
||||||
{
|
|
||||||
return security_secid_to_secctx(secid, &cp->context, &cp->len);
|
|
||||||
}
|
}
|
||||||
static void __security_release_secctx(struct lsm_context *cp)
|
|
||||||
{
|
|
||||||
return security_release_secctx(cp->context, cp->len);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#define __security_secid_to_secctx security_secid_to_secctx
|
|
||||||
#define __security_release_secctx security_release_secctx
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool is_task_ksu_domain(const struct cred* cred)
|
|
||||||
{
|
|
||||||
struct lsm_context ctx;
|
|
||||||
bool result;
|
|
||||||
if (!cred) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const struct task_security_struct *tsec = selinux_cred(cred);
|
|
||||||
if (!tsec) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int err = __security_secid_to_secctx(tsec->sid, &ctx);
|
|
||||||
if (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
result = strncmp(KERNEL_SU_DOMAIN, ctx.context, ctx.len) == 0;
|
|
||||||
__security_release_secctx(&ctx);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_ksu_domain()
|
bool is_ksu_domain()
|
||||||
{
|
{
|
||||||
current_sid();
|
char *domain;
|
||||||
return is_task_ksu_domain(current_cred());
|
u32 seclen;
|
||||||
}
|
|
||||||
|
|
||||||
bool is_context(const struct cred* cred, const char* context)
|
|
||||||
{
|
|
||||||
if (!cred) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const struct task_security_struct * tsec = selinux_cred(cred);
|
|
||||||
if (!tsec) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
struct lsm_context ctx;
|
|
||||||
bool result;
|
bool result;
|
||||||
int err = __security_secid_to_secctx(tsec->sid, &ctx);
|
int err = security_secid_to_secctx(current_sid(), &domain, &seclen);
|
||||||
if (err) {
|
if (err) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
result = strncmp(context, ctx.context, ctx.len) == 0;
|
result = strncmp(KERNEL_SU_DOMAIN, domain, seclen) == 0;
|
||||||
__security_release_secctx(&ctx);
|
security_release_secctx(domain, seclen);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_zygote(const struct cred* cred)
|
bool is_zygote(void *sec)
|
||||||
{
|
{
|
||||||
return is_context(cred, "u:r:zygote:s0");
|
struct task_security_struct *tsec = (struct task_security_struct *)sec;
|
||||||
|
if (!tsec) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
char *domain;
|
||||||
bool is_init(const struct cred* cred) {
|
u32 seclen;
|
||||||
return is_context(cred, "u:r:init:s0");
|
bool result;
|
||||||
}
|
int err = security_secid_to_secctx(tsec->sid, &domain, &seclen);
|
||||||
|
|
||||||
#define KSU_FILE_DOMAIN "u:object_r:ksu_file:s0"
|
|
||||||
|
|
||||||
u32 ksu_get_ksu_file_sid()
|
|
||||||
{
|
|
||||||
u32 ksu_file_sid = 0;
|
|
||||||
int err = security_secctx_to_secid(KSU_FILE_DOMAIN, strlen(KSU_FILE_DOMAIN),
|
|
||||||
&ksu_file_sid);
|
|
||||||
if (err) {
|
if (err) {
|
||||||
pr_info("get ksufile sid err %d\n", err);
|
return false;
|
||||||
}
|
}
|
||||||
return ksu_file_sid;
|
result = strncmp("u:r:zygote:s0", domain, seclen) == 0;
|
||||||
|
security_release_secctx(domain, seclen);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DEVPTS_DOMAIN "u:object_r:ksu_file:s0"
|
||||||
|
|
||||||
|
u32 ksu_get_devpts_sid()
|
||||||
|
{
|
||||||
|
u32 devpts_sid = 0;
|
||||||
|
int err = security_secctx_to_secid(DEVPTS_DOMAIN, strlen(DEVPTS_DOMAIN),
|
||||||
|
&devpts_sid);
|
||||||
|
if (err) {
|
||||||
|
pr_info("get devpts sid err %d\n", err);
|
||||||
|
}
|
||||||
|
return devpts_sid;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
#include "linux/types.h"
|
#include "linux/types.h"
|
||||||
#include "linux/version.h"
|
#include "linux/version.h"
|
||||||
#include "linux/cred.h"
|
|
||||||
|
|
||||||
void setup_selinux(const char *);
|
void setup_selinux(const char *);
|
||||||
|
|
||||||
|
|
@ -11,18 +10,12 @@ void setenforce(bool);
|
||||||
|
|
||||||
bool getenforce();
|
bool getenforce();
|
||||||
|
|
||||||
bool is_task_ksu_domain(const struct cred* cred);
|
|
||||||
|
|
||||||
bool is_ksu_domain();
|
bool is_ksu_domain();
|
||||||
|
|
||||||
bool is_zygote(const struct cred* cred);
|
bool is_zygote(void *cred);
|
||||||
|
|
||||||
bool is_init(const struct cred* cred);
|
|
||||||
|
|
||||||
void apply_kernelsu_rules();
|
void apply_kernelsu_rules();
|
||||||
|
|
||||||
u32 ksu_get_ksu_file_sid();
|
u32 ksu_get_devpts_sid();
|
||||||
|
|
||||||
int handle_sepolicy(unsigned long arg3, void __user *arg4);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "sepolicy.h"
|
#include "sepolicy.h"
|
||||||
#include "../klog.h" // IWYU pragma: keep
|
#include "../klog.h" // IWYU pragma: keep
|
||||||
#include "ss/symtab.h"
|
#include "ss/symtab.h"
|
||||||
|
#include "../kernel_compat.h" // Add check Huawei Device
|
||||||
|
|
||||||
#define KSU_SUPPORT_ADD_TYPE
|
#define KSU_SUPPORT_ADD_TYPE
|
||||||
|
|
||||||
|
|
@ -354,7 +355,7 @@ static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
|
||||||
|
|
||||||
if (datum->u.xperms == NULL) {
|
if (datum->u.xperms == NULL) {
|
||||||
datum->u.xperms =
|
datum->u.xperms =
|
||||||
(struct avtab_extended_perms *)(kzalloc(
|
(struct avtab_extended_perms *)(kmalloc(
|
||||||
sizeof(xperms), GFP_KERNEL));
|
sizeof(xperms), GFP_KERNEL));
|
||||||
if (!datum->u.xperms) {
|
if (!datum->u.xperms) {
|
||||||
pr_err("alloc xperms failed\n");
|
pr_err("alloc xperms failed\n");
|
||||||
|
|
@ -545,10 +546,10 @@ static bool add_filename_trans(struct policydb *db, const char *s,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trans == NULL) {
|
if (trans == NULL) {
|
||||||
trans = (struct filename_trans_datum *)kcalloc(1 ,sizeof(*trans),
|
trans = (struct filename_trans_datum *)kcalloc(sizeof(*trans),
|
||||||
GFP_ATOMIC);
|
1, GFP_ATOMIC);
|
||||||
struct filename_trans_key *new_key =
|
struct filename_trans_key *new_key =
|
||||||
(struct filename_trans_key *)kzalloc(sizeof(*new_key),
|
(struct filename_trans_key *)kmalloc(sizeof(*new_key),
|
||||||
GFP_ATOMIC);
|
GFP_ATOMIC);
|
||||||
*new_key = key;
|
*new_key = key;
|
||||||
new_key->name = kstrdup(key.name, GFP_ATOMIC);
|
new_key->name = kstrdup(key.name, GFP_ATOMIC);
|
||||||
|
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
#include <linux/compiler.h>
|
|
||||||
#include <linux/sched/signal.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/task_work.h>
|
|
||||||
#include <linux/thread_info.h>
|
|
||||||
#include <linux/seccomp.h>
|
|
||||||
#include <linux/bpf.h>
|
|
||||||
#include <linux/capability.h>
|
|
||||||
#include <linux/cred.h>
|
|
||||||
#include <linux/dcache.h>
|
|
||||||
#include <linux/err.h>
|
|
||||||
#include <linux/fs.h>
|
|
||||||
#include <linux/init.h>
|
|
||||||
#include <linux/init_task.h>
|
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/kprobes.h>
|
|
||||||
#include <linux/mm.h>
|
|
||||||
#include <linux/mount.h>
|
|
||||||
#include <linux/namei.h>
|
|
||||||
#include <linux/nsproxy.h>
|
|
||||||
#include <linux/path.h>
|
|
||||||
#include <linux/printk.h>
|
|
||||||
#include <linux/sched.h>
|
|
||||||
#include <linux/security.h>
|
|
||||||
#include <linux/stddef.h>
|
|
||||||
#include <linux/string.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/uaccess.h>
|
|
||||||
#include <linux/uidgid.h>
|
|
||||||
#include <linux/version.h>
|
|
||||||
#include <linux/binfmts.h>
|
|
||||||
#include <linux/tty.h>
|
|
||||||
|
|
||||||
#include "allowlist.h"
|
|
||||||
#include "setuid_hook.h"
|
|
||||||
#include "feature.h"
|
|
||||||
#include "klog.h" // IWYU pragma: keep
|
|
||||||
#include "manager.h"
|
|
||||||
#include "selinux/selinux.h"
|
|
||||||
#include "seccomp_cache.h"
|
|
||||||
#include "supercalls.h"
|
|
||||||
#include "syscall_hook_manager.h"
|
|
||||||
#include "kernel_umount.h"
|
|
||||||
#include "app_profile.h"
|
|
||||||
|
|
||||||
static bool ksu_enhanced_security_enabled = false;
|
|
||||||
|
|
||||||
static int enhanced_security_feature_get(u64 *value)
|
|
||||||
{
|
|
||||||
*value = ksu_enhanced_security_enabled ? 1 : 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int enhanced_security_feature_set(u64 value)
|
|
||||||
{
|
|
||||||
bool enable = value != 0;
|
|
||||||
ksu_enhanced_security_enabled = enable;
|
|
||||||
pr_info("enhanced_security: set to %d\n", enable);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct ksu_feature_handler enhanced_security_handler = {
|
|
||||||
.feature_id = KSU_FEATURE_ENHANCED_SECURITY,
|
|
||||||
.name = "enhanced_security",
|
|
||||||
.get_handler = enhanced_security_feature_get,
|
|
||||||
.set_handler = enhanced_security_feature_set,
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline bool is_allow_su()
|
|
||||||
{
|
|
||||||
if (is_manager()) {
|
|
||||||
// we are manager, allow!
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return ksu_is_allow_uid_for_current(current_uid().val);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid)
|
|
||||||
{
|
|
||||||
uid_t new_uid = ruid;
|
|
||||||
uid_t old_uid = current_uid().val;
|
|
||||||
|
|
||||||
pr_info("handle_setresuid from %d to %d\n", old_uid, new_uid);
|
|
||||||
|
|
||||||
// if old process is root, ignore it.
|
|
||||||
if (old_uid != 0 && ksu_enhanced_security_enabled) {
|
|
||||||
// disallow any non-ksu domain escalation from non-root to root!
|
|
||||||
// euid is what we care about here as it controls permission
|
|
||||||
if (unlikely(euid == 0)) {
|
|
||||||
if (!is_ksu_domain()) {
|
|
||||||
pr_warn("find suspicious EoP: %d %s, from %d to %d\n",
|
|
||||||
current->pid, current->comm, old_uid, new_uid);
|
|
||||||
force_sig(SIGKILL);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// disallow appuid decrease to any other uid if it is not allowed to su
|
|
||||||
if (is_appuid(old_uid)) {
|
|
||||||
if (euid < current_euid().val && !ksu_is_allow_uid_for_current(old_uid)) {
|
|
||||||
pr_warn("find suspicious EoP: %d %s, from %d to %d\n",
|
|
||||||
current->pid, current->comm, old_uid, new_uid);
|
|
||||||
force_sig(SIGKILL);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if on private space, see if its possibly the manager
|
|
||||||
if (new_uid > PER_USER_RANGE && new_uid % PER_USER_RANGE == ksu_get_manager_uid()) {
|
|
||||||
ksu_set_manager_uid(new_uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
|
|
||||||
if (ksu_get_manager_uid() == new_uid) {
|
|
||||||
pr_info("install fd for manager: %d\n", new_uid);
|
|
||||||
ksu_install_fd();
|
|
||||||
spin_lock_irq(¤t->sighand->siglock);
|
|
||||||
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
|
|
||||||
ksu_set_task_tracepoint_flag(current);
|
|
||||||
spin_unlock_irq(¤t->sighand->siglock);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ksu_is_allow_uid_for_current(new_uid)) {
|
|
||||||
if (current->seccomp.mode == SECCOMP_MODE_FILTER &&
|
|
||||||
current->seccomp.filter) {
|
|
||||||
spin_lock_irq(¤t->sighand->siglock);
|
|
||||||
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
|
|
||||||
spin_unlock_irq(¤t->sighand->siglock);
|
|
||||||
}
|
|
||||||
ksu_set_task_tracepoint_flag(current);
|
|
||||||
} else {
|
|
||||||
ksu_clear_task_tracepoint_flag_if_needed(current);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (ksu_is_allow_uid_for_current(new_uid)) {
|
|
||||||
spin_lock_irq(¤t->sighand->siglock);
|
|
||||||
disable_seccomp();
|
|
||||||
spin_unlock_irq(¤t->sighand->siglock);
|
|
||||||
|
|
||||||
if (ksu_get_manager_uid() == new_uid) {
|
|
||||||
pr_info("install fd for ksu manager(uid=%d)\n",
|
|
||||||
new_uid);
|
|
||||||
ksu_install_fd();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Handle kernel umount
|
|
||||||
ksu_handle_umount(old_uid, new_uid);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_setuid_hook_init(void)
|
|
||||||
{
|
|
||||||
ksu_kernel_umount_init();
|
|
||||||
if (ksu_register_feature_handler(&enhanced_security_handler)) {
|
|
||||||
pr_err("Failed to register enhanced security feature handler\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_setuid_hook_exit(void)
|
|
||||||
{
|
|
||||||
pr_info("ksu_core_exit\n");
|
|
||||||
ksu_kernel_umount_exit();
|
|
||||||
ksu_unregister_feature_handler(KSU_FEATURE_ENHANCED_SECURITY);
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#ifndef __KSU_H_KSU_CORE
|
|
||||||
#define __KSU_H_KSU_CORE
|
|
||||||
|
|
||||||
#include <linux/init.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include "apk_sign.h"
|
|
||||||
#include <linux/thread_info.h>
|
|
||||||
|
|
||||||
void ksu_setuid_hook_init(void);
|
|
||||||
void ksu_setuid_hook_exit(void);
|
|
||||||
|
|
||||||
int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,54 +1,35 @@
|
||||||
#include "linux/compiler.h"
|
#include <linux/dcache.h>
|
||||||
#include "linux/printk.h"
|
#include <linux/security.h>
|
||||||
#include <asm/current.h>
|
#include <asm/current.h>
|
||||||
#include <linux/cred.h>
|
#include <linux/cred.h>
|
||||||
|
#include <linux/err.h>
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
|
#include <linux/kprobes.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
#include <linux/version.h>
|
#include <linux/version.h>
|
||||||
#include <linux/sched/task_stack.h>
|
#include <linux/sched/task_stack.h>
|
||||||
#include <linux/ptrace.h>
|
|
||||||
|
|
||||||
|
#include "objsec.h"
|
||||||
#include "allowlist.h"
|
#include "allowlist.h"
|
||||||
#include "feature.h"
|
#include "arch.h"
|
||||||
#include "klog.h" // IWYU pragma: keep
|
#include "klog.h" // IWYU pragma: keep
|
||||||
#include "ksud.h"
|
#include "ksud.h"
|
||||||
#include "sucompat.h"
|
#include "kernel_compat.h"
|
||||||
#include "app_profile.h"
|
|
||||||
#include "syscall_hook_manager.h"
|
|
||||||
|
|
||||||
#include "sulog.h"
|
|
||||||
|
|
||||||
#define SU_PATH "/system/bin/su"
|
#define SU_PATH "/system/bin/su"
|
||||||
#define SH_PATH "/system/bin/sh"
|
#define SH_PATH "/system/bin/sh"
|
||||||
|
|
||||||
bool ksu_su_compat_enabled __read_mostly = true;
|
extern void escape_to_root();
|
||||||
|
|
||||||
static int su_compat_feature_get(u64 *value)
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
{
|
static bool ksu_sucompat_hook_state __read_mostly = true;
|
||||||
*value = ksu_su_compat_enabled ? 1 : 0;
|
#endif
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int su_compat_feature_set(u64 value)
|
|
||||||
{
|
|
||||||
bool enable = value != 0;
|
|
||||||
ksu_su_compat_enabled = enable;
|
|
||||||
pr_info("su_compat: set to %d\n", enable);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct ksu_feature_handler su_compat_handler = {
|
|
||||||
.feature_id = KSU_FEATURE_SU_COMPAT,
|
|
||||||
.name = "su_compat",
|
|
||||||
.get_handler = su_compat_feature_get,
|
|
||||||
.set_handler = su_compat_feature_set,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void __user *userspace_stack_buffer(const void *d, size_t len)
|
static void __user *userspace_stack_buffer(const void *d, size_t len)
|
||||||
{
|
{
|
||||||
// To avoid having to mmap a page in userspace, just write below the stack
|
/* To avoid having to mmap a page in userspace, just write below the stack
|
||||||
// pointer.
|
* pointer. */
|
||||||
char __user *p = (void __user *)current_user_stack_pointer() - len;
|
char __user *p = (void __user *)current_user_stack_pointer() - len;
|
||||||
|
|
||||||
return copy_to_user(p, d, len) ? NULL : p;
|
return copy_to_user(p, d, len) ? NULL : p;
|
||||||
|
|
@ -73,18 +54,21 @@ int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
|
||||||
{
|
{
|
||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
|
|
||||||
if (!ksu_is_allow_uid_for_current(current_uid().val)) {
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_hook_state) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
char path[sizeof(su) + 1];
|
char path[sizeof(su) + 1];
|
||||||
memset(path, 0, sizeof(path));
|
memset(path, 0, sizeof(path));
|
||||||
strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||||
|
|
||||||
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
||||||
#if __SULOG_GATE
|
|
||||||
ksu_sulog_report_syscall(current_uid().val, NULL, "faccessat", path);
|
|
||||||
#endif
|
|
||||||
pr_info("faccessat su->sh!\n");
|
pr_info("faccessat su->sh!\n");
|
||||||
*filename_user = sh_user_path();
|
*filename_user = sh_user_path();
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +81,12 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||||
// const char sh[] = SH_PATH;
|
// const char sh[] = SH_PATH;
|
||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
|
|
||||||
if (!ksu_is_allow_uid_for_current(current_uid().val)) {
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_hook_state) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!ksu_is_allow_uid(current_uid().val)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,12 +110,9 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||||
pr_info("vfs_statx su->sh!\n");
|
pr_info("vfs_statx su->sh!\n");
|
||||||
memcpy((void *)filename->name, sh, sizeof(sh));
|
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||||
#else
|
#else
|
||||||
strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||||
|
|
||||||
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
if (unlikely(!memcmp(path, su, sizeof(su)))) {
|
||||||
#if __SULOG_GATE
|
|
||||||
ksu_sulog_report_syscall(current_uid().val, NULL, "newfstatat", path);
|
|
||||||
#endif
|
|
||||||
pr_info("newfstatat su->sh!\n");
|
pr_info("newfstatat su->sh!\n");
|
||||||
*filename_user = sh_user_path();
|
*filename_user = sh_user_path();
|
||||||
}
|
}
|
||||||
|
|
@ -135,53 +121,217 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ksu_handle_execve_sucompat(const char __user **filename_user,
|
int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv,
|
||||||
|
void *envp, int *flags)
|
||||||
|
{
|
||||||
|
return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the call from execve_handler_pre won't provided correct value for __never_use_argument, use them after fix execve_handler_pre, keeping them for consistence for manually patched code
|
||||||
|
int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
|
||||||
|
void *__never_use_argv, void *__never_use_envp,
|
||||||
|
int *__never_use_flags)
|
||||||
|
{
|
||||||
|
struct filename *filename;
|
||||||
|
const char sh[] = KSUD_PATH;
|
||||||
|
const char su[] = SU_PATH;
|
||||||
|
|
||||||
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_hook_state) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (unlikely(!filename_ptr))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
filename = *filename_ptr;
|
||||||
|
if (IS_ERR(filename)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (likely(memcmp(filename->name, su, sizeof(su))))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!ksu_is_allow_uid(current_uid().val))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
pr_info("do_execveat_common su found\n");
|
||||||
|
memcpy((void *)filename->name, sh, sizeof(sh));
|
||||||
|
|
||||||
|
escape_to_root();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ksu_handle_execve_sucompat(int *fd, const char __user **filename_user,
|
||||||
void *__never_use_argv, void *__never_use_envp,
|
void *__never_use_argv, void *__never_use_envp,
|
||||||
int *__never_use_flags)
|
int *__never_use_flags)
|
||||||
{
|
{
|
||||||
const char su[] = SU_PATH;
|
const char su[] = SU_PATH;
|
||||||
char path[sizeof(su) + 1];
|
char path[sizeof(su) + 1];
|
||||||
|
|
||||||
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_hook_state){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (unlikely(!filename_user))
|
if (unlikely(!filename_user))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
memset(path, 0, sizeof(path));
|
memset(path, 0, sizeof(path));
|
||||||
strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
||||||
|
|
||||||
if (likely(memcmp(path, su, sizeof(su))))
|
if (likely(memcmp(path, su, sizeof(su))))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
#if __SULOG_GATE
|
if (!ksu_is_allow_uid(current_uid().val))
|
||||||
bool is_allowed = ksu_is_allow_uid_for_current(current_uid().val);
|
|
||||||
ksu_sulog_report_syscall(current_uid().val, NULL, "execve", path);
|
|
||||||
|
|
||||||
if (!is_allowed)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
ksu_sulog_report_su_attempt(current_uid().val, NULL, path, is_allowed);
|
|
||||||
#else
|
|
||||||
if (!ksu_is_allow_uid_for_current(current_uid().val)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
pr_info("sys_execve su found\n");
|
pr_info("sys_execve su found\n");
|
||||||
*filename_user = ksud_user_path();
|
*filename_user = ksud_user_path();
|
||||||
|
|
||||||
escape_with_root_profile();
|
escape_to_root();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sucompat: permitted process can execute 'su' to gain root access.
|
// dummified
|
||||||
|
int ksu_handle_devpts(struct inode *inode)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int __ksu_handle_devpts(struct inode *inode)
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifndef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
if (!ksu_sucompat_hook_state)
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!current->mm) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uid_t uid = current_uid().val;
|
||||||
|
if (uid % 100000 < 10000) {
|
||||||
|
// not untrusted_app, ignore it
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (likely(!ksu_is_allow_uid(uid)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
struct inode_security_struct *sec = selinux_inode(inode);
|
||||||
|
|
||||||
|
if (ksu_devpts_sid && sec)
|
||||||
|
sec->sid = ksu_devpts_sid;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||||
|
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||||
|
const char __user **filename_user =
|
||||||
|
(const char **)&PT_REGS_PARM2(real_regs);
|
||||||
|
int *mode = (int *)&PT_REGS_PARM3(real_regs);
|
||||||
|
|
||||||
|
return ksu_handle_faccessat(dfd, filename_user, mode, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||||
|
int *dfd = (int *)&PT_REGS_PARM1(real_regs);
|
||||||
|
const char __user **filename_user =
|
||||||
|
(const char **)&PT_REGS_PARM2(real_regs);
|
||||||
|
int *flags = (int *)&PT_REGS_SYSCALL_PARM4(real_regs);
|
||||||
|
|
||||||
|
return ksu_handle_stat(dfd, filename_user, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
struct pt_regs *real_regs = PT_REAL_REGS(regs);
|
||||||
|
const char __user **filename_user =
|
||||||
|
(const char **)&PT_REGS_PARM1(real_regs);
|
||||||
|
|
||||||
|
return ksu_handle_execve_sucompat(AT_FDCWD, filename_user, NULL, NULL,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct kprobe *su_kps[4];
|
||||||
|
static int pts_unix98_lookup_pre(struct kprobe *p, struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
struct inode *inode;
|
||||||
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)
|
||||||
|
struct file *file = (struct file *)PT_REGS_PARM2(regs);
|
||||||
|
inode = file->f_path.dentry->d_inode;
|
||||||
|
#else
|
||||||
|
inode = (struct inode *)PT_REGS_PARM2(regs);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ksu_handle_devpts(inode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct kprobe *init_kprobe(const char *name,
|
||||||
|
kprobe_pre_handler_t handler)
|
||||||
|
{
|
||||||
|
struct kprobe *kp = kzalloc(sizeof(struct kprobe), GFP_KERNEL);
|
||||||
|
if (!kp)
|
||||||
|
return NULL;
|
||||||
|
kp->symbol_name = name;
|
||||||
|
kp->pre_handler = handler;
|
||||||
|
|
||||||
|
int ret = register_kprobe(kp);
|
||||||
|
pr_info("sucompat: register_%s kprobe: %d\n", name, ret);
|
||||||
|
if (ret) {
|
||||||
|
kfree(kp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy_kprobe(struct kprobe **kp_ptr)
|
||||||
|
{
|
||||||
|
struct kprobe *kp = *kp_ptr;
|
||||||
|
if (!kp)
|
||||||
|
return;
|
||||||
|
unregister_kprobe(kp);
|
||||||
|
synchronize_rcu();
|
||||||
|
kfree(kp);
|
||||||
|
*kp_ptr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// sucompat: permited process can execute 'su' to gain root access.
|
||||||
void ksu_sucompat_init()
|
void ksu_sucompat_init()
|
||||||
{
|
{
|
||||||
if (ksu_register_feature_handler(&su_compat_handler)) {
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
pr_err("Failed to register su_compat feature handler\n");
|
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre);
|
||||||
}
|
su_kps[1] = init_kprobe(SYS_FACCESSAT_SYMBOL, faccessat_handler_pre);
|
||||||
|
su_kps[2] = init_kprobe(SYS_NEWFSTATAT_SYMBOL, newfstatat_handler_pre);
|
||||||
|
su_kps[3] = init_kprobe("pts_unix98_lookup", pts_unix98_lookup_pre);
|
||||||
|
#else
|
||||||
|
ksu_sucompat_hook_state = true;
|
||||||
|
pr_info("ksu_sucompat_init: hooks enabled: execve/execveat_su, faccessat, stat\n");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ksu_sucompat_exit()
|
void ksu_sucompat_exit()
|
||||||
{
|
{
|
||||||
ksu_unregister_feature_handler(KSU_FEATURE_SU_COMPAT);
|
#ifdef CONFIG_KSU_KPROBES_HOOK
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < ARRAY_SIZE(su_kps); i++) {
|
||||||
|
destroy_kprobe(&su_kps[i]);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ksu_sucompat_hook_state = false;
|
||||||
|
pr_info("ksu_sucompat_exit: hooks disabled: execve/execveat_su, faccessat, stat\n");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
#ifndef __KSU_H_SUCOMPAT
|
|
||||||
#define __KSU_H_SUCOMPAT
|
|
||||||
#include <linux/types.h>
|
|
||||||
|
|
||||||
extern bool ksu_su_compat_enabled;
|
|
||||||
|
|
||||||
void ksu_sucompat_init(void);
|
|
||||||
void ksu_sucompat_exit(void);
|
|
||||||
|
|
||||||
// Handler functions exported for hook_manager
|
|
||||||
int ksu_handle_faccessat(int *dfd, const char __user **filename_user,
|
|
||||||
int *mode, int *__unused_flags);
|
|
||||||
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags);
|
|
||||||
int ksu_handle_execve_sucompat(const char __user **filename_user,
|
|
||||||
void *__never_use_argv, void *__never_use_envp,
|
|
||||||
int *__never_use_flags);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
369
kernel/sulog.c
369
kernel/sulog.c
|
|
@ -1,369 +0,0 @@
|
||||||
#include <linux/fs.h>
|
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/printk.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/string.h>
|
|
||||||
#include <linux/time.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/uaccess.h>
|
|
||||||
#include <linux/workqueue.h>
|
|
||||||
#include <linux/cred.h>
|
|
||||||
#include <linux/sched.h>
|
|
||||||
#include <linux/mm.h>
|
|
||||||
#include <linux/mutex.h>
|
|
||||||
#include <linux/spinlock.h>
|
|
||||||
|
|
||||||
#include "klog.h"
|
|
||||||
|
|
||||||
#include "sulog.h"
|
|
||||||
#include "ksu.h"
|
|
||||||
#include "feature.h"
|
|
||||||
|
|
||||||
#if __SULOG_GATE
|
|
||||||
|
|
||||||
struct dedup_entry dedup_tbl[SULOG_COMM_LEN];
|
|
||||||
static DEFINE_SPINLOCK(dedup_lock);
|
|
||||||
static LIST_HEAD(sulog_queue);
|
|
||||||
static struct workqueue_struct *sulog_workqueue;
|
|
||||||
static struct work_struct sulog_work;
|
|
||||||
static bool sulog_enabled __read_mostly = true;
|
|
||||||
|
|
||||||
static int sulog_feature_get(u64 *value)
|
|
||||||
{
|
|
||||||
*value = sulog_enabled ? 1 : 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int sulog_feature_set(u64 value)
|
|
||||||
{
|
|
||||||
bool enable = value != 0;
|
|
||||||
sulog_enabled = enable;
|
|
||||||
pr_info("sulog: set to %d\n", enable);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct ksu_feature_handler sulog_handler = {
|
|
||||||
.feature_id = KSU_FEATURE_SULOG,
|
|
||||||
.name = "sulog",
|
|
||||||
.get_handler = sulog_feature_get,
|
|
||||||
.set_handler = sulog_feature_set,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void get_timestamp(char *buf, size_t len)
|
|
||||||
{
|
|
||||||
struct timespec64 ts;
|
|
||||||
struct tm tm;
|
|
||||||
|
|
||||||
ktime_get_real_ts64(&ts);
|
|
||||||
time64_to_tm(ts.tv_sec - sys_tz.tz_minuteswest * 60, 0, &tm);
|
|
||||||
|
|
||||||
snprintf(buf, len, "%04ld-%02d-%02d %02d:%02d:%02d",
|
|
||||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
|
||||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ksu_get_cmdline(char *full_comm, const char *comm, size_t buf_len)
|
|
||||||
{
|
|
||||||
if (!full_comm || buf_len <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (comm && strlen(comm) > 0) {
|
|
||||||
KSU_STRSCPY(full_comm, comm, buf_len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_atomic() || in_interrupt() || irqs_disabled()) {
|
|
||||||
KSU_STRSCPY(full_comm, current->comm, buf_len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!current->mm) {
|
|
||||||
KSU_STRSCPY(full_comm, current->comm, buf_len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int n = get_cmdline(current, full_comm, buf_len);
|
|
||||||
if (n <= 0) {
|
|
||||||
KSU_STRSCPY(full_comm, current->comm, buf_len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < n && i < buf_len - 1; i++) {
|
|
||||||
if (full_comm[i] == '\0')
|
|
||||||
full_comm[i] = ' ';
|
|
||||||
}
|
|
||||||
full_comm[n < buf_len ? n : buf_len - 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sanitize_string(char *str, size_t len)
|
|
||||||
{
|
|
||||||
if (!str || len == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
size_t read_pos = 0, write_pos = 0;
|
|
||||||
|
|
||||||
while (read_pos < len && str[read_pos] != '\0') {
|
|
||||||
char c = str[read_pos];
|
|
||||||
|
|
||||||
if (c == '\n' || c == '\r') {
|
|
||||||
read_pos++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == ' ' && write_pos > 0 && str[write_pos - 1] == ' ') {
|
|
||||||
read_pos++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
str[write_pos++] = c;
|
|
||||||
read_pos++;
|
|
||||||
}
|
|
||||||
|
|
||||||
str[write_pos] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool dedup_should_print(uid_t uid, u8 type, const char *content, size_t len)
|
|
||||||
{
|
|
||||||
struct dedup_key key = {
|
|
||||||
.crc = dedup_calc_hash(content, len),
|
|
||||||
.uid = uid,
|
|
||||||
.type = type,
|
|
||||||
};
|
|
||||||
u64 now = ktime_get_ns();
|
|
||||||
u64 delta_ns = DEDUP_SECS * NSEC_PER_SEC;
|
|
||||||
|
|
||||||
u32 idx = key.crc & (SULOG_COMM_LEN - 1);
|
|
||||||
spin_lock(&dedup_lock);
|
|
||||||
|
|
||||||
struct dedup_entry *e = &dedup_tbl[idx];
|
|
||||||
if (e->key.crc == key.crc &&
|
|
||||||
e->key.uid == key.uid &&
|
|
||||||
e->key.type == key.type &&
|
|
||||||
(now - e->ts_ns) < delta_ns) {
|
|
||||||
spin_unlock(&dedup_lock);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
e->key = key;
|
|
||||||
e->ts_ns = now;
|
|
||||||
spin_unlock(&dedup_lock);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sulog_work_handler(struct work_struct *work)
|
|
||||||
{
|
|
||||||
struct file *fp;
|
|
||||||
struct sulog_entry *entry, *tmp;
|
|
||||||
LIST_HEAD(local_queue);
|
|
||||||
loff_t pos = 0;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&dedup_lock, flags);
|
|
||||||
list_splice_init(&sulog_queue, &local_queue);
|
|
||||||
spin_unlock_irqrestore(&dedup_lock, flags);
|
|
||||||
|
|
||||||
if (list_empty(&local_queue))
|
|
||||||
return;
|
|
||||||
|
|
||||||
fp = filp_open(SULOG_PATH, O_WRONLY | O_CREAT | O_APPEND, 0640);
|
|
||||||
if (IS_ERR(fp)) {
|
|
||||||
pr_err("sulog: failed to open log file: %ld\n", PTR_ERR(fp));
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fp->f_inode->i_size > SULOG_MAX_SIZE) {
|
|
||||||
if (vfs_truncate(&fp->f_path, 0))
|
|
||||||
pr_err("sulog: failed to truncate log file\n");
|
|
||||||
pos = 0;
|
|
||||||
} else {
|
|
||||||
pos = fp->f_inode->i_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
list_for_each_entry(entry, &local_queue, list)
|
|
||||||
kernel_write(fp, entry->content, strlen(entry->content), &pos);
|
|
||||||
|
|
||||||
vfs_fsync(fp, 0);
|
|
||||||
filp_close(fp, 0);
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
list_for_each_entry_safe(entry, tmp, &local_queue, list) {
|
|
||||||
list_del(&entry->list);
|
|
||||||
kfree(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sulog_add_entry(char *log_buf, size_t len, uid_t uid, u8 dedup_type)
|
|
||||||
{
|
|
||||||
struct sulog_entry *entry;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
if (!sulog_enabled || !log_buf || len == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!dedup_should_print(uid, dedup_type, log_buf, len))
|
|
||||||
return;
|
|
||||||
|
|
||||||
entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
|
|
||||||
if (!entry)
|
|
||||||
return;
|
|
||||||
|
|
||||||
KSU_STRSCPY(entry->content, log_buf, SULOG_ENTRY_MAX_LEN);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&dedup_lock, flags);
|
|
||||||
list_add_tail(&entry->list, &sulog_queue);
|
|
||||||
spin_unlock_irqrestore(&dedup_lock, flags);
|
|
||||||
|
|
||||||
if (sulog_workqueue)
|
|
||||||
queue_work(sulog_workqueue, &sulog_work);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method)
|
|
||||||
{
|
|
||||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
|
||||||
char timestamp[32];
|
|
||||||
char full_comm[SULOG_COMM_LEN];
|
|
||||||
|
|
||||||
if (!sulog_enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
get_timestamp(timestamp, sizeof(timestamp));
|
|
||||||
ksu_get_cmdline(full_comm, comm, sizeof(full_comm));
|
|
||||||
|
|
||||||
sanitize_string(full_comm, sizeof(full_comm));
|
|
||||||
|
|
||||||
snprintf(log_buf, sizeof(log_buf),
|
|
||||||
"[%s] SU_GRANT: UID=%d COMM=%s METHOD=%s PID=%d\n",
|
|
||||||
timestamp, uid, full_comm, method ? method : "unknown", current->pid);
|
|
||||||
|
|
||||||
sulog_add_entry(log_buf, strlen(log_buf), uid, DEDUP_SU_GRANT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target_path, bool success)
|
|
||||||
{
|
|
||||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
|
||||||
char timestamp[32];
|
|
||||||
char full_comm[SULOG_COMM_LEN];
|
|
||||||
|
|
||||||
if (!sulog_enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
get_timestamp(timestamp, sizeof(timestamp));
|
|
||||||
ksu_get_cmdline(full_comm, comm, sizeof(full_comm));
|
|
||||||
|
|
||||||
sanitize_string(full_comm, sizeof(full_comm));
|
|
||||||
|
|
||||||
snprintf(log_buf, sizeof(log_buf),
|
|
||||||
"[%s] SU_EXEC: UID=%d COMM=%s TARGET=%s RESULT=%s PID=%d\n",
|
|
||||||
timestamp, uid, full_comm, target_path ? target_path : "unknown",
|
|
||||||
success ? "SUCCESS" : "DENIED", current->pid);
|
|
||||||
|
|
||||||
sulog_add_entry(log_buf, strlen(log_buf), uid, DEDUP_SU_ATTEMPT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed)
|
|
||||||
{
|
|
||||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
|
||||||
char timestamp[32];
|
|
||||||
char full_comm[SULOG_COMM_LEN];
|
|
||||||
|
|
||||||
if (!sulog_enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
get_timestamp(timestamp, sizeof(timestamp));
|
|
||||||
ksu_get_cmdline(full_comm, comm, sizeof(full_comm));
|
|
||||||
|
|
||||||
sanitize_string(full_comm, sizeof(full_comm));
|
|
||||||
|
|
||||||
snprintf(log_buf, sizeof(log_buf),
|
|
||||||
"[%s] PERM_CHECK: UID=%d COMM=%s RESULT=%s PID=%d\n",
|
|
||||||
timestamp, uid, full_comm, allowed ? "ALLOWED" : "DENIED", current->pid);
|
|
||||||
|
|
||||||
sulog_add_entry(log_buf, strlen(log_buf), uid, DEDUP_PERM_CHECK);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid, uid_t target_uid)
|
|
||||||
{
|
|
||||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
|
||||||
char timestamp[32];
|
|
||||||
char full_comm[SULOG_COMM_LEN];
|
|
||||||
|
|
||||||
if (!sulog_enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
get_timestamp(timestamp, sizeof(timestamp));
|
|
||||||
ksu_get_cmdline(full_comm, NULL, sizeof(full_comm));
|
|
||||||
|
|
||||||
sanitize_string(full_comm, sizeof(full_comm));
|
|
||||||
|
|
||||||
snprintf(log_buf, sizeof(log_buf),
|
|
||||||
"[%s] MANAGER_OP: OP=%s MANAGER_UID=%d TARGET_UID=%d COMM=%s PID=%d\n",
|
|
||||||
timestamp, operation ? operation : "unknown", manager_uid, target_uid, full_comm, current->pid);
|
|
||||||
|
|
||||||
sulog_add_entry(log_buf, strlen(log_buf), manager_uid, DEDUP_MANAGER_OP);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_sulog_report_syscall(uid_t uid, const char *comm, const char *syscall, const char *args)
|
|
||||||
{
|
|
||||||
char log_buf[SULOG_ENTRY_MAX_LEN];
|
|
||||||
char timestamp[32];
|
|
||||||
char full_comm[SULOG_COMM_LEN];
|
|
||||||
|
|
||||||
if (!sulog_enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
get_timestamp(timestamp, sizeof(timestamp));
|
|
||||||
ksu_get_cmdline(full_comm, comm, sizeof(full_comm));
|
|
||||||
|
|
||||||
sanitize_string(full_comm, sizeof(full_comm));
|
|
||||||
|
|
||||||
snprintf(log_buf, sizeof(log_buf),
|
|
||||||
"[%s] SYSCALL: UID=%d COMM=%s SYSCALL=%s ARGS=%s PID=%d\n",
|
|
||||||
timestamp, uid, full_comm, syscall ? syscall : "unknown",
|
|
||||||
args ? args : "none", current->pid);
|
|
||||||
|
|
||||||
sulog_add_entry(log_buf, strlen(log_buf), uid, DEDUP_SYSCALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_sulog_init(void)
|
|
||||||
{
|
|
||||||
if (ksu_register_feature_handler(&sulog_handler)) {
|
|
||||||
pr_err("Failed to register sulog feature handler\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
sulog_workqueue = alloc_workqueue("ksu_sulog", WQ_UNBOUND | WQ_HIGHPRI, 1);
|
|
||||||
if (!sulog_workqueue) {
|
|
||||||
pr_err("sulog: failed to create workqueue\n");
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
INIT_WORK(&sulog_work, sulog_work_handler);
|
|
||||||
pr_info("sulog: initialized successfully\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_sulog_exit(void)
|
|
||||||
{
|
|
||||||
struct sulog_entry *entry, *tmp;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
ksu_unregister_feature_handler(KSU_FEATURE_SULOG);
|
|
||||||
|
|
||||||
sulog_enabled = false;
|
|
||||||
|
|
||||||
if (sulog_workqueue) {
|
|
||||||
flush_workqueue(sulog_workqueue);
|
|
||||||
destroy_workqueue(sulog_workqueue);
|
|
||||||
sulog_workqueue = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock_irqsave(&dedup_lock, flags);
|
|
||||||
list_for_each_entry_safe(entry, tmp, &sulog_queue, list) {
|
|
||||||
list_del(&entry->list);
|
|
||||||
kfree(entry);
|
|
||||||
}
|
|
||||||
spin_unlock_irqrestore(&dedup_lock, flags);
|
|
||||||
|
|
||||||
pr_info("sulog: cleaned up successfully\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // __SULOG_GATE
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
#ifndef __KSU_SULOG_H
|
|
||||||
#define __KSU_SULOG_H
|
|
||||||
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/version.h>
|
|
||||||
#include <linux/crc32.h> // needed for function dedup_calc_hash
|
|
||||||
|
|
||||||
#define __SULOG_GATE 1
|
|
||||||
|
|
||||||
#if __SULOG_GATE
|
|
||||||
|
|
||||||
extern struct timezone sys_tz;
|
|
||||||
|
|
||||||
#define SULOG_PATH "/data/adb/ksu/log/sulog.log"
|
|
||||||
#define SULOG_MAX_SIZE (32 * 1024 * 1024) // 128MB
|
|
||||||
#define SULOG_ENTRY_MAX_LEN 512
|
|
||||||
#define SULOG_COMM_LEN 256
|
|
||||||
#define DEDUP_SECS 10
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 10, 0)
|
|
||||||
static inline size_t strlcpy(char *dest, const char *src, size_t size)
|
|
||||||
{
|
|
||||||
return strscpy(dest, src, size);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define KSU_STRSCPY(dst, src, size) \
|
|
||||||
do { \
|
|
||||||
if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)) { \
|
|
||||||
strscpy(dst, src, size); \
|
|
||||||
} else { \
|
|
||||||
strlcpy(dst, src, size); \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
|
|
||||||
#include <linux/rtc.h>
|
|
||||||
|
|
||||||
static inline void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)
|
|
||||||
{
|
|
||||||
struct rtc_time rtc_tm;
|
|
||||||
rtc_time64_to_tm(totalsecs, &rtc_tm);
|
|
||||||
|
|
||||||
result->tm_sec = rtc_tm.tm_sec;
|
|
||||||
result->tm_min = rtc_tm.tm_min;
|
|
||||||
result->tm_hour = rtc_tm.tm_hour;
|
|
||||||
result->tm_mday = rtc_tm.tm_mday;
|
|
||||||
result->tm_mon = rtc_tm.tm_mon;
|
|
||||||
result->tm_year = rtc_tm.tm_year;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct dedup_key {
|
|
||||||
u32 crc;
|
|
||||||
uid_t uid;
|
|
||||||
u8 type;
|
|
||||||
u8 _pad[1];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct dedup_entry {
|
|
||||||
struct dedup_key key;
|
|
||||||
u64 ts_ns;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
|
||||||
DEDUP_SU_GRANT = 0,
|
|
||||||
DEDUP_SU_ATTEMPT,
|
|
||||||
DEDUP_PERM_CHECK,
|
|
||||||
DEDUP_MANAGER_OP,
|
|
||||||
DEDUP_SYSCALL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline u32 dedup_calc_hash(const char *content, size_t len)
|
|
||||||
{
|
|
||||||
return crc32(0, content, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sulog_entry {
|
|
||||||
struct list_head list;
|
|
||||||
char content[SULOG_ENTRY_MAX_LEN];
|
|
||||||
};
|
|
||||||
|
|
||||||
void ksu_sulog_report_su_grant(uid_t uid, const char *comm, const char *method);
|
|
||||||
void ksu_sulog_report_su_attempt(uid_t uid, const char *comm, const char *target_path, bool success);
|
|
||||||
void ksu_sulog_report_permission_check(uid_t uid, const char *comm, bool allowed);
|
|
||||||
void ksu_sulog_report_manager_operation(const char *operation, uid_t manager_uid, uid_t target_uid);
|
|
||||||
void ksu_sulog_report_syscall(uid_t uid, const char *comm, const char *syscall, const char *args);
|
|
||||||
|
|
||||||
int ksu_sulog_init(void);
|
|
||||||
void ksu_sulog_exit(void);
|
|
||||||
#endif // __SULOG_GATE
|
|
||||||
|
|
||||||
#endif /* __KSU_SULOG_H */
|
|
||||||
1072
kernel/supercalls.c
1072
kernel/supercalls.c
File diff suppressed because it is too large
Load diff
|
|
@ -1,197 +0,0 @@
|
||||||
#ifndef __KSU_H_SUPERCALLS
|
|
||||||
#define __KSU_H_SUPERCALLS
|
|
||||||
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/ioctl.h>
|
|
||||||
#include "ksu.h"
|
|
||||||
#include "app_profile.h"
|
|
||||||
|
|
||||||
#ifdef CONFIG_KPM
|
|
||||||
#include "kpm/kpm.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Magic numbers for reboot hook to install fd
|
|
||||||
#define KSU_INSTALL_MAGIC1 0xDEADBEEF
|
|
||||||
#define KSU_INSTALL_MAGIC2 0xCAFEBABE
|
|
||||||
|
|
||||||
// Command structures for ioctl
|
|
||||||
|
|
||||||
struct ksu_become_daemon_cmd {
|
|
||||||
__u8 token[65]; // Input: daemon token (null-terminated)
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_info_cmd {
|
|
||||||
__u32 version; // Output: KERNEL_SU_VERSION
|
|
||||||
__u32 flags; // Output: flags (bit 0: MODULE mode)
|
|
||||||
__u32 features; // Output: max feature ID supported
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_report_event_cmd {
|
|
||||||
__u32 event; // Input: EVENT_POST_FS_DATA, EVENT_BOOT_COMPLETED, etc.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_set_sepolicy_cmd {
|
|
||||||
__u64 cmd; // Input: sepolicy command
|
|
||||||
__aligned_u64 arg; // Input: sepolicy argument pointer
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_check_safemode_cmd {
|
|
||||||
__u8 in_safe_mode; // Output: true if in safe mode, false otherwise
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_allow_list_cmd {
|
|
||||||
__u32 uids[128]; // Output: array of allowed/denied UIDs
|
|
||||||
__u32 count; // Output: number of UIDs in array
|
|
||||||
__u8 allow; // Input: true for allow list, false for deny list
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_uid_granted_root_cmd {
|
|
||||||
__u32 uid; // Input: target UID to check
|
|
||||||
__u8 granted; // Output: true if granted, false otherwise
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_uid_should_umount_cmd {
|
|
||||||
__u32 uid; // Input: target UID to check
|
|
||||||
__u8 should_umount; // Output: true if should umount, false otherwise
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_manager_uid_cmd {
|
|
||||||
__u32 uid; // Output: manager UID
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_app_profile_cmd {
|
|
||||||
struct app_profile profile; // Input/Output: app profile structure
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_set_app_profile_cmd {
|
|
||||||
struct app_profile profile; // Input: app profile structure
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_feature_cmd {
|
|
||||||
__u32 feature_id; // Input: feature ID (enum ksu_feature_id)
|
|
||||||
__u64 value; // Output: feature value/state
|
|
||||||
__u8 supported; // Output: true if feature is supported, false otherwise
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_set_feature_cmd {
|
|
||||||
__u32 feature_id; // Input: feature ID (enum ksu_feature_id)
|
|
||||||
__u64 value; // Input: feature value/state to set
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_wrapper_fd_cmd {
|
|
||||||
__u32 fd; // Input: userspace fd
|
|
||||||
__u32 flags; // Input: flags of userspace fd
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_manage_mark_cmd {
|
|
||||||
__u32 operation; // Input: KSU_MARK_*
|
|
||||||
__s32 pid; // Input: target pid (0 for all processes)
|
|
||||||
__u32 result; // Output: for get operation - mark status or reg_count
|
|
||||||
};
|
|
||||||
|
|
||||||
#define KSU_MARK_GET 1
|
|
||||||
#define KSU_MARK_MARK 2
|
|
||||||
#define KSU_MARK_UNMARK 3
|
|
||||||
#define KSU_MARK_REFRESH 4
|
|
||||||
|
|
||||||
struct ksu_nuke_ext4_sysfs_cmd {
|
|
||||||
__aligned_u64 arg; // Input: mnt pointer
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_add_try_umount_cmd {
|
|
||||||
__aligned_u64 arg; // char ptr, this is the mountpoint
|
|
||||||
__u32 flags; // this is the flag we use for it
|
|
||||||
__u8 mode; // denotes what to do with it 0:wipe_list 1:add_to_list 2:delete_entry
|
|
||||||
};
|
|
||||||
|
|
||||||
#define KSU_UMOUNT_WIPE 0 // ignore everything and wipe list
|
|
||||||
#define KSU_UMOUNT_ADD 1 // add entry (path + flags)
|
|
||||||
#define KSU_UMOUNT_DEL 2 // delete entry, strcmp
|
|
||||||
|
|
||||||
|
|
||||||
// Other command structures
|
|
||||||
struct ksu_get_full_version_cmd {
|
|
||||||
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_hook_type_cmd {
|
|
||||||
char hook_type[32]; // Output: hook type string
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_enable_kpm_cmd {
|
|
||||||
__u8 enabled; // Output: true if KPM is enabled
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_dynamic_manager_cmd {
|
|
||||||
struct dynamic_manager_user_config config; // Input/Output: dynamic manager config
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_managers_cmd {
|
|
||||||
struct manager_list_info manager_info; // Output: manager list information
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_enable_uid_scanner_cmd {
|
|
||||||
__u32 operation; // Input: operation type (UID_SCANNER_OP_GET_STATUS, UID_SCANNER_OP_TOGGLE, UID_SCANNER_OP_CLEAR_ENV)
|
|
||||||
__u32 enabled; // Input: enable or disable (for UID_SCANNER_OP_TOGGLE)
|
|
||||||
void __user *status_ptr; // Input: pointer to store status (for UID_SCANNER_OP_GET_STATUS)
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
|
||||||
struct ksu_manual_su_cmd {
|
|
||||||
__u32 option; // Input: operation type (MANUAL_SU_OP_GENERATE_TOKEN, MANUAL_SU_OP_ESCALATE, MANUAL_SU_OP_ADD_PENDING)
|
|
||||||
__u32 target_uid; // Input: target UID
|
|
||||||
__u32 target_pid; // Input: target PID
|
|
||||||
char token_buffer[33]; // Input/Output: token buffer
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// IOCTL command definitions
|
|
||||||
#define KSU_IOCTL_GRANT_ROOT _IOC(_IOC_NONE, 'K', 1, 0)
|
|
||||||
#define KSU_IOCTL_GET_INFO _IOC(_IOC_READ, 'K', 2, 0)
|
|
||||||
#define KSU_IOCTL_REPORT_EVENT _IOC(_IOC_WRITE, 'K', 3, 0)
|
|
||||||
#define KSU_IOCTL_SET_SEPOLICY _IOC(_IOC_READ|_IOC_WRITE, 'K', 4, 0)
|
|
||||||
#define KSU_IOCTL_CHECK_SAFEMODE _IOC(_IOC_READ, 'K', 5, 0)
|
|
||||||
#define KSU_IOCTL_GET_ALLOW_LIST _IOC(_IOC_READ|_IOC_WRITE, 'K', 6, 0)
|
|
||||||
#define KSU_IOCTL_GET_DENY_LIST _IOC(_IOC_READ|_IOC_WRITE, 'K', 7, 0)
|
|
||||||
#define KSU_IOCTL_UID_GRANTED_ROOT _IOC(_IOC_READ|_IOC_WRITE, 'K', 8, 0)
|
|
||||||
#define KSU_IOCTL_UID_SHOULD_UMOUNT _IOC(_IOC_READ|_IOC_WRITE, 'K', 9, 0)
|
|
||||||
#define KSU_IOCTL_GET_MANAGER_UID _IOC(_IOC_READ, 'K', 10, 0)
|
|
||||||
#define KSU_IOCTL_GET_APP_PROFILE _IOC(_IOC_READ|_IOC_WRITE, 'K', 11, 0)
|
|
||||||
#define KSU_IOCTL_SET_APP_PROFILE _IOC(_IOC_WRITE, 'K', 12, 0)
|
|
||||||
#define KSU_IOCTL_GET_FEATURE _IOC(_IOC_READ|_IOC_WRITE, 'K', 13, 0)
|
|
||||||
#define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 0)
|
|
||||||
#define KSU_IOCTL_GET_WRAPPER_FD _IOC(_IOC_WRITE, 'K', 15, 0)
|
|
||||||
#define KSU_IOCTL_MANAGE_MARK _IOC(_IOC_READ|_IOC_WRITE, 'K', 16, 0)
|
|
||||||
#define KSU_IOCTL_NUKE_EXT4_SYSFS _IOC(_IOC_WRITE, 'K', 17, 0)
|
|
||||||
#define KSU_IOCTL_ADD_TRY_UMOUNT _IOC(_IOC_WRITE, 'K', 18, 0)
|
|
||||||
// Other IOCTL command definitions
|
|
||||||
#define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0)
|
|
||||||
#define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0)
|
|
||||||
#define KSU_IOCTL_ENABLE_KPM _IOC(_IOC_READ, 'K', 102, 0)
|
|
||||||
#define KSU_IOCTL_DYNAMIC_MANAGER _IOC(_IOC_READ|_IOC_WRITE, 'K', 103, 0)
|
|
||||||
#define KSU_IOCTL_GET_MANAGERS _IOC(_IOC_READ|_IOC_WRITE, 'K', 104, 0)
|
|
||||||
#define KSU_IOCTL_ENABLE_UID_SCANNER _IOC(_IOC_READ|_IOC_WRITE, 'K', 105, 0)
|
|
||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
|
||||||
#define KSU_IOCTL_MANUAL_SU _IOC(_IOC_READ|_IOC_WRITE, 'K', 106, 0)
|
|
||||||
#endif
|
|
||||||
#define KSU_IOCTL_UMOUNT_MANAGER _IOC(_IOC_READ|_IOC_WRITE, 'K', 107, 0)
|
|
||||||
|
|
||||||
// IOCTL handler types
|
|
||||||
typedef int (*ksu_ioctl_handler_t)(void __user *arg);
|
|
||||||
typedef bool (*ksu_perm_check_t)(void);
|
|
||||||
|
|
||||||
// IOCTL command mapping
|
|
||||||
struct ksu_ioctl_cmd_map {
|
|
||||||
unsigned int cmd;
|
|
||||||
const char *name;
|
|
||||||
ksu_ioctl_handler_t handler;
|
|
||||||
ksu_perm_check_t perm_check; // Permission check function
|
|
||||||
};
|
|
||||||
|
|
||||||
// Install KSU fd to current process
|
|
||||||
int ksu_install_fd(void);
|
|
||||||
|
|
||||||
void ksu_supercalls_init(void);
|
|
||||||
void ksu_supercalls_exit(void);
|
|
||||||
|
|
||||||
#endif // __KSU_H_SUPERCALLS
|
|
||||||
|
|
@ -1,374 +0,0 @@
|
||||||
#include "linux/compiler.h"
|
|
||||||
#include "linux/cred.h"
|
|
||||||
#include "linux/printk.h"
|
|
||||||
#include "selinux/selinux.h"
|
|
||||||
#include <linux/spinlock.h>
|
|
||||||
#include <linux/kprobes.h>
|
|
||||||
#include <linux/tracepoint.h>
|
|
||||||
#include <asm/syscall.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/ptrace.h>
|
|
||||||
#include <trace/events/syscalls.h>
|
|
||||||
#include <linux/namei.h>
|
|
||||||
|
|
||||||
#include "allowlist.h"
|
|
||||||
#include "arch.h"
|
|
||||||
#include "klog.h" // IWYU pragma: keep
|
|
||||||
#include "syscall_hook_manager.h"
|
|
||||||
#include "sucompat.h"
|
|
||||||
#include "setuid_hook.h"
|
|
||||||
#include "selinux/selinux.h"
|
|
||||||
|
|
||||||
// Tracepoint registration count management
|
|
||||||
// == 1: just us
|
|
||||||
// > 1: someone else is also using syscall tracepoint e.g. ftrace
|
|
||||||
static int tracepoint_reg_count = 0;
|
|
||||||
static DEFINE_SPINLOCK(tracepoint_reg_lock);
|
|
||||||
|
|
||||||
void ksu_clear_task_tracepoint_flag_if_needed(struct task_struct *t)
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
|
||||||
if (tracepoint_reg_count <= 1) {
|
|
||||||
ksu_clear_task_tracepoint_flag(t);
|
|
||||||
}
|
|
||||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process marking management
|
|
||||||
static void handle_process_mark(bool mark)
|
|
||||||
{
|
|
||||||
struct task_struct *p, *t;
|
|
||||||
read_lock(&tasklist_lock);
|
|
||||||
for_each_process_thread(p, t) {
|
|
||||||
if (mark)
|
|
||||||
ksu_set_task_tracepoint_flag(t);
|
|
||||||
else
|
|
||||||
ksu_clear_task_tracepoint_flag(t);
|
|
||||||
}
|
|
||||||
read_unlock(&tasklist_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_mark_all_process(void)
|
|
||||||
{
|
|
||||||
handle_process_mark(true);
|
|
||||||
pr_info("hook_manager: mark all user process done!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_unmark_all_process(void)
|
|
||||||
{
|
|
||||||
handle_process_mark(false);
|
|
||||||
pr_info("hook_manager: unmark all user process done!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ksu_mark_running_process_locked()
|
|
||||||
{
|
|
||||||
struct task_struct *p, *t;
|
|
||||||
read_lock(&tasklist_lock);
|
|
||||||
for_each_process_thread (p, t) {
|
|
||||||
if (!t->mm) { // only user processes
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int uid = task_uid(t).val;
|
|
||||||
const struct cred *cred = get_task_cred(t);
|
|
||||||
bool ksu_root_process =
|
|
||||||
uid == 0 && is_task_ksu_domain(cred);
|
|
||||||
bool is_zygote_process = is_zygote(cred);
|
|
||||||
bool is_shell = uid == 2000;
|
|
||||||
// before boot completed, we shall mark init for marking zygote
|
|
||||||
bool is_init = t->pid == 1;
|
|
||||||
if (ksu_root_process || is_zygote_process || is_shell || is_init
|
|
||||||
|| ksu_is_allow_uid(uid)) {
|
|
||||||
ksu_set_task_tracepoint_flag(t);
|
|
||||||
pr_info("hook_manager: mark process: pid:%d, uid: %d, comm:%s\n",
|
|
||||||
t->pid, uid, t->comm);
|
|
||||||
} else {
|
|
||||||
ksu_clear_task_tracepoint_flag(t);
|
|
||||||
pr_info("hook_manager: unmark process: pid:%d, uid: %d, comm:%s\n",
|
|
||||||
t->pid, uid, t->comm);
|
|
||||||
}
|
|
||||||
put_cred(cred);
|
|
||||||
}
|
|
||||||
read_unlock(&tasklist_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_mark_running_process()
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
|
||||||
if (tracepoint_reg_count <= 1) {
|
|
||||||
ksu_mark_running_process_locked();
|
|
||||||
} else {
|
|
||||||
pr_info("hook_manager: not mark running process since syscall tracepoint is in use\n");
|
|
||||||
}
|
|
||||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get task mark status
|
|
||||||
// Returns: 1 if marked, 0 if not marked, -ESRCH if task not found
|
|
||||||
int ksu_get_task_mark(pid_t pid)
|
|
||||||
{
|
|
||||||
struct task_struct *task;
|
|
||||||
int marked = -ESRCH;
|
|
||||||
|
|
||||||
rcu_read_lock();
|
|
||||||
task = find_task_by_vpid(pid);
|
|
||||||
if (task) {
|
|
||||||
get_task_struct(task);
|
|
||||||
rcu_read_unlock();
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
|
||||||
marked = test_task_syscall_work(task, SYSCALL_TRACEPOINT) ? 1 : 0;
|
|
||||||
#else
|
|
||||||
marked = test_tsk_thread_flag(task, TIF_SYSCALL_TRACEPOINT) ? 1 : 0;
|
|
||||||
#endif
|
|
||||||
put_task_struct(task);
|
|
||||||
} else {
|
|
||||||
rcu_read_unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return marked;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set task mark status
|
|
||||||
// Returns: 0 on success, -ESRCH if task not found
|
|
||||||
int ksu_set_task_mark(pid_t pid, bool mark)
|
|
||||||
{
|
|
||||||
struct task_struct *task;
|
|
||||||
int ret = -ESRCH;
|
|
||||||
|
|
||||||
rcu_read_lock();
|
|
||||||
task = find_task_by_vpid(pid);
|
|
||||||
if (task) {
|
|
||||||
get_task_struct(task);
|
|
||||||
rcu_read_unlock();
|
|
||||||
if (mark) {
|
|
||||||
ksu_set_task_tracepoint_flag(task);
|
|
||||||
pr_info("hook_manager: marked task pid=%d comm=%s\n", pid, task->comm);
|
|
||||||
} else {
|
|
||||||
ksu_clear_task_tracepoint_flag(task);
|
|
||||||
pr_info("hook_manager: unmarked task pid=%d comm=%s\n", pid, task->comm);
|
|
||||||
}
|
|
||||||
put_task_struct(task);
|
|
||||||
ret = 0;
|
|
||||||
} else {
|
|
||||||
rcu_read_unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_KRETPROBES
|
|
||||||
|
|
||||||
static struct kretprobe *init_kretprobe(const char *name,
|
|
||||||
kretprobe_handler_t handler)
|
|
||||||
{
|
|
||||||
struct kretprobe *rp = kzalloc(sizeof(struct kretprobe), GFP_KERNEL);
|
|
||||||
if (!rp)
|
|
||||||
return NULL;
|
|
||||||
rp->kp.symbol_name = name;
|
|
||||||
rp->handler = handler;
|
|
||||||
rp->data_size = 0;
|
|
||||||
rp->maxactive = 0;
|
|
||||||
|
|
||||||
int ret = register_kretprobe(rp);
|
|
||||||
pr_info("hook_manager: register_%s kretprobe: %d\n", name, ret);
|
|
||||||
if (ret) {
|
|
||||||
kfree(rp);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rp;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void destroy_kretprobe(struct kretprobe **rp_ptr)
|
|
||||||
{
|
|
||||||
struct kretprobe *rp = *rp_ptr;
|
|
||||||
if (!rp)
|
|
||||||
return;
|
|
||||||
unregister_kretprobe(rp);
|
|
||||||
synchronize_rcu();
|
|
||||||
kfree(rp);
|
|
||||||
*rp_ptr = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int syscall_regfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
|
||||||
if (tracepoint_reg_count < 1) {
|
|
||||||
// while install our tracepoint, mark our processes
|
|
||||||
ksu_mark_running_process_locked();
|
|
||||||
} else if (tracepoint_reg_count == 1) {
|
|
||||||
// while other tracepoint first added, mark all processes
|
|
||||||
ksu_mark_all_process();
|
|
||||||
}
|
|
||||||
tracepoint_reg_count++;
|
|
||||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int syscall_unregfunc_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
spin_lock_irqsave(&tracepoint_reg_lock, flags);
|
|
||||||
tracepoint_reg_count--;
|
|
||||||
if (tracepoint_reg_count <= 0) {
|
|
||||||
// while no tracepoint left, unmark all processes
|
|
||||||
ksu_unmark_all_process();
|
|
||||||
} else if (tracepoint_reg_count == 1) {
|
|
||||||
// while just our tracepoint left, unmark disallowed processes
|
|
||||||
ksu_mark_running_process_locked();
|
|
||||||
}
|
|
||||||
spin_unlock_irqrestore(&tracepoint_reg_lock, flags);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct kretprobe *syscall_regfunc_rp = NULL;
|
|
||||||
static struct kretprobe *syscall_unregfunc_rp = NULL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static inline bool check_syscall_fastpath(int nr)
|
|
||||||
{
|
|
||||||
switch (nr) {
|
|
||||||
case __NR_newfstatat:
|
|
||||||
case __NR_faccessat:
|
|
||||||
case __NR_execve:
|
|
||||||
case __NR_setresuid:
|
|
||||||
case __NR_clone:
|
|
||||||
case __NR_clone3:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmark init's child that are not zygote, adbd or ksud
|
|
||||||
int ksu_handle_init_mark_tracker(const char __user **filename_user)
|
|
||||||
{
|
|
||||||
char path[64];
|
|
||||||
|
|
||||||
if (unlikely(!filename_user))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
memset(path, 0, sizeof(path));
|
|
||||||
strncpy_from_user_nofault(path, *filename_user, sizeof(path));
|
|
||||||
|
|
||||||
if (likely(strstr(path, "/app_process") == NULL && strstr(path, "/adbd") == NULL && strstr(path, "/ksud") == NULL)) {
|
|
||||||
pr_info("hook_manager: unmark %d exec %s", current->pid, path);
|
|
||||||
ksu_clear_task_tracepoint_flag_if_needed(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
|
||||||
#include "manual_su.h"
|
|
||||||
static inline void ksu_handle_task_alloc(struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
ksu_try_escalate_for_uid(current_uid().val);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
|
|
||||||
// Generic sys_enter handler that dispatches to specific handlers
|
|
||||||
static void ksu_sys_enter_handler(void *data, struct pt_regs *regs, long id)
|
|
||||||
{
|
|
||||||
if (unlikely(check_syscall_fastpath(id))) {
|
|
||||||
#ifdef KSU_TP_HOOK
|
|
||||||
if (ksu_su_compat_enabled) {
|
|
||||||
// Handle newfstatat
|
|
||||||
if (id == __NR_newfstatat) {
|
|
||||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
|
||||||
const char __user **filename_user =
|
|
||||||
(const char __user **)&PT_REGS_PARM2(regs);
|
|
||||||
int *flags = (int *)&PT_REGS_SYSCALL_PARM4(regs);
|
|
||||||
ksu_handle_stat(dfd, filename_user, flags);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle faccessat
|
|
||||||
if (id == __NR_faccessat) {
|
|
||||||
int *dfd = (int *)&PT_REGS_PARM1(regs);
|
|
||||||
const char __user **filename_user =
|
|
||||||
(const char __user **)&PT_REGS_PARM2(regs);
|
|
||||||
int *mode = (int *)&PT_REGS_PARM3(regs);
|
|
||||||
ksu_handle_faccessat(dfd, filename_user, mode, NULL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle execve
|
|
||||||
if (id == __NR_execve) {
|
|
||||||
const char __user **filename_user =
|
|
||||||
(const char __user **)&PT_REGS_PARM1(regs);
|
|
||||||
if (current->pid != 1 && is_init(get_current_cred())) {
|
|
||||||
ksu_handle_init_mark_tracker(filename_user);
|
|
||||||
} else {
|
|
||||||
ksu_handle_execve_sucompat(filename_user, NULL, NULL, NULL);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Handle setresuid
|
|
||||||
if (id == __NR_setresuid) {
|
|
||||||
uid_t ruid = (uid_t)PT_REGS_PARM1(regs);
|
|
||||||
uid_t euid = (uid_t)PT_REGS_PARM2(regs);
|
|
||||||
uid_t suid = (uid_t)PT_REGS_PARM3(regs);
|
|
||||||
ksu_handle_setresuid(ruid, euid, suid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_KSU_MANUAL_SU
|
|
||||||
// Handle task_alloc via clone/fork
|
|
||||||
if (id == __NR_clone || id == __NR_clone3)
|
|
||||||
return ksu_handle_task_alloc(regs);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void ksu_syscall_hook_manager_init(void)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
pr_info("hook_manager: ksu_hook_manager_init called\n");
|
|
||||||
|
|
||||||
#ifdef CONFIG_KRETPROBES
|
|
||||||
// Register kretprobe for syscall_regfunc
|
|
||||||
syscall_regfunc_rp = init_kretprobe("syscall_regfunc", syscall_regfunc_handler);
|
|
||||||
// Register kretprobe for syscall_unregfunc
|
|
||||||
syscall_unregfunc_rp = init_kretprobe("syscall_unregfunc", syscall_unregfunc_handler);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
|
|
||||||
ret = register_trace_sys_enter(ksu_sys_enter_handler, NULL);
|
|
||||||
#ifndef CONFIG_KRETPROBES
|
|
||||||
ksu_mark_running_process_locked();
|
|
||||||
#endif
|
|
||||||
if (ret) {
|
|
||||||
pr_err("hook_manager: failed to register sys_enter tracepoint: %d\n", ret);
|
|
||||||
} else {
|
|
||||||
pr_info("hook_manager: sys_enter tracepoint registered\n");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ksu_setuid_hook_init();
|
|
||||||
ksu_sucompat_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_syscall_hook_manager_exit(void)
|
|
||||||
{
|
|
||||||
pr_info("hook_manager: ksu_hook_manager_exit called\n");
|
|
||||||
#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS
|
|
||||||
unregister_trace_sys_enter(ksu_sys_enter_handler, NULL);
|
|
||||||
tracepoint_synchronize_unregister();
|
|
||||||
pr_info("hook_manager: sys_enter tracepoint unregistered\n");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONFIG_KRETPROBES
|
|
||||||
destroy_kretprobe(&syscall_regfunc_rp);
|
|
||||||
destroy_kretprobe(&syscall_unregfunc_rp);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ksu_sucompat_exit();
|
|
||||||
ksu_setuid_hook_exit();
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
#ifndef __KSU_H_HOOK_MANAGER
|
|
||||||
#define __KSU_H_HOOK_MANAGER
|
|
||||||
|
|
||||||
#include <linux/version.h>
|
|
||||||
#include <linux/sched.h>
|
|
||||||
#include <linux/thread_info.h>
|
|
||||||
#include <linux/init.h>
|
|
||||||
#include <linux/binfmts.h>
|
|
||||||
#include <linux/tty.h>
|
|
||||||
#include <linux/fs.h>
|
|
||||||
#include "selinux/selinux.h"
|
|
||||||
|
|
||||||
// Hook manager initialization and cleanup
|
|
||||||
void ksu_syscall_hook_manager_init(void);
|
|
||||||
void ksu_syscall_hook_manager_exit(void);
|
|
||||||
|
|
||||||
// Process marking for tracepoint
|
|
||||||
void ksu_mark_all_process(void);
|
|
||||||
void ksu_unmark_all_process(void);
|
|
||||||
void ksu_mark_running_process(void);
|
|
||||||
|
|
||||||
// Per-task mark operations
|
|
||||||
int ksu_get_task_mark(pid_t pid);
|
|
||||||
int ksu_set_task_mark(pid_t pid, bool mark);
|
|
||||||
|
|
||||||
|
|
||||||
static inline void ksu_set_task_tracepoint_flag(struct task_struct *t)
|
|
||||||
{
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
|
||||||
set_task_syscall_work(t, SYSCALL_TRACEPOINT);
|
|
||||||
#else
|
|
||||||
set_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void ksu_clear_task_tracepoint_flag(struct task_struct *t)
|
|
||||||
{
|
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
|
|
||||||
clear_task_syscall_work(t, SYSCALL_TRACEPOINT);
|
|
||||||
#else
|
|
||||||
clear_tsk_thread_flag(t, TIF_SYSCALL_TRACEPOINT);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_clear_task_tracepoint_flag_if_needed(struct task_struct *t);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -7,17 +7,12 @@
|
||||||
|
|
||||||
#include "klog.h"
|
#include "klog.h"
|
||||||
#include "throne_comm.h"
|
#include "throne_comm.h"
|
||||||
#include "ksu.h"
|
|
||||||
|
|
||||||
#define PROC_UID_SCANNER "ksu_uid_scanner"
|
#define PROC_UID_SCANNER "ksu_uid_scanner"
|
||||||
#define UID_SCANNER_STATE_FILE "/data/adb/ksu/.uid_scanner"
|
|
||||||
|
|
||||||
static struct proc_dir_entry *proc_entry = NULL;
|
static struct proc_dir_entry *proc_entry = NULL;
|
||||||
static struct workqueue_struct *scanner_wq = NULL;
|
static struct workqueue_struct *scanner_wq = NULL;
|
||||||
static struct work_struct scan_work;
|
static struct work_struct scan_work;
|
||||||
static struct work_struct ksu_state_save_work;
|
|
||||||
static struct work_struct ksu_state_load_work;
|
|
||||||
|
|
||||||
|
|
||||||
// Signal userspace to rescan
|
// Signal userspace to rescan
|
||||||
static bool need_rescan = false;
|
static bool need_rescan = false;
|
||||||
|
|
@ -43,67 +38,6 @@ void ksu_handle_userspace_update(void)
|
||||||
pr_info("userspace uid list updated\n");
|
pr_info("userspace uid list updated\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void do_save_throne_state(struct work_struct *work)
|
|
||||||
{
|
|
||||||
struct file *fp;
|
|
||||||
char state_char = ksu_uid_scanner_enabled ? '1' : '0';
|
|
||||||
loff_t off = 0;
|
|
||||||
|
|
||||||
fp = filp_open(UID_SCANNER_STATE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
||||||
if (IS_ERR(fp)) {
|
|
||||||
pr_err("save_throne_state create file failed: %ld\n", PTR_ERR(fp));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kernel_write(fp, &state_char, sizeof(state_char), &off) != sizeof(state_char)) {
|
|
||||||
pr_err("save_throne_state write failed\n");
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
pr_info("throne state saved: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled");
|
|
||||||
|
|
||||||
exit:
|
|
||||||
filp_close(fp, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void do_load_throne_state(struct work_struct *work)
|
|
||||||
{
|
|
||||||
struct file *fp;
|
|
||||||
char state_char;
|
|
||||||
loff_t off = 0;
|
|
||||||
ssize_t ret;
|
|
||||||
|
|
||||||
fp = filp_open(UID_SCANNER_STATE_FILE, O_RDONLY, 0);
|
|
||||||
if (IS_ERR(fp)) {
|
|
||||||
pr_info("throne state file not found, using default: disabled\n");
|
|
||||||
ksu_uid_scanner_enabled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = kernel_read(fp, &state_char, sizeof(state_char), &off);
|
|
||||||
if (ret != sizeof(state_char)) {
|
|
||||||
pr_err("load_throne_state read err: %zd\n", ret);
|
|
||||||
ksu_uid_scanner_enabled = false;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
ksu_uid_scanner_enabled = (state_char == '1');
|
|
||||||
pr_info("throne state loaded: %s\n", ksu_uid_scanner_enabled ? "enabled" : "disabled");
|
|
||||||
|
|
||||||
exit:
|
|
||||||
filp_close(fp, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ksu_throne_comm_load_state(void)
|
|
||||||
{
|
|
||||||
return ksu_queue_work(&ksu_state_load_work);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_throne_comm_save_state(void)
|
|
||||||
{
|
|
||||||
ksu_queue_work(&ksu_state_save_work);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int uid_scanner_show(struct seq_file *m, void *v)
|
static int uid_scanner_show(struct seq_file *m, void *v)
|
||||||
{
|
{
|
||||||
if (need_rescan) {
|
if (need_rescan) {
|
||||||
|
|
@ -144,7 +78,6 @@ static ssize_t uid_scanner_write(struct file *file, const char __user *buffer,
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef KSU_COMPAT_HAS_PROC_OPS
|
|
||||||
static const struct proc_ops uid_scanner_proc_ops = {
|
static const struct proc_ops uid_scanner_proc_ops = {
|
||||||
.proc_open = uid_scanner_open,
|
.proc_open = uid_scanner_open,
|
||||||
.proc_read = seq_read,
|
.proc_read = seq_read,
|
||||||
|
|
@ -152,16 +85,6 @@ static const struct proc_ops uid_scanner_proc_ops = {
|
||||||
.proc_lseek = seq_lseek,
|
.proc_lseek = seq_lseek,
|
||||||
.proc_release = single_release,
|
.proc_release = single_release,
|
||||||
};
|
};
|
||||||
#else
|
|
||||||
static const struct file_operations uid_scanner_proc_ops = {
|
|
||||||
.owner = THIS_MODULE,
|
|
||||||
.open = uid_scanner_open,
|
|
||||||
.read = seq_read,
|
|
||||||
.write = uid_scanner_write,
|
|
||||||
.llseek = seq_lseek,
|
|
||||||
.release = single_release,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int ksu_throne_comm_init(void)
|
int ksu_throne_comm_init(void)
|
||||||
{
|
{
|
||||||
|
|
@ -200,15 +123,3 @@ void ksu_throne_comm_exit(void)
|
||||||
|
|
||||||
pr_info("throne communication cleaned up\n");
|
pr_info("throne communication cleaned up\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
int ksu_uid_init(void)
|
|
||||||
{
|
|
||||||
INIT_WORK(&ksu_state_save_work, do_save_throne_state);
|
|
||||||
INIT_WORK(&ksu_state_load_work, do_load_throne_state);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_uid_exit(void)
|
|
||||||
{
|
|
||||||
do_save_throne_state(NULL);
|
|
||||||
}
|
|
||||||
|
|
@ -9,14 +9,4 @@ int ksu_throne_comm_init(void);
|
||||||
|
|
||||||
void ksu_throne_comm_exit(void);
|
void ksu_throne_comm_exit(void);
|
||||||
|
|
||||||
int ksu_uid_init(void);
|
|
||||||
|
|
||||||
void ksu_uid_exit(void);
|
|
||||||
|
|
||||||
bool ksu_throne_comm_load_state(void);
|
|
||||||
|
|
||||||
void ksu_throne_comm_save_state(void);
|
|
||||||
|
|
||||||
void do_load_throne_state(struct work_struct *work);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -10,18 +10,18 @@
|
||||||
|
|
||||||
#include "allowlist.h"
|
#include "allowlist.h"
|
||||||
#include "klog.h" // IWYU pragma: keep
|
#include "klog.h" // IWYU pragma: keep
|
||||||
|
#include "ksu.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "throne_tracker.h"
|
#include "throne_tracker.h"
|
||||||
#include "apk_sign.h"
|
#include "kernel_compat.h"
|
||||||
#include "dynamic_manager.h"
|
#include "dynamic_manager.h"
|
||||||
#include "throne_comm.h"
|
#include "throne_comm.h"
|
||||||
|
|
||||||
uid_t ksu_manager_uid = KSU_INVALID_UID;
|
uid_t ksu_manager_uid = KSU_INVALID_UID;
|
||||||
static uid_t locked_manager_uid = KSU_INVALID_UID;
|
|
||||||
static uid_t locked_dynamic_manager_uid = KSU_INVALID_UID;
|
|
||||||
|
|
||||||
#define KSU_UID_LIST_PATH "/data/misc/user_uid/uid_list"
|
#define KSU_UID_LIST_PATH "/data/misc/user_uid/uid_list"
|
||||||
#define SYSTEM_PACKAGES_LIST_PATH "/data/system/packages.list"
|
#define USER_DATA_PATH "/data/user_de/0"
|
||||||
|
#define USER_DATA_PATH_LEN 256
|
||||||
|
|
||||||
struct uid_data {
|
struct uid_data {
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
|
|
@ -29,75 +29,109 @@ struct uid_data {
|
||||||
char package[KSU_MAX_PACKAGE_NAME];
|
char package[KSU_MAX_PACKAGE_NAME];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try read /data/misc/user_uid/uid_list
|
// Try read whitelist first, fallback if failed
|
||||||
static int uid_from_um_list(struct list_head *uid_list)
|
static int read_uid_whitelist(struct list_head *uid_list)
|
||||||
{
|
{
|
||||||
struct file *fp;
|
struct file *fp;
|
||||||
char *buf = NULL;
|
char *file_content = NULL;
|
||||||
loff_t size, pos = 0;
|
char *line, *next_line;
|
||||||
ssize_t nr;
|
loff_t file_size;
|
||||||
int cnt = 0;
|
loff_t pos = 0;
|
||||||
|
int count = 0;
|
||||||
|
ssize_t bytes_read;
|
||||||
|
|
||||||
fp = filp_open(KSU_UID_LIST_PATH, O_RDONLY, 0);
|
fp = ksu_filp_open_compat(KSU_UID_LIST_PATH, O_RDONLY, 0);
|
||||||
if (IS_ERR(fp))
|
if (IS_ERR(fp)) {
|
||||||
|
pr_info("whitelist not found, fallback needed\n");
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
size = fp->f_inode->i_size;
|
file_size = fp->f_inode->i_size;
|
||||||
if (size <= 0) {
|
if (file_size <= 0) {
|
||||||
|
pr_info("whitelist file is empty\n");
|
||||||
filp_close(fp, NULL);
|
filp_close(fp, NULL);
|
||||||
return -ENODATA;
|
return -ENODATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
buf = kzalloc(size + 1, GFP_ATOMIC);
|
file_content = kzalloc(file_size + 1, GFP_ATOMIC);
|
||||||
if (!buf) {
|
if (!file_content) {
|
||||||
pr_err("uid_list: OOM %lld B\n", size);
|
pr_err("failed to allocate memory for whitelist file (%lld bytes)\n", file_size);
|
||||||
filp_close(fp, NULL);
|
filp_close(fp, NULL);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
nr = kernel_read(fp, buf, size, &pos);
|
bytes_read = ksu_kernel_read_compat(fp, file_content, file_size, &pos);
|
||||||
|
if (bytes_read != file_size) {
|
||||||
|
pr_err("failed to read whitelist file: read %zd bytes, expected %lld bytes\n",
|
||||||
|
bytes_read, file_size);
|
||||||
|
kfree(file_content);
|
||||||
filp_close(fp, NULL);
|
filp_close(fp, NULL);
|
||||||
if (nr != size) {
|
|
||||||
pr_err("uid_list: short read %zd/%lld\n", nr, size);
|
|
||||||
kfree(buf);
|
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
buf[size] = '\0';
|
|
||||||
|
|
||||||
for (char *line = buf, *next; line; line = next) {
|
file_content[file_size] = '\0';
|
||||||
next = strchr(line, '\n');
|
filp_close(fp, NULL);
|
||||||
if (next) *next++ = '\0';
|
|
||||||
|
|
||||||
while (*line == ' ' || *line == '\t' || *line == '\r') ++line;
|
pr_info("successfully read whitelist file (%lld bytes), parsing lines...\n", file_size);
|
||||||
if (!*line) continue;
|
|
||||||
|
|
||||||
char *uid_str = strsep(&line, " \t");
|
line = file_content;
|
||||||
char *pkg = line;
|
while (line && *line) {
|
||||||
if (!pkg) continue;
|
next_line = strchr(line, '\n');
|
||||||
while (*pkg == ' ' || *pkg == '\t') ++pkg;
|
if (next_line) {
|
||||||
if (!*pkg) continue;
|
*next_line = '\0';
|
||||||
|
next_line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *trimmed_line = line;
|
||||||
|
while (*trimmed_line == ' ' || *trimmed_line == '\t' || *trimmed_line == '\r') {
|
||||||
|
trimmed_line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(trimmed_line) > 0) {
|
||||||
|
char *line_copy = trimmed_line;
|
||||||
|
char *uid_str = strsep(&line_copy, " \t");
|
||||||
|
char *package_name = line_copy;
|
||||||
|
|
||||||
|
if (package_name) {
|
||||||
|
while (*package_name == ' ' || *package_name == '\t') {
|
||||||
|
package_name++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid_str && package_name && strlen(package_name) > 0) {
|
||||||
u32 uid;
|
u32 uid;
|
||||||
if (kstrtou32(uid_str, 10, &uid)) {
|
if (!kstrtou32(uid_str, 10, &uid)) {
|
||||||
pr_warn_once("uid_list: bad uid <%s>\n", uid_str);
|
struct uid_data *data = kzalloc(sizeof(struct uid_data), GFP_ATOMIC);
|
||||||
continue;
|
if (data) {
|
||||||
|
data->uid = uid;
|
||||||
|
size_t pkg_len = strlen(package_name);
|
||||||
|
size_t copy_len = min(pkg_len, (size_t)(KSU_MAX_PACKAGE_NAME - 1));
|
||||||
|
strncpy(data->package, package_name, copy_len);
|
||||||
|
data->package[copy_len] = '\0';
|
||||||
|
|
||||||
|
list_add_tail(&data->list, uid_list);
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count % 100 == 0) {
|
||||||
|
pr_info("parsed %d packages so far...\n", count);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pr_err("failed to allocate memory for uid_data\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pr_warn("invalid uid format in line: %s\n", trimmed_line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pr_warn("invalid line format: %s\n", trimmed_line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct uid_data *d = kzalloc(sizeof(*d), GFP_ATOMIC);
|
line = next_line;
|
||||||
if (unlikely(!d)) {
|
|
||||||
pr_err("uid_list: OOM uid=%u\n", uid);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d->uid = uid;
|
kfree(file_content);
|
||||||
strscpy(d->package, pkg, KSU_MAX_PACKAGE_NAME);
|
pr_info("successfully loaded %d uids from whitelist\n", count);
|
||||||
list_add_tail(&d->list, uid_list);
|
return count > 0 ? 0 : -ENODATA;
|
||||||
++cnt;
|
|
||||||
}
|
|
||||||
|
|
||||||
kfree(buf);
|
|
||||||
pr_info("uid_list: loaded %d entries\n", cnt);
|
|
||||||
return cnt > 0 ? 0 : -ENODATA;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_pkg_from_apk_path(char *pkg, const char *path)
|
static int get_pkg_from_apk_path(char *pkg, const char *path)
|
||||||
|
|
@ -157,41 +191,22 @@ static void crown_manager(const char *apk, struct list_head *uid_data, int signa
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
struct list_head *list = (struct list_head *)uid_data;
|
||||||
struct uid_data *np;
|
struct uid_data *np;
|
||||||
|
|
||||||
list_for_each_entry(np, uid_data, list) {
|
list_for_each_entry (np, list, list) {
|
||||||
if (strncmp(np->package, pkg, KSU_MAX_PACKAGE_NAME) == 0) {
|
if (strncmp(np->package, pkg, KSU_MAX_PACKAGE_NAME) == 0) {
|
||||||
bool is_dynamic = (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2);
|
pr_info("Crowning manager: %s(uid=%d, signature_index=%d)\n", pkg, np->uid, signature_index);
|
||||||
|
|
||||||
if (is_dynamic) {
|
// Dynamic Sign index (1) or multi-manager signatures (2+)
|
||||||
if (locked_dynamic_manager_uid != KSU_INVALID_UID && locked_dynamic_manager_uid != np->uid) {
|
if (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2) {
|
||||||
pr_info("Unlocking previous dynamic manager UID: %d\n", locked_dynamic_manager_uid);
|
|
||||||
ksu_remove_manager(locked_dynamic_manager_uid);
|
|
||||||
locked_dynamic_manager_uid = KSU_INVALID_UID;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (locked_manager_uid != KSU_INVALID_UID && locked_manager_uid != np->uid) {
|
|
||||||
pr_info("Unlocking previous manager UID: %d\n", locked_manager_uid);
|
|
||||||
ksu_invalidate_manager_uid(); // unlock old one
|
|
||||||
locked_manager_uid = KSU_INVALID_UID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pr_info("Crowning %s manager: %s (uid=%d, signature_index=%d)\n",
|
|
||||||
is_dynamic ? "dynamic" : "traditional", pkg, np->uid, signature_index);
|
|
||||||
|
|
||||||
if (is_dynamic) {
|
|
||||||
ksu_add_manager(np->uid, signature_index);
|
ksu_add_manager(np->uid, signature_index);
|
||||||
locked_dynamic_manager_uid = np->uid;
|
|
||||||
|
|
||||||
// If there is no traditional manager, set it to the current UID
|
|
||||||
if (!ksu_is_manager_uid_valid()) {
|
if (!ksu_is_manager_uid_valid()) {
|
||||||
ksu_set_manager_uid(np->uid);
|
ksu_set_manager_uid(np->uid);
|
||||||
locked_manager_uid = np->uid;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ksu_set_manager_uid(np->uid); // throne new UID
|
ksu_set_manager_uid(np->uid);
|
||||||
locked_manager_uid = np->uid; // store locked UID
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -233,6 +248,138 @@ struct my_dir_context {
|
||||||
#define FILLDIR_ACTOR_CONTINUE 0
|
#define FILLDIR_ACTOR_CONTINUE 0
|
||||||
#define FILLDIR_ACTOR_STOP -EINVAL
|
#define FILLDIR_ACTOR_STOP -EINVAL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct uid_scan_stats {
|
||||||
|
size_t total_found;
|
||||||
|
size_t errors_encountered;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct user_data_context {
|
||||||
|
struct dir_context ctx;
|
||||||
|
struct list_head *uid_list;
|
||||||
|
struct uid_scan_stats *stats;
|
||||||
|
};
|
||||||
|
|
||||||
|
FILLDIR_RETURN_TYPE user_data_actor(struct dir_context *ctx, const char *name,
|
||||||
|
int namelen, loff_t off, u64 ino,
|
||||||
|
unsigned int d_type)
|
||||||
|
{
|
||||||
|
struct user_data_context *my_ctx =
|
||||||
|
container_of(ctx, struct user_data_context, ctx);
|
||||||
|
|
||||||
|
if (!my_ctx || !my_ctx->uid_list) {
|
||||||
|
return FILLDIR_ACTOR_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strncmp(name, "..", namelen) || !strncmp(name, ".", namelen))
|
||||||
|
return FILLDIR_ACTOR_CONTINUE;
|
||||||
|
|
||||||
|
if (d_type != DT_DIR)
|
||||||
|
return FILLDIR_ACTOR_CONTINUE;
|
||||||
|
|
||||||
|
if (namelen >= KSU_MAX_PACKAGE_NAME) {
|
||||||
|
pr_warn("Package name too long: %.*s\n", namelen, name);
|
||||||
|
if (my_ctx->stats)
|
||||||
|
my_ctx->stats->errors_encountered++;
|
||||||
|
return FILLDIR_ACTOR_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
char package_path[USER_DATA_PATH_LEN];
|
||||||
|
if (snprintf(package_path, sizeof(package_path), "%s/%.*s",
|
||||||
|
USER_DATA_PATH, namelen, name) >= sizeof(package_path)) {
|
||||||
|
pr_err("Path too long for package: %.*s\n", namelen, name);
|
||||||
|
if (my_ctx->stats)
|
||||||
|
my_ctx->stats->errors_encountered++;
|
||||||
|
return FILLDIR_ACTOR_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct path path;
|
||||||
|
int err = kern_path(package_path, LOOKUP_FOLLOW, &path);
|
||||||
|
if (err) {
|
||||||
|
pr_debug("Package path lookup failed: %s (err: %d)\n", package_path, err);
|
||||||
|
if (my_ctx->stats)
|
||||||
|
my_ctx->stats->errors_encountered++;
|
||||||
|
return FILLDIR_ACTOR_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct kstat stat;
|
||||||
|
err = vfs_getattr(&path, &stat, STATX_UID, AT_STATX_SYNC_AS_STAT);
|
||||||
|
path_put(&path);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
pr_debug("Failed to get attributes for: %s (err: %d)\n", package_path, err);
|
||||||
|
if (my_ctx->stats)
|
||||||
|
my_ctx->stats->errors_encountered++;
|
||||||
|
return FILLDIR_ACTOR_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
uid_t uid = from_kuid(&init_user_ns, stat.uid);
|
||||||
|
if (uid == (uid_t)-1) {
|
||||||
|
pr_warn("Invalid UID for package: %.*s\n", namelen, name);
|
||||||
|
if (my_ctx->stats)
|
||||||
|
my_ctx->stats->errors_encountered++;
|
||||||
|
return FILLDIR_ACTOR_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct uid_data *data = kzalloc(sizeof(struct uid_data), GFP_ATOMIC);
|
||||||
|
if (!data) {
|
||||||
|
pr_err("Failed to allocate memory for package: %.*s\n", namelen, name);
|
||||||
|
if (my_ctx->stats)
|
||||||
|
my_ctx->stats->errors_encountered++;
|
||||||
|
return FILLDIR_ACTOR_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->uid = uid;
|
||||||
|
size_t copy_len = min(namelen, KSU_MAX_PACKAGE_NAME - 1);
|
||||||
|
strncpy(data->package, name, copy_len);
|
||||||
|
data->package[copy_len] = '\0';
|
||||||
|
|
||||||
|
list_add_tail(&data->list, my_ctx->uid_list);
|
||||||
|
|
||||||
|
if (my_ctx->stats)
|
||||||
|
my_ctx->stats->total_found++;
|
||||||
|
|
||||||
|
pr_info("UserDE UID: Found package: %s, uid: %u\n", data->package, data->uid);
|
||||||
|
|
||||||
|
return FILLDIR_ACTOR_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int scan_user_data_for_uids(struct list_head *uid_list)
|
||||||
|
{
|
||||||
|
struct file *dir_file;
|
||||||
|
struct uid_scan_stats stats = {0};
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!uid_list) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir_file = ksu_filp_open_compat(USER_DATA_PATH, O_RDONLY, 0);
|
||||||
|
if (IS_ERR(dir_file)) {
|
||||||
|
pr_err("UserDE UID: Failed to open %s: %ld\n", USER_DATA_PATH, PTR_ERR(dir_file));
|
||||||
|
return PTR_ERR(dir_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct user_data_context ctx = {
|
||||||
|
.ctx.actor = user_data_actor,
|
||||||
|
.uid_list = uid_list,
|
||||||
|
.stats = &stats
|
||||||
|
};
|
||||||
|
|
||||||
|
ret = iterate_dir(dir_file, &ctx.ctx);
|
||||||
|
filp_close(dir_file, NULL);
|
||||||
|
|
||||||
|
if (stats.errors_encountered > 0) {
|
||||||
|
pr_warn("Encountered %zu errors while scanning user data directory\n",
|
||||||
|
stats.errors_encountered);
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_info("UserDE UID: Scanned user data directory, found %zu packages with %zu errors\n",
|
||||||
|
stats.total_found, stats.errors_encountered);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||||
int namelen, loff_t off, u64 ino,
|
int namelen, loff_t off, u64 ino,
|
||||||
unsigned int d_type)
|
unsigned int d_type)
|
||||||
|
|
@ -259,6 +406,7 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||||
return FILLDIR_ACTOR_CONTINUE; // Skip staging package
|
return FILLDIR_ACTOR_CONTINUE; // Skip staging package
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir,
|
if (snprintf(dirpath, DATA_PATH_LEN, "%s/%.*s", my_ctx->parent_dir,
|
||||||
namelen, name) >= DATA_PATH_LEN) {
|
namelen, name) >= DATA_PATH_LEN) {
|
||||||
pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen,
|
pr_err("Path too long: %s/%.*s\n", my_ctx->parent_dir, namelen,
|
||||||
|
|
@ -268,7 +416,7 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||||
|
|
||||||
if (d_type == DT_DIR && my_ctx->depth > 0 &&
|
if (d_type == DT_DIR && my_ctx->depth > 0 &&
|
||||||
(my_ctx->stop && !*my_ctx->stop)) {
|
(my_ctx->stop && !*my_ctx->stop)) {
|
||||||
struct data_path *data = kzalloc(sizeof(struct data_path), GFP_ATOMIC);
|
struct data_path *data = kmalloc(sizeof(struct data_path), GFP_ATOMIC);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
pr_err("Failed to allocate memory for %s\n", dirpath);
|
pr_err("Failed to allocate memory for %s\n", dirpath);
|
||||||
|
|
@ -281,11 +429,7 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||||
} else {
|
} else {
|
||||||
if ((namelen == 8) && (strncmp(name, "base.apk", namelen) == 0)) {
|
if ((namelen == 8) && (strncmp(name, "base.apk", namelen) == 0)) {
|
||||||
struct apk_path_hash *pos, *n;
|
struct apk_path_hash *pos, *n;
|
||||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
|
|
||||||
unsigned int hash = full_name_hash(dirpath, strlen(dirpath));
|
|
||||||
#else
|
|
||||||
unsigned int hash = full_name_hash(NULL, dirpath, strlen(dirpath));
|
unsigned int hash = full_name_hash(NULL, dirpath, strlen(dirpath));
|
||||||
#endif
|
|
||||||
list_for_each_entry(pos, &apk_path_hash_list, list) {
|
list_for_each_entry(pos, &apk_path_hash_list, list) {
|
||||||
if (hash == pos->hash) {
|
if (hash == pos->hash) {
|
||||||
pos->exists = true;
|
pos->exists = true;
|
||||||
|
|
@ -303,24 +447,30 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name,
|
||||||
// Check for dynamic sign or multi-manager signatures
|
// Check for dynamic sign or multi-manager signatures
|
||||||
if (is_multi_manager && (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2)) {
|
if (is_multi_manager && (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2)) {
|
||||||
crown_manager(dirpath, my_ctx->private_data, signature_index);
|
crown_manager(dirpath, my_ctx->private_data, signature_index);
|
||||||
} else if (is_manager_apk(dirpath)) {
|
|
||||||
crown_manager(dirpath, my_ctx->private_data, 0);
|
|
||||||
*my_ctx->stop = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct apk_path_hash *apk_data = kzalloc(sizeof(*apk_data), GFP_ATOMIC);
|
struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC);
|
||||||
if (apk_data) {
|
if (apk_data) {
|
||||||
apk_data->hash = hash;
|
apk_data->hash = hash;
|
||||||
apk_data->exists = true;
|
apk_data->exists = true;
|
||||||
list_add_tail(&apk_data->list, &apk_path_hash_list);
|
list_add_tail(&apk_data->list, &apk_path_hash_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_manager_apk(dirpath)) {
|
} else if (is_manager_apk(dirpath)) {
|
||||||
|
crown_manager(dirpath, my_ctx->private_data, 0);
|
||||||
|
*my_ctx->stop = 1;
|
||||||
|
|
||||||
// Manager found, clear APK cache list
|
// Manager found, clear APK cache list
|
||||||
list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) {
|
list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) {
|
||||||
list_del(&pos->list);
|
list_del(&pos->list);
|
||||||
kfree(pos);
|
kfree(pos);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
struct apk_path_hash *apk_data = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC);
|
||||||
|
if (apk_data) {
|
||||||
|
apk_data->hash = hash;
|
||||||
|
apk_data->exists = true;
|
||||||
|
list_add_tail(&apk_data->list, &apk_path_hash_list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -360,10 +510,9 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
|
||||||
struct file *file;
|
struct file *file;
|
||||||
|
|
||||||
if (!stop) {
|
if (!stop) {
|
||||||
file = filp_open(pos->dirpath, O_RDONLY | O_NOFOLLOW, 0);
|
file = ksu_filp_open_compat(pos->dirpath, O_RDONLY | O_NOFOLLOW, 0);
|
||||||
if (IS_ERR(file)) {
|
if (IS_ERR(file)) {
|
||||||
pr_err("Failed to open directory: %s, err: %ld\n",
|
pr_err("Failed to open directory: %s, err: %ld\n", pos->dirpath, PTR_ERR(file));
|
||||||
pos->dirpath, PTR_ERR(file));
|
|
||||||
goto skip_iterate;
|
goto skip_iterate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,8 +520,7 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
|
||||||
if (!data_app_magic) {
|
if (!data_app_magic) {
|
||||||
if (file->f_inode->i_sb->s_magic) {
|
if (file->f_inode->i_sb->s_magic) {
|
||||||
data_app_magic = file->f_inode->i_sb->s_magic;
|
data_app_magic = file->f_inode->i_sb->s_magic;
|
||||||
pr_info("%s: dir: %s got magic! 0x%lx\n", __func__,
|
pr_info("%s: dir: %s got magic! 0x%lx\n", __func__, pos->dirpath, data_app_magic);
|
||||||
pos->dirpath, data_app_magic);
|
|
||||||
} else {
|
} else {
|
||||||
filp_close(file, NULL);
|
filp_close(file, NULL);
|
||||||
goto skip_iterate;
|
goto skip_iterate;
|
||||||
|
|
@ -380,8 +528,7 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file->f_inode->i_sb->s_magic != data_app_magic) {
|
if (file->f_inode->i_sb->s_magic != data_app_magic) {
|
||||||
pr_info("%s: skip: %s magic: 0x%lx expected: 0x%lx\n",
|
pr_info("%s: skip: %s magic: 0x%lx expected: 0x%lx\n", __func__, pos->dirpath,
|
||||||
__func__, pos->dirpath,
|
|
||||||
file->f_inode->i_sb->s_magic, data_app_magic);
|
file->f_inode->i_sb->s_magic, data_app_magic);
|
||||||
filp_close(file, NULL);
|
filp_close(file, NULL);
|
||||||
goto skip_iterate;
|
goto skip_iterate;
|
||||||
|
|
@ -422,127 +569,74 @@ static bool is_uid_exist(uid_t uid, char *package, void *data)
|
||||||
return exist;
|
return exist;
|
||||||
}
|
}
|
||||||
|
|
||||||
void track_throne(bool prune_only)
|
void track_throne()
|
||||||
{
|
{
|
||||||
struct list_head uid_list;
|
struct list_head uid_list;
|
||||||
struct uid_data *np, *n;
|
|
||||||
struct file *fp;
|
|
||||||
char chr = 0;
|
|
||||||
loff_t pos = 0;
|
|
||||||
loff_t line_start = 0;
|
|
||||||
char buf[KSU_MAX_PACKAGE_NAME];
|
|
||||||
static bool manager_exist = false;
|
|
||||||
static bool dynamic_manager_exist = false;
|
|
||||||
int current_manager_uid = ksu_get_manager_uid() % 100000;
|
|
||||||
|
|
||||||
// init uid list head
|
|
||||||
INIT_LIST_HEAD(&uid_list);
|
INIT_LIST_HEAD(&uid_list);
|
||||||
|
|
||||||
if (ksu_uid_scanner_enabled) {
|
pr_info("track_throne triggered, attempting whitelist read\n");
|
||||||
pr_info("Scanning %s directory..\n", KSU_UID_LIST_PATH);
|
|
||||||
|
|
||||||
if (uid_from_um_list(&uid_list) == 0) {
|
// Try read whitelist first
|
||||||
pr_info("Loaded UIDs from %s success\n", KSU_UID_LIST_PATH);
|
int ret = read_uid_whitelist(&uid_list);
|
||||||
goto uid_ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
pr_warn("%s read failed, fallback to %s\n",
|
if (ret < 0) {
|
||||||
KSU_UID_LIST_PATH, SYSTEM_PACKAGES_LIST_PATH);
|
pr_info("whitelist read failed (%d), request userspace scan, falling back to user_de \n", ret);
|
||||||
}
|
|
||||||
|
|
||||||
{
|
int ret_user = scan_user_data_for_uids(&uid_list);
|
||||||
fp = filp_open(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
|
|
||||||
if (IS_ERR(fp)) {
|
|
||||||
pr_err("%s: open " SYSTEM_PACKAGES_LIST_PATH " failed: %ld\n", __func__, PTR_ERR(fp));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
if (ret_user < 0) {
|
||||||
ssize_t count =
|
|
||||||
kernel_read(fp, &chr, sizeof(chr), &pos);
|
|
||||||
if (count != sizeof(chr))
|
|
||||||
break;
|
|
||||||
if (chr != '\n')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
count = kernel_read(fp, buf, sizeof(buf),
|
|
||||||
&line_start);
|
|
||||||
struct uid_data *data =
|
|
||||||
kzalloc(sizeof(struct uid_data), GFP_ATOMIC);
|
|
||||||
if (!data) {
|
|
||||||
filp_close(fp, 0);
|
|
||||||
goto out;
|
goto out;
|
||||||
|
} else {
|
||||||
|
pr_info("UserDE UID: Successfully loaded %zu packages from user data directory\n", list_count_nodes(&uid_list));
|
||||||
}
|
}
|
||||||
|
|
||||||
char *tmp = buf;
|
} else {
|
||||||
const char *delim = " ";
|
pr_info("loaded uids from whitelist successfully\n");
|
||||||
char *package = strsep(&tmp, delim);
|
|
||||||
char *uid = strsep(&tmp, delim);
|
|
||||||
if (!uid || !package) {
|
|
||||||
pr_err("update_uid: package or uid is NULL!\n");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 res;
|
// now update uid list
|
||||||
if (kstrtou32(uid, 10, &res)) {
|
struct uid_data *np;
|
||||||
pr_err("update_uid: uid parse err\n");
|
struct uid_data *n;
|
||||||
break;
|
|
||||||
}
|
|
||||||
data->uid = res;
|
|
||||||
strncpy(data->package, package, KSU_MAX_PACKAGE_NAME);
|
|
||||||
list_add_tail(&data->list, &uid_list);
|
|
||||||
// reset line start
|
|
||||||
line_start = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
filp_close(fp, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
uid_ready:
|
|
||||||
if (prune_only)
|
|
||||||
goto prune;
|
|
||||||
|
|
||||||
// first, check if manager_uid exist!
|
// first, check if manager_uid exist!
|
||||||
|
bool manager_exist = false;
|
||||||
|
bool dynamic_manager_exist = false;
|
||||||
|
|
||||||
list_for_each_entry (np, &uid_list, list) {
|
list_for_each_entry (np, &uid_list, list) {
|
||||||
if (np->uid == current_manager_uid) {
|
// if manager is installed in work profile, the uid in packages.list is still equals main profile
|
||||||
|
// don't delete it in this case!
|
||||||
|
int manager_uid = ksu_get_manager_uid() % 100000;
|
||||||
|
if (np->uid == manager_uid) {
|
||||||
manager_exist = true;
|
manager_exist = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!manager_exist && locked_manager_uid != KSU_INVALID_UID) {
|
// Check for dynamic managers
|
||||||
pr_info("Manager APK removed, unlock previous UID: %d\n",
|
if (!dynamic_manager_exist && ksu_is_dynamic_manager_enabled()) {
|
||||||
locked_manager_uid);
|
|
||||||
ksu_invalidate_manager_uid();
|
|
||||||
locked_manager_uid = KSU_INVALID_UID;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the Dynamic Manager exists (only check locked UIDs)
|
|
||||||
if (ksu_is_dynamic_manager_enabled() &&
|
|
||||||
locked_dynamic_manager_uid != KSU_INVALID_UID) {
|
|
||||||
list_for_each_entry (np, &uid_list, list) {
|
list_for_each_entry (np, &uid_list, list) {
|
||||||
if (np->uid == locked_dynamic_manager_uid) {
|
// Check if this uid is a dynamic manager (not the traditional manager)
|
||||||
|
if (ksu_is_any_manager(np->uid) && np->uid != ksu_get_manager_uid()) {
|
||||||
dynamic_manager_exist = true;
|
dynamic_manager_exist = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dynamic_manager_exist) {
|
|
||||||
pr_info("Dynamic manager APK removed, unlock previous UID: %d\n",
|
|
||||||
locked_dynamic_manager_uid);
|
|
||||||
ksu_remove_manager(locked_dynamic_manager_uid);
|
|
||||||
locked_dynamic_manager_uid = KSU_INVALID_UID;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool need_search = !manager_exist;
|
if (!manager_exist) {
|
||||||
if (ksu_is_dynamic_manager_enabled() && !dynamic_manager_exist)
|
if (ksu_is_manager_uid_valid()) {
|
||||||
need_search = true;
|
pr_info("manager is uninstalled, invalidate it!\n");
|
||||||
|
ksu_invalidate_manager_uid();
|
||||||
if (need_search) {
|
goto prune;
|
||||||
pr_info("Searching for manager(s)...\n");
|
}
|
||||||
|
pr_info("Searching manager...\n");
|
||||||
search_manager("/data/app", 2, &uid_list);
|
search_manager("/data/app", 2, &uid_list);
|
||||||
pr_info("Manager search finished\n");
|
pr_info("Search manager finished\n");
|
||||||
|
} else if (!dynamic_manager_exist && ksu_is_dynamic_manager_enabled()) {
|
||||||
|
// Always perform search when called from dynamic manager rescan
|
||||||
|
pr_info("Dynamic sign enabled, Searching manager...\n");
|
||||||
|
search_manager("/data/app", 2, &uid_list);
|
||||||
|
pr_info("Search Dynamic sign manager finished\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
prune:
|
prune:
|
||||||
|
|
@ -556,12 +650,12 @@ out:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ksu_throne_tracker_init(void)
|
void ksu_throne_tracker_init()
|
||||||
{
|
{
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
void ksu_throne_tracker_exit(void)
|
void ksu_throne_tracker_exit()
|
||||||
{
|
{
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,6 @@ void ksu_throne_tracker_init();
|
||||||
|
|
||||||
void ksu_throne_tracker_exit();
|
void ksu_throne_tracker_exit();
|
||||||
|
|
||||||
void track_throne(bool prune_only);
|
void track_throne();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/string.h>
|
|
||||||
#include <linux/uaccess.h>
|
|
||||||
#include <linux/namei.h>
|
|
||||||
#include <linux/path.h>
|
|
||||||
#include <linux/mount.h>
|
|
||||||
#include <linux/cred.h>
|
|
||||||
|
|
||||||
#include "klog.h"
|
|
||||||
#include "kernel_umount.h"
|
|
||||||
#include "umount_manager.h"
|
|
||||||
|
|
||||||
static struct umount_manager g_umount_mgr = {
|
|
||||||
.entry_count = 0,
|
|
||||||
.max_entries = 64,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void try_umount_path(struct umount_entry *entry)
|
|
||||||
{
|
|
||||||
try_umount(entry->path, entry->flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct umount_entry *find_entry_locked(const char *path)
|
|
||||||
{
|
|
||||||
struct umount_entry *entry;
|
|
||||||
|
|
||||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
|
||||||
if (strcmp(entry->path, path) == 0) {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_umount_manager_init(void)
|
|
||||||
{
|
|
||||||
INIT_LIST_HEAD(&g_umount_mgr.entry_list);
|
|
||||||
spin_lock_init(&g_umount_mgr.lock);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_umount_manager_exit(void)
|
|
||||||
{
|
|
||||||
struct umount_entry *entry, *tmp;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
|
||||||
|
|
||||||
list_for_each_entry_safe(entry, tmp, &g_umount_mgr.entry_list, list) {
|
|
||||||
list_del(&entry->list);
|
|
||||||
kfree(entry);
|
|
||||||
g_umount_mgr.entry_count--;
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
|
||||||
|
|
||||||
pr_info("Umount manager cleaned up\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_umount_manager_add(const char *path, int flags, bool is_default)
|
|
||||||
{
|
|
||||||
struct umount_entry *entry;
|
|
||||||
unsigned long irqflags;
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
if (flags == -1)
|
|
||||||
flags = MNT_DETACH;
|
|
||||||
|
|
||||||
if (!path || strlen(path) == 0 || strlen(path) >= 256) {
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock_irqsave(&g_umount_mgr.lock, irqflags);
|
|
||||||
|
|
||||||
if (g_umount_mgr.entry_count >= g_umount_mgr.max_entries) {
|
|
||||||
pr_err("Umount manager: max entries reached\n");
|
|
||||||
ret = -ENOMEM;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (find_entry_locked(path)) {
|
|
||||||
pr_warn("Umount manager: path already exists: %s\n", path);
|
|
||||||
ret = -EEXIST;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
|
|
||||||
if (!entry) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy(entry->path, path, sizeof(entry->path) - 1);
|
|
||||||
entry->flags = flags;
|
|
||||||
entry->state = UMOUNT_STATE_IDLE;
|
|
||||||
entry->is_default = is_default;
|
|
||||||
entry->ref_count = 0;
|
|
||||||
|
|
||||||
list_add_tail(&entry->list, &g_umount_mgr.entry_list);
|
|
||||||
g_umount_mgr.entry_count++;
|
|
||||||
|
|
||||||
pr_info("Umount manager: added %s entry: %s\n",
|
|
||||||
is_default ? "default" : "custom", path);
|
|
||||||
|
|
||||||
out:
|
|
||||||
spin_unlock_irqrestore(&g_umount_mgr.lock, irqflags);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_umount_manager_remove(const char *path)
|
|
||||||
{
|
|
||||||
struct umount_entry *entry;
|
|
||||||
unsigned long flags;
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
if (!path) {
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
|
||||||
|
|
||||||
entry = find_entry_locked(path);
|
|
||||||
if (!entry) {
|
|
||||||
ret = -ENOENT;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry->is_default) {
|
|
||||||
pr_err("Umount manager: cannot remove default entry: %s\n", path);
|
|
||||||
ret = -EPERM;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry->state == UMOUNT_STATE_BUSY || entry->ref_count > 0) {
|
|
||||||
pr_err("Umount manager: entry is busy: %s\n", path);
|
|
||||||
ret = -EBUSY;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
list_del(&entry->list);
|
|
||||||
g_umount_mgr.entry_count--;
|
|
||||||
kfree(entry);
|
|
||||||
|
|
||||||
pr_info("Umount manager: removed entry: %s\n", path);
|
|
||||||
|
|
||||||
out:
|
|
||||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ksu_umount_manager_execute_all(const struct cred *cred)
|
|
||||||
{
|
|
||||||
struct umount_entry *entry;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
|
||||||
|
|
||||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
|
||||||
if (entry->state == UMOUNT_STATE_IDLE) {
|
|
||||||
entry->ref_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
|
||||||
|
|
||||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
|
||||||
if (entry->ref_count > 0 && entry->state == UMOUNT_STATE_IDLE) {
|
|
||||||
try_umount_path(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
|
||||||
|
|
||||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
|
||||||
if (entry->ref_count > 0) {
|
|
||||||
entry->ref_count--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries, u32 *count)
|
|
||||||
{
|
|
||||||
struct umount_entry *entry;
|
|
||||||
struct ksu_umount_entry_info info;
|
|
||||||
unsigned long flags;
|
|
||||||
u32 idx = 0;
|
|
||||||
u32 max_count = *count;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
|
||||||
|
|
||||||
list_for_each_entry(entry, &g_umount_mgr.entry_list, list) {
|
|
||||||
if (idx >= max_count) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&info, 0, sizeof(info));
|
|
||||||
strncpy(info.path, entry->path, sizeof(info.path) - 1);
|
|
||||||
info.flags = entry->flags;
|
|
||||||
info.is_default = entry->is_default;
|
|
||||||
info.state = entry->state;
|
|
||||||
info.ref_count = entry->ref_count;
|
|
||||||
|
|
||||||
if (copy_to_user(&entries[idx], &info, sizeof(info))) {
|
|
||||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
|
||||||
return -EFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
*count = idx;
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ksu_umount_manager_clear_custom(void)
|
|
||||||
{
|
|
||||||
struct umount_entry *entry, *tmp;
|
|
||||||
unsigned long flags;
|
|
||||||
u32 cleared = 0;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&g_umount_mgr.lock, flags);
|
|
||||||
|
|
||||||
list_for_each_entry_safe(entry, tmp, &g_umount_mgr.entry_list, list) {
|
|
||||||
if (!entry->is_default && entry->state == UMOUNT_STATE_IDLE && entry->ref_count == 0) {
|
|
||||||
list_del(&entry->list);
|
|
||||||
kfree(entry);
|
|
||||||
g_umount_mgr.entry_count--;
|
|
||||||
cleared++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&g_umount_mgr.lock, flags);
|
|
||||||
|
|
||||||
pr_info("Umount manager: cleared %u custom entries\n", cleared);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
#ifndef __KSU_H_UMOUNT_MANAGER
|
|
||||||
#define __KSU_H_UMOUNT_MANAGER
|
|
||||||
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/list.h>
|
|
||||||
#include <linux/spinlock.h>
|
|
||||||
|
|
||||||
struct cred;
|
|
||||||
|
|
||||||
enum umount_entry_state {
|
|
||||||
UMOUNT_STATE_IDLE = 0,
|
|
||||||
UMOUNT_STATE_ACTIVE = 1,
|
|
||||||
UMOUNT_STATE_BUSY = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct umount_entry {
|
|
||||||
struct list_head list;
|
|
||||||
char path[256];
|
|
||||||
int flags;
|
|
||||||
enum umount_entry_state state;
|
|
||||||
bool is_default;
|
|
||||||
u32 ref_count;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct umount_manager {
|
|
||||||
struct list_head entry_list;
|
|
||||||
spinlock_t lock;
|
|
||||||
u32 entry_count;
|
|
||||||
u32 max_entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum umount_manager_op {
|
|
||||||
UMOUNT_OP_ADD = 0,
|
|
||||||
UMOUNT_OP_REMOVE = 1,
|
|
||||||
UMOUNT_OP_LIST = 2,
|
|
||||||
UMOUNT_OP_CLEAR_CUSTOM = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_umount_manager_cmd {
|
|
||||||
__u32 operation;
|
|
||||||
char path[256];
|
|
||||||
__s32 flags;
|
|
||||||
__u32 count;
|
|
||||||
__aligned_u64 entries_ptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_umount_entry_info {
|
|
||||||
char path[256];
|
|
||||||
__s32 flags;
|
|
||||||
__u8 is_default;
|
|
||||||
__u32 state;
|
|
||||||
__u32 ref_count;
|
|
||||||
};
|
|
||||||
|
|
||||||
int ksu_umount_manager_init(void);
|
|
||||||
void ksu_umount_manager_exit(void);
|
|
||||||
int ksu_umount_manager_add(const char *path, int flags, bool is_default);
|
|
||||||
int ksu_umount_manager_remove(const char *path);
|
|
||||||
void ksu_umount_manager_execute_all(const struct cred *cred);
|
|
||||||
int ksu_umount_manager_get_entries(struct ksu_umount_entry_info __user *entries, u32 *count);
|
|
||||||
int ksu_umount_manager_clear_custom(void);
|
|
||||||
|
|
||||||
#endif // __KSU_H_UMOUNT_MANAGER
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||||
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.agp.app)
|
alias(libs.plugins.agp.app)
|
||||||
|
|
@ -16,7 +17,6 @@ plugins {
|
||||||
|
|
||||||
val managerVersionCode: Int by rootProject.extra
|
val managerVersionCode: Int by rootProject.extra
|
||||||
val managerVersionName: String by rootProject.extra
|
val managerVersionName: String by rootProject.extra
|
||||||
val androidCmakeVersion: String by rootProject.extra
|
|
||||||
|
|
||||||
apksign {
|
apksign {
|
||||||
storeFileProperty = "KEYSTORE_FILE"
|
storeFileProperty = "KEYSTORE_FILE"
|
||||||
|
|
@ -51,12 +51,15 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
aidl = true
|
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
compose = true
|
compose = true
|
||||||
prefab = true
|
prefab = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(21)
|
||||||
|
}
|
||||||
|
|
||||||
packaging {
|
packaging {
|
||||||
jniLibs {
|
jniLibs {
|
||||||
useLegacyPackaging = true
|
useLegacyPackaging = true
|
||||||
|
|
@ -74,8 +77,7 @@ android {
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path = file("src/main/cpp/CMakeLists.txt")
|
path("src/main/cpp/CMakeLists.txt")
|
||||||
version = androidCmakeVersion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +125,6 @@ dependencies {
|
||||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||||
implementation(libs.androidx.foundation)
|
implementation(libs.androidx.foundation)
|
||||||
implementation(libs.androidx.documentfile)
|
implementation(libs.androidx.documentfile)
|
||||||
implementation(libs.androidx.compose.foundation)
|
|
||||||
|
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
|
|
|
||||||
2
manager/app/proguard-rules.pro
vendored
2
manager/app/proguard-rules.pro
vendored
|
|
@ -44,5 +44,3 @@
|
||||||
-keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; }
|
-keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; }
|
||||||
|
|
||||||
-keep,allowobfuscation class * extends com.dergoogler.mmrl.platform.content.IService { *; }
|
-keep,allowobfuscation class * extends com.dergoogler.mmrl.platform.content.IService { *; }
|
||||||
|
|
||||||
-keep interface com.sukisu.zako.** { *; }
|
|
||||||
|
|
@ -18,82 +18,31 @@
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.KernelSU"
|
android:theme="@style/Theme.KernelSU"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
tools:targetApi="34">
|
tools:targetApi="34">
|
||||||
<!-- 专门为小米手机桌面卸载添加了提示,提升用户体验 -->
|
|
||||||
<meta-data
|
|
||||||
android:name="app_description_title"
|
|
||||||
android:resource="@string/miui_uninstall_title" />
|
|
||||||
<meta-data
|
|
||||||
android:name="app_description_content"
|
|
||||||
android:resource="@string/miui_uninstall_content" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:enabled="true"
|
|
||||||
android:launchMode="standard"
|
|
||||||
android:documentLaunchMode="intoExisting"
|
|
||||||
android:autoRemoveFromRecents="true"
|
|
||||||
android:theme="@style/Theme.KernelSU">
|
android:theme="@style/Theme.KernelSU">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:mimeType="application/zip" />
|
|
||||||
<data android:mimeType="application/vnd.android.package-archive" />
|
|
||||||
<data android:scheme="content" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="application/zip" />
|
|
||||||
<data android:mimeType="application/vnd.android.package-archive" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="application/zip" />
|
|
||||||
<data android:mimeType="application/vnd.android.package-archive" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<!-- 切换图标 -->
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name=".ui.MainActivityAlias"
|
android:name=".ui.MainActivityAlias"
|
||||||
android:targetActivity=".ui.MainActivity"
|
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
android:icon="@mipmap/ic_launcher_alt"
|
android:icon="@mipmap/ic_launcher_alt"
|
||||||
android:roundIcon="@mipmap/ic_launcher_alt_round">
|
android:roundIcon="@mipmap/ic_launcher_alt_round"
|
||||||
|
android:targetActivity=".ui.MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="content" />
|
|
||||||
<data android:mimeType="application/zip" />
|
|
||||||
<data android:mimeType="application/vnd.android.package-archive" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="application/zip" />
|
|
||||||
<data android:mimeType="application/vnd.android.package-archive" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="application/zip" />
|
|
||||||
<data android:mimeType="application/vnd.android.package-archive" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
// IKsuInterface.aidl
|
|
||||||
package com.sukisu.zako;
|
|
||||||
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
interface IKsuInterface {
|
|
||||||
int getPackageCount();
|
|
||||||
List<PackageInfo> getPackages(int start, int maxCount);
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
BIN
manager/app/src/main/assets/ksu_susfs_1.5.7
Normal file
BIN
manager/app/src/main/assets/ksu_susfs_1.5.7
Normal file
Binary file not shown.
BIN
manager/app/src/main/assets/ksu_susfs_1.5.8
Normal file
BIN
manager/app/src/main/assets/ksu_susfs_1.5.8
Normal file
Binary file not shown.
BIN
manager/app/src/main/assets/ksu_susfs_1.5.9
Normal file
BIN
manager/app/src/main/assets/ksu_susfs_1.5.9
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -6,11 +6,10 @@ cmake_minimum_required(VERSION 3.18.1)
|
||||||
|
|
||||||
project("kernelsu")
|
project("kernelsu")
|
||||||
|
|
||||||
add_library(kernelsu
|
add_library(zako
|
||||||
SHARED
|
SHARED
|
||||||
jni.c
|
jni.c
|
||||||
ksu.c
|
ksu.c
|
||||||
legacy.c
|
|
||||||
)
|
)
|
||||||
|
|
||||||
find_library(log-lib log)
|
find_library(log-lib log)
|
||||||
|
|
@ -22,7 +21,7 @@ elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ANDROID_ABI STREQUAL "arm64-v8a" OR ANDROID_ABI STREQUAL "armeabi-v7a")
|
if(ANDROID_ABI STREQUAL "arm64-v8a" OR ANDROID_ABI STREQUAL "armeabi-v7a")
|
||||||
target_link_libraries(kernelsu ${log-lib} ${zakosign-lib})
|
target_link_libraries(zako ${log-lib} ${zakosign-lib})
|
||||||
else()
|
else()
|
||||||
target_link_libraries(kernelsu ${log-lib})
|
target_link_libraries(zako ${log-lib})
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,18 @@
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <linux/capability.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
|
NativeBridge(becomeManager, jboolean, jstring pkg) {
|
||||||
|
const char* cpkg = GetEnvironment()->GetStringUTFChars(env, pkg, JNI_FALSE);
|
||||||
|
bool result = become_manager(cpkg);
|
||||||
|
|
||||||
|
GetEnvironment()->ReleaseStringUTFChars(env, pkg, cpkg);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
NativeBridgeNP(getVersion, jint) {
|
NativeBridgeNP(getVersion, jint) {
|
||||||
uint32_t version = get_version();
|
return get_version();
|
||||||
if (version > 0) {
|
|
||||||
return (jint)version;
|
|
||||||
}
|
|
||||||
// try legacy method as fallback
|
|
||||||
return legacy_get_info().version;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get VERSION FULL
|
// get VERSION FULL
|
||||||
|
|
@ -25,18 +27,15 @@ NativeBridgeNP(getFullVersion, jstring) {
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeBridgeNP(getAllowList, jintArray) {
|
NativeBridgeNP(getAllowList, jintArray) {
|
||||||
struct ksu_get_allow_list_cmd cmd = {};
|
int uids[1024];
|
||||||
bool result = get_allow_list(&cmd);
|
int size = 0;
|
||||||
|
bool result = get_allow_list(uids, &size);
|
||||||
|
|
||||||
|
LogDebug("getAllowList: %d, size: %d", result, size);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
jsize array_size = (jsize)cmd.count;
|
jintArray array = GetEnvironment()->NewIntArray(env, size);
|
||||||
if (array_size < 0 || (unsigned int)array_size != cmd.count) {
|
GetEnvironment()->SetIntArrayRegion(env, array, 0, size, uids);
|
||||||
LogDebug("Invalid array size: %u", cmd.count);
|
|
||||||
return GetEnvironment()->NewIntArray(env, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
jintArray array = GetEnvironment()->NewIntArray(env, array_size);
|
|
||||||
GetEnvironment()->SetIntArrayRegion(env, array, 0, array_size, (const jint *)(cmd.uids));
|
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
@ -52,10 +51,6 @@ NativeBridgeNP(isLkmMode, jboolean) {
|
||||||
return is_lkm_mode();
|
return is_lkm_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeBridgeNP(isManager, jboolean) {
|
|
||||||
return is_manager();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fillIntArray(JNIEnv *env, jobject list, int *data, int count) {
|
static void fillIntArray(JNIEnv *env, jobject list, int *data, int count) {
|
||||||
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
jclass cls = GetEnvironment()->GetObjectClass(env, list);
|
||||||
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
jmethodID add = GetEnvironment()->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
|
||||||
|
|
@ -129,7 +124,7 @@ NativeBridge(getAppProfile, jobject, jstring pkg, jint uid) {
|
||||||
strcpy(profile.key, key);
|
strcpy(profile.key, key);
|
||||||
profile.current_uid = uid;
|
profile.current_uid = uid;
|
||||||
|
|
||||||
bool useDefaultProfile = get_app_profile(&profile) != 0;
|
bool useDefaultProfile = !get_app_profile(key, &profile);
|
||||||
|
|
||||||
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$Profile");
|
||||||
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
|
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
|
||||||
|
|
@ -303,38 +298,6 @@ NativeBridge(setSuEnabled, jboolean, jboolean enabled) {
|
||||||
return set_su_enabled(enabled);
|
return set_su_enabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeBridgeNP(isKernelUmountEnabled, jboolean) {
|
|
||||||
return is_kernel_umount_enabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBridge(setKernelUmountEnabled, jboolean, jboolean enabled) {
|
|
||||||
return set_kernel_umount_enabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBridgeNP(isEnhancedSecurityEnabled, jboolean) {
|
|
||||||
return is_enhanced_security_enabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBridge(setEnhancedSecurityEnabled, jboolean, jboolean enabled) {
|
|
||||||
return set_enhanced_security_enabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBridgeNP(isSuLogEnabled, jboolean) {
|
|
||||||
return is_sulog_enabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBridge(setSuLogEnabled, jboolean, jboolean enabled) {
|
|
||||||
return set_sulog_enabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBridge(getUserName, jstring, jint uid) {
|
|
||||||
struct passwd *pw = getpwuid((uid_t) uid);
|
|
||||||
if (pw && pw->pw_name && pw->pw_name[0] != '\0') {
|
|
||||||
return GetEnvironment()->NewStringUTF(env, pw->pw_name);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if KPM is enabled
|
// Check if KPM is enabled
|
||||||
NativeBridgeNP(isKPMEnabled, jboolean) {
|
NativeBridgeNP(isKPMEnabled, jboolean) {
|
||||||
return is_KPM_enable();
|
return is_KPM_enable();
|
||||||
|
|
@ -342,11 +305,42 @@ NativeBridgeNP(isKPMEnabled, jboolean) {
|
||||||
|
|
||||||
// Get HOOK type
|
// Get HOOK type
|
||||||
NativeBridgeNP(getHookType, jstring) {
|
NativeBridgeNP(getHookType, jstring) {
|
||||||
char hook_type[32] = { 0 };
|
char hook_type[16];
|
||||||
get_hook_type((char *) &hook_type);
|
get_hook_type(hook_type, sizeof(hook_type));
|
||||||
return GetEnvironment()->NewStringUTF(env, hook_type);
|
return GetEnvironment()->NewStringUTF(env, hook_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SuSFS Related Function Status
|
||||||
|
NativeBridgeNP(getSusfsFeatureStatus, jobject) {
|
||||||
|
struct susfs_feature_status status;
|
||||||
|
bool result = get_susfs_feature_status(&status);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$SusfsFeatureStatus");
|
||||||
|
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
|
||||||
|
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
|
||||||
|
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusSusPath, status.status_sus_path);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusSusMount, status.status_sus_mount);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusAutoDefaultMount, status.status_auto_default_mount);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusAutoBindMount, status.status_auto_bind_mount);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusSusKstat, status.status_sus_kstat);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusTryUmount, status.status_try_umount);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusAutoTryUmountBind, status.status_auto_try_umount_bind);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusSpoofUname, status.status_spoof_uname);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusEnableLog, status.status_enable_log);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusHideSymbols, status.status_hide_symbols);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusSpoofCmdline, status.status_spoof_cmdline);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusOpenRedirect, status.status_open_redirect);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusMagicMount, status.status_magic_mount);
|
||||||
|
SET_BOOLEAN_FIELD(obj, cls, statusSusSu, status.status_sus_su);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
// dynamic manager
|
// dynamic manager
|
||||||
NativeBridge(setDynamicManager, jboolean, jint size, jstring hash) {
|
NativeBridge(setDynamicManager, jboolean, jint size, jstring hash) {
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
|
|
@ -438,15 +432,3 @@ NativeBridge(verifyModuleSignature, jboolean, jstring modulePath) {
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeBridgeNP(isUidScannerEnabled, jboolean) {
|
|
||||||
return is_uid_scanner_enabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBridge(setUidScannerEnabled, jboolean, jboolean enabled) {
|
|
||||||
return set_uid_scanner_enabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeBridgeNP(clearUidScannerEnvironment, jboolean) {
|
|
||||||
return clear_uid_scanner_environment();
|
|
||||||
}
|
|
||||||
|
|
@ -2,14 +2,11 @@
|
||||||
// Created by weishu on 2022/12/9.
|
// Created by weishu on 2022/12/9.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <sys/prctl.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <android/log.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#include "prelude.h"
|
#include "prelude.h"
|
||||||
#include "ksu.h"
|
#include "ksu.h"
|
||||||
|
|
@ -24,328 +21,177 @@ extern const char* zako_file_verrcidx2str(uint8_t index);
|
||||||
|
|
||||||
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM
|
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM
|
||||||
|
|
||||||
static int fd = -1;
|
#define KERNEL_SU_OPTION 0xDEADBEEF
|
||||||
|
|
||||||
static inline int scan_driver_fd() {
|
#define CMD_GRANT_ROOT 0
|
||||||
const char *kName = "[ksu_driver]";
|
|
||||||
DIR *fd_dir = opendir("/proc/self/fd");
|
#define CMD_BECOME_MANAGER 1
|
||||||
if (!fd_dir) {
|
#define CMD_GET_VERSION 2
|
||||||
return -1;
|
#define CMD_ALLOW_SU 3
|
||||||
|
#define CMD_DENY_SU 4
|
||||||
|
#define CMD_GET_SU_LIST 5
|
||||||
|
#define CMD_GET_DENY_LIST 6
|
||||||
|
#define CMD_CHECK_SAFEMODE 9
|
||||||
|
|
||||||
|
#define CMD_GET_APP_PROFILE 10
|
||||||
|
#define CMD_SET_APP_PROFILE 11
|
||||||
|
|
||||||
|
#define CMD_IS_UID_GRANTED_ROOT 12
|
||||||
|
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
||||||
|
#define CMD_IS_SU_ENABLED 14
|
||||||
|
#define CMD_ENABLE_SU 15
|
||||||
|
|
||||||
|
#define CMD_GET_VERSION_FULL 0xC0FFEE1A
|
||||||
|
|
||||||
|
#define CMD_ENABLE_KPM 100
|
||||||
|
#define CMD_HOOK_TYPE 101
|
||||||
|
#define CMD_GET_SUSFS_FEATURE_STATUS 102
|
||||||
|
#define CMD_DYNAMIC_MANAGER 103
|
||||||
|
#define CMD_GET_MANAGERS 104
|
||||||
|
|
||||||
|
#define DYNAMIC_MANAGER_OP_SET 0
|
||||||
|
#define DYNAMIC_MANAGER_OP_GET 1
|
||||||
|
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
||||||
|
|
||||||
|
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
||||||
|
int32_t result = 0;
|
||||||
|
int32_t rtn = prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
|
||||||
|
|
||||||
|
return result == KERNEL_SU_OPTION && rtn == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int found = -1;
|
bool become_manager(const char* pkg) {
|
||||||
struct dirent *de;
|
char param[128];
|
||||||
char path[64];
|
uid_t uid = getuid();
|
||||||
char target[PATH_MAX];
|
uint32_t userId = uid / 100000;
|
||||||
|
if (userId == 0) {
|
||||||
while ((de = readdir(fd_dir)) != NULL) {
|
sprintf(param, "/data/data/%s", pkg);
|
||||||
if (de->d_name[0] == '.') {
|
} else {
|
||||||
continue;
|
snprintf(param, sizeof(param), "/data/user/%d/%s", userId, pkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *endptr = nullptr;
|
return ksuctl(CMD_BECOME_MANAGER, param, NULL);
|
||||||
long fd_long = strtol(de->d_name, &endptr, 10);
|
|
||||||
if (!de->d_name[0] || *endptr != '\0' || fd_long < 0 || fd_long > INT_MAX) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(path, sizeof(path), "/proc/self/fd/%s", de->d_name);
|
// cache the result to avoid unnecessary syscall
|
||||||
ssize_t n = readlink(path, target, sizeof(target) - 1);
|
static bool is_lkm;
|
||||||
if (n < 0) {
|
int get_version() {
|
||||||
continue;
|
int32_t version = -1;
|
||||||
|
int32_t flags = 0;
|
||||||
|
ksuctl(CMD_GET_VERSION, &version, &flags);
|
||||||
|
if (!is_lkm && (flags & 0x1)) {
|
||||||
|
is_lkm = true;
|
||||||
}
|
}
|
||||||
target[n] = '\0';
|
return version;
|
||||||
|
|
||||||
const char *base = strrchr(target, '/');
|
|
||||||
base = base ? base + 1 : target;
|
|
||||||
|
|
||||||
if (strstr(base, kName)) {
|
|
||||||
found = (int)fd_long;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closedir(fd_dir);
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ksuctl(unsigned long op, void* arg) {
|
|
||||||
if (fd < 0) {
|
|
||||||
fd = scan_driver_fd();
|
|
||||||
}
|
|
||||||
return ioctl(fd, op, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct ksu_get_info_cmd g_version = {0};
|
|
||||||
|
|
||||||
struct ksu_get_info_cmd get_info() {
|
|
||||||
if (!g_version.version) {
|
|
||||||
ksuctl(KSU_IOCTL_GET_INFO, &g_version);
|
|
||||||
}
|
|
||||||
return g_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t get_version() {
|
|
||||||
auto info = get_info();
|
|
||||||
return info.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_allow_list(struct ksu_get_allow_list_cmd *cmd) {
|
|
||||||
if (ksuctl(KSU_IOCTL_GET_ALLOW_LIST, cmd) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback to legacy
|
|
||||||
int size = 0;
|
|
||||||
int uids[1024];
|
|
||||||
if (legacy_get_allow_list(uids, &size)) {
|
|
||||||
cmd->count = size;
|
|
||||||
memcpy(cmd->uids, uids, sizeof(int) * size);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_safe_mode() {
|
|
||||||
struct ksu_check_safemode_cmd cmd = {};
|
|
||||||
if (ksuctl(KSU_IOCTL_CHECK_SAFEMODE, &cmd) == 0) {
|
|
||||||
return cmd.in_safe_mode;
|
|
||||||
}
|
|
||||||
// fallback
|
|
||||||
return legacy_is_safe_mode();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_lkm_mode() {
|
|
||||||
auto info = get_info();
|
|
||||||
if (info.version > 0) {
|
|
||||||
return (info.flags & 0x1) != 0;
|
|
||||||
}
|
|
||||||
// Legacy Compatible
|
|
||||||
return (legacy_get_info().flags & 0x1) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_manager() {
|
|
||||||
auto info = get_info();
|
|
||||||
if (info.version > 0) {
|
|
||||||
return (info.flags & 0x2) != 0;
|
|
||||||
}
|
|
||||||
// Legacy Compatible
|
|
||||||
return legacy_get_info().version > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool uid_should_umount(int uid) {
|
|
||||||
struct ksu_uid_should_umount_cmd cmd = {};
|
|
||||||
cmd.uid = uid;
|
|
||||||
if (ksuctl(KSU_IOCTL_UID_SHOULD_UMOUNT, &cmd) == 0) {
|
|
||||||
return cmd.should_umount;
|
|
||||||
}
|
|
||||||
return legacy_uid_should_umount(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_app_profile(const struct app_profile *profile) {
|
|
||||||
struct ksu_set_app_profile_cmd cmd = {};
|
|
||||||
cmd.profile = *profile;
|
|
||||||
if (ksuctl(KSU_IOCTL_SET_APP_PROFILE, &cmd) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return legacy_set_app_profile(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_app_profile(struct app_profile *profile) {
|
|
||||||
struct ksu_get_app_profile_cmd cmd = {.profile = *profile};
|
|
||||||
int ret = ksuctl(KSU_IOCTL_GET_APP_PROFILE, &cmd);
|
|
||||||
if (ret == 0) {
|
|
||||||
*profile = cmd.profile;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return legacy_get_app_profile(profile->key, profile) ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_su_enabled(bool enabled) {
|
|
||||||
struct ksu_set_feature_cmd cmd = {};
|
|
||||||
cmd.feature_id = KSU_FEATURE_SU_COMPAT;
|
|
||||||
cmd.value = enabled ? 1 : 0;
|
|
||||||
if (ksuctl(KSU_IOCTL_SET_FEATURE, &cmd) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return legacy_set_su_enabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_su_enabled() {
|
|
||||||
struct ksu_get_feature_cmd cmd = {};
|
|
||||||
cmd.feature_id = KSU_FEATURE_SU_COMPAT;
|
|
||||||
if (ksuctl(KSU_IOCTL_GET_FEATURE, &cmd) == 0 && cmd.supported) {
|
|
||||||
return cmd.value != 0;
|
|
||||||
}
|
|
||||||
return legacy_is_su_enabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool get_feature(uint32_t feature_id, uint64_t *out_value, bool *out_supported) {
|
|
||||||
struct ksu_get_feature_cmd cmd = {};
|
|
||||||
cmd.feature_id = feature_id;
|
|
||||||
if (ksuctl(KSU_IOCTL_GET_FEATURE, &cmd) != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (out_value) *out_value = cmd.value;
|
|
||||||
if (out_supported) *out_supported = cmd.supported;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool set_feature(uint32_t feature_id, uint64_t value) {
|
|
||||||
struct ksu_set_feature_cmd cmd = {};
|
|
||||||
cmd.feature_id = feature_id;
|
|
||||||
cmd.value = value;
|
|
||||||
return ksuctl(KSU_IOCTL_SET_FEATURE, &cmd) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_kernel_umount_enabled(bool enabled) {
|
|
||||||
return set_feature(KSU_FEATURE_KERNEL_UMOUNT, enabled ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_kernel_umount_enabled() {
|
|
||||||
uint64_t value = 0;
|
|
||||||
bool supported = false;
|
|
||||||
if (!get_feature(KSU_FEATURE_KERNEL_UMOUNT, &value, &supported)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!supported) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return value != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_enhanced_security_enabled(bool enabled) {
|
|
||||||
return set_feature(KSU_FEATURE_ENHANCED_SECURITY, enabled ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_enhanced_security_enabled() {
|
|
||||||
uint64_t value = 0;
|
|
||||||
bool supported = false;
|
|
||||||
if (!get_feature(KSU_FEATURE_ENHANCED_SECURITY, &value, &supported)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!supported) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return value != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_sulog_enabled(bool enabled) {
|
|
||||||
return set_feature(KSU_FEATURE_SULOG, enabled ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_sulog_enabled() {
|
|
||||||
uint64_t value = 0;
|
|
||||||
bool supported = false;
|
|
||||||
if (!get_feature(KSU_FEATURE_SULOG, &value, &supported)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!supported) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return value != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void get_full_version(char* buff) {
|
void get_full_version(char* buff) {
|
||||||
struct ksu_get_full_version_cmd cmd = {0};
|
ksuctl(CMD_GET_VERSION_FULL, buff, NULL);
|
||||||
if (ksuctl(KSU_IOCTL_GET_FULL_VERSION, &cmd) == 0) {
|
|
||||||
strncpy(buff, cmd.version_full, KSU_FULL_VERSION_STRING - 1);
|
|
||||||
buff[KSU_FULL_VERSION_STRING - 1] = '\0';
|
|
||||||
} else {
|
|
||||||
return legacy_get_full_version(buff);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_KPM_enable(void) {
|
bool get_allow_list(int *uids, int *size) {
|
||||||
struct ksu_enable_kpm_cmd cmd = {};
|
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
||||||
if (ksuctl(KSU_IOCTL_ENABLE_KPM, &cmd) == 0 && cmd.enabled) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return legacy_is_KPM_enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void get_hook_type(char *buff) {
|
bool is_safe_mode() {
|
||||||
struct ksu_hook_type_cmd cmd = {0};
|
return ksuctl(CMD_CHECK_SAFEMODE, NULL, NULL);
|
||||||
if (ksuctl(KSU_IOCTL_HOOK_TYPE, &cmd) == 0) {
|
|
||||||
strncpy(buff, cmd.hook_type, 32 - 1);
|
|
||||||
buff[32 - 1] = '\0';
|
|
||||||
} else {
|
|
||||||
legacy_get_hook_type(buff, 32);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool set_dynamic_manager(unsigned int size, const char *hash)
|
bool is_lkm_mode() {
|
||||||
{
|
// you should call get_version first!
|
||||||
struct ksu_dynamic_manager_cmd cmd = {0};
|
return is_lkm;
|
||||||
cmd.config.operation = DYNAMIC_MANAGER_OP_SET;
|
|
||||||
cmd.config.size = size;
|
|
||||||
strlcpy(cmd.config.hash, hash, sizeof(cmd.config.hash));
|
|
||||||
|
|
||||||
return ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get_dynamic_manager(struct dynamic_manager_user_config *cfg)
|
bool uid_should_umount(int uid) {
|
||||||
{
|
int should;
|
||||||
if (!cfg)
|
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, (void*) ((size_t) uid), &should) && should;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set_app_profile(const struct app_profile* profile) {
|
||||||
|
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_app_profile(char* key, struct app_profile* profile) {
|
||||||
|
return ksuctl(CMD_GET_APP_PROFILE, profile, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set_su_enabled(bool enabled) {
|
||||||
|
return ksuctl(CMD_ENABLE_SU, (void*) enabled, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_su_enabled() {
|
||||||
|
int enabled = true;
|
||||||
|
// if ksuctl failed, we assume su is enabled, and it cannot be disabled.
|
||||||
|
ksuctl(CMD_IS_SU_ENABLED, &enabled, NULL);
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_KPM_enable() {
|
||||||
|
int enabled = false;
|
||||||
|
ksuctl(CMD_ENABLE_KPM, &enabled, NULL);
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_hook_type(char* hook_type, size_t size) {
|
||||||
|
if (hook_type == NULL || size == 0) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
struct ksu_dynamic_manager_cmd cmd = {0};
|
static char cached_hook_type[16] = {0};
|
||||||
cmd.config.operation = DYNAMIC_MANAGER_OP_GET;
|
if (cached_hook_type[0] == '\0') {
|
||||||
|
if (!ksuctl(CMD_HOOK_TYPE, cached_hook_type, NULL)) {
|
||||||
|
strcpy(cached_hook_type, "Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) != 0)
|
strncpy(hook_type, cached_hook_type, size);
|
||||||
return false;
|
hook_type[size - 1] = '\0';
|
||||||
|
|
||||||
*cfg = cmd.config;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool clear_dynamic_manager(void)
|
bool get_susfs_feature_status(struct susfs_feature_status* status) {
|
||||||
{
|
if (status == NULL) {
|
||||||
struct ksu_dynamic_manager_cmd cmd = {0};
|
|
||||||
cmd.config.operation = DYNAMIC_MANAGER_OP_CLEAR;
|
|
||||||
return ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_managers_list(struct manager_list_info *info)
|
|
||||||
{
|
|
||||||
if (!info)
|
|
||||||
return false;
|
return false;
|
||||||
struct ksu_get_managers_cmd cmd = {0};
|
}
|
||||||
if (ksuctl(KSU_IOCTL_GET_MANAGERS, &cmd) != 0)
|
|
||||||
|
return ksuctl(CMD_GET_SUSFS_FEATURE_STATUS, status, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set_dynamic_manager(unsigned int size, const char* hash) {
|
||||||
|
if (hash == NULL) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
*info = cmd.manager_info;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_uid_scanner_enabled(void)
|
struct dynamic_manager_user_config config;
|
||||||
{
|
config.operation = DYNAMIC_MANAGER_OP_SET;
|
||||||
bool status = false;
|
config.size = size;
|
||||||
|
strncpy(config.hash, hash, sizeof(config.hash) - 1);
|
||||||
|
config.hash[sizeof(config.hash) - 1] = '\0';
|
||||||
|
|
||||||
struct ksu_enable_uid_scanner_cmd cmd = {
|
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
||||||
.operation = UID_SCANNER_OP_GET_STATUS,
|
|
||||||
.status_ptr = (__u64)(uintptr_t)&status
|
|
||||||
};
|
|
||||||
|
|
||||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd) == 0 != 0 && status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool set_uid_scanner_enabled(bool enabled)
|
bool get_dynamic_manager(struct dynamic_manager_user_config* config) {
|
||||||
{
|
if (config == NULL) {
|
||||||
struct ksu_enable_uid_scanner_cmd cmd = {
|
return false;
|
||||||
.operation = UID_SCANNER_OP_TOGGLE,
|
|
||||||
.enabled = enabled
|
|
||||||
};
|
|
||||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool clear_uid_scanner_environment(void)
|
config->operation = DYNAMIC_MANAGER_OP_GET;
|
||||||
{
|
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL);
|
||||||
struct ksu_enable_uid_scanner_cmd cmd = {
|
}
|
||||||
.operation = UID_SCANNER_OP_CLEAR_ENV
|
|
||||||
};
|
bool clear_dynamic_manager() {
|
||||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
|
struct dynamic_manager_user_config config;
|
||||||
|
config.operation = DYNAMIC_MANAGER_OP_CLEAR;
|
||||||
|
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_managers_list(struct manager_list_info* info) {
|
||||||
|
if (info == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ksuctl(CMD_GET_MANAGERS, info, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool verify_module_signature(const char* input) {
|
bool verify_module_signature(const char* input) {
|
||||||
|
|
@ -355,13 +201,13 @@ bool verify_module_signature(const char* input) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int file_fd = zako_sys_file_open(input);
|
int fd = zako_sys_file_open(input);
|
||||||
if (file_fd < 0) {
|
if (fd < 0) {
|
||||||
LogDebug("verify_module_signature: failed to open file: %s", input);
|
LogDebug("verify_module_signature: failed to open file: %s", input);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t results = zako_file_verify_esig(file_fd, 0);
|
uint32_t results = zako_file_verify_esig(fd, 0);
|
||||||
|
|
||||||
if (results != 0) {
|
if (results != 0) {
|
||||||
/* If important error occured, verification process should
|
/* If important error occured, verification process should
|
||||||
|
|
@ -395,7 +241,7 @@ bool verify_module_signature(const char* input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
close(file_fd);
|
close(fd);
|
||||||
LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s",
|
LogDebug("verify_module_signature: path=%s, results=0x%x, success=%s",
|
||||||
input, results, (results == 0) ? "true" : "false");
|
input, results, (results == 0) ? "true" : "false");
|
||||||
return results == 0;
|
return results == 0;
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,16 @@
|
||||||
#define KERNELSU_KSU_H
|
#define KERNELSU_KSU_H
|
||||||
|
|
||||||
#include "prelude.h"
|
#include "prelude.h"
|
||||||
#include <stdint.h>
|
#include <linux/capability.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
|
|
||||||
#define KSU_FULL_VERSION_STRING 255
|
bool become_manager(const char *);
|
||||||
|
|
||||||
uint32_t get_version();
|
void get_full_version(char* buff);
|
||||||
|
|
||||||
|
int get_version();
|
||||||
|
|
||||||
|
bool get_allow_list(int *uids, int *size);
|
||||||
|
|
||||||
bool uid_should_umount(int uid);
|
bool uid_should_umount(int uid);
|
||||||
|
|
||||||
|
|
@ -22,10 +23,6 @@ bool is_safe_mode();
|
||||||
|
|
||||||
bool is_lkm_mode();
|
bool is_lkm_mode();
|
||||||
|
|
||||||
bool is_manager();
|
|
||||||
|
|
||||||
void get_full_version(char* buff);
|
|
||||||
|
|
||||||
#define KSU_APP_PROFILE_VER 2
|
#define KSU_APP_PROFILE_VER 2
|
||||||
#define KSU_MAX_PACKAGE_NAME 256
|
#define KSU_MAX_PACKAGE_NAME 256
|
||||||
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
|
||||||
|
|
@ -36,16 +33,29 @@ void get_full_version(char* buff);
|
||||||
#define DYNAMIC_MANAGER_OP_GET 1
|
#define DYNAMIC_MANAGER_OP_GET 1
|
||||||
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
#define DYNAMIC_MANAGER_OP_CLEAR 2
|
||||||
|
|
||||||
#define UID_SCANNER_OP_GET_STATUS 0
|
|
||||||
#define UID_SCANNER_OP_TOGGLE 1
|
|
||||||
#define UID_SCANNER_OP_CLEAR_ENV 2
|
|
||||||
|
|
||||||
struct dynamic_manager_user_config {
|
struct dynamic_manager_user_config {
|
||||||
unsigned int operation;
|
unsigned int operation;
|
||||||
unsigned int size;
|
unsigned int size;
|
||||||
char hash[65];
|
char hash[65];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SUSFS Functional State Structures
|
||||||
|
struct susfs_feature_status {
|
||||||
|
bool status_sus_path;
|
||||||
|
bool status_sus_mount;
|
||||||
|
bool status_auto_default_mount;
|
||||||
|
bool status_auto_bind_mount;
|
||||||
|
bool status_sus_kstat;
|
||||||
|
bool status_try_umount;
|
||||||
|
bool status_auto_try_umount_bind;
|
||||||
|
bool status_spoof_uname;
|
||||||
|
bool status_enable_log;
|
||||||
|
bool status_hide_symbols;
|
||||||
|
bool status_spoof_cmdline;
|
||||||
|
bool status_open_redirect;
|
||||||
|
bool status_magic_mount;
|
||||||
|
bool status_sus_su;
|
||||||
|
};
|
||||||
|
|
||||||
struct root_profile {
|
struct root_profile {
|
||||||
int32_t uid;
|
int32_t uid;
|
||||||
|
|
@ -105,11 +115,17 @@ struct manager_list_info {
|
||||||
|
|
||||||
bool set_app_profile(const struct app_profile* profile);
|
bool set_app_profile(const struct app_profile* profile);
|
||||||
|
|
||||||
int get_app_profile(struct app_profile* profile);
|
bool get_app_profile(char* key, struct app_profile* profile);
|
||||||
|
|
||||||
|
bool set_su_enabled(bool enabled);
|
||||||
|
|
||||||
|
bool is_su_enabled();
|
||||||
|
|
||||||
bool is_KPM_enable();
|
bool is_KPM_enable();
|
||||||
|
|
||||||
void get_hook_type(char* hook_type);
|
bool get_hook_type(char* hook_type, size_t size);
|
||||||
|
|
||||||
|
bool get_susfs_feature_status(struct susfs_feature_status* status);
|
||||||
|
|
||||||
bool set_dynamic_manager(unsigned int size, const char* hash);
|
bool set_dynamic_manager(unsigned int size, const char* hash);
|
||||||
|
|
||||||
|
|
@ -121,180 +137,4 @@ bool get_managers_list(struct manager_list_info* info);
|
||||||
|
|
||||||
bool verify_module_signature(const char* input);
|
bool verify_module_signature(const char* input);
|
||||||
|
|
||||||
bool is_uid_scanner_enabled();
|
|
||||||
|
|
||||||
bool set_uid_scanner_enabled(bool enabled);
|
|
||||||
|
|
||||||
bool clear_uid_scanner_environment();
|
|
||||||
|
|
||||||
// Feature IDs
|
|
||||||
enum ksu_feature_id {
|
|
||||||
KSU_FEATURE_SU_COMPAT = 0,
|
|
||||||
KSU_FEATURE_KERNEL_UMOUNT = 1,
|
|
||||||
KSU_FEATURE_ENHANCED_SECURITY = 2,
|
|
||||||
KSU_FEATURE_SULOG = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generic feature API
|
|
||||||
struct ksu_get_feature_cmd {
|
|
||||||
uint32_t feature_id; // Input: feature ID
|
|
||||||
uint64_t value; // Output: feature value/state
|
|
||||||
uint8_t supported; // Output: whether the feature is supported
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_set_feature_cmd {
|
|
||||||
uint32_t feature_id; // Input: feature ID
|
|
||||||
uint64_t value; // Input: feature value/state to set
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_become_daemon_cmd {
|
|
||||||
uint8_t token[65]; // Input: daemon token (null-terminated)
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_info_cmd {
|
|
||||||
uint32_t version; // Output: KERNEL_SU_VERSION
|
|
||||||
uint32_t flags; // Output: flags (bit 0: MODULE mode)
|
|
||||||
uint32_t features; // Output: max feature ID supported (KSU_FEATURE_MAX)
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_report_event_cmd {
|
|
||||||
uint32_t event; // Input: EVENT_POST_FS_DATA, EVENT_BOOT_COMPLETED, etc.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_set_sepolicy_cmd {
|
|
||||||
uint64_t cmd; // Input: sepolicy command
|
|
||||||
uint64_t arg; // Input: sepolicy argument pointer
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_check_safemode_cmd {
|
|
||||||
uint8_t in_safe_mode; // Output: true if in safe mode, false otherwise
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_allow_list_cmd {
|
|
||||||
uint32_t uids[128]; // Output: array of allowed/denied UIDs
|
|
||||||
uint32_t count; // Output: number of UIDs in array
|
|
||||||
uint8_t allow; // Input: true for allow list, false for deny list
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_uid_granted_root_cmd {
|
|
||||||
uint32_t uid; // Input: target UID to check
|
|
||||||
uint8_t granted; // Output: true if granted, false otherwise
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_uid_should_umount_cmd {
|
|
||||||
uint32_t uid; // Input: target UID to check
|
|
||||||
uint8_t should_umount; // Output: true if should umount, false otherwise
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_manager_uid_cmd {
|
|
||||||
uint32_t uid; // Output: manager UID
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_set_manager_uid_cmd {
|
|
||||||
uint32_t uid; // Input: new manager UID
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_app_profile_cmd {
|
|
||||||
struct app_profile profile; // Input/Output: app profile structure
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_set_app_profile_cmd {
|
|
||||||
struct app_profile profile; // Input: app profile structure
|
|
||||||
};
|
|
||||||
|
|
||||||
// Su compat
|
|
||||||
bool set_su_enabled(bool enabled);
|
|
||||||
bool is_su_enabled();
|
|
||||||
|
|
||||||
// Kernel umount
|
|
||||||
bool set_kernel_umount_enabled(bool enabled);
|
|
||||||
bool is_kernel_umount_enabled();
|
|
||||||
|
|
||||||
// Enhanced security
|
|
||||||
bool set_enhanced_security_enabled(bool enabled);
|
|
||||||
bool is_enhanced_security_enabled();
|
|
||||||
|
|
||||||
// Su log
|
|
||||||
bool set_sulog_enabled(bool enabled);
|
|
||||||
bool is_sulog_enabled();
|
|
||||||
|
|
||||||
// Other command structures
|
|
||||||
struct ksu_get_full_version_cmd {
|
|
||||||
char version_full[KSU_FULL_VERSION_STRING]; // Output: full version string
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_hook_type_cmd {
|
|
||||||
char hook_type[32]; // Output: hook type string
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_enable_kpm_cmd {
|
|
||||||
uint8_t enabled; // Output: true if KPM is enabled
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_dynamic_manager_cmd {
|
|
||||||
struct dynamic_manager_user_config config; // Input/Output: dynamic manager config
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_get_managers_cmd {
|
|
||||||
struct manager_list_info manager_info; // Output: manager list information
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ksu_enable_uid_scanner_cmd {
|
|
||||||
uint32_t operation; // Input: operation type (UID_SCANNER_OP_GET_STATUS, UID_SCANNER_OP_TOGGLE, UID_SCANNER_OP_CLEAR_ENV)
|
|
||||||
uint32_t enabled; // Input: enable or disable (for UID_SCANNER_OP_TOGGLE)
|
|
||||||
uint64_t status_ptr; // Input: pointer to store status (for UID_SCANNER_OP_GET_STATUS)
|
|
||||||
};
|
|
||||||
|
|
||||||
// IOCTL command definitions
|
|
||||||
#define KSU_IOCTL_GRANT_ROOT _IOC(_IOC_NONE, 'K', 1, 0)
|
|
||||||
#define KSU_IOCTL_GET_INFO _IOC(_IOC_READ, 'K', 2, 0)
|
|
||||||
#define KSU_IOCTL_REPORT_EVENT _IOC(_IOC_WRITE, 'K', 3, 0)
|
|
||||||
#define KSU_IOCTL_SET_SEPOLICY _IOC(_IOC_READ|_IOC_WRITE, 'K', 4, 0)
|
|
||||||
#define KSU_IOCTL_CHECK_SAFEMODE _IOC(_IOC_READ, 'K', 5, 0)
|
|
||||||
#define KSU_IOCTL_GET_ALLOW_LIST _IOC(_IOC_READ|_IOC_WRITE, 'K', 6, 0)
|
|
||||||
#define KSU_IOCTL_GET_DENY_LIST _IOC(_IOC_READ|_IOC_WRITE, 'K', 7, 0)
|
|
||||||
#define KSU_IOCTL_UID_GRANTED_ROOT _IOC(_IOC_READ|_IOC_WRITE, 'K', 8, 0)
|
|
||||||
#define KSU_IOCTL_UID_SHOULD_UMOUNT _IOC(_IOC_READ|_IOC_WRITE, 'K', 9, 0)
|
|
||||||
#define KSU_IOCTL_GET_MANAGER_UID _IOC(_IOC_READ, 'K', 10, 0)
|
|
||||||
#define KSU_IOCTL_GET_APP_PROFILE _IOC(_IOC_READ|_IOC_WRITE, 'K', 11, 0)
|
|
||||||
#define KSU_IOCTL_SET_APP_PROFILE _IOC(_IOC_WRITE, 'K', 12, 0)
|
|
||||||
#define KSU_IOCTL_GET_FEATURE _IOC(_IOC_READ|_IOC_WRITE, 'K', 13, 0)
|
|
||||||
#define KSU_IOCTL_SET_FEATURE _IOC(_IOC_WRITE, 'K', 14, 0)
|
|
||||||
|
|
||||||
// Other IOCTL command definitions
|
|
||||||
#define KSU_IOCTL_GET_FULL_VERSION _IOC(_IOC_READ, 'K', 100, 0)
|
|
||||||
#define KSU_IOCTL_HOOK_TYPE _IOC(_IOC_READ, 'K', 101, 0)
|
|
||||||
#define KSU_IOCTL_ENABLE_KPM _IOC(_IOC_READ, 'K', 102, 0)
|
|
||||||
#define KSU_IOCTL_DYNAMIC_MANAGER _IOC(_IOC_READ|_IOC_WRITE, 'K', 103, 0)
|
|
||||||
#define KSU_IOCTL_GET_MANAGERS _IOC(_IOC_READ|_IOC_WRITE, 'K', 104, 0)
|
|
||||||
#define KSU_IOCTL_ENABLE_UID_SCANNER _IOC(_IOC_READ|_IOC_WRITE, 'K', 105, 0)
|
|
||||||
|
|
||||||
bool get_allow_list(struct ksu_get_allow_list_cmd *);
|
|
||||||
|
|
||||||
// Legacy Compatible
|
|
||||||
struct ksu_version_info legacy_get_info();
|
|
||||||
|
|
||||||
struct ksu_version_info {
|
|
||||||
int32_t version;
|
|
||||||
int32_t flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool legacy_get_allow_list(int *uids, int *size);
|
|
||||||
bool legacy_is_safe_mode();
|
|
||||||
bool legacy_uid_should_umount(int uid);
|
|
||||||
bool legacy_set_app_profile(const struct app_profile* profile);
|
|
||||||
bool legacy_get_app_profile(char* key, struct app_profile* profile);
|
|
||||||
bool legacy_set_su_enabled(bool enabled);
|
|
||||||
bool legacy_is_su_enabled();
|
|
||||||
bool legacy_is_KPM_enable();
|
|
||||||
bool legacy_get_hook_type(char* hook_type, size_t size);
|
|
||||||
void legacy_get_full_version(char* buff);
|
|
||||||
bool legacy_set_dynamic_manager(unsigned int size, const char* hash);
|
|
||||||
bool legacy_get_dynamic_manager(struct dynamic_manager_user_config* config);
|
|
||||||
bool legacy_clear_dynamic_manager();
|
|
||||||
bool legacy_get_managers_list(struct manager_list_info* info);
|
|
||||||
bool legacy_is_uid_scanner_enabled();
|
|
||||||
bool legacy_set_uid_scanner_enabled(bool enabled);
|
|
||||||
bool legacy_clear_uid_scanner_environment();
|
|
||||||
|
|
||||||
#endif //KERNELSU_KSU_H
|
#endif //KERNELSU_KSU_H
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
//
|
|
||||||
// Created by shirkneko on 2025/11/3.
|
|
||||||
//
|
|
||||||
// Legacy Compatible
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <android/log.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#include "prelude.h"
|
|
||||||
#include "ksu.h"
|
|
||||||
|
|
||||||
#define KERNEL_SU_OPTION 0xDEADBEEF
|
|
||||||
|
|
||||||
#define CMD_GRANT_ROOT 0
|
|
||||||
|
|
||||||
#define CMD_BECOME_MANAGER 1
|
|
||||||
#define CMD_GET_VERSION 2
|
|
||||||
#define CMD_ALLOW_SU 3
|
|
||||||
#define CMD_DENY_SU 4
|
|
||||||
#define CMD_GET_SU_LIST 5
|
|
||||||
#define CMD_GET_DENY_LIST 6
|
|
||||||
#define CMD_CHECK_SAFEMODE 9
|
|
||||||
|
|
||||||
#define CMD_GET_APP_PROFILE 10
|
|
||||||
#define CMD_SET_APP_PROFILE 11
|
|
||||||
|
|
||||||
#define CMD_IS_UID_GRANTED_ROOT 12
|
|
||||||
#define CMD_IS_UID_SHOULD_UMOUNT 13
|
|
||||||
#define CMD_IS_SU_ENABLED 14
|
|
||||||
#define CMD_ENABLE_SU 15
|
|
||||||
|
|
||||||
#define CMD_GET_VERSION_FULL 0xC0FFEE1A
|
|
||||||
|
|
||||||
#define CMD_ENABLE_KPM 100
|
|
||||||
#define CMD_HOOK_TYPE 101
|
|
||||||
#define CMD_DYNAMIC_MANAGER 103
|
|
||||||
#define CMD_GET_MANAGERS 104
|
|
||||||
#define CMD_ENABLE_UID_SCANNER 105
|
|
||||||
|
|
||||||
static bool ksuctl(int cmd, void* arg1, void* arg2) {
|
|
||||||
int32_t result = 0;
|
|
||||||
int32_t rtn = prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
|
|
||||||
return result == KERNEL_SU_OPTION && rtn == -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ksu_version_info legacy_get_info()
|
|
||||||
{
|
|
||||||
int32_t version = -1;
|
|
||||||
int32_t flags = 0;
|
|
||||||
ksuctl(CMD_GET_VERSION, &version, &flags);
|
|
||||||
return (struct ksu_version_info){version, flags};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_get_allow_list(int *uids, int *size) {
|
|
||||||
return ksuctl(CMD_GET_SU_LIST, uids, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_is_safe_mode() {
|
|
||||||
return ksuctl(CMD_CHECK_SAFEMODE, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_uid_should_umount(int uid) {
|
|
||||||
int should;
|
|
||||||
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, (void*) ((size_t) uid), &should) && should;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_set_app_profile(const struct app_profile* profile) {
|
|
||||||
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_get_app_profile(char* key, struct app_profile* profile) {
|
|
||||||
return ksuctl(CMD_GET_APP_PROFILE, profile, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_set_su_enabled(bool enabled) {
|
|
||||||
return ksuctl(CMD_ENABLE_SU, (void*) enabled, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_is_su_enabled() {
|
|
||||||
int enabled = true;
|
|
||||||
// if ksuctl failed, we assume su is enabled, and it cannot be disabled.
|
|
||||||
ksuctl(CMD_IS_SU_ENABLED, &enabled, NULL);
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_is_KPM_enable() {
|
|
||||||
int enabled = false;
|
|
||||||
ksuctl(CMD_ENABLE_KPM, &enabled, NULL);
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_get_hook_type(char* hook_type, size_t size) {
|
|
||||||
if (hook_type == NULL || size == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char cached_hook_type[16] = {0};
|
|
||||||
if (cached_hook_type[0] == '\0') {
|
|
||||||
if (!ksuctl(CMD_HOOK_TYPE, cached_hook_type, NULL)) {
|
|
||||||
strcpy(cached_hook_type, "Unknown");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy(hook_type, cached_hook_type, size - 1);
|
|
||||||
hook_type[size - 1] = '\0';
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void legacy_get_full_version(char* buff) {
|
|
||||||
ksuctl(CMD_GET_VERSION_FULL, buff, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_set_dynamic_manager(unsigned int size, const char* hash) {
|
|
||||||
if (hash == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
struct dynamic_manager_user_config config;
|
|
||||||
config.operation = DYNAMIC_MANAGER_OP_SET;
|
|
||||||
config.size = size;
|
|
||||||
strncpy(config.hash, hash, sizeof(config.hash) - 1);
|
|
||||||
config.hash[sizeof(config.hash) - 1] = '\0';
|
|
||||||
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_get_dynamic_manager(struct dynamic_manager_user_config* config) {
|
|
||||||
if (config == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
config->operation = DYNAMIC_MANAGER_OP_GET;
|
|
||||||
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_clear_dynamic_manager() {
|
|
||||||
struct dynamic_manager_user_config config;
|
|
||||||
config.operation = DYNAMIC_MANAGER_OP_CLEAR;
|
|
||||||
return ksuctl(CMD_DYNAMIC_MANAGER, &config, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_get_managers_list(struct manager_list_info* info) {
|
|
||||||
if (info == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return ksuctl(CMD_GET_MANAGERS, info, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_is_uid_scanner_enabled() {
|
|
||||||
bool status = false;
|
|
||||||
ksuctl(CMD_ENABLE_UID_SCANNER, (void*)0, &status);
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_set_uid_scanner_enabled(bool enabled) {
|
|
||||||
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)1, (void*)enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool legacy_clear_uid_scanner_environment() {
|
|
||||||
return ksuctl(CMD_ENABLE_UID_SCANNER, (void*)2, NULL);
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +1,94 @@
|
||||||
package com.sukisu.ultra
|
package com.sukisu.ultra
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.ActivityOptions
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.system.Os
|
import android.content.Context
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import android.content.res.Configuration
|
||||||
import androidx.lifecycle.ViewModelStore
|
import android.content.res.Resources
|
||||||
import androidx.lifecycle.ViewModelStoreOwner
|
import android.os.Build
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import android.os.Bundle
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.dergoogler.mmrl.platform.Platform
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||||
import okhttp3.Cache
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.*
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
lateinit var ksuApp: KernelSUApplication
|
lateinit var ksuApp: KernelSUApplication
|
||||||
|
|
||||||
class KernelSUApplication : Application(), ViewModelStoreOwner {
|
class KernelSUApplication : Application() {
|
||||||
|
private var currentActivity: Activity? = null
|
||||||
|
|
||||||
lateinit var okhttpClient: OkHttpClient
|
private val activityLifecycleCallbacks = object : ActivityLifecycleCallbacks {
|
||||||
private val appViewModelStore by lazy { ViewModelStore() }
|
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||||
|
currentActivity = activity
|
||||||
|
}
|
||||||
|
override fun onActivityStarted(activity: Activity) {
|
||||||
|
currentActivity = activity
|
||||||
|
}
|
||||||
|
override fun onActivityResumed(activity: Activity) {
|
||||||
|
currentActivity = activity
|
||||||
|
}
|
||||||
|
override fun onActivityPaused(activity: Activity) {}
|
||||||
|
override fun onActivityStopped(activity: Activity) {}
|
||||||
|
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
||||||
|
override fun onActivityDestroyed(activity: Activity) {
|
||||||
|
if (currentActivity == activity) {
|
||||||
|
currentActivity = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
val prefs = base.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||||
|
|
||||||
|
var context = base
|
||||||
|
if (languageCode.isNotEmpty()) {
|
||||||
|
val locale = Locale.forLanguageTag(languageCode)
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
|
||||||
|
val config = Configuration(base.resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
|
||||||
|
context = base.createConfigurationContext(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.attachBaseContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
override fun getResources(): Resources {
|
||||||
|
val resources = super.getResources()
|
||||||
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||||
|
|
||||||
|
if (languageCode.isNotEmpty()) {
|
||||||
|
val locale = Locale.forLanguageTag(languageCode)
|
||||||
|
val config = Configuration(resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
return createConfigurationContext(config).resources
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
resources.updateConfiguration(config, resources.displayMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
ksuApp = this
|
ksuApp = this
|
||||||
|
|
||||||
// For faster response when first entering superuser or webui activity
|
// 注册Activity生命周期回调
|
||||||
val superUserViewModel = ViewModelProvider(this)[SuperUserViewModel::class.java]
|
registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
superUserViewModel.fetchAppList()
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.setHiddenApiExemptions()
|
Platform.setHiddenApiExemptions()
|
||||||
|
|
||||||
|
|
@ -53,20 +107,45 @@ class KernelSUApplication : Application(), ViewModelStoreOwner {
|
||||||
if (!webroot.exists()) {
|
if (!webroot.exists()) {
|
||||||
webroot.mkdir()
|
webroot.mkdir()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Provide working env for rust's temp_dir()
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
Os.setenv("TMPDIR", cacheDir.absolutePath, true)
|
super.onConfigurationChanged(newConfig)
|
||||||
|
applyLanguageSetting()
|
||||||
|
}
|
||||||
|
|
||||||
okhttpClient =
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
OkHttpClient.Builder().cache(Cache(File(cacheDir, "okhttp"), 10 * 1024 * 1024))
|
private fun applyLanguageSetting() {
|
||||||
.addInterceptor { block ->
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
block.proceed(
|
val languageCode = prefs.getString("app_language", "") ?: ""
|
||||||
block.request().newBuilder()
|
|
||||||
.header("User-Agent", "SukiSU/${BuildConfig.VERSION_CODE}")
|
if (languageCode.isNotEmpty()) {
|
||||||
.header("Accept-Language", Locale.getDefault().toLanguageTag()).build()
|
val locale = Locale.forLanguageTag(languageCode)
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
|
||||||
|
val resources = resources
|
||||||
|
val config = Configuration(resources.configuration)
|
||||||
|
config.setLocale(locale)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
createConfigurationContext(config)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
resources.updateConfiguration(config, resources.displayMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加刷新当前Activity的方法
|
||||||
|
fun refreshCurrentActivity() {
|
||||||
|
currentActivity?.let { activity ->
|
||||||
|
val intent = activity.intent
|
||||||
|
activity.finish()
|
||||||
|
|
||||||
|
val options = ActivityOptions.makeCustomAnimation(
|
||||||
|
activity, android.R.anim.fade_in, android.R.anim.fade_out
|
||||||
)
|
)
|
||||||
}.build()
|
activity.startActivity(intent, options.toBundle())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
override val viewModelStore: ViewModelStore
|
|
||||||
get() = appViewModelStore
|
|
||||||
}
|
}
|
||||||
|
|
@ -16,22 +16,23 @@ object Natives {
|
||||||
// 10946: add capabilities
|
// 10946: add capabilities
|
||||||
// 10977: change groups_count and groups to avoid overflow write
|
// 10977: change groups_count and groups to avoid overflow write
|
||||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
// 11071: Fix the issue of failing to set a custom SELinux type.
|
||||||
// 12143: breaking: new supercall impl
|
const val MINIMAL_SUPPORTED_KERNEL = 11071
|
||||||
const val MINIMAL_SUPPORTED_KERNEL = 12143
|
const val MINIMAL_SUPPORTED_KERNEL_FULL = "v3.1.5"
|
||||||
|
|
||||||
|
// 11640: Support query working mode, LKM or GKI
|
||||||
|
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
||||||
|
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
||||||
|
|
||||||
// 12040: Support disable sucompat mode
|
// 12040: Support disable sucompat mode
|
||||||
|
const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
|
||||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||||
|
|
||||||
const val MINIMAL_SUPPORTED_KERNEL_FULL = "v3.1.8"
|
|
||||||
|
|
||||||
const val MINIMAL_SUPPORTED_KPM = 12800
|
const val MINIMAL_SUPPORTED_KPM = 12800
|
||||||
|
|
||||||
const val MINIMAL_SUPPORTED_DYNAMIC_MANAGER = 13215
|
const val MINIMAL_SUPPORTED_DYNAMIC_MANAGER = 13215
|
||||||
|
|
||||||
const val MINIMAL_SUPPORTED_UID_SCANNER = 13347
|
const val MINIMAL_SUPPORTED_UID_SCANNER = 13347
|
||||||
|
|
||||||
const val MINIMAL_NEW_IOCTL_KERNEL = 13490
|
|
||||||
|
|
||||||
const val ROOT_UID = 0
|
const val ROOT_UID = 0
|
||||||
const val ROOT_GID = 0
|
const val ROOT_GID = 0
|
||||||
|
|
||||||
|
|
@ -62,9 +63,11 @@ object Natives {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("zakosign")
|
System.loadLibrary("zakosign")
|
||||||
System.loadLibrary("kernelsu")
|
System.loadLibrary("zako")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// become root manager, return true if success.
|
||||||
|
external fun becomeManager(pkg: String?): Boolean
|
||||||
val version: Int
|
val version: Int
|
||||||
external get
|
external get
|
||||||
|
|
||||||
|
|
@ -78,9 +81,6 @@ object Natives {
|
||||||
val isLkmMode: Boolean
|
val isLkmMode: Boolean
|
||||||
external get
|
external get
|
||||||
|
|
||||||
val isManager: Boolean
|
|
||||||
external get
|
|
||||||
|
|
||||||
external fun uidShouldUmount(uid: Int): Boolean
|
external fun uidShouldUmount(uid: Int): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -99,34 +99,6 @@ object Natives {
|
||||||
*/
|
*/
|
||||||
external fun isSuEnabled(): Boolean
|
external fun isSuEnabled(): Boolean
|
||||||
external fun setSuEnabled(enabled: Boolean): Boolean
|
external fun setSuEnabled(enabled: Boolean): Boolean
|
||||||
|
|
||||||
/**
|
|
||||||
* Kernel module umount can be disabled temporarily.
|
|
||||||
* 0: disabled
|
|
||||||
* 1: enabled
|
|
||||||
* negative : error
|
|
||||||
*/
|
|
||||||
external fun isKernelUmountEnabled(): Boolean
|
|
||||||
external fun setKernelUmountEnabled(enabled: Boolean): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enhanced security can be enabled/disabled.
|
|
||||||
* 0: disabled
|
|
||||||
* 1: enabled
|
|
||||||
* negative : error
|
|
||||||
*/
|
|
||||||
external fun isEnhancedSecurityEnabled(): Boolean
|
|
||||||
external fun setEnhancedSecurityEnabled(enabled: Boolean): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Su Log can be enabled/disabled.
|
|
||||||
* 0: disabled
|
|
||||||
* 1: enabled
|
|
||||||
* negative : error
|
|
||||||
*/
|
|
||||||
external fun isSuLogEnabled(): Boolean
|
|
||||||
external fun setSuLogEnabled(enabled: Boolean): Boolean
|
|
||||||
|
|
||||||
external fun isKPMEnabled(): Boolean
|
external fun isKPMEnabled(): Boolean
|
||||||
external fun getHookType(): String
|
external fun getHookType(): String
|
||||||
|
|
||||||
|
|
@ -134,6 +106,7 @@ object Natives {
|
||||||
* Get SUSFS feature status from kernel
|
* Get SUSFS feature status from kernel
|
||||||
* @return SusfsFeatureStatus object containing all feature states, or null if failed
|
* @return SusfsFeatureStatus object containing all feature states, or null if failed
|
||||||
*/
|
*/
|
||||||
|
external fun getSusfsFeatureStatus(): SusfsFeatureStatus?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set dynamic managerature configuration
|
* Set dynamic managerature configuration
|
||||||
|
|
@ -165,28 +138,6 @@ object Natives {
|
||||||
// 模块签名验证
|
// 模块签名验证
|
||||||
external fun verifyModuleSignature(modulePath: String): Boolean
|
external fun verifyModuleSignature(modulePath: String): Boolean
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if UID scanner is currently enabled
|
|
||||||
* @return true if UID scanner is enabled, false otherwise
|
|
||||||
*/
|
|
||||||
external fun isUidScannerEnabled(): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable or disable UID scanner
|
|
||||||
* @param enabled true to enable, false to disable
|
|
||||||
* @return true if operation was successful, false otherwise
|
|
||||||
*/
|
|
||||||
external fun setUidScannerEnabled(enabled: Boolean): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear UID scanner environment (force exit)
|
|
||||||
* This will forcefully stop all UID scanner operations and clear the environment
|
|
||||||
* @return true if operation was successful, false otherwise
|
|
||||||
*/
|
|
||||||
external fun clearUidScannerEnvironment(): Boolean
|
|
||||||
|
|
||||||
external fun getUserName(uid: Int): String?
|
|
||||||
|
|
||||||
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
|
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
|
||||||
private const val NOBODY_UID = 9999
|
private const val NOBODY_UID = 9999
|
||||||
|
|
||||||
|
|
@ -208,10 +159,31 @@ object Natives {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requireNewKernel(): Boolean {
|
fun requireNewKernel(): Boolean {
|
||||||
if (version != -1 && version < MINIMAL_SUPPORTED_KERNEL) return true
|
if (version < MINIMAL_SUPPORTED_KERNEL) return true
|
||||||
return isVersionLessThan(getFullVersion(), MINIMAL_SUPPORTED_KERNEL_FULL)
|
return isVersionLessThan(getFullVersion(), MINIMAL_SUPPORTED_KERNEL_FULL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@Parcelize
|
||||||
|
@Keep
|
||||||
|
data class SusfsFeatureStatus(
|
||||||
|
val statusSusPath: Boolean = false,
|
||||||
|
val statusSusMount: Boolean = false,
|
||||||
|
val statusAutoDefaultMount: Boolean = false,
|
||||||
|
val statusAutoBindMount: Boolean = false,
|
||||||
|
val statusSusKstat: Boolean = false,
|
||||||
|
val statusTryUmount: Boolean = false,
|
||||||
|
val statusAutoTryUmountBind: Boolean = false,
|
||||||
|
val statusSpoofUname: Boolean = false,
|
||||||
|
val statusEnableLog: Boolean = false,
|
||||||
|
val statusHideSymbols: Boolean = false,
|
||||||
|
val statusSpoofCmdline: Boolean = false,
|
||||||
|
val statusOpenRedirect: Boolean = false,
|
||||||
|
val statusMagicMount: Boolean = false,
|
||||||
|
val statusOverlayfsAutoKstat: Boolean = false,
|
||||||
|
val statusSusSu: Boolean = false
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@Keep
|
@Keep
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,137 @@
|
||||||
package com.sukisu.ultra.ui
|
package com.sukisu.ultra.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.topjohnwu.superuser.ipc.RootService
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
import com.sukisu.zako.IKsuInterface
|
import rikka.parcelablelist.ParcelableListSlice
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ShirkNeko
|
* @author ShirkNeko
|
||||||
* @date 2025/10/17.
|
* @date 2025/7/2.
|
||||||
*/
|
*/
|
||||||
class KsuService : RootService() {
|
class KsuService : RootService() {
|
||||||
|
|
||||||
private val TAG = "KsuService"
|
companion object {
|
||||||
|
private const val TAG = "KsuService"
|
||||||
private val cacheLock = Object()
|
private const val DESCRIPTOR = "com.sukisu.ultra.IKsuInterface"
|
||||||
private var _all: List<PackageInfo>? = null
|
private const val TRANSACTION_GET_PACKAGES = IBinder.FIRST_CALL_TRANSACTION + 0
|
||||||
private val allPackages: List<PackageInfo>
|
|
||||||
get() = synchronized(cacheLock) {
|
|
||||||
_all ?: loadAllPackages().also { _all = it }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadAllPackages(): List<PackageInfo> {
|
interface IKsuInterface : IInterface {
|
||||||
val tmp = arrayListOf<PackageInfo>()
|
fun getPackages(flags: Int): ParcelableListSlice<PackageInfo>
|
||||||
for (user in (getSystemService(USER_SERVICE) as UserManager).userProfiles) {
|
|
||||||
val userId = user.getUserIdCompat()
|
|
||||||
tmp += getInstalledPackagesAsUser(userId)
|
|
||||||
}
|
|
||||||
return tmp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal inner class Stub : IKsuInterface.Stub() {
|
abstract class Stub : Binder(), IKsuInterface {
|
||||||
override fun getPackageCount(): Int = allPackages.size
|
init {
|
||||||
|
attachInterface(this, DESCRIPTOR)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getPackages(start: Int, maxCount: Int): List<PackageInfo> {
|
companion object {
|
||||||
val list = allPackages
|
fun asInterface(obj: IBinder?): IKsuInterface? {
|
||||||
val end = (start + maxCount).coerceAtMost(list.size)
|
if (obj == null) return null
|
||||||
return if (start >= list.size) emptyList()
|
val iin = obj.queryLocalInterface(DESCRIPTOR)
|
||||||
else list.subList(start, end)
|
return if (iin != null && iin is IKsuInterface) {
|
||||||
|
iin
|
||||||
|
} else {
|
||||||
|
Proxy(obj)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder = Stub()
|
override fun asBinder(): IBinder = this
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||||
private fun getInstalledPackagesAsUser(userId: Int): List<PackageInfo> {
|
val descriptor = DESCRIPTOR
|
||||||
|
when (code) {
|
||||||
|
INTERFACE_TRANSACTION -> {
|
||||||
|
reply?.writeString(descriptor)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
TRANSACTION_GET_PACKAGES -> {
|
||||||
|
data.enforceInterface(descriptor)
|
||||||
|
val flagsArg = data.readInt()
|
||||||
|
val result = getPackages(flagsArg)
|
||||||
|
reply?.writeNoException()
|
||||||
|
reply?.writeInt(1)
|
||||||
|
result.writeToParcel(reply!!, Parcelable.PARCELABLE_WRITE_RETURN_VALUE)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onTransact(code, data, reply, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Proxy(private val mRemote: IBinder) : IKsuInterface {
|
||||||
|
override fun getPackages(flags: Int): ParcelableListSlice<PackageInfo> {
|
||||||
|
val data = Parcel.obtain()
|
||||||
|
val reply = Parcel.obtain()
|
||||||
|
return try {
|
||||||
|
data.writeInterfaceToken(DESCRIPTOR)
|
||||||
|
data.writeInt(flags)
|
||||||
|
mRemote.transact(TRANSACTION_GET_PACKAGES, data, reply, 0)
|
||||||
|
reply.readException()
|
||||||
|
if (reply.readInt() != 0) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
ParcelableListSlice.CREATOR.createFromParcel(reply) as ParcelableListSlice<PackageInfo>
|
||||||
|
} else {
|
||||||
|
ParcelableListSlice(emptyList<PackageInfo>())
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
reply.recycle()
|
||||||
|
data.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun asBinder(): IBinder = mRemote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class KsuInterfaceImpl : Stub() {
|
||||||
|
override fun getPackages(flags: Int): ParcelableListSlice<PackageInfo> {
|
||||||
|
val list = getInstalledPackagesAll(flags)
|
||||||
|
Log.i(TAG, "getPackages: ${list.size}")
|
||||||
|
return ParcelableListSlice(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder {
|
||||||
|
return KsuInterfaceImpl()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUserIds(): List<Int> {
|
||||||
|
val result = mutableListOf<Int>()
|
||||||
|
val um = getSystemService(USER_SERVICE) as UserManager
|
||||||
|
val userProfiles = um.userProfiles
|
||||||
|
for (userProfile in userProfiles) {
|
||||||
|
result.add(userProfile.hashCode())
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInstalledPackagesAll(flags: Int): ArrayList<PackageInfo> {
|
||||||
|
val packages = ArrayList<PackageInfo>()
|
||||||
|
for (userId in getUserIds()) {
|
||||||
|
Log.i(TAG, "getInstalledPackagesAll: $userId")
|
||||||
|
packages.addAll(getInstalledPackagesAsUser(flags, userId))
|
||||||
|
}
|
||||||
|
return packages
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInstalledPackagesAsUser(flags: Int, userId: Int): List<PackageInfo> {
|
||||||
return try {
|
return try {
|
||||||
val pm = packageManager
|
val pm = packageManager
|
||||||
val m = pm.javaClass.getDeclaredMethod(
|
val getInstalledPackagesAsUser: Method = pm.javaClass.getDeclaredMethod(
|
||||||
"getInstalledPackagesAsUser",
|
"getInstalledPackagesAsUser",
|
||||||
Int::class.java,
|
Int::class.java,
|
||||||
Int::class.java
|
Int::class.java
|
||||||
)
|
)
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
m.invoke(pm, 0, userId) as List<PackageInfo>
|
getInstalledPackagesAsUser.invoke(pm, flags, userId) as List<PackageInfo>
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e(TAG, "getInstalledPackagesAsUser", e)
|
Log.e(TAG, "err", e)
|
||||||
emptyList()
|
ArrayList()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun UserHandle.getUserIdCompat(): Int {
|
|
||||||
return try {
|
|
||||||
javaClass.getDeclaredField("identifier").apply { isAccessible = true }.getInt(this)
|
|
||||||
} catch (_: NoSuchFieldException) {
|
|
||||||
javaClass.getDeclaredMethod("getIdentifier").invoke(this) as Int
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Log.e("KsuService", "getUserIdCompat", e)
|
|
||||||
0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
package com.sukisu.ultra.ui
|
package com.sukisu.ultra.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
|
@ -10,10 +9,13 @@ import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
|
|
@ -24,8 +26,6 @@ import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationSty
|
||||||
import com.ramcosta.composedestinations.generated.NavGraphs
|
import com.ramcosta.composedestinations.generated.NavGraphs
|
||||||
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
|
||||||
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
|
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
|
||||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
|
||||||
import zako.zako.zako.zakoui.screen.moreSettings.util.LocaleHelper
|
|
||||||
import com.sukisu.ultra.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||||
import com.sukisu.ultra.ui.theme.KernelSUTheme
|
import com.sukisu.ultra.ui.theme.KernelSUTheme
|
||||||
|
|
@ -34,11 +34,11 @@ import com.sukisu.ultra.ui.util.install
|
||||||
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
||||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||||
import com.sukisu.ultra.ui.webui.initPlatform
|
import com.sukisu.ultra.ui.webui.initPlatform
|
||||||
import com.sukisu.ultra.ui.component.*
|
import io.sukisu.ultra.UltraToolInstall
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import com.sukisu.ultra.ui.activity.component.BottomBar
|
import zako.zako.zako.zakoui.activity.component.BottomBar
|
||||||
import com.sukisu.ultra.ui.activity.util.*
|
import zako.zako.zako.zakoui.activity.util.*
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var superUserViewModel: SuperUserViewModel
|
private lateinit var superUserViewModel: SuperUserViewModel
|
||||||
|
|
@ -50,18 +50,21 @@ class MainActivity : ComponentActivity() {
|
||||||
val showKpmInfo: Boolean = false
|
val showKpmInfo: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
private var showConfirmationDialog = mutableStateOf(false)
|
|
||||||
private var pendingZipFiles = mutableStateOf<List<ZipFileInfo>>(emptyList())
|
|
||||||
|
|
||||||
private lateinit var themeChangeObserver: ThemeChangeContentObserver
|
private lateinit var themeChangeObserver: ThemeChangeContentObserver
|
||||||
|
|
||||||
|
// 添加标记避免重复初始化
|
||||||
private var isInitialized = false
|
private var isInitialized = false
|
||||||
|
|
||||||
override fun attachBaseContext(newBase: Context?) {
|
override fun attachBaseContext(newBase: Context) {
|
||||||
super.attachBaseContext(newBase?.let { LocaleHelper.applyLanguage(it) })
|
val context = LocaleUtils.applyLocale(newBase)
|
||||||
|
super.attachBaseContext(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
try {
|
try {
|
||||||
|
// 确保应用正确的语言设置
|
||||||
|
LocaleUtils.applyLanguageSetting(this)
|
||||||
|
|
||||||
// 应用自定义 DPI
|
// 应用自定义 DPI
|
||||||
DisplayUtils.applyCustomDpi(this)
|
DisplayUtils.applyCustomDpi(this)
|
||||||
|
|
||||||
|
|
@ -74,11 +77,6 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val isManager = Natives.isManager
|
|
||||||
if (isManager && !Natives.requireNewKernel()) {
|
|
||||||
install()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用标记控制初始化流程
|
// 使用标记控制初始化流程
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
initializeViewModels()
|
initializeViewModels()
|
||||||
|
|
@ -86,39 +84,6 @@ class MainActivity : ComponentActivity() {
|
||||||
isInitialized = true
|
isInitialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if launched with a ZIP file
|
|
||||||
val zipUri: ArrayList<Uri>? = when (intent?.action) {
|
|
||||||
Intent.ACTION_SEND -> {
|
|
||||||
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
intent.getParcelableExtra(Intent.EXTRA_STREAM)
|
|
||||||
}
|
|
||||||
uri?.let { arrayListOf(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent.ACTION_SEND_MULTIPLE -> {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> when {
|
|
||||||
intent?.data != null -> arrayListOf(intent.data!!)
|
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
|
|
||||||
intent.getParcelableArrayListExtra("uris", Uri::class.java)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
intent.getParcelableArrayListExtra("uris")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
KernelSUTheme {
|
KernelSUTheme {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
@ -129,38 +94,6 @@ class MainActivity : ComponentActivity() {
|
||||||
BottomBarDestination.entries.map { it.direction.route }.toSet()
|
BottomBarDestination.entries.map { it.direction.route }.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
val navigator = navController.rememberDestinationsNavigator()
|
|
||||||
|
|
||||||
InstallConfirmationDialog(
|
|
||||||
show = showConfirmationDialog.value,
|
|
||||||
zipFiles = pendingZipFiles.value,
|
|
||||||
onConfirm = { confirmedFiles ->
|
|
||||||
showConfirmationDialog.value = false
|
|
||||||
UltraActivityUtils.navigateToFlashScreen(this, confirmedFiles, navigator)
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
showConfirmationDialog.value = false
|
|
||||||
pendingZipFiles.value = emptyList()
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
LaunchedEffect(zipUri) {
|
|
||||||
if (!zipUri.isNullOrEmpty()) {
|
|
||||||
// 检测 ZIP 文件类型并显示确认对话框
|
|
||||||
lifecycleScope.launch {
|
|
||||||
UltraActivityUtils.detectZipTypeAndShowConfirmation(this@MainActivity, zipUri) { infos ->
|
|
||||||
if (infos.isNotEmpty()) {
|
|
||||||
pendingZipFiles.value = infos
|
|
||||||
showConfirmationDialog.value = true
|
|
||||||
} else {
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val showBottomBar = when (currentDestination?.route) {
|
val showBottomBar = when (currentDestination?.route) {
|
||||||
ExecuteModuleActionScreenDestination.route -> false
|
ExecuteModuleActionScreenDestination.route -> false
|
||||||
else -> true
|
else -> true
|
||||||
|
|
@ -254,17 +187,32 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
homeViewModel.initializeData()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 数据刷新协程
|
// 数据刷新协程
|
||||||
DataRefreshUtils.startDataRefreshCoroutine(lifecycleScope)
|
DataRefreshUtils.startDataRefreshCoroutine(lifecycleScope)
|
||||||
DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow)
|
DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow)
|
||||||
|
|
||||||
// 初始化主题相关设置
|
// 初始化主题相关设置
|
||||||
ThemeUtils.initializeThemeSettings(this, settingsStateFlow)
|
ThemeUtils.initializeThemeSettings(this, settingsStateFlow)
|
||||||
|
|
||||||
|
val isManager = Natives.becomeManager(packageName)
|
||||||
|
if (isManager) {
|
||||||
|
install()
|
||||||
|
UltraToolInstall.tryToInstall()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
try {
|
try {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
LocaleUtils.applyLanguageSetting(this)
|
||||||
ThemeUtils.onActivityResume()
|
ThemeUtils.onActivityResume()
|
||||||
|
|
||||||
// 仅在需要时刷新数据
|
// 仅在需要时刷新数据
|
||||||
|
|
@ -280,6 +228,7 @@ class MainActivity : ComponentActivity() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
superUserViewModel.fetchAppList()
|
superUserViewModel.fetchAppList()
|
||||||
|
homeViewModel.initializeData()
|
||||||
DataRefreshUtils.refreshData(lifecycleScope)
|
DataRefreshUtils.refreshData(lifecycleScope)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
@ -304,4 +253,13 @@ class MainActivity : ComponentActivity() {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
try {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
LocaleUtils.applyLanguageSetting(this)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,236 +0,0 @@
|
||||||
package com.sukisu.ultra.ui.activity.util
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.animation.slideInVertically
|
|
||||||
import androidx.compose.animation.slideOutVertically
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
|
||||||
import com.sukisu.ultra.Natives
|
|
||||||
import com.sukisu.ultra.ui.MainActivity
|
|
||||||
import com.sukisu.ultra.ui.util.*
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.util.*
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|
||||||
import com.sukisu.ultra.ui.component.ZipFileDetector
|
|
||||||
import com.sukisu.ultra.ui.component.ZipFileInfo
|
|
||||||
import com.sukisu.ultra.ui.component.ZipType
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
|
||||||
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
|
|
||||||
import com.sukisu.ultra.ui.screen.FlashIt
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import androidx.core.content.edit
|
|
||||||
|
|
||||||
object AnimatedBottomBar {
|
|
||||||
@Composable
|
|
||||||
fun AnimatedBottomBarWrapper(
|
|
||||||
showBottomBar: Boolean,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = showBottomBar,
|
|
||||||
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
|
|
||||||
exit = slideOutVertically(targetOffsetY = { it }) + fadeOut()
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object UltraActivityUtils {
|
|
||||||
|
|
||||||
suspend fun detectZipTypeAndShowConfirmation(
|
|
||||||
activity: MainActivity,
|
|
||||||
zipUris: ArrayList<Uri>,
|
|
||||||
onResult: (List<ZipFileInfo>) -> Unit
|
|
||||||
) {
|
|
||||||
val infos = ZipFileDetector.detectAndParseZipFiles(activity, zipUris)
|
|
||||||
withContext(Dispatchers.Main) { onResult(infos) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun navigateToFlashScreen(
|
|
||||||
activity: MainActivity,
|
|
||||||
zipFiles: List<ZipFileInfo>,
|
|
||||||
navigator: DestinationsNavigator
|
|
||||||
) {
|
|
||||||
activity.lifecycleScope.launch {
|
|
||||||
val moduleUris = zipFiles.filter { it.type == ZipType.MODULE }.map { it.uri }
|
|
||||||
val kernelUris = zipFiles.filter { it.type == ZipType.KERNEL }.map { it.uri }
|
|
||||||
|
|
||||||
when {
|
|
||||||
kernelUris.isNotEmpty() && moduleUris.isEmpty() -> {
|
|
||||||
if (kernelUris.size == 1 && rootAvailable()) {
|
|
||||||
navigator.navigate(
|
|
||||||
InstallScreenDestination(
|
|
||||||
preselectedKernelUri = kernelUris.first().toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
setAutoExitAfterFlash(activity)
|
|
||||||
}
|
|
||||||
|
|
||||||
moduleUris.isNotEmpty() -> {
|
|
||||||
navigator.navigate(
|
|
||||||
FlashScreenDestination(
|
|
||||||
FlashIt.FlashModules(ArrayList(moduleUris))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setAutoExitAfterFlash(activity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setAutoExitAfterFlash(activity: Context) {
|
|
||||||
activity.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
|
||||||
.edit {
|
|
||||||
putBoolean("auto_exit_after_flash", true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object AppData {
|
|
||||||
object DataRefreshManager {
|
|
||||||
// 私有状态流
|
|
||||||
private val _superuserCount = MutableStateFlow(0)
|
|
||||||
private val _moduleCount = MutableStateFlow(0)
|
|
||||||
private val _kpmModuleCount = MutableStateFlow(0)
|
|
||||||
|
|
||||||
// 公开的只读状态流
|
|
||||||
val superuserCount: StateFlow<Int> = _superuserCount.asStateFlow()
|
|
||||||
val moduleCount: StateFlow<Int> = _moduleCount.asStateFlow()
|
|
||||||
val kpmModuleCount: StateFlow<Int> = _kpmModuleCount.asStateFlow()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新所有数据计数
|
|
||||||
*/
|
|
||||||
fun refreshData() {
|
|
||||||
_superuserCount.value = getSuperuserCountUse()
|
|
||||||
_moduleCount.value = getModuleCountUse()
|
|
||||||
_kpmModuleCount.value = getKpmModuleCountUse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取超级用户应用计数
|
|
||||||
*/
|
|
||||||
fun getSuperuserCountUse(): Int {
|
|
||||||
return try {
|
|
||||||
if (!rootAvailable()) return 0
|
|
||||||
getSuperuserCount()
|
|
||||||
} catch (_: Exception) {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取模块计数
|
|
||||||
*/
|
|
||||||
fun getModuleCountUse(): Int {
|
|
||||||
return try {
|
|
||||||
if (!rootAvailable()) return 0
|
|
||||||
getModuleCount()
|
|
||||||
} catch (_: Exception) {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取KPM模块计数
|
|
||||||
*/
|
|
||||||
fun getKpmModuleCountUse(): Int {
|
|
||||||
return try {
|
|
||||||
if (!rootAvailable()) return 0
|
|
||||||
val kpmVersion = getKpmVersionUse()
|
|
||||||
if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) return 0
|
|
||||||
getKpmModuleCount()
|
|
||||||
} catch (_: Exception) {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取KPM版本
|
|
||||||
*/
|
|
||||||
fun getKpmVersionUse(): String {
|
|
||||||
return try {
|
|
||||||
if (!rootAvailable()) return ""
|
|
||||||
val version = getKpmVersion()
|
|
||||||
version.ifEmpty { "" }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
"Error: ${e.message}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否是完整功能模式
|
|
||||||
*/
|
|
||||||
fun isFullFeatured(): Boolean {
|
|
||||||
val isManager = Natives.isManager
|
|
||||||
return isManager && !Natives.requireNewKernel() && rootAvailable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object DataRefreshUtils {
|
|
||||||
fun startDataRefreshCoroutine(scope: LifecycleCoroutineScope) {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
while (isActive) {
|
|
||||||
AppData.DataRefreshManager.refreshData()
|
|
||||||
delay(5000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startSettingsMonitorCoroutine(
|
|
||||||
scope: LifecycleCoroutineScope,
|
|
||||||
activity: MainActivity,
|
|
||||||
settingsStateFlow: MutableStateFlow<MainActivity.SettingsState>
|
|
||||||
) {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
while (isActive) {
|
|
||||||
val prefs = activity.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
settingsStateFlow.value = MainActivity.SettingsState(
|
|
||||||
isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false),
|
|
||||||
showKpmInfo = prefs.getBoolean("show_kpm_info", false)
|
|
||||||
)
|
|
||||||
delay(1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refreshData(scope: LifecycleCoroutineScope) {
|
|
||||||
scope.launch {
|
|
||||||
AppData.DataRefreshManager.refreshData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object DisplayUtils {
|
|
||||||
fun applyCustomDpi(context: Context) {
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
val customDpi = prefs.getInt("app_dpi", 0)
|
|
||||||
|
|
||||||
if (customDpi > 0) {
|
|
||||||
try {
|
|
||||||
val resources = context.resources
|
|
||||||
val metrics = resources.displayMetrics
|
|
||||||
metrics.density = customDpi / 160f
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
metrics.scaledDensity = customDpi / 160f
|
|
||||||
metrics.densityDpi = customDpi
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,16 +8,11 @@ import android.text.method.LinkMovementMethod
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.Saver
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
|
@ -433,17 +428,7 @@ private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, di
|
||||||
@Composable
|
@Composable
|
||||||
private fun MarkdownContent(content: String) {
|
private fun MarkdownContent(content: String) {
|
||||||
val contentColor = LocalContentColor.current
|
val contentColor = LocalContentColor.current
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.verticalScroll(
|
|
||||||
state = scrollState,
|
|
||||||
flingBehavior = ScrollableDefaults.flingBehavior()
|
|
||||||
)
|
|
||||||
.padding(12.dp)
|
|
||||||
) {
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = { context ->
|
factory = { context ->
|
||||||
TextView(context).apply {
|
TextView(context).apply {
|
||||||
|
|
@ -454,15 +439,16 @@ private fun MarkdownContent(content: String) {
|
||||||
}
|
}
|
||||||
hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE
|
hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
update = {
|
update = {
|
||||||
Markwon.create(it.context).setMarkdown(it, content)
|
Markwon.create(it.context).setMarkdown(it, content)
|
||||||
it.setTextColor(contentColor.toArgb())
|
it.setTextColor(contentColor.toArgb())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
package com.sukisu.ultra.ui.component
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.Fullscreen
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import com.sukisu.ultra.R
|
||||||
|
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
||||||
|
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImageEditorDialog(
|
||||||
|
imageUri: Uri,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onConfirm: (Uri) -> Unit
|
||||||
|
) {
|
||||||
|
var scale by remember { mutableFloatStateOf(1f) }
|
||||||
|
var offsetX by remember { mutableFloatStateOf(0f) }
|
||||||
|
var offsetY by remember { mutableFloatStateOf(0f) }
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
var lastScale by remember { mutableFloatStateOf(1f) }
|
||||||
|
var lastOffsetX by remember { mutableFloatStateOf(0f) }
|
||||||
|
var lastOffsetY by remember { mutableFloatStateOf(0f) }
|
||||||
|
var imageSize by remember { mutableStateOf(Size.Zero) }
|
||||||
|
var screenSize by remember { mutableStateOf(Size.Zero) }
|
||||||
|
val animatedScale by animateFloatAsState(
|
||||||
|
targetValue = scale,
|
||||||
|
label = "ScaleAnimation"
|
||||||
|
)
|
||||||
|
val animatedOffsetX by animateFloatAsState(
|
||||||
|
targetValue = offsetX,
|
||||||
|
label = "OffsetXAnimation"
|
||||||
|
)
|
||||||
|
val animatedOffsetY by animateFloatAsState(
|
||||||
|
targetValue = offsetY,
|
||||||
|
label = "OffsetYAnimation"
|
||||||
|
)
|
||||||
|
val updateTransformation = remember {
|
||||||
|
{ newScale: Float, newOffsetX: Float, newOffsetY: Float ->
|
||||||
|
val scaleDiff = kotlin.math.abs(newScale - lastScale)
|
||||||
|
val offsetXDiff = kotlin.math.abs(newOffsetX - lastOffsetX)
|
||||||
|
val offsetYDiff = kotlin.math.abs(newOffsetY - lastOffsetY)
|
||||||
|
if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) {
|
||||||
|
scale = newScale
|
||||||
|
offsetX = newOffsetX
|
||||||
|
offsetY = newOffsetY
|
||||||
|
lastScale = newScale
|
||||||
|
lastOffsetX = newOffsetX
|
||||||
|
lastOffsetY = newOffsetY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val scaleToFullScreen = remember {
|
||||||
|
{
|
||||||
|
if (imageSize.height > 0 && screenSize.height > 0) {
|
||||||
|
val newScale = screenSize.height / imageSize.height
|
||||||
|
updateTransformation(newScale, 0f, 0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = true,
|
||||||
|
dismissOnClickOutside = false,
|
||||||
|
usePlatformDefaultWidth = false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black.copy(alpha = 0.9f))
|
||||||
|
.onSizeChanged { size ->
|
||||||
|
screenSize = Size(size.width.toFloat(), size.height.toFloat())
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(imageUri)
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
|
contentDescription = stringResource(R.string.settings_custom_background),
|
||||||
|
contentScale = ContentScale.Fit,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.graphicsLayer(
|
||||||
|
scaleX = animatedScale,
|
||||||
|
scaleY = animatedScale,
|
||||||
|
translationX = animatedOffsetX,
|
||||||
|
translationY = animatedOffsetY
|
||||||
|
)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTransformGestures { _, pan, zoom, _ ->
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
val newScale = (scale * zoom).coerceIn(0.5f, 3f)
|
||||||
|
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
|
||||||
|
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
|
||||||
|
val newOffsetX = if (maxOffsetX > 0) {
|
||||||
|
(offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
val newOffsetY = if (maxOffsetY > 0) {
|
||||||
|
(offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
updateTransformation(newScale, newOffsetX, newOffsetY)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
updateTransformation(lastScale, lastOffsetX, lastOffsetY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onSizeChanged { size ->
|
||||||
|
imageSize = Size(size.width.toFloat(), size.height.toFloat())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.align(Alignment.TopCenter),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onDismiss,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(Color.Black.copy(alpha = 0.6f))
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = stringResource(R.string.cancel),
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = { scaleToFullScreen() },
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(Color.Black.copy(alpha = 0.6f))
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Fullscreen,
|
||||||
|
contentDescription = stringResource(R.string.reprovision),
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
val transformation = BackgroundTransformation(scale, offsetX, offsetY)
|
||||||
|
val savedUri = context.saveTransformedBackground(imageUri, transformation)
|
||||||
|
savedUri?.let { onConfirm(it) }
|
||||||
|
} catch (_: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(Color.Black.copy(alpha = 0.6f))
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = stringResource(R.string.confirm),
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(Color.Black.copy(alpha = 0.6f))
|
||||||
|
.padding(16.dp)
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.image_editor_hint),
|
||||||
|
color = Color.White,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,441 +0,0 @@
|
||||||
package com.sukisu.ultra.ui.component
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.Help
|
|
||||||
import androidx.compose.material.icons.filled.Extension
|
|
||||||
import androidx.compose.material.icons.filled.GetApp
|
|
||||||
import androidx.compose.material.icons.filled.Memory
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.sukisu.ultra.R
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.util.zip.ZipInputStream
|
|
||||||
|
|
||||||
enum class ZipType {
|
|
||||||
MODULE,
|
|
||||||
KERNEL,
|
|
||||||
UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ZipFileInfo(
|
|
||||||
val uri: Uri,
|
|
||||||
val type: ZipType,
|
|
||||||
val name: String = "",
|
|
||||||
val version: String = "",
|
|
||||||
val versionCode: String = "",
|
|
||||||
val author: String = "",
|
|
||||||
val description: String = "",
|
|
||||||
val kernelVersion: String = "",
|
|
||||||
val supported: String = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
object ZipFileDetector {
|
|
||||||
|
|
||||||
fun detectZipType(context: Context, uri: Uri): ZipType {
|
|
||||||
return try {
|
|
||||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
|
||||||
ZipInputStream(inputStream).use { zipStream ->
|
|
||||||
var hasModuleProp = false
|
|
||||||
var hasToolsFolder = false
|
|
||||||
var hasAnykernelSh = false
|
|
||||||
|
|
||||||
var entry = zipStream.nextEntry
|
|
||||||
while (entry != null) {
|
|
||||||
val entryName = entry.name.lowercase()
|
|
||||||
|
|
||||||
when {
|
|
||||||
entryName == "module.prop" || entryName.endsWith("/module.prop") -> {
|
|
||||||
hasModuleProp = true
|
|
||||||
}
|
|
||||||
entryName.startsWith("tools/") || entryName == "tools" -> {
|
|
||||||
hasToolsFolder = true
|
|
||||||
}
|
|
||||||
entryName == "anykernel.sh" || entryName.endsWith("/anykernel.sh") -> {
|
|
||||||
hasAnykernelSh = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zipStream.closeEntry()
|
|
||||||
entry = zipStream.nextEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
when {
|
|
||||||
hasModuleProp -> ZipType.MODULE
|
|
||||||
hasToolsFolder && hasAnykernelSh -> ZipType.KERNEL
|
|
||||||
else -> ZipType.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: ZipType.UNKNOWN
|
|
||||||
} catch (e: IOException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
ZipType.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseModuleInfo(context: Context, uri: Uri): ZipFileInfo {
|
|
||||||
var zipInfo = ZipFileInfo(uri = uri, type = ZipType.MODULE)
|
|
||||||
|
|
||||||
try {
|
|
||||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
|
||||||
ZipInputStream(inputStream).use { zipStream ->
|
|
||||||
var entry = zipStream.nextEntry
|
|
||||||
while (entry != null) {
|
|
||||||
if (entry.name.lowercase() == "module.prop" || entry.name.endsWith("/module.prop")) {
|
|
||||||
val reader = BufferedReader(InputStreamReader(zipStream))
|
|
||||||
val props = mutableMapOf<String, String>()
|
|
||||||
|
|
||||||
var line = reader.readLine()
|
|
||||||
while (line != null) {
|
|
||||||
if (line.contains("=") && !line.startsWith("#")) {
|
|
||||||
val parts = line.split("=", limit = 2)
|
|
||||||
if (parts.size == 2) {
|
|
||||||
props[parts[0].trim()] = parts[1].trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
line = reader.readLine()
|
|
||||||
}
|
|
||||||
|
|
||||||
zipInfo = zipInfo.copy(
|
|
||||||
name = props["name"] ?: context.getString(R.string.unknown_module),
|
|
||||||
version = props["version"] ?: "",
|
|
||||||
versionCode = props["versionCode"] ?: "",
|
|
||||||
author = props["author"] ?: "",
|
|
||||||
description = props["description"] ?: ""
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
zipStream.closeEntry()
|
|
||||||
entry = zipStream.nextEntry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
return zipInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseKernelInfo(context: Context, uri: Uri): ZipFileInfo {
|
|
||||||
var zipInfo = ZipFileInfo(uri = uri, type = ZipType.KERNEL)
|
|
||||||
|
|
||||||
try {
|
|
||||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
|
||||||
ZipInputStream(inputStream).use { zipStream ->
|
|
||||||
var entry = zipStream.nextEntry
|
|
||||||
while (entry != null) {
|
|
||||||
if (entry.name.lowercase() == "anykernel.sh" || entry.name.endsWith("/anykernel.sh")) {
|
|
||||||
val reader = BufferedReader(InputStreamReader(zipStream))
|
|
||||||
val props = mutableMapOf<String, String>()
|
|
||||||
|
|
||||||
var inPropertiesBlock = false
|
|
||||||
var line = reader.readLine()
|
|
||||||
while (line != null) {
|
|
||||||
if (line.contains("properties()")) {
|
|
||||||
inPropertiesBlock = true
|
|
||||||
} else if (inPropertiesBlock && line.contains("'; }")) {
|
|
||||||
inPropertiesBlock = false
|
|
||||||
} else if (inPropertiesBlock) {
|
|
||||||
val propertyLine = line.trim()
|
|
||||||
if (propertyLine.contains("=") && !propertyLine.startsWith("#")) {
|
|
||||||
val parts = propertyLine.split("=", limit = 2)
|
|
||||||
if (parts.size == 2) {
|
|
||||||
val key = parts[0].trim()
|
|
||||||
val value = parts[1].trim().removeSurrounding("'").removeSurrounding("\"")
|
|
||||||
when (key) {
|
|
||||||
"kernel.string" -> props["name"] = value
|
|
||||||
"supported.versions" -> props["supported"] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析普通变量定义
|
|
||||||
if (line.contains("kernel.string=") && !inPropertiesBlock) {
|
|
||||||
val value = line.substringAfter("kernel.string=").trim().removeSurrounding("\"")
|
|
||||||
props["name"] = value
|
|
||||||
}
|
|
||||||
if (line.contains("supported.versions=") && !inPropertiesBlock) {
|
|
||||||
val value = line.substringAfter("supported.versions=").trim().removeSurrounding("\"")
|
|
||||||
props["supported"] = value
|
|
||||||
}
|
|
||||||
if (line.contains("kernel.version=") && !inPropertiesBlock) {
|
|
||||||
val value = line.substringAfter("kernel.version=").trim().removeSurrounding("\"")
|
|
||||||
props["version"] = value
|
|
||||||
}
|
|
||||||
if (line.contains("kernel.author=") && !inPropertiesBlock) {
|
|
||||||
val value = line.substringAfter("kernel.author=").trim().removeSurrounding("\"")
|
|
||||||
props["author"] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
line = reader.readLine()
|
|
||||||
}
|
|
||||||
|
|
||||||
zipInfo = zipInfo.copy(
|
|
||||||
name = props["name"] ?: context.getString(R.string.unknown_kernel),
|
|
||||||
version = props["version"] ?: "",
|
|
||||||
author = props["author"] ?: "",
|
|
||||||
supported = props["supported"] ?: "",
|
|
||||||
kernelVersion = props["version"] ?: ""
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
zipStream.closeEntry()
|
|
||||||
entry = zipStream.nextEntry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
return zipInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun detectAndParseZipFiles(context: Context, zipUris: List<Uri>): List<ZipFileInfo> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
val zipFileInfos = mutableListOf<ZipFileInfo>()
|
|
||||||
|
|
||||||
for (uri in zipUris) {
|
|
||||||
val zipType = detectZipType(context, uri)
|
|
||||||
val zipInfo = when (zipType) {
|
|
||||||
ZipType.MODULE -> parseModuleInfo(context, uri)
|
|
||||||
ZipType.KERNEL -> parseKernelInfo(context, uri)
|
|
||||||
ZipType.UNKNOWN -> ZipFileInfo(
|
|
||||||
uri = uri,
|
|
||||||
type = ZipType.UNKNOWN,
|
|
||||||
name = context.getString(R.string.unknown_file)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
zipFileInfos.add(zipInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
zipFileInfos.filter { it.type != ZipType.UNKNOWN }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun InstallConfirmationDialog(
|
|
||||||
show: Boolean,
|
|
||||||
zipFiles: List<ZipFileInfo>,
|
|
||||||
onConfirm: (List<ZipFileInfo>) -> Unit,
|
|
||||||
onDismiss: () -> Unit
|
|
||||||
) {
|
|
||||||
if (show && zipFiles.isNotEmpty()) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
title = {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = if (zipFiles.any { it.type == ZipType.KERNEL })
|
|
||||||
Icons.Default.Memory else Icons.Default.Extension,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text(
|
|
||||||
text = if (zipFiles.size == 1) {
|
|
||||||
context.getString(R.string.confirm_installation)
|
|
||||||
} else {
|
|
||||||
context.getString(R.string.confirm_multiple_installation, zipFiles.size)
|
|
||||||
},
|
|
||||||
style = MaterialTheme.typography.headlineSmall
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(max = 400.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
items(zipFiles.size) { index ->
|
|
||||||
val zipFile = zipFiles[index]
|
|
||||||
InstallItemCard(zipFile = zipFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
Button(
|
|
||||||
onClick = { onConfirm(zipFiles) },
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.GetApp,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(18.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(context.getString(R.string.install_confirm))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismiss) {
|
|
||||||
Text(
|
|
||||||
context.getString(android.R.string.cancel),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.widthIn(min = 320.dp, max = 560.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun InstallItemCard(zipFile: ZipFileInfo) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
ElevatedCard(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
colors = CardDefaults.elevatedCardColors(
|
|
||||||
containerColor = when (zipFile.type) {
|
|
||||||
ZipType.MODULE -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
|
|
||||||
ZipType.KERNEL -> MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.3f)
|
|
||||||
else -> MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
}
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 0.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = when (zipFile.type) {
|
|
||||||
ZipType.MODULE -> Icons.Default.Extension
|
|
||||||
ZipType.KERNEL -> Icons.Default.Memory
|
|
||||||
else -> Icons.AutoMirrored.Filled.Help
|
|
||||||
},
|
|
||||||
contentDescription = null,
|
|
||||||
tint = when (zipFile.type) {
|
|
||||||
ZipType.MODULE -> MaterialTheme.colorScheme.primary
|
|
||||||
ZipType.KERNEL -> MaterialTheme.colorScheme.tertiary
|
|
||||||
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
text = zipFile.name.ifEmpty {
|
|
||||||
when (zipFile.type) {
|
|
||||||
ZipType.MODULE -> context.getString(R.string.unknown_module)
|
|
||||||
ZipType.KERNEL -> context.getString(R.string.unknown_kernel)
|
|
||||||
else -> context.getString(R.string.unknown_file)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = when (zipFile.type) {
|
|
||||||
ZipType.MODULE -> context.getString(R.string.module_package)
|
|
||||||
ZipType.KERNEL -> context.getString(R.string.kernel_package)
|
|
||||||
else -> context.getString(R.string.unknown_package)
|
|
||||||
},
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 详细信息
|
|
||||||
if (zipFile.version.isNotEmpty() || zipFile.author.isNotEmpty() ||
|
|
||||||
zipFile.description.isNotEmpty() || zipFile.supported.isNotEmpty()) {
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
HorizontalDivider(
|
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f),
|
|
||||||
thickness = 0.5.dp
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
// 版本信息
|
|
||||||
if (zipFile.version.isNotEmpty()) {
|
|
||||||
InfoRow(
|
|
||||||
label = context.getString(R.string.version),
|
|
||||||
value = zipFile.version + if (zipFile.versionCode.isNotEmpty()) " (${zipFile.versionCode})" else ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 作者信息
|
|
||||||
if (zipFile.author.isNotEmpty()) {
|
|
||||||
InfoRow(
|
|
||||||
label = context.getString(R.string.author),
|
|
||||||
value = zipFile.author
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 描述信息 (仅模块)
|
|
||||||
if (zipFile.description.isNotEmpty() && zipFile.type == ZipType.MODULE) {
|
|
||||||
InfoRow(
|
|
||||||
label = context.getString(R.string.description),
|
|
||||||
value = zipFile.description
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 支持设备 (仅内核)
|
|
||||||
if (zipFile.supported.isNotEmpty() && zipFile.type == ZipType.KERNEL) {
|
|
||||||
InfoRow(
|
|
||||||
label = context.getString(R.string.supported_devices),
|
|
||||||
value = zipFile.supported
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun InfoRow(label: String, value: String) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 2.dp),
|
|
||||||
verticalAlignment = Alignment.Top
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "$label:",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.widthIn(min = 60.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = value,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,7 @@ import com.sukisu.ultra.ksuApp
|
||||||
fun KsuIsValid(
|
fun KsuIsValid(
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val isManager = Natives.isManager
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
val ksuVersion = if (isManager) Natives.version else null
|
||||||
|
|
||||||
if (ksuVersion != null) {
|
if (ksuVersion != null) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package zako.zako.zako.zakoui.screen.kernelFlash.component
|
package com.sukisu.ultra.ui.component
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.sukisu.ultra.ui.susfs.component
|
package com.sukisu.ultra.ui.component
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
|
|
@ -29,7 +29,7 @@ import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.ui.susfs.util.SuSFSManager
|
import com.sukisu.ultra.ui.util.SuSFSManager
|
||||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -464,7 +464,7 @@ fun AddTryUmountDialog(
|
||||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = umountModeExpanded) },
|
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = umountModeExpanded) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true),
|
.menuAnchor(MenuAnchorType.PrimaryEditable, true),
|
||||||
shape = RoundedCornerShape(8.dp)
|
shape = RoundedCornerShape(8.dp)
|
||||||
)
|
)
|
||||||
ExposedDropdownMenu(
|
ExposedDropdownMenu(
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.sukisu.ultra.ui.susfs.component
|
package com.sukisu.ultra.ui.component
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
@ -18,8 +18,8 @@ import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.ui.susfs.util.SuSFSManager
|
import com.sukisu.ultra.ui.util.SuSFSManager
|
||||||
import com.sukisu.ultra.ui.susfs.util.SuSFSManager.isSusVersion158
|
import com.sukisu.ultra.ui.util.SuSFSManager.isSusVersion158
|
||||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -310,116 +310,6 @@ fun SusLoopPathsContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* SUS Maps内容组件
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun SusMapsContent(
|
|
||||||
susMaps: Set<String>,
|
|
||||||
isLoading: Boolean,
|
|
||||||
onAddSusMap: () -> Unit,
|
|
||||||
onRemoveSusMap: (String) -> Unit,
|
|
||||||
onEditSusMap: ((String) -> Unit)? = null
|
|
||||||
) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
// 说明卡片
|
|
||||||
item {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
|
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(12.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(12.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.sus_maps_description_title),
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.sus_maps_description_text),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.sus_maps_warning),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.secondary
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.sus_maps_debug_info),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (susMaps.isEmpty()) {
|
|
||||||
item {
|
|
||||||
EmptyStateCard(
|
|
||||||
message = stringResource(R.string.susfs_no_sus_maps_configured)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
item {
|
|
||||||
SectionHeader(
|
|
||||||
title = stringResource(R.string.sus_maps_section),
|
|
||||||
subtitle = null,
|
|
||||||
icon = Icons.Default.Security,
|
|
||||||
count = susMaps.size
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
items(susMaps.toList()) { map ->
|
|
||||||
PathItemCard(
|
|
||||||
path = map,
|
|
||||||
icon = Icons.Default.Security,
|
|
||||||
onDelete = { onRemoveSusMap(map) },
|
|
||||||
onEdit = if (onEditSusMap != null) { { onEditSusMap(map) } } else null,
|
|
||||||
isLoading = isLoading
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 16.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
onClick = onAddSusMap,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(48.dp),
|
|
||||||
shape = RoundedCornerShape(8.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Add,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(text = stringResource(R.string.add))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SUS挂载内容组件
|
* SUS挂载内容组件
|
||||||
*/
|
*/
|
||||||
|
|
@ -505,6 +395,7 @@ fun TryUmountContent(
|
||||||
umountForZygoteIsoService: Boolean,
|
umountForZygoteIsoService: Boolean,
|
||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
onAddUmount: () -> Unit,
|
onAddUmount: () -> Unit,
|
||||||
|
onRunUmount: () -> Unit,
|
||||||
onRemoveUmount: (String) -> Unit,
|
onRemoveUmount: (String) -> Unit,
|
||||||
onEditUmount: ((String) -> Unit)? = null,
|
onEditUmount: ((String) -> Unit)? = null,
|
||||||
onToggleUmountForZygoteIsoService: (Boolean) -> Unit
|
onToggleUmountForZygoteIsoService: (Boolean) -> Unit
|
||||||
|
|
@ -618,6 +509,24 @@ fun TryUmountContent(
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(text = stringResource(R.string.add))
|
Text(text = stringResource(R.string.add))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tryUmounts.isNotEmpty()) {
|
||||||
|
Button(
|
||||||
|
onClick = onRunUmount,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(48.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.PlayArrow,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(text = stringResource(R.string.susfs_run))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,250 +0,0 @@
|
||||||
package com.sukisu.ultra.ui.component
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
|
||||||
import androidx.compose.material.icons.filled.Check
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun SuperDropdown(
|
|
||||||
items: List<String>,
|
|
||||||
selectedIndex: Int,
|
|
||||||
title: String,
|
|
||||||
summary: String? = null,
|
|
||||||
icon: ImageVector? = null,
|
|
||||||
enabled: Boolean = true,
|
|
||||||
showValue: Boolean = true,
|
|
||||||
maxHeight: Dp? = 400.dp,
|
|
||||||
colors: SuperDropdownColors = SuperDropdownDefaults.colors(),
|
|
||||||
leftAction: (@Composable () -> Unit)? = null,
|
|
||||||
onSelectedIndexChange: (Int) -> Unit
|
|
||||||
) {
|
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
|
||||||
val selectedItemText = items.getOrNull(selectedIndex) ?: ""
|
|
||||||
val itemsNotEmpty = items.isNotEmpty()
|
|
||||||
val actualEnabled = enabled && itemsNotEmpty
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable(enabled = actualEnabled) { showDialog = true }
|
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.Top
|
|
||||||
) {
|
|
||||||
if (leftAction != null) {
|
|
||||||
leftAction()
|
|
||||||
} else if (icon != null) {
|
|
||||||
Icon(
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = if (actualEnabled) colors.iconColor else colors.disabledIconColor,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 16.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
color = if (actualEnabled) colors.titleColor else colors.disabledTitleColor
|
|
||||||
)
|
|
||||||
|
|
||||||
if (summary != null) {
|
|
||||||
Spacer(modifier = Modifier.height(3.dp))
|
|
||||||
Text(
|
|
||||||
text = summary,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = if (actualEnabled) colors.summaryColor else colors.disabledSummaryColor
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showValue && itemsNotEmpty) {
|
|
||||||
Spacer(modifier = Modifier.height(3.dp))
|
|
||||||
Text(
|
|
||||||
text = selectedItemText,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = if (actualEnabled) colors.valueColor else colors.disabledValueColor,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = if (actualEnabled) colors.arrowColor else colors.disabledArrowColor,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showDialog && itemsNotEmpty) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showDialog = false },
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.headlineSmall
|
|
||||||
)
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
val dialogMaxHeight = maxHeight ?: 400.dp
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(max = dialogMaxHeight),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
items(items.size) { index ->
|
|
||||||
DropdownItem(
|
|
||||||
text = items[index],
|
|
||||||
isSelected = selectedIndex == index,
|
|
||||||
colors = colors,
|
|
||||||
onClick = {
|
|
||||||
onSelectedIndexChange(index)
|
|
||||||
showDialog = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { showDialog = false }) {
|
|
||||||
Text(text = stringResource(id = android.R.string.cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
containerColor = colors.dialogBackgroundColor,
|
|
||||||
shape = MaterialTheme.shapes.extraLarge,
|
|
||||||
tonalElevation = 4.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun DropdownItem(
|
|
||||||
text: String,
|
|
||||||
isSelected: Boolean,
|
|
||||||
colors: SuperDropdownColors,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
val backgroundColor = if (isSelected) {
|
|
||||||
colors.selectedBackgroundColor
|
|
||||||
} else {
|
|
||||||
Color.Transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
val contentColor = if (isSelected) {
|
|
||||||
colors.selectedContentColor
|
|
||||||
} else {
|
|
||||||
colors.contentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
.background(backgroundColor)
|
|
||||||
.clickable(onClick = onClick)
|
|
||||||
.padding(vertical = 12.dp, horizontal = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
RadioButton(
|
|
||||||
selected = isSelected,
|
|
||||||
onClick = null,
|
|
||||||
colors = RadioButtonDefaults.colors(
|
|
||||||
selectedColor = colors.selectedContentColor,
|
|
||||||
unselectedColor = colors.contentColor
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
color = contentColor,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isSelected) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Check,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = colors.selectedContentColor,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
data class SuperDropdownColors(
|
|
||||||
val titleColor: Color,
|
|
||||||
val summaryColor: Color,
|
|
||||||
val valueColor: Color,
|
|
||||||
val iconColor: Color,
|
|
||||||
val arrowColor: Color,
|
|
||||||
val disabledTitleColor: Color,
|
|
||||||
val disabledSummaryColor: Color,
|
|
||||||
val disabledValueColor: Color,
|
|
||||||
val disabledIconColor: Color,
|
|
||||||
val disabledArrowColor: Color,
|
|
||||||
val dialogBackgroundColor: Color,
|
|
||||||
val contentColor: Color,
|
|
||||||
val selectedContentColor: Color,
|
|
||||||
val selectedBackgroundColor: Color
|
|
||||||
)
|
|
||||||
|
|
||||||
object SuperDropdownDefaults {
|
|
||||||
@Composable
|
|
||||||
fun colors(
|
|
||||||
titleColor: Color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
summaryColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
valueColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
iconColor: Color = MaterialTheme.colorScheme.primary,
|
|
||||||
arrowColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
disabledTitleColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
|
||||||
disabledSummaryColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
|
||||||
disabledValueColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
|
||||||
disabledIconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
|
||||||
disabledArrowColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
|
||||||
dialogBackgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
|
||||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
selectedContentColor: Color = MaterialTheme.colorScheme.primary,
|
|
||||||
selectedBackgroundColor: Color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
|
|
||||||
): SuperDropdownColors {
|
|
||||||
return SuperDropdownColors(
|
|
||||||
titleColor = titleColor,
|
|
||||||
summaryColor = summaryColor,
|
|
||||||
valueColor = valueColor,
|
|
||||||
iconColor = iconColor,
|
|
||||||
arrowColor = arrowColor,
|
|
||||||
disabledTitleColor = disabledTitleColor,
|
|
||||||
disabledSummaryColor = disabledSummaryColor,
|
|
||||||
disabledValueColor = disabledValueColor,
|
|
||||||
disabledIconColor = disabledIconColor,
|
|
||||||
disabledArrowColor = disabledArrowColor,
|
|
||||||
dialogBackgroundColor = dialogBackgroundColor,
|
|
||||||
contentColor = contentColor,
|
|
||||||
selectedContentColor = selectedContentColor,
|
|
||||||
selectedBackgroundColor = selectedBackgroundColor
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
|
|
||||||
|
// 菜单项数据类
|
||||||
data class FabMenuItem(
|
data class FabMenuItem(
|
||||||
val icon: ImageVector,
|
val icon: ImageVector,
|
||||||
val labelRes: Int,
|
val labelRes: Int,
|
||||||
|
|
@ -28,6 +29,7 @@ data class FabMenuItem(
|
||||||
val onClick: () -> Unit
|
val onClick: () -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 动画配置
|
||||||
object FabAnimationConfig {
|
object FabAnimationConfig {
|
||||||
const val ANIMATION_DURATION = 300
|
const val ANIMATION_DURATION = 300
|
||||||
const val STAGGER_DELAY = 50
|
const val STAGGER_DELAY = 50
|
||||||
|
|
@ -51,15 +53,23 @@ fun VerticalExpandableFab(
|
||||||
) {
|
) {
|
||||||
var isExpanded by remember { mutableStateOf(false) }
|
var isExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// 主按钮旋转动画
|
||||||
val rotationAngle by animateFloatAsState(
|
val rotationAngle by animateFloatAsState(
|
||||||
targetValue = if (isExpanded) 45f else 0f,
|
targetValue = if (isExpanded) 45f else 0f,
|
||||||
animationSpec = tween(animationDurationMs, easing = FastOutSlowInEasing),
|
animationSpec = tween(
|
||||||
|
durationMillis = animationDurationMs,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
),
|
||||||
label = "mainButtonRotation"
|
label = "mainButtonRotation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 主按钮缩放动画
|
||||||
val mainButtonScale by animateFloatAsState(
|
val mainButtonScale by animateFloatAsState(
|
||||||
targetValue = if (isExpanded) 1.1f else 1f,
|
targetValue = if (isExpanded) 1.1f else 1f,
|
||||||
animationSpec = tween(animationDurationMs, easing = FastOutSlowInEasing),
|
animationSpec = tween(
|
||||||
|
durationMillis = animationDurationMs,
|
||||||
|
easing = FastOutSlowInEasing
|
||||||
|
),
|
||||||
label = "mainButtonScale"
|
label = "mainButtonScale"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -67,9 +77,14 @@ fun VerticalExpandableFab(
|
||||||
modifier = modifier.wrapContentSize(),
|
modifier = modifier.wrapContentSize(),
|
||||||
contentAlignment = Alignment.BottomEnd
|
contentAlignment = Alignment.BottomEnd
|
||||||
) {
|
) {
|
||||||
|
// 子菜单按钮
|
||||||
menuItems.forEachIndexed { index, menuItem ->
|
menuItems.forEachIndexed { index, menuItem ->
|
||||||
val animatedOffsetY by animateFloatAsState(
|
val animatedOffsetY by animateFloatAsState(
|
||||||
targetValue = if (isExpanded) -(buttonSpacing.value * (index + 1)) else 0f,
|
targetValue = if (isExpanded) {
|
||||||
|
-(buttonSpacing.value * (index + 1))
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
},
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = animationDurationMs,
|
durationMillis = animationDurationMs,
|
||||||
delayMillis = if (isExpanded) {
|
delayMillis = if (isExpanded) {
|
||||||
|
|
@ -110,6 +125,7 @@ fun VerticalExpandableFab(
|
||||||
label = "fabAlpha$index"
|
label = "fabAlpha$index"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 子按钮容器(包含标签)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.offset(y = animatedOffsetY.dp)
|
.offset(y = animatedOffsetY.dp)
|
||||||
|
|
@ -118,6 +134,7 @@ fun VerticalExpandableFab(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.End
|
horizontalArrangement = Arrangement.End
|
||||||
) {
|
) {
|
||||||
|
// 标签
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = isExpanded && animatedScale > 0.5f,
|
visible = isExpanded && animatedScale > 0.5f,
|
||||||
enter = slideInHorizontally(
|
enter = slideInHorizontally(
|
||||||
|
|
@ -144,6 +161,7 @@ fun VerticalExpandableFab(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 子按钮
|
||||||
SmallFloatingActionButton(
|
SmallFloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
menuItem.onClick()
|
menuItem.onClick()
|
||||||
|
|
@ -175,12 +193,15 @@ fun VerticalExpandableFab(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 主按钮
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onMainButtonClick?.invoke()
|
onMainButtonClick?.invoke()
|
||||||
isExpanded = !isExpanded
|
isExpanded = !isExpanded
|
||||||
},
|
},
|
||||||
modifier = Modifier.size(buttonSize).scale(mainButtonScale),
|
modifier = Modifier
|
||||||
|
.size(buttonSize)
|
||||||
|
.scale(mainButtonScale),
|
||||||
elevation = FloatingActionButtonDefaults.elevation(
|
elevation = FloatingActionButtonDefaults.elevation(
|
||||||
defaultElevation = 6.dp,
|
defaultElevation = 6.dp,
|
||||||
pressedElevation = 8.dp,
|
pressedElevation = 8.dp,
|
||||||
|
|
@ -200,6 +221,7 @@ fun VerticalExpandableFab(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预设菜单项
|
||||||
object FabMenuPresets {
|
object FabMenuPresets {
|
||||||
fun getScrollMenuItems(
|
fun getScrollMenuItems(
|
||||||
onScrollToTop: () -> Unit,
|
onScrollToTop: () -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ fun TemplateConfig(
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
|
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
label = { Text(stringResource(R.string.profile_template)) },
|
label = { Text(stringResource(R.string.profile_template)) },
|
||||||
|
|
|
||||||
|
|
@ -248,12 +248,7 @@ private fun AppProfileInner(
|
||||||
ProfileBox(mode, true) {
|
ProfileBox(mode, true) {
|
||||||
// template mode shouldn't change profile here!
|
// template mode shouldn't change profile here!
|
||||||
if (it == Mode.Default || it == Mode.Custom) {
|
if (it == Mode.Default || it == Mode.Custom) {
|
||||||
onProfileChange(
|
onProfileChange(profile.copy(rootUseDefault = it == Mode.Default))
|
||||||
profile.copy(
|
|
||||||
rootUseDefault = it == Mode.Default,
|
|
||||||
rootTemplate = null
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
mode = it
|
mode = it
|
||||||
}
|
}
|
||||||
|
|
@ -484,10 +479,7 @@ private fun ProfileBox(
|
||||||
|
|
||||||
@SuppressLint("UnusedBoxWithConstraintsScope")
|
@SuppressLint("UnusedBoxWithConstraintsScope")
|
||||||
@Composable
|
@Composable
|
||||||
private fun AppMenuBox(
|
private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) {
|
||||||
packageName: String,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
var touchPoint: Offset by remember { mutableStateOf(Offset.Zero) }
|
var touchPoint: Offset by remember { mutableStateOf(Offset.Zero) }
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
@ -507,15 +499,15 @@ private fun AppMenuBox(
|
||||||
content()
|
content()
|
||||||
|
|
||||||
val (offsetX, offsetY) = with(density) {
|
val (offsetX, offsetY) = with(density) {
|
||||||
(touchPoint.x.toDp()) to (-touchPoint.y.toDp())
|
(touchPoint.x.toDp()) to (touchPoint.y.toDp())
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
offset = DpOffset(offsetX, offsetY),
|
offset = DpOffset(offsetX, -offsetY),
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
expanded = false
|
expanded = false
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
AppMenuOption(
|
AppMenuOption(
|
||||||
text = stringResource(id = R.string.launch_app),
|
text = stringResource(id = R.string.launch_app),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package com.sukisu.ultra.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
|
@ -32,7 +30,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||||
|
|
@ -51,9 +48,6 @@ import kotlinx.parcelize.Parcelize
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import androidx.core.content.edit
|
|
||||||
import com.sukisu.ultra.ui.util.module.ModuleOperationUtils
|
|
||||||
import com.sukisu.ultra.ui.util.module.ModuleUtils
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ShirkNeko
|
* @author ShirkNeko
|
||||||
|
|
@ -125,29 +119,6 @@ fun setModuleVerificationStatus(uri: Uri, isVerified: Boolean) {
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val shouldAutoExit = remember {
|
|
||||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
|
||||||
sharedPref.getBoolean("auto_exit_after_flash", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否通过从外部启动的模块安装
|
|
||||||
val isExternalInstall = remember {
|
|
||||||
when (flashIt) {
|
|
||||||
is FlashIt.FlashModule -> {
|
|
||||||
(context as? ComponentActivity)?.intent?.let { intent ->
|
|
||||||
intent.action == Intent.ACTION_VIEW || intent.action == Intent.ACTION_SEND
|
|
||||||
} ?: false
|
|
||||||
}
|
|
||||||
is FlashIt.FlashModules -> {
|
|
||||||
(context as? ComponentActivity)?.intent?.let { intent ->
|
|
||||||
intent.action == Intent.ACTION_VIEW || intent.action == Intent.ACTION_SEND
|
|
||||||
} ?: false
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var text by rememberSaveable { mutableStateOf("") }
|
var text by rememberSaveable { mutableStateOf("") }
|
||||||
var tempText: String
|
var tempText: String
|
||||||
val logContent = rememberSaveable { StringBuilder() }
|
val logContent = rememberSaveable { StringBuilder() }
|
||||||
|
|
@ -232,25 +203,8 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||||
if (showReboot) {
|
if (showReboot) {
|
||||||
text += "\n\n\n"
|
text += "\n\n\n"
|
||||||
showFloatAction = true
|
showFloatAction = true
|
||||||
|
|
||||||
// 如果是内部安装,显示重启按钮后不自动返回
|
|
||||||
if (isExternalInstall) {
|
|
||||||
return@flashModuleUpdate
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
hasUpdateCompleted = true
|
hasUpdateCompleted = true
|
||||||
|
|
||||||
// 如果是外部安装或需要自动退出的模块更新且不需要重启,延迟后自动返回
|
|
||||||
if (isExternalInstall || shouldAutoExit) {
|
|
||||||
scope.launch {
|
|
||||||
kotlinx.coroutines.delay(1000)
|
|
||||||
if (shouldAutoExit) {
|
|
||||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
|
||||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
|
||||||
}
|
|
||||||
(context as? ComponentActivity)?.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, onStdout = {
|
}, onStdout = {
|
||||||
tempText = "$it\n"
|
tempText = "$it\n"
|
||||||
if (tempText.startsWith("[H[J")) { // clear command
|
if (tempText.startsWith("[H[J")) { // clear command
|
||||||
|
|
@ -343,26 +297,6 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||||
kotlinx.coroutines.delay(500)
|
kotlinx.coroutines.delay(500)
|
||||||
navigator.navigate(FlashScreenDestination(nextFlashIt))
|
navigator.navigate(FlashScreenDestination(nextFlashIt))
|
||||||
}
|
}
|
||||||
} else if ((isExternalInstall || shouldAutoExit) && flashIt is FlashIt.FlashModules && flashIt.currentIndex >= flashIt.uris.size - 1) {
|
|
||||||
// 如果是外部安装或需要自动退出且是最后一个模块,安装完成后自动返回
|
|
||||||
scope.launch {
|
|
||||||
kotlinx.coroutines.delay(1000)
|
|
||||||
if (shouldAutoExit) {
|
|
||||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
|
||||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
|
||||||
}
|
|
||||||
(context as? ComponentActivity)?.finish()
|
|
||||||
}
|
|
||||||
} else if ((isExternalInstall || shouldAutoExit) && flashIt is FlashIt.FlashModule) {
|
|
||||||
// 如果是外部安装或需要自动退出的单个模块,安装完成后自动返回
|
|
||||||
scope.launch {
|
|
||||||
kotlinx.coroutines.delay(1000)
|
|
||||||
if (shouldAutoExit) {
|
|
||||||
val sharedPref = context.getSharedPreferences("kernel_flash_prefs", Context.MODE_PRIVATE)
|
|
||||||
sharedPref.edit { remove("auto_exit_after_flash") }
|
|
||||||
}
|
|
||||||
(context as? ComponentActivity)?.finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, onStdout = {
|
}, onStdout = {
|
||||||
tempText = "$it\n"
|
tempText = "$it\n"
|
||||||
|
|
@ -385,9 +319,6 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canGoBack) {
|
if (canGoBack) {
|
||||||
if (isExternalInstall) {
|
|
||||||
(context as? ComponentActivity)?.finish()
|
|
||||||
} else {
|
|
||||||
if (flashIt is FlashIt.FlashModules || flashIt is FlashIt.FlashModuleUpdate) {
|
if (flashIt is FlashIt.FlashModules || flashIt is FlashIt.FlashModuleUpdate) {
|
||||||
viewModel.markNeedRefresh()
|
viewModel.markNeedRefresh()
|
||||||
viewModel.fetchModuleList()
|
viewModel.fetchModuleList()
|
||||||
|
|
@ -399,7 +330,6 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(enabled = true) {
|
BackHandler(enabled = true) {
|
||||||
onBack()
|
onBack()
|
||||||
|
|
@ -689,7 +619,7 @@ private fun TopBar(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getModuleNameFromUri(context: Context, uri: Uri): String {
|
suspend fun getModuleNameFromUri(context: android.content.Context, uri: Uri): String {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
if (uri == Uri.EMPTY) {
|
if (uri == Uri.EMPTY) {
|
||||||
|
|
@ -707,7 +637,7 @@ suspend fun getModuleNameFromUri(context: Context, uri: Uri): String {
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
sealed class FlashIt : Parcelable {
|
sealed class FlashIt : Parcelable {
|
||||||
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean, val partition: String? = null) : FlashIt()
|
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : FlashIt()
|
||||||
data class FlashModule(val uri: Uri) : FlashIt()
|
data class FlashModule(val uri: Uri) : FlashIt()
|
||||||
data class FlashModules(val uris: List<Uri>, val currentIndex: Int = 0) : FlashIt()
|
data class FlashModules(val uris: List<Uri>, val currentIndex: Int = 0) : FlashIt()
|
||||||
data class FlashModuleUpdate(val uri: Uri) : FlashIt() // 模块更新
|
data class FlashModuleUpdate(val uri: Uri) : FlashIt() // 模块更新
|
||||||
|
|
@ -736,7 +666,6 @@ fun flashIt(
|
||||||
flashIt.boot,
|
flashIt.boot,
|
||||||
flashIt.lkm,
|
flashIt.lkm,
|
||||||
flashIt.ota,
|
flashIt.ota,
|
||||||
flashIt.partition,
|
|
||||||
onFinish,
|
onFinish,
|
||||||
onStdout,
|
onStdout,
|
||||||
onStderr
|
onStderr
|
||||||
|
|
|
||||||
|
|
@ -50,21 +50,20 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||||
import com.sukisu.ultra.ui.theme.getCardColors
|
import com.sukisu.ultra.ui.theme.getCardColors
|
||||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||||
import com.sukisu.ultra.ui.susfs.util.SuSFSManager
|
import com.sukisu.ultra.ui.util.SuSFSManager
|
||||||
import com.sukisu.ultra.ui.util.checkNewVersion
|
import com.sukisu.ultra.ui.util.checkNewVersion
|
||||||
import com.sukisu.ultra.ui.util.getSuSFSVersion
|
import com.sukisu.ultra.ui.util.getSuSFS
|
||||||
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||||
import com.sukisu.ultra.ui.util.reboot
|
import com.sukisu.ultra.ui.util.reboot
|
||||||
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ShirkNeko
|
* @author ShirkNeko
|
||||||
* @date 2025/9/29.
|
* @date 2025/5/31.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||||
@Destination<RootGraph>(start = true)
|
@Destination<RootGraph>(start = true)
|
||||||
|
|
@ -74,35 +73,16 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||||
val viewModel = viewModel<HomeViewModel>()
|
val viewModel = viewModel<HomeViewModel>()
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val pullRefreshState = rememberPullRefreshState(
|
|
||||||
refreshing = viewModel.isRefreshing,
|
|
||||||
onRefresh = {
|
|
||||||
viewModel.onPullRefresh(context)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = navigator) {
|
LaunchedEffect(key1 = navigator) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.refreshAllData(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
viewModel.loadUserSettings(context)
|
viewModel.loadUserSettings(context)
|
||||||
coroutineScope.launch {
|
viewModel.initializeData()
|
||||||
viewModel.loadCoreData()
|
viewModel.checkForUpdates(context)
|
||||||
delay(100)
|
|
||||||
viewModel.loadExtendedData(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动数据变化监听
|
|
||||||
coroutineScope.launch {
|
|
||||||
while (true) {
|
|
||||||
delay(5000) // 每5秒检查一次
|
|
||||||
viewModel.autoRefreshIfNeeded(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听数据刷新状态流
|
|
||||||
LaunchedEffect(viewModel.dataRefreshTrigger) {
|
|
||||||
viewModel.dataRefreshTrigger.collect { _ ->
|
|
||||||
// 数据刷新时的额外处理可以在这里添加
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
|
@ -112,14 +92,22 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
navigator = navigator,
|
navigator = navigator
|
||||||
isDataLoaded = viewModel.isCoreDataLoaded
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(
|
contentWindowInsets = WindowInsets.safeDrawing.only(
|
||||||
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
||||||
)
|
)
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
val pullRefreshState = rememberPullRefreshState(
|
||||||
|
refreshing = false,
|
||||||
|
onRefresh = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.refreshAllData(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
|
|
@ -133,16 +121,13 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||||
.padding(top = 12.dp, start = 16.dp, end = 16.dp),
|
.padding(top = 12.dp, start = 16.dp, end = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
// 状态卡片
|
|
||||||
if (viewModel.isCoreDataLoaded) {
|
|
||||||
StatusCard(
|
StatusCard(
|
||||||
systemStatus = viewModel.systemStatus,
|
systemStatus = viewModel.systemStatus,
|
||||||
onClickInstall = {
|
onClickInstall = {
|
||||||
navigator.navigate(InstallScreenDestination(preselectedKernelUri = null))
|
navigator.navigate(InstallScreenDestination)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 警告信息
|
|
||||||
if (viewModel.systemStatus.requireNewKernel) {
|
if (viewModel.systemStatus.requireNewKernel) {
|
||||||
WarningCard(
|
WarningCard(
|
||||||
stringResource(id = R.string.require_kernel_version).format(
|
stringResource(id = R.string.require_kernel_version).format(
|
||||||
|
|
@ -158,25 +143,12 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只有在没有其他警告信息时才显示不兼容内核警告
|
|
||||||
val shouldShowWarnings = viewModel.systemStatus.requireNewKernel ||
|
|
||||||
(viewModel.systemStatus.ksuVersion != null && !viewModel.systemStatus.isRootAvailable)
|
|
||||||
|
|
||||||
if (Natives.version <= Natives.MINIMAL_NEW_IOCTL_KERNEL && !shouldShowWarnings && viewModel.systemStatus.ksuVersion != null) {
|
|
||||||
IncompatibleKernelCard()
|
|
||||||
Spacer(Modifier.height(12.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新检查
|
|
||||||
if (viewModel.isExtendedDataLoaded) {
|
|
||||||
val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
.getBoolean("check_update", true)
|
.getBoolean("check_update", true)
|
||||||
if (checkUpdate) {
|
if (checkUpdate) {
|
||||||
UpdateCard()
|
UpdateCard()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 信息卡片
|
|
||||||
InfoCard(
|
InfoCard(
|
||||||
systemInfo = viewModel.systemInfo,
|
systemInfo = viewModel.systemInfo,
|
||||||
isSimpleMode = viewModel.isSimpleMode,
|
isSimpleMode = viewModel.isSimpleMode,
|
||||||
|
|
@ -186,25 +158,13 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||||
lkmMode = viewModel.systemStatus.lkmMode,
|
lkmMode = viewModel.systemStatus.lkmMode,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 链接卡片
|
if (!viewModel.isSimpleMode) {
|
||||||
if (!viewModel.isSimpleMode && !viewModel.isHideLinkCard) {
|
if (!viewModel.isHideLinkCard) {
|
||||||
ContributionCard()
|
ContributionCard()
|
||||||
DonateCard()
|
DonateCard()
|
||||||
LearnMoreCard()
|
LearnMoreCard()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!viewModel.isExtendedDataLoaded) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(24.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
CircularProgressIndicator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -271,8 +231,7 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(
|
private fun TopBar(
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
navigator: DestinationsNavigator,
|
navigator: DestinationsNavigator
|
||||||
isDataLoaded: Boolean = false
|
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
|
@ -294,10 +253,8 @@ private fun TopBar(
|
||||||
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||||
),
|
),
|
||||||
actions = {
|
actions = {
|
||||||
if (isDataLoaded) {
|
|
||||||
// SuSFS 配置按钮
|
// SuSFS 配置按钮
|
||||||
val susfsVersion = getSuSFSVersion()
|
if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) {
|
||||||
if (susfsVersion.isNotEmpty() && !susfsVersion.startsWith("[-]") && SuSFSManager.isBinaryAvailable(context)) {
|
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
navigator.navigate(SuSFSConfigScreenDestination)
|
navigator.navigate(SuSFSConfigScreenDestination)
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -337,7 +294,6 @@ private fun TopBar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
|
|
@ -785,6 +741,7 @@ private fun InfoCard(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSimpleMode) {
|
if (!isSimpleMode) {
|
||||||
|
// 根据showKpmInfo决定是否显示KPM信息
|
||||||
if (lkmMode != true && !showKpmInfo) {
|
if (lkmMode != true && !showKpmInfo) {
|
||||||
val displayVersion =
|
val displayVersion =
|
||||||
if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) {
|
if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) {
|
||||||
|
|
@ -828,13 +785,19 @@ private fun InfoCard(
|
||||||
private fun SuSFSInfoText(systemInfo: HomeViewModel.SystemInfo): String = buildString {
|
private fun SuSFSInfoText(systemInfo: HomeViewModel.SystemInfo): String = buildString {
|
||||||
append(systemInfo.suSFSVersion)
|
append(systemInfo.suSFSVersion)
|
||||||
|
|
||||||
|
val isSUS_SU = systemInfo.suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU"
|
||||||
|
val isKprobesHook = Natives.getHookType() == "Kprobes"
|
||||||
|
|
||||||
when {
|
when {
|
||||||
Natives.getHookType() == "Manual" -> {
|
isSUS_SU && isKprobesHook -> {
|
||||||
append(" (${stringResource(R.string.manual_hook)})")
|
append(" (${systemInfo.suSFSVariant})")
|
||||||
|
if (systemInfo.susSUMode.isNotEmpty()) {
|
||||||
|
append(" ${stringResource(R.string.sus_su_mode)} ${systemInfo.susSUMode}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Natives.getHookType() == "Inline" -> {
|
Natives.getHookType() == "Manual" -> {
|
||||||
append(" (${stringResource(R.string.inline_hook)})")
|
append(" (${stringResource(R.string.manual_hook)})")
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
|
@ -866,7 +829,7 @@ private fun StatusCardPreview() {
|
||||||
StatusCard(
|
StatusCard(
|
||||||
HomeViewModel.SystemStatus(
|
HomeViewModel.SystemStatus(
|
||||||
isManager = true,
|
isManager = true,
|
||||||
ksuVersion = 40000,
|
ksuVersion = 20000,
|
||||||
lkmMode = true,
|
lkmMode = true,
|
||||||
kernelVersion = KernelVersion(5, 10, 101),
|
kernelVersion = KernelVersion(5, 10, 101),
|
||||||
isRootAvailable = true
|
isRootAvailable = true
|
||||||
|
|
@ -895,23 +858,6 @@ private fun StatusCardPreview() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun IncompatibleKernelCard() {
|
|
||||||
val currentKver = remember { Natives.version }
|
|
||||||
val threshold = Natives.MINIMAL_NEW_IOCTL_KERNEL
|
|
||||||
|
|
||||||
val msg = stringResource(
|
|
||||||
id = R.string.incompatible_kernel_msg,
|
|
||||||
currentKver,
|
|
||||||
threshold
|
|
||||||
)
|
|
||||||
|
|
||||||
WarningCard(
|
|
||||||
message = msg,
|
|
||||||
color = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun WarningCardPreview() {
|
private fun WarningCardPreview() {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue