Compare commits

..

2 commits

Author SHA1 Message Date
3f2388004d Updated main branch 2025-11-20 21:30:08 +01:00
9a3987bc1e Updated main branch 2025-11-20 21:28:06 +01:00
260 changed files with 25104 additions and 14134 deletions

View file

@ -1,2 +0,0 @@
# sukisu

View file

@ -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_sucompat_hook((int *)AT_FDCWD, &filename, NULL, NULL, NULL); /* 32-bit su */ + trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0); // 32-bit su and 32-on-64 support
+#endif +#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0); return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
} }
@ -237,34 +237,3 @@ 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);
```

View file

@ -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_sucompat_hook((int *)AT_FDCWD, &filename, NULL, NULL, NULL); /* 32-bit su */ + trace_ksu_trace_execveat_hook((int *)AT_FDCWD, &filename, &argv, &envp, 0)); // 32-bit su and 32-on-64 support
+#endif +#endif
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0); return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
} }
@ -237,34 +237,3 @@
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);
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

View file

@ -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: 8 ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 8 ContinuationIndentWidth: 4
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: 8 IndentWidth: 4
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: 8 ObjCBlockIndentWidth: 4
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: 8 TabWidth: 4
UseTab: Always UseTab: Never
... ...

22
kernel/.gitignore vendored Normal file
View file

@ -0,0 +1,22 @@
.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 Normal file
View file

@ -0,0 +1,11 @@
{
"configurations": [
{
"name": "Linux",
"cStandard": "c11",
"intelliSenseMode": "gcc-arm64",
"compileCommands": "${workspaceFolder}/compile_commands.json"
}
],
"version": 4
}

92
kernel/.vscode/generate_compdb.py vendored Normal file
View file

@ -0,0 +1,92 @@
#!/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 Normal file
View file

@ -0,0 +1,16 @@
{
// 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": []
}
]
}

View file

@ -2,7 +2,6 @@ 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.
@ -16,6 +15,13 @@ 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
@ -27,31 +33,10 @@ 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

View file

@ -1,17 +1,28 @@
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 += core_hook.o kernelsu-objs += pkg_observer.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 += kernel_compat.o kernelsu-objs += seccomp_cache.o
kernelsu-objs += file_wrapper.o
kernelsu-objs += throne_comm.o kernelsu-objs += throne_comm.o
kernelsu-objs += sulog.o
ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y) ifeq ($(CONFIG_KSU_MANUAL_SU), y)
kernelsu-objs += ksu_trace.o ccflags-y += -DCONFIG_KSU_MANUAL_SU
kernelsu-objs += manual_su.o
endif endif
kernelsu-objs += selinux/selinux.o kernelsu-objs += selinux/selinux.o
@ -21,46 +32,62 @@ 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 := 3.2.0 KSU_VERSION_API := 4.0.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')
LOCAL_GIT_EXISTS := $(shell test -e $(srctree)/$(src)/../.git && echo 1 || echo 0) ifeq ($(findstring $(srctree),$(src)),$(srctree))
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 $(srctree)/$(src); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(srctree)/$(src); $(GIT_BIN) rev-parse --abbrev-ref HEAD) v$1-$(shell cd $(KSU_SRC); $(GIT_BIN) rev-parse --short=8 HEAD)@$(shell cd $(KSU_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 $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow) $(shell cd $(KSU_SRC); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow)
KSU_LOCAL_VERSION := $(shell cd $(srctree)/$(src); $(GIT_BIN) rev-list --count $(REPO_BRANCH)) KSU_LOCAL_VERSION := $(shell cd $(KSU_SRC); $(GIT_BIN) rev-list --count $(REPO_BRANCH))
KSU_VERSION := $(shell expr 10000 + $(KSU_LOCAL_VERSION) + 700) KSU_VERSION := $(shell expr 40000 + $(KSU_LOCAL_VERSION) - 2815)
$(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 10000 + $(KSU_GITHUB_VERSION_COMMIT) + 700) KSU_VERSION := $(shell expr 40000 + $(KSU_GITHUB_VERSION_COMMIT) - 2815)
$(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 $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow) $(shell cd $(KSU_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))
@ -69,7 +96,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 $(srctree)/$(src); [ -f ../.git/shallow ] && $(GIT_BIN) fetch --unshallow) $(shell cd $(KSU_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
@ -93,14 +120,13 @@ 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
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM)) ifeq ($(CONFIG_KSU_MANUAL_HOOK), y)
ccflags-y += -DKSU_MANUAL_HOOK
ifeq ($(CONFIG_KSU_KPROBES_HOOK), y) $(info -- SukiSU: KSU_MANUAL_HOOK Temporarily discontinued)
$(info -- SukiSU: CONFIG_KSU_KPROBES_HOOK) else
else ifeq ($(CONFIG_KSU_TRACEPOINT_HOOK), y) ccflags-y += -DKSU_KPROBES_HOOK
$(info -- SukiSU: CONFIG_KSU_TRACEPOINT_HOOK) ccflags-y += -DKSU_TP_HOOK
else ifeq ($(CONFIG_KSU_MANUAL_HOOK), y) $(info -- SukiSU: KSU_TRACEPOINT_HOOK)
$(info -- SukiSU: CONFIG_KSU_MANUAL_HOOK)
endif endif
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL) KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
@ -117,14 +143,30 @@ 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
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat # Check new vfs_getattr()
ccflags-y += -Wno-declaration-after-statement -Wno-unused-function ifeq ($(shell grep -A1 "^int vfs_getattr" $(srctree)/fs/stat.c | grep -q "query_flags" ; echo $$?),0)
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

View file

@ -1,3 +1,5 @@
#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>
@ -8,14 +10,16 @@
#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
@ -29,7 +33,10 @@ 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;
static int allow_list_arr[PAGE_SIZE / sizeof(int)] __read_mostly __aligned(PAGE_SIZE); void persistent_allow_list(void);
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)
@ -40,7 +47,7 @@ static void remove_uid_from_arr(uid_t uid)
if (allow_list_pointer == 0) if (allow_list_pointer == 0)
return; return;
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL); temp_arr = kzalloc(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;
@ -61,7 +68,7 @@ static void remove_uid_from_arr(uid_t uid)
kfree(temp_arr); kfree(temp_arr);
} }
static void init_default_profiles() static void init_default_profiles(void)
{ {
kernel_cap_t full_cap = CAP_FULL_SET; kernel_cap_t full_cap = CAP_FULL_SET;
@ -90,11 +97,6 @@ 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;
@ -108,7 +110,7 @@ void ksu_show_allow_list(void)
} }
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
static void ksu_grant_root_to_shell() static void ksu_grant_root_to_shell(void)
{ {
struct app_profile profile = { struct app_profile profile = {
.version = KSU_APP_PROFILE_VER, .version = KSU_APP_PROFILE_VER,
@ -116,7 +118,8 @@ static void ksu_grant_root_to_shell()
.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, KSU_DEFAULT_SELINUX_DOMAIN); strcpy(profile.rp_config.profile.selinux_domain,
KSU_DEFAULT_SELINUX_DOMAIN);
ksu_set_app_profile(&profile, false); ksu_set_app_profile(&profile, false);
} }
#endif #endif
@ -142,9 +145,10 @@ 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 SYSTEM_UID 1000 #define SHELL_UID 2000
#define SYSTEM_UID 1000
return uid < SHELL_UID && uid != SYSTEM_UID; return uid < SHELL_UID && uid != SYSTEM_UID;
} }
@ -196,7 +200,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 *)kmalloc(sizeof(struct perm_data), GFP_KERNEL); p = (struct perm_data *)kzalloc(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;
@ -218,9 +222,11 @@ 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] |= 1 << (profile->current_uid % BITS_PER_BYTE); allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |=
1 << (profile->current_uid % BITS_PER_BYTE);
else else
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE)); allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &=
~(1 << (profile->current_uid % BITS_PER_BYTE));
} else { } else {
if (profile->allow_su) { if (profile->allow_su) {
/* /*
@ -252,8 +258,11 @@ 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;
} }
@ -262,23 +271,20 @@ 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()) && unlikely(ksu_get_manager_uid() == uid)) { if (likely(ksu_is_manager_uid_valid()) &&
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] & (1 << (uid % BITS_PER_BYTE))); return !!(allow_list_bitmap[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)
@ -289,10 +295,20 @@ 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()) && unlikely(ksu_get_manager_uid() == uid)) { if (likely(ksu_is_manager_uid_valid()) &&
unlikely(ksu_get_manager_uid() == uid)) {
// we should not umount on manager! // we should not umount on manager!
return false; return false;
} }
@ -349,7 +365,7 @@ bool ksu_get_allow_list(int *array, int *length, bool allow)
return true; return true;
} }
void do_save_allow_list(struct work_struct *work) static void do_persistent_allow_list(struct callback_head *_cb)
{ {
u32 magic = FILE_MAGIC; u32 magic = FILE_MAGIC;
u32 version = FILE_FORMAT_VERSION; u32 version = FILE_FORMAT_VERSION;
@ -357,41 +373,64 @@ void do_save_allow_list(struct work_struct *work)
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 =
ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT | O_TRUNC, 0644); filp_open(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));
return; goto unlock;
} }
// store magic and version // store magic and version
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != if (kernel_write(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
sizeof(magic)) {
pr_err("save_allow_list write magic failed.\n"); pr_err("save_allow_list write magic failed.\n");
goto exit; goto close_file;
} }
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != if (kernel_write(fp, &version, sizeof(version), &off) != sizeof(version)) {
sizeof(version)) {
pr_err("save_allow_list write version failed.\n"); pr_err("save_allow_list write version failed.\n");
goto exit; goto close_file;
} }
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.key, p->profile.current_uid, p->profile.allow_su);
p->profile.allow_su);
ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile), kernel_write(fp, &p->profile, sizeof(p->profile), &off);
&off);
} }
exit: close_file:
filp_close(fp, 0); filp_close(fp, 0);
unlock:
mutex_unlock(&allowlist_mutex);
kfree(_cb);
} }
void do_load_allow_list(struct work_struct *work) void persistent_allow_list()
{
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;
@ -405,22 +444,20 @@ void do_load_allow_list(struct work_struct *work)
#endif #endif
// load allowlist now! // load allowlist now!
fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0); fp = filp_open(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 (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != if (kernel_read(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
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 (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != if (kernel_read(fp, &version, sizeof(version), &off) != sizeof(version)) {
sizeof(version)) {
pr_err("allowlist read version: %d failed\n", version); pr_err("allowlist read version: %d failed\n", version);
goto exit; goto exit;
} }
@ -430,16 +467,15 @@ void do_load_allow_list(struct work_struct *work)
while (true) { while (true) {
struct app_profile profile; struct app_profile profile;
ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile), ret = kernel_read(fp, &profile, sizeof(profile), &off);
&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", pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n", profile.key,
profile.key, profile.current_uid, profile.allow_su); profile.current_uid, profile.allow_su);
ksu_set_app_profile(&profile, false); ksu_set_app_profile(&profile, false);
} }
@ -448,11 +484,17 @@ exit:
filp_close(fp, 0); filp_close(fp, 0);
} }
void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data) void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *),
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);
@ -466,7 +508,8 @@ void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data
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] &= ~(1 << (uid % BITS_PER_BYTE)); allow_list_bitmap[uid / BITS_PER_BYTE] &=
~(1 << (uid % BITS_PER_BYTE));
} }
remove_uid_from_arr(uid); remove_uid_from_arr(uid);
smp_mb(); smp_mb();
@ -480,17 +523,6 @@ void ksu_prune_allowlist(bool (*is_uid_valid)(uid_t, char *, void *), void *data
} }
} }
// 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;
@ -503,9 +535,6 @@ 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();
} }
@ -514,8 +543,6 @@ 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) {
@ -524,3 +551,81 @@ 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

View file

@ -2,19 +2,29 @@
#define __KSU_H_ALLOWLIST #define __KSU_H_ALLOWLIST
#include <linux/types.h> #include <linux/types.h>
#include "ksu.h" #include <linux/uidgid.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);
bool ksu_load_allow_list(void); void 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);
@ -24,4 +34,16 @@ 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

View file

@ -17,7 +17,6 @@
#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 {
@ -25,10 +24,7 @@ struct sdesc {
char ctx[]; char ctx[];
}; };
static struct apk_sign_key { static apk_sign_key_t apk_sign_keys[] = {
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
@ -41,7 +37,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 = kmalloc(size, GFP_KERNEL); sdesc = kzalloc(size, GFP_KERNEL);
if (!sdesc) if (!sdesc)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
sdesc->shash.tfm = alg; sdesc->shash.tfm = alg;
@ -105,7 +101,7 @@ static bool check_dynamic_sign(struct file *fp, u32 size4, loff_t *pos, int *mat
return false; return false;
} }
ksu_kernel_read_compat(fp, cert, size4, pos); kernel_read(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) {
@ -132,22 +128,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;
struct apk_sign_key sign_key; apk_sign_key_t sign_key;
bool signature_valid = false; bool signature_valid = false;
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length kernel_read(fp, size4, 0x4, pos); // signer-sequence length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length kernel_read(fp, size4, 0x4, pos); // signer length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length kernel_read(fp, size4, 0x4, pos); // signed data length
*offset += 0x4 * 3; *offset += 0x4 * 3;
ksu_kernel_read_compat(fp, size4, 0x4, pos); // digests-sequence length kernel_read(fp, size4, 0x4, pos); // digests-sequence length
*pos += *size4; *pos += *size4;
*offset += 0x4 + *size4; *offset += 0x4 + *size4;
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificates length kernel_read(fp, size4, 0x4, pos); // certificates length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length kernel_read(fp, size4, 0x4, pos); // certificate length
*offset += 0x4 * 2; *offset += 0x4 * 2;
if (ksu_is_dynamic_manager_enabled()) { if (ksu_is_dynamic_manager_enabled()) {
@ -172,9 +168,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;
} }
ksu_kernel_read_compat(fp, cert, *size4, pos); kernel_read(fp, cert, *size4, pos);
unsigned char digest[SHA256_DIGEST_SIZE]; unsigned char digest[SHA256_DIGEST_SIZE];
if (IS_ERR(ksu_sha256(cert, *size4, digest))) { if (ksu_sha256(cert, *size4, digest) < 0 ) {
pr_info("sha256 error\n"); pr_info("sha256 error\n");
return false; return false;
} }
@ -218,7 +214,7 @@ static bool has_v1_signature_file(struct file *fp)
loff_t pos = 0; loff_t pos = 0;
while (ksu_kernel_read_compat(fp, &header, while (kernel_read(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) {
@ -228,12 +224,13 @@ 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)];
ksu_kernel_read_compat(fp, fileName, kernel_read(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) == 0) { if (strncmp(MANIFEST, fileName, sizeof(MANIFEST) - 1) ==
0) {
return true; return true;
} }
} else { } else {
@ -253,14 +250,16 @@ 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 = ksu_filp_open_compat(path, O_RDONLY, 0); struct file *fp = filp_open(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;
@ -279,10 +278,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);
ksu_kernel_read_compat(fp, &n, 2, &pos); kernel_read(fp, &n, 2, &pos);
if (n == i) { if (n == i) {
pos -= 22; pos -= 22;
ksu_kernel_read_compat(fp, &size4, 4, &pos); kernel_read(fp, &size4, 4, &pos);
if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) { if ((size4 ^ 0xcafebabeu) == 0xccfbf1eeu) {
break; break;
} }
@ -295,17 +294,17 @@ static __always_inline bool check_v2_signature(char *path, bool check_multi_mana
pos += 12; pos += 12;
// offset // offset
ksu_kernel_read_compat(fp, &size4, 0x4, &pos); kernel_read(fp, &size4, 0x4, &pos);
pos = size4 - 0x18; pos = size4 - 0x18;
ksu_kernel_read_compat(fp, &size8, 0x8, &pos); kernel_read(fp, &size8, 0x8, &pos);
ksu_kernel_read_compat(fp, buffer, 0x10, &pos); kernel_read(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);
ksu_kernel_read_compat(fp, &size_of_block, 0x8, &pos); kernel_read(fp, &size_of_block, 0x8, &pos);
if (size_of_block != size8) { if (size_of_block != size8) {
goto clean; goto clean;
} }
@ -314,12 +313,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;
ksu_kernel_read_compat(fp, &size8, 0x8, kernel_read(fp, &size8, 0x8,
&pos); // sequence length &pos); // sequence length
if (size8 == size_of_block) { if (size8 == size_of_block) {
break; break;
} }
ksu_kernel_read_compat(fp, &id, 0x4, &pos); // id kernel_read(fp, &id, 0x4, &pos); // id
offset = 4; offset = 4;
if (id == 0x7109871au) { if (id == 0x7109871au) {
v2_signing_blocks++; v2_signing_blocks++;

303
kernel/app_profile.c Normal file
View file

@ -0,0 +1,303 @@
#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(&current->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(&current->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(&current->sighand->siglock);
disable_seccomp();
spin_unlock_irq(&current->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

70
kernel/app_profile.h Normal file
View file

@ -0,0 +1,70 @@
#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

View file

@ -18,10 +18,8 @@
#define __PT_SP_REG sp #define __PT_SP_REG sp
#define __PT_IP_REG pc #define __PT_IP_REG pc
#define PRCTL_SYMBOL "__arm64_sys_prctl" #define REBOOT_SYMBOL "__arm64_sys_reboot"
#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__)
@ -39,10 +37,8 @@
#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 PRCTL_SYMBOL "__x64_sys_prctl" #define REBOOT_SYMBOL "__x64_sys_reboot"
#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

File diff suppressed because it is too large Load diff

View file

@ -1,10 +0,0 @@
#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

View file

@ -17,7 +17,6 @@
#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
@ -233,23 +232,23 @@ static void do_save_dynamic_manager(struct work_struct *work)
return; return;
} }
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644); fp = filp_open(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 (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) { if (kernel_write(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 (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) { if (kernel_write(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 (ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) { if (kernel_write(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;
} }
@ -271,7 +270,7 @@ static void do_load_dynamic_manager(struct work_struct *work)
unsigned long flags; unsigned long flags;
int i; int i;
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_RDONLY, 0); fp = filp_open(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");
@ -281,20 +280,20 @@ static void do_load_dynamic_manager(struct work_struct *work)
return; return;
} }
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) || if (kernel_read(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 (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) { if (kernel_read(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 = ksu_kernel_read_compat(fp, &loaded_config, sizeof(loaded_config), &off); ret = kernel_read(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;
@ -348,14 +347,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 = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_MANAGER, O_WRONLY | O_CREAT | O_TRUNC, 0644); fp = filp_open(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 (ksu_kernel_write_compat(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) { if (kernel_write(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 Normal file
View file

@ -0,0 +1,173 @@
#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");
}

37
kernel/feature.h Normal file
View file

@ -0,0 +1,37 @@
#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

341
kernel/file_wrapper.c Normal file
View file

@ -0,0 +1,341 @@
#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);
}

14
kernel/file_wrapper.h Normal file
View file

@ -0,0 +1,14 @@
#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

View file

@ -1,28 +0,0 @@
#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

View file

@ -1,94 +0,0 @@
#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);
}

View file

@ -3,63 +3,7 @@
#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

175
kernel/kernel_umount.c Normal file
View file

@ -0,0 +1,175 @@
#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);
}

25
kernel/kernel_umount.h Normal file
View file

@ -0,0 +1,25 @@
#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

View file

@ -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(uid) ? 1 : 0; return ksu_is_allow_uid_for_current(uid) ? 1 : 0;
} }
static int sukisu_get_ap_mod_exclude(uid_t uid) static int sukisu_get_ap_mod_exclude(uid_t uid)

View file

@ -9,13 +9,10 @@
* 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>
@ -25,26 +22,25 @@
#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")))
@ -56,156 +52,231 @@
#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, void __user *result) const char *args, void *ptr, int *result)
{ {
int res = -1; pr_info("kpm: Stub function called (sukisu_kpm_load_module_path). "
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, void __user *result) void *ptr, int *result)
{ {
int res = -1; pr_info("kpm: Stub function called (sukisu_kpm_unload_module). "
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(void __user *result) noinline NO_OPTIMIZE void sukisu_kpm_num(int *result)
{ {
int res = 0; pr_info("kpm: Stub function called (sukisu_kpm_num).\n");
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, void __user *out, noinline NO_OPTIMIZE void sukisu_kpm_info(const char *name, char *buf, int bufferSize,
void __user *result) int *size)
{ {
int res = -1; pr_info("kpm: Stub function called (sukisu_kpm_info). "
"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 __user *out, unsigned int bufferSize, noinline NO_OPTIMIZE void sukisu_kpm_list(void *out, int bufferSize,
void __user *result) int *result)
{ {
int res = -1; pr_info("kpm: Stub function called (sukisu_kpm_list). "
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(void __user *name, void __user *args, noinline NO_OPTIMIZE void sukisu_kpm_control(const char *name, const char *args, long arg_len,
void __user *result) int *result)
{ {
int res = -1; pr_info("kpm: Stub function called (sukisu_kpm_control). "
"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(void __user *out, unsigned int bufferSize, noinline NO_OPTIMIZE void sukisu_kpm_version(char *buf, int bufferSize)
void __user *result)
{ {
int res = -1; pr_info("kpm: Stub function called (sukisu_kpm_version). "
"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 arg2, unsigned long arg3, unsigned long arg4, noinline int sukisu_handle_kpm(unsigned long control_code, unsigned long arg1, unsigned long arg2,
unsigned long arg5) unsigned long result_code)
{ {
if (arg2 == SUKISU_KPM_LOAD) { int res = -1;
char kernel_load_path[256] = { 0 }; if (control_code == SUKISU_KPM_LOAD) {
char kernel_args_buffer[256] = { 0 }; char kernel_load_path[256];
char kernel_args_buffer[256];
if (arg3 == 0) if (arg1 == 0) {
return -1; res = -EINVAL;
goto exit;
strncpy_from_user((char *)&kernel_load_path, (const char __user *)arg3, 255);
if (arg4 != 0)
strncpy_from_user((char *)&kernel_args_buffer, (const char __user *)arg4, 255);
sukisu_kpm_load_module_path((const char *)&kernel_load_path,
(const char *)&kernel_args_buffer, NULL, (void __user *)arg5);
} else if (arg2 == SUKISU_KPM_UNLOAD) {
char kernel_name_buffer[256] = { 0 };
if (arg3 == 0)
return -1;
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, 255)) {
goto invalid_arg;
}
strncpy_from_user((char *)&kernel_load_path, (const char *)arg1, 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,
(const char *)&kernel_args_buffer, NULL, &res);
} else if (control_code == SUKISU_KPM_UNLOAD) {
char kernel_name_buffer[256];
if (arg1 == 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 *)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 arg2) { int sukisu_is_kpm_control_code(unsigned long control_code) {
return (arg2 >= CMD_KPM_CONTROL && return (control_code >= CMD_KPM_CONTROL &&
arg2 <= CMD_KPM_CONTROL_MAX) ? 1 : 0; control_code <= 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);
}

View file

@ -1,58 +1,70 @@
#ifndef __SUKISU_KPM_H #ifndef __SUKISU_KPM_H
#define __SUKISU_KPM_H #define __SUKISU_KPM_H
extern int sukisu_handle_kpm(unsigned long arg2, unsigned long arg3, unsigned long arg4, #include <linux/types.h>
unsigned long arg5); #include <linux/ioctl.h>
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 28 #define CMD_KPM_CONTROL 1
#define CMD_KPM_CONTROL_MAX 35 #define CMD_KPM_CONTROL_MAX 10
/* Control Code */ /* Control Code */
/* /*
* prctl(xxx, 28, "PATH", "ARGS") * prctl(xxx, 1, "PATH", "ARGS")
* success return 0, error return -N * success return 0, error return -N
*/ */
#define SUKISU_KPM_LOAD 28 #define SUKISU_KPM_LOAD 1
/* /*
* prctl(xxx, 29, "NAME") * prctl(xxx, 2, "NAME")
* success return 0, error return -N * success return 0, error return -N
*/ */
#define SUKISU_KPM_UNLOAD 29 #define SUKISU_KPM_UNLOAD 2
/* /*
* num = prctl(xxx, 30) * num = prctl(xxx, 3)
* error return -N * error return -N
* success return +num or 0 * success return +num or 0
*/ */
#define SUKISU_KPM_NUM 30 #define SUKISU_KPM_NUM 3
/* /*
* prctl(xxx, 31, Buffer, BufferSize) * prctl(xxx, 4, Buffer, BufferSize)
* success return +out, error return -N * success return +out, error return -N
*/ */
#define SUKISU_KPM_LIST 31 #define SUKISU_KPM_LIST 4
/* /*
* prctl(xxx, 32, "NAME", Buffer[256]) * prctl(xxx, 5, "NAME", Buffer[256])
* success return +out, error return -N * success return +out, error return -N
*/ */
#define SUKISU_KPM_INFO 32 #define SUKISU_KPM_INFO 5
/* /*
* prctl(xxx, 33, "NAME", "ARGS") * prctl(xxx, 6, "NAME", "ARGS")
* success return KPM's result value * success return KPM's result value
* error return -N * error return -N
*/ */
#define SUKISU_KPM_CONTROL 33 #define SUKISU_KPM_CONTROL 6
/* /*
* prctl(xxx, 34, buffer, bufferSize) * prctl(xxx, 7, buffer, bufferSize)
* success return KPM's result value * success return KPM's result value
* error return -N * error return -N
*/ */
#define SUKISU_KPM_VERSION 34 #define SUKISU_KPM_VERSION 7
#endif #endif

View file

@ -3,13 +3,19 @@
#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 "arch.h" #include "feature.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;
@ -18,17 +24,19 @@ bool ksu_queue_work(struct work_struct *work)
return queue_work(ksu_workqueue, work); return queue_work(ksu_workqueue, work);
} }
extern int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr, void sukisu_custom_config_init(void)
void *argv, void *envp, int *flags); {
}
extern void ksu_sucompat_init(); void sukisu_custom_config_exit(void)
extern void ksu_sucompat_exit(); {
extern void ksu_ksud_init(); ksu_uid_exit();
extern void ksu_ksud_exit(); ksu_throne_comm_exit();
#ifdef CONFIG_KSU_TRACEPOINT_HOOK ksu_dynamic_manager_exit();
extern void ksu_trace_register(); #if __SULOG_GATE
extern void ksu_trace_unregister(); ksu_sulog_exit();
#endif #endif
}
int __init kernelsu_init(void) int __init kernelsu_init(void)
{ {
@ -42,24 +50,26 @@ int __init kernelsu_init(void)
pr_alert("*************************************************************"); pr_alert("*************************************************************");
#endif #endif
ksu_core_init(); ksu_feature_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
ksu_sucompat_init(); #ifdef KSU_KPROBES_HOOK
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);
@ -68,24 +78,28 @@ 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 CONFIG_KSU_KPROBES_HOOK #ifdef KSU_KPROBES_HOOK
ksu_ksud_exit(); ksu_ksud_exit();
ksu_sucompat_exit();
#endif #endif
#ifdef CONFIG_KSU_TRACEPOINT_HOOK ksu_syscall_hook_manager_exit();
ksu_trace_unregister();
#endif
ksu_core_exit(); sukisu_custom_config_exit();
ksu_supercalls_exit();
ksu_feature_exit();
} }
module_init(kernelsu_init); module_init(kernelsu_init);
@ -94,4 +108,9 @@ 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

View file

@ -7,40 +7,12 @@
#define KERNEL_SU_VERSION KSU_VERSION #define KERNEL_SU_VERSION KSU_VERSION
#define KERNEL_SU_OPTION 0xDEADBEEF #define KERNEL_SU_OPTION 0xDEADBEEF
#define CMD_GRANT_ROOT 0 extern bool ksu_uid_scanner_enabled;
#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"
@ -51,6 +23,10 @@
#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;
@ -65,56 +41,9 @@ 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));
@ -128,5 +57,6 @@ 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

View file

@ -1,69 +0,0 @@
#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);
}

View file

@ -1,37 +0,0 @@
#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>

View file

@ -1,8 +0,0 @@
#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);

View file

@ -1,3 +1,6 @@
#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>
@ -11,15 +14,19 @@
#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"
@ -48,17 +55,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 CONFIG_KSU_KPROBES_HOOK #ifdef 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_devpts_sid; u32 ksu_file_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;
@ -73,21 +80,142 @@ 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);
} }
// since _ksud handler only uses argv and envp for comparisons extern void ext4_unregister_sysfs(struct super_block *sb);
// this can probably work int nuke_ext4_sysfs(const char* mnt)
// 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;
@ -97,142 +225,110 @@ static int ksu_handle_bprm_ksud(const char *filename, const char *argv1, const c
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;
// return early when disabled if (!filename_ptr)
if (!ksu_execveat_hook)
return 0; return 0;
if (!filename) filename = *filename_ptr;
if (IS_ERR(filename)) {
return 0; return 0;
}
// debug! remove me! if (unlikely(!memcmp(filename->name, system_bin_init,
pr_info("%s: filename: %s argv1: %s envp_len: %zu\n", __func__, filename, argv1, envp_len); sizeof(system_bin_init) - 1) &&
argv)) {
#ifdef CONFIG_KSU_DEBUG // /system/bin/init executed
const char *envp_n = envp; int argc = count(*argv, MAX_ARG_STRINGS);
unsigned int envc = 1; pr_info("/system/bin/init argc: %d\n", argc);
do { if (argc > 1 && !init_second_stage_executed) {
pr_info("%s: envp[%d]: %s\n", __func__, envc, envp_n); const char __user *p = get_user_arg_ptr(*argv, 1);
envp_n += strlen(envp_n) + 1; if (p && !IS_ERR(p)) {
envc++; char first_arg[16];
} while (envp_n < envp + 256); strncpy_from_user_nofault(first_arg, p, sizeof(first_arg));
#endif pr_info("/system/bin/init first arg: %s\n", first_arg);
if (!strcmp(first_arg, "second_stage")) {
if (init_second_stage_executed) pr_info("/system/bin/init second_stage executed\n");
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,
// /init with argv1 sizeof(old_system_init) - 1) &&
if (!init_second_stage_executed argv)) {
&& (!memcmp(filename, old_system_init, sizeof(old_system_init) - 1))) { // /init executed
if (argv1 && !strcmp(argv1, "--second-stage")) { int argc = count(*argv, MAX_ARG_STRINGS);
pr_info("%s: /init --second-stage executed\n", __func__); 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(); apply_kernelsu_rules();
init_second_stage_executed = true; init_second_stage_executed = true;
ksu_android_ns_fs_check();
} }
} else {
pr_err("/init parse args err!\n");
} }
} else if (argc == 1 && !init_second_stage_executed && envp) {
if (!envp || !envp_len) /* This applies to versions between Android 8 ~ 9 */
goto first_app_process; int envc = count(*envp, MAX_ARG_STRINGS);
if (envc > 0) {
// /init without argv1/useless-argv1 but usable envp int n;
// untested! TODO: test and debug me! for (n = 1; n <= envc; n++) {
if (!init_second_stage_executed && (!memcmp(filename, old_system_init, sizeof(old_system_init) - 1))) { const char __user *p = get_user_arg_ptr(*envp, n);
if (!p || IS_ERR(p)) {
// we hunt for "INIT_SECOND_STAGE" continue;
const char *envp_n = envp; }
unsigned int envc = 1; char env[256];
do { // Reading environment variable strings from user space
if (strstarts(envp_n, "INIT_SECOND_STAGE")) if (strncpy_from_user_nofault(env, p, sizeof(env)) < 0)
break; continue;
envp_n += strlen(envp_n) + 1; // Parsing environment variable names and values
envc++; char *env_name = env;
} while (envp_n < envp + envp_len); char *env_value = strchr(env, '=');
pr_info("%s: envp[%d]: %s\n", __func__, envc, envp_n); if (env_value == NULL)
continue;
if (!strcmp(envp_n, "INIT_SECOND_STAGE=1") // Replace equal sign with string terminator
|| !strcmp(envp_n, "INIT_SECOND_STAGE=true") ) { *env_value = '\0';
pr_info("%s: /init +envp: INIT_SECOND_STAGE executed\n", __func__); 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(); apply_kernelsu_rules();
init_second_stage_executed = true; init_second_stage_executed = true;
ksu_android_ns_fs_check(); }
}
}
} }
} }
first_app_process: if (unlikely(first_app_process && !memcmp(filename->name, app_process,
if (first_app_process && !memcmp(filename, app_process, sizeof(app_process) - 1)) { sizeof(app_process) - 1))) {
first_app_process = false; first_app_process = false;
pr_info("%s: exec app_process, /data prepared, second_stage: %d\n", __func__, init_second_stage_executed); pr_info("exec app_process, /data prepared, second_stage: %d\n",
on_post_fs_data(); init_second_stage_executed);
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;
@ -244,8 +340,7 @@ 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, pr_info("read_proxy append %ld + %ld\n", ret, read_count_append);
read_count_append);
ret += read_count_append; ret += read_count_append;
} }
return ret; return ret;
@ -256,17 +351,16 @@ 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, pr_info("read_iter_proxy append %ld + %ld\n", ret, read_count_append);
read_count_append);
ret += read_count_append; ret += read_count_append;
} }
return ret; return ret;
} }
int ksu_handle_vfs_read(struct file **file_ptr, char __user **buf_ptr, static 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 CONFIG_KSU_KPROBES_HOOK #ifndef KSU_KPROBES_HOOK
if (!ksu_vfs_read_hook) { if (!ksu_vfs_read_hook) {
return 0; return 0;
} }
@ -379,7 +473,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 CONFIG_KSU_KPROBES_HOOK #ifndef KSU_KPROBES_HOOK
if (!ksu_input_hook) { if (!ksu_input_hook) {
return 0; return 0;
} }
@ -421,124 +515,28 @@ bool ksu_is_safe_mode()
return false; return false;
} }
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef 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 *)PT_REGS_PARM1(real_regs); const char __user **filename_user =
const char __user *const __user *__argv = (const char __user *const __user *)PT_REGS_PARM2(real_regs); (const char **)&PT_REGS_PARM1(real_regs);
const char __user *const __user *__envp = (const char __user *const __user *)PT_REGS_PARM3(real_regs); const char __user *const __user *__argv =
(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;
// filename stage memset(path, 0, sizeof(path));
if (ksu_copy_from_user_retry(path, filename_user, sizeof(path))) strncpy_from_user_nofault(path, *filename_user, 32);
return 0; filename_in.name = path;
path[sizeof(path) - 1] = '\0'; filename_p = &filename_in;
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)
@ -575,7 +573,6 @@ 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);
@ -594,7 +591,7 @@ static void do_stop_input_hook(struct work_struct *work)
static void stop_vfs_read_hook() static void stop_vfs_read_hook()
{ {
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef 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
@ -605,13 +602,13 @@ static void stop_vfs_read_hook()
static void stop_execve_hook() static void stop_execve_hook()
{ {
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef 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()
@ -621,7 +618,7 @@ static void stop_input_hook()
return; return;
} }
input_hook_stopped = true; input_hook_stopped = true;
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef 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
@ -633,7 +630,7 @@ static void stop_input_hook()
// ksud: module support // ksud: module support
void ksu_ksud_init() void ksu_ksud_init()
{ {
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef KSU_KPROBES_HOOK
int ret; int ret;
ret = register_kprobe(&execve_kp); ret = register_kprobe(&execve_kp);
@ -653,12 +650,11 @@ void ksu_ksud_init()
void ksu_ksud_exit() void ksu_ksud_exit()
{ {
#ifdef CONFIG_KSU_KPROBES_HOOK #ifdef 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;
} }

View file

@ -3,13 +3,19 @@
#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);
extern u32 ksu_devpts_sid; int nuke_ext4_sysfs(const char* mnt);
extern bool ksu_execveat_hook __read_mostly; extern u32 ksu_file_sid;
extern int ksu_handle_pre_ksud(const char *filename); extern bool ksu_module_mounted;
extern bool ksu_boot_completed;
#endif #endif

View file

@ -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() static inline bool ksu_is_manager_uid_valid(void)
{ {
return ksu_manager_uid != KSU_INVALID_UID; return ksu_manager_uid != KSU_INVALID_UID;
} }
static inline bool is_manager() static inline bool is_manager(void)
{ {
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() static inline uid_t ksu_get_manager_uid(void)
{ {
return ksu_manager_uid; return ksu_manager_uid;
} }
@ -34,9 +34,10 @@ 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() static inline void ksu_invalidate_manager_uid(void)
{ {
ksu_manager_uid = KSU_INVALID_UID; ksu_manager_uid = KSU_INVALID_UID;
} }
int ksu_observer_init(void);
#endif #endif

View file

@ -9,5 +9,9 @@
#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 */

357
kernel/manual_su.c Normal file
View file

@ -0,0 +1,357 @@
#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);
}

49
kernel/manual_su.h Normal file
View file

@ -0,0 +1,49 @@
#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

133
kernel/pkg_observer.c Normal file
View file

@ -0,0 +1,133 @@
// 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");
}

69
kernel/seccomp_cache.c Normal file
View file

@ -0,0 +1,69 @@
#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

12
kernel/seccomp_cache.h Normal file
View file

@ -0,0 +1,12 @@
#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

View file

@ -2,15 +2,7 @@ obj-y += selinux.o
obj-y += sepolicy.o obj-y += sepolicy.o
obj-y += rules.o obj-y += rules.o
ifeq ($(shell grep -q " current_sid(void)" $(srctree)/security/selinux/include/objsec.h; echo $$?),0) ccflags-y += -Wno-strict-prototypes -Wno-int-conversion
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

View file

@ -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" #include "linux/lsm_audit.h" // IWYU pragma: keep
#include "xfrm.h" #include "xfrm.h"
#define SELINUX_POLICY_INSTEAD_SELINUX_SS #define SELINUX_POLICY_INSTEAD_SELINUX_SS
@ -145,45 +145,17 @@ 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;
u64 field_sepol1; char __user *sepol1;
u64 field_sepol2; char __user *sepol2;
u64 field_sepol3; char __user *sepol3;
u64 field_sepol4; char __user *sepol4;
u64 field_sepol5; char __user *sepol5;
u64 field_sepol6; char __user *sepol6;
u64 field_sepol7; char __user *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)
@ -194,14 +166,18 @@ 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 -1; return -EINVAL;
} }
*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()
{ {
@ -223,71 +199,27 @@ int handle_sepolicy(unsigned long arg3, void __user *arg4)
struct policydb *db; struct policydb *db;
if (!arg4) { if (!arg4) {
return -1; return -EINVAL;
} }
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; struct sepol_data data;
char __user *sepol1, *sepol2, *sepol3, *sepol4, *sepol5, *sepol6, *sepol7; if (copy_from_user(&data, arg4, sizeof(struct sepol_data))) {
pr_err("sepol: copy sepol_data failed.\n");
return -EINVAL;
}
#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT) u32 cmd = data.cmd;
if (unlikely(ksu_is_compat)) { u32 subcmd = data.subcmd;
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;
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;
}
#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 = -1; int ret = -EINVAL;
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];
@ -295,22 +227,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, sepol1, sizeof(src_buf), &s) < 0) { if (get_object(src_buf, data.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, sepol2, sizeof(tgt_buf), &t) < 0) { if (get_object(tgt_buf, data.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, sepol3, sizeof(cls_buf), &c) < 0) { if (get_object(cls_buf, data.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, sepol4, sizeof(perm_buf), &p) < if (get_object(perm_buf, data.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;
@ -328,7 +260,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 : -1; ret = success ? 0 : -EINVAL;
} else if (cmd == CMD_XPERM) { } else if (cmd == CMD_XPERM) {
char src_buf[MAX_SEPOL_LEN]; char src_buf[MAX_SEPOL_LEN];
@ -340,24 +272,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, sepol1, sizeof(src_buf), &s) < 0) { if (get_object(src_buf, data.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, sepol2, sizeof(tgt_buf), &t) < 0) { if (get_object(tgt_buf, data.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, sepol3, sizeof(cls_buf), &c) < 0) { if (get_object(cls_buf, data.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, sepol4, if (strncpy_from_user(operation, data.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, sepol5, sizeof(perm_set)) < if (strncpy_from_user(perm_set, data.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;
@ -373,11 +305,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 : -1; ret = success ? 0 : -EINVAL;
} 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, sepol1, sizeof(src)) < 0) { if (strncpy_from_user(src, data.sepol1, sizeof(src)) < 0) {
pr_err("sepol: copy src failed.\n"); pr_err("sepol: copy src failed.\n");
goto exit; goto exit;
} }
@ -397,11 +329,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, sepol1, sizeof(type)) < 0) { if (strncpy_from_user(type, data.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, sepol2, sizeof(attr)) < 0) { if (strncpy_from_user(attr, data.sepol2, sizeof(attr)) < 0) {
pr_err("sepol: copy attr failed.\n"); pr_err("sepol: copy attr failed.\n");
goto exit; goto exit;
} }
@ -421,7 +353,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, sepol1, sizeof(attr)) < 0) { if (strncpy_from_user(attr, data.sepol1, sizeof(attr)) < 0) {
pr_err("sepol: copy attr failed.\n"); pr_err("sepol: copy attr failed.\n");
goto exit; goto exit;
} }
@ -438,28 +370,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, sepol1, sizeof(src)) < 0) { if (strncpy_from_user(src, data.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, sepol2, sizeof(tgt)) < 0) { if (strncpy_from_user(tgt, data.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, sepol3, sizeof(cls)) < 0) { if (strncpy_from_user(cls, data.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, sepol4, if (strncpy_from_user(default_type, data.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 (sepol5 == NULL) { if (data.sepol5 == NULL) {
real_object = NULL; real_object = NULL;
} else { } else {
if (strncpy_from_user(object, sepol5, if (strncpy_from_user(object, data.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;
@ -478,19 +410,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, sepol1, sizeof(src)) < 0) { if (strncpy_from_user(src, data.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, sepol2, sizeof(tgt)) < 0) { if (strncpy_from_user(tgt, data.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, sepol3, sizeof(cls)) < 0) { if (strncpy_from_user(cls, data.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, sepol4, if (strncpy_from_user(default_type, data.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;
@ -511,15 +443,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, sepol1, sizeof(name)) < 0) { if (strncpy_from_user(name, data.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, sepol2, sizeof(path)) < 0) { if (strncpy_from_user(path, data.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, sepol3, sizeof(context)) < if (strncpy_from_user(context, data.sepol3, sizeof(context)) <
0) { 0) {
pr_err("sepol: copy context failed.\n"); pr_err("sepol: copy context failed.\n");
goto exit; goto exit;

View file

@ -1,4 +1,6 @@
#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
@ -40,13 +42,6 @@ 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)
@ -71,60 +66,89 @@ bool getenforce()
#endif #endif
} }
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) && \ #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 14, 0)
!defined(KSU_COMPAT_HAS_CURRENT_SID) struct lsm_context {
/* char *context;
* get the subjective security ID of the current task u32 len;
*/ };
static inline u32 current_sid(void)
{
const struct task_security_struct *tsec = current_security();
return tsec->sid; static int __security_secid_to_secctx(u32 secid, struct lsm_context *cp)
{
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_ksu_domain() bool is_task_ksu_domain(const struct cred* cred)
{ {
char *domain; struct lsm_context ctx;
u32 seclen;
bool result; bool result;
int err = security_secid_to_secctx(current_sid(), &domain, &seclen); if (!cred) {
if (err) {
return false; return false;
} }
result = strncmp(KERNEL_SU_DOMAIN, domain, seclen) == 0; const struct task_security_struct *tsec = selinux_cred(cred);
security_release_secctx(domain, seclen);
return result;
}
bool is_zygote(void *sec)
{
struct task_security_struct *tsec = (struct task_security_struct *)sec;
if (!tsec) { if (!tsec) {
return false; return false;
} }
char *domain; int err = __security_secid_to_secctx(tsec->sid, &ctx);
u32 seclen;
bool result;
int err = security_secid_to_secctx(tsec->sid, &domain, &seclen);
if (err) { if (err) {
return false; return false;
} }
result = strncmp("u:r:zygote:s0", domain, seclen) == 0; result = strncmp(KERNEL_SU_DOMAIN, ctx.context, ctx.len) == 0;
security_release_secctx(domain, seclen); __security_release_secctx(&ctx);
return result; return result;
} }
#define DEVPTS_DOMAIN "u:object_r:ksu_file:s0" bool is_ksu_domain()
u32 ksu_get_devpts_sid()
{ {
u32 devpts_sid = 0; current_sid();
int err = security_secctx_to_secid(DEVPTS_DOMAIN, strlen(DEVPTS_DOMAIN), return is_task_ksu_domain(current_cred());
&devpts_sid); }
if (err) {
pr_info("get devpts sid err %d\n", err); bool is_context(const struct cred* cred, const char* context)
} {
return devpts_sid; if (!cred) {
return false;
}
const struct task_security_struct * tsec = selinux_cred(cred);
if (!tsec) {
return false;
}
struct lsm_context ctx;
bool result;
int err = __security_secid_to_secctx(tsec->sid, &ctx);
if (err) {
return false;
}
result = strncmp(context, ctx.context, ctx.len) == 0;
__security_release_secctx(&ctx);
return result;
}
bool is_zygote(const struct cred* cred)
{
return is_context(cred, "u:r:zygote:s0");
}
bool is_init(const struct cred* cred) {
return is_context(cred, "u:r:init:s0");
}
#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) {
pr_info("get ksufile sid err %d\n", err);
}
return ksu_file_sid;
} }

View file

@ -3,6 +3,7 @@
#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 *);
@ -10,12 +11,18 @@ 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(void *cred); bool is_zygote(const struct cred* cred);
bool is_init(const struct cred* cred);
void apply_kernelsu_rules(); void apply_kernelsu_rules();
u32 ksu_get_devpts_sid(); u32 ksu_get_ksu_file_sid();
int handle_sepolicy(unsigned long arg3, void __user *arg4);
#endif #endif

View file

@ -6,7 +6,6 @@
#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
@ -355,7 +354,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 *)(kmalloc( (struct avtab_extended_perms *)(kzalloc(
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");
@ -546,10 +545,10 @@ static bool add_filename_trans(struct policydb *db, const char *s,
} }
if (trans == NULL) { if (trans == NULL) {
trans = (struct filename_trans_datum *)kcalloc(sizeof(*trans), trans = (struct filename_trans_datum *)kcalloc(1 ,sizeof(*trans),
1, GFP_ATOMIC); GFP_ATOMIC);
struct filename_trans_key *new_key = struct filename_trans_key *new_key =
(struct filename_trans_key *)kmalloc(sizeof(*new_key), (struct filename_trans_key *)kzalloc(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);

171
kernel/setuid_hook.c Normal file
View file

@ -0,0 +1,171 @@
#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(&current->sighand->siglock);
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
ksu_set_task_tracepoint_flag(current);
spin_unlock_irq(&current->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(&current->sighand->siglock);
ksu_seccomp_allow_cache(current->seccomp.filter, __NR_reboot);
spin_unlock_irq(&current->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(&current->sighand->siglock);
disable_seccomp();
spin_unlock_irq(&current->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);
}

14
kernel/setuid_hook.h Normal file
View file

@ -0,0 +1,14 @@
#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

View file

@ -1,35 +1,54 @@
#include <linux/dcache.h> #include "linux/compiler.h"
#include <linux/security.h> #include "linux/printk.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 "arch.h" #include "feature.h"
#include "klog.h" // IWYU pragma: keep #include "klog.h" // IWYU pragma: keep
#include "ksud.h" #include "ksud.h"
#include "kernel_compat.h" #include "sucompat.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"
extern void escape_to_root(); bool ksu_su_compat_enabled __read_mostly = true;
#ifndef CONFIG_KSU_KPROBES_HOOK static int su_compat_feature_get(u64 *value)
static bool ksu_sucompat_hook_state __read_mostly = true; {
#endif *value = ksu_su_compat_enabled ? 1 : 0;
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;
@ -54,21 +73,18 @@ int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
{ {
const char su[] = SU_PATH; const char su[] = SU_PATH;
#ifndef CONFIG_KSU_KPROBES_HOOK if (!ksu_is_allow_uid_for_current(current_uid().val)) {
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));
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); 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();
} }
@ -81,12 +97,7 @@ 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;
#ifndef CONFIG_KSU_KPROBES_HOOK if (!ksu_is_allow_uid_for_current(current_uid().val)) {
if (!ksu_sucompat_hook_state) {
return 0;
}
#endif
if (!ksu_is_allow_uid(current_uid().val)) {
return 0; return 0;
} }
@ -110,9 +121,12 @@ 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
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); 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();
} }
@ -121,217 +135,53 @@ int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
return 0; return 0;
} }
int ksu_handle_execveat(int *fd, struct filename **filename_ptr, void *argv, int ksu_handle_execve_sucompat(const char __user **filename_user,
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));
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path)); 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 (!ksu_is_allow_uid(current_uid().val)) #if __SULOG_GATE
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_to_root(); escape_with_root_profile();
return 0; return 0;
} }
// dummified // sucompat: permitted process can execute 'su' to gain root access.
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()
{ {
#ifdef CONFIG_KSU_KPROBES_HOOK if (ksu_register_feature_handler(&su_compat_handler)) {
su_kps[0] = init_kprobe(SYS_EXECVE_SYMBOL, execve_handler_pre); pr_err("Failed to register su_compat feature handler\n");
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()
{ {
#ifdef CONFIG_KSU_KPROBES_HOOK ksu_unregister_feature_handler(KSU_FEATURE_SU_COMPAT);
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
} }

18
kernel/sucompat.h Normal file
View file

@ -0,0 +1,18 @@
#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 Normal file
View file

@ -0,0 +1,369 @@
#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

93
kernel/sulog.h Normal file
View file

@ -0,0 +1,93 @@
#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 Normal file

File diff suppressed because it is too large Load diff

197
kernel/supercalls.h Normal file
View file

@ -0,0 +1,197 @@
#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

View file

@ -0,0 +1,374 @@
#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();
}

View file

@ -0,0 +1,47 @@
#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

View file

@ -7,12 +7,17 @@
#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;
@ -38,6 +43,67 @@ 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) {
@ -78,6 +144,7 @@ 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,
@ -85,6 +152,16 @@ 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)
{ {
@ -123,3 +200,15 @@ 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);
}

View file

@ -9,4 +9,14 @@ 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

View file

@ -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 "kernel_compat.h" #include "apk_sign.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 USER_DATA_PATH "/data/user_de/0" #define SYSTEM_PACKAGES_LIST_PATH "/data/system/packages.list"
#define USER_DATA_PATH_LEN 256
struct uid_data { struct uid_data {
struct list_head list; struct list_head list;
@ -29,109 +29,75 @@ struct uid_data {
char package[KSU_MAX_PACKAGE_NAME]; char package[KSU_MAX_PACKAGE_NAME];
}; };
// Try read whitelist first, fallback if failed // Try read /data/misc/user_uid/uid_list
static int read_uid_whitelist(struct list_head *uid_list) static int uid_from_um_list(struct list_head *uid_list)
{ {
struct file *fp; struct file *fp;
char *file_content = NULL; char *buf = NULL;
char *line, *next_line; loff_t size, pos = 0;
loff_t file_size; ssize_t nr;
loff_t pos = 0; int cnt = 0;
int count = 0;
ssize_t bytes_read;
fp = ksu_filp_open_compat(KSU_UID_LIST_PATH, O_RDONLY, 0); fp = filp_open(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;
}
file_size = fp->f_inode->i_size; size = fp->f_inode->i_size;
if (file_size <= 0) { if (size <= 0) {
pr_info("whitelist file is empty\n");
filp_close(fp, NULL); filp_close(fp, NULL);
return -ENODATA; return -ENODATA;
} }
file_content = kzalloc(file_size + 1, GFP_ATOMIC); buf = kzalloc(size + 1, GFP_ATOMIC);
if (!file_content) { if (!buf) {
pr_err("failed to allocate memory for whitelist file (%lld bytes)\n", file_size); pr_err("uid_list: OOM %lld B\n", size);
filp_close(fp, NULL); filp_close(fp, NULL);
return -ENOMEM; return -ENOMEM;
} }
bytes_read = ksu_kernel_read_compat(fp, file_content, file_size, &pos); nr = kernel_read(fp, buf, 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';
file_content[file_size] = '\0'; for (char *line = buf, *next; line; line = next) {
filp_close(fp, NULL); next = strchr(line, '\n');
if (next) *next++ = '\0';
pr_info("successfully read whitelist file (%lld bytes), parsing lines...\n", file_size); while (*line == ' ' || *line == '\t' || *line == '\r') ++line;
if (!*line) continue;
line = file_content; char *uid_str = strsep(&line, " \t");
while (line && *line) { char *pkg = line;
next_line = strchr(line, '\n'); if (!pkg) continue;
if (next_line) { while (*pkg == ' ' || *pkg == '\t') ++pkg;
*next_line = '\0'; if (!*pkg) continue;
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)) {
struct uid_data *data = kzalloc(sizeof(struct uid_data), GFP_ATOMIC); pr_warn_once("uid_list: bad uid <%s>\n", uid_str);
if (data) { continue;
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);
}
} }
line = next_line; struct uid_data *d = kzalloc(sizeof(*d), GFP_ATOMIC);
if (unlikely(!d)) {
pr_err("uid_list: OOM uid=%u\n", uid);
continue;
} }
kfree(file_content); d->uid = uid;
pr_info("successfully loaded %d uids from whitelist\n", count); strscpy(d->package, pkg, KSU_MAX_PACKAGE_NAME);
return count > 0 ? 0 : -ENODATA; list_add_tail(&d->list, uid_list);
++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)
@ -191,22 +157,41 @@ 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, list, list) { list_for_each_entry(np, uid_data, list) {
if (strncmp(np->package, pkg, KSU_MAX_PACKAGE_NAME) == 0) { if (strncmp(np->package, pkg, KSU_MAX_PACKAGE_NAME) == 0) {
pr_info("Crowning manager: %s(uid=%d, signature_index=%d)\n", pkg, np->uid, signature_index); bool is_dynamic = (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2);
// Dynamic Sign index (1) or multi-manager signatures (2+) if (is_dynamic) {
if (signature_index == DYNAMIC_SIGN_INDEX || signature_index >= 2) { if (locked_dynamic_manager_uid != KSU_INVALID_UID && locked_dynamic_manager_uid != np->uid) {
ksu_add_manager(np->uid, signature_index); pr_info("Unlocking previous dynamic manager UID: %d\n", locked_dynamic_manager_uid);
ksu_remove_manager(locked_dynamic_manager_uid);
if (!ksu_is_manager_uid_valid()) { locked_dynamic_manager_uid = KSU_INVALID_UID;
ksu_set_manager_uid(np->uid);
} }
} else { } 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);
locked_dynamic_manager_uid = np->uid;
// If there is no traditional manager, set it to the current UID
if (!ksu_is_manager_uid_valid()) {
ksu_set_manager_uid(np->uid); ksu_set_manager_uid(np->uid);
locked_manager_uid = np->uid;
}
} else {
ksu_set_manager_uid(np->uid); // throne new UID
locked_manager_uid = np->uid; // store locked UID
} }
break; break;
} }
@ -248,138 +233,6 @@ 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)
@ -406,7 +259,6 @@ 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,
@ -416,7 +268,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 = kmalloc(sizeof(struct data_path), GFP_ATOMIC); struct data_path *data = kzalloc(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);
@ -429,7 +281,11 @@ 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;
@ -447,30 +303,24 @@ 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 = kmalloc(sizeof(struct apk_path_hash), GFP_ATOMIC); struct apk_path_hash *apk_data = kzalloc(sizeof(*apk_data), 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);
} }
} else if (is_manager_apk(dirpath)) { 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);
}
} }
} }
} }
@ -487,7 +337,7 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
// Initialize APK cache list // Initialize APK cache list
struct apk_path_hash *pos, *n; struct apk_path_hash *pos, *n;
list_for_each_entry(pos, &apk_path_hash_list, list) { list_for_each_entry (pos, &apk_path_hash_list, list) {
pos->exists = false; pos->exists = false;
} }
@ -500,7 +350,7 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
for (i = depth; i >= 0; i--) { for (i = depth; i >= 0; i--) {
struct data_path *pos, *n; struct data_path *pos, *n;
list_for_each_entry_safe(pos, n, &data_path_list, list) { list_for_each_entry_safe (pos, n, &data_path_list, list) {
struct my_dir_context ctx = { .ctx.actor = my_actor, struct my_dir_context ctx = { .ctx.actor = my_actor,
.data_path_list = &data_path_list, .data_path_list = &data_path_list,
.parent_dir = pos->dirpath, .parent_dir = pos->dirpath,
@ -510,9 +360,10 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
struct file *file; struct file *file;
if (!stop) { if (!stop) {
file = ksu_filp_open_compat(pos->dirpath, O_RDONLY | O_NOFOLLOW, 0); file = filp_open(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", pos->dirpath, PTR_ERR(file)); pr_err("Failed to open directory: %s, err: %ld\n",
pos->dirpath, PTR_ERR(file));
goto skip_iterate; goto skip_iterate;
} }
@ -520,7 +371,8 @@ 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__, pos->dirpath, data_app_magic); pr_info("%s: dir: %s got magic! 0x%lx\n", __func__,
pos->dirpath, data_app_magic);
} else { } else {
filp_close(file, NULL); filp_close(file, NULL);
goto skip_iterate; goto skip_iterate;
@ -528,7 +380,8 @@ 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", __func__, pos->dirpath, pr_info("%s: skip: %s magic: 0x%lx expected: 0x%lx\n",
__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;
@ -537,7 +390,7 @@ void search_manager(const char *path, int depth, struct list_head *uid_data)
iterate_dir(file, &ctx.ctx); iterate_dir(file, &ctx.ctx);
filp_close(file, NULL); filp_close(file, NULL);
} }
skip_iterate: skip_iterate:
list_del(&pos->list); list_del(&pos->list);
if (pos != &data) if (pos != &data)
kfree(pos); kfree(pos);
@ -545,7 +398,7 @@ skip_iterate:
} }
// Remove stale cached APK entries // Remove stale cached APK entries
list_for_each_entry_safe(pos, n, &apk_path_hash_list, list) { list_for_each_entry_safe (pos, n, &apk_path_hash_list, list) {
if (!pos->exists) { if (!pos->exists) {
list_del(&pos->list); list_del(&pos->list);
kfree(pos); kfree(pos);
@ -569,74 +422,127 @@ static bool is_uid_exist(uid_t uid, char *package, void *data)
return exist; return exist;
} }
void track_throne() void track_throne(bool prune_only)
{ {
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);
pr_info("track_throne triggered, attempting whitelist read\n"); if (ksu_uid_scanner_enabled) {
pr_info("Scanning %s directory..\n", KSU_UID_LIST_PATH);
// Try read whitelist first if (uid_from_um_list(&uid_list) == 0) {
int ret = read_uid_whitelist(&uid_list); pr_info("Loaded UIDs from %s success\n", KSU_UID_LIST_PATH);
goto uid_ready;
}
if (ret < 0) { pr_warn("%s read failed, fallback to %s\n",
pr_info("whitelist read failed (%d), request userspace scan, falling back to user_de \n", ret); KSU_UID_LIST_PATH, SYSTEM_PACKAGES_LIST_PATH);
}
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;
}
if (ret_user < 0) { for (;;) {
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));
} }
} else { char *tmp = buf;
pr_info("loaded uids from whitelist successfully\n"); const char *delim = " ";
char *package = strsep(&tmp, delim);
char *uid = strsep(&tmp, delim);
if (!uid || !package) {
pr_err("update_uid: package or uid is NULL!\n");
break;
} }
// now update uid list u32 res;
struct uid_data *np; if (kstrtou32(uid, 10, &res)) {
struct uid_data *n; pr_err("update_uid: uid parse err\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; list_for_each_entry(np, &uid_list, list) {
bool dynamic_manager_exist = false; if (np->uid == current_manager_uid) {
list_for_each_entry (np, &uid_list, list) {
// 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;
} }
} }
// Check for dynamic managers if (!manager_exist && locked_manager_uid != KSU_INVALID_UID) {
if (!dynamic_manager_exist && ksu_is_dynamic_manager_enabled()) { pr_info("Manager APK removed, unlock previous UID: %d\n",
list_for_each_entry (np, &uid_list, list) { locked_manager_uid);
// Check if this uid is a dynamic manager (not the traditional manager) ksu_invalidate_manager_uid();
if (ksu_is_any_manager(np->uid) && np->uid != ksu_get_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) {
if (np->uid == locked_dynamic_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;
}
} }
if (!manager_exist) { bool need_search = !manager_exist;
if (ksu_is_manager_uid_valid()) { if (ksu_is_dynamic_manager_enabled() && !dynamic_manager_exist)
pr_info("manager is uninstalled, invalidate it!\n"); need_search = true;
ksu_invalidate_manager_uid();
goto prune; if (need_search) {
} 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("Search manager finished\n"); pr_info("Manager search 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:
@ -644,18 +550,18 @@ prune:
ksu_prune_allowlist(is_uid_exist, &uid_list); ksu_prune_allowlist(is_uid_exist, &uid_list);
out: out:
// free uid_list // free uid_list
list_for_each_entry_safe (np, n, &uid_list, list) { list_for_each_entry_safe(np, n, &uid_list, list) {
list_del(&np->list); list_del(&np->list);
kfree(np); kfree(np);
} }
} }
void ksu_throne_tracker_init() void ksu_throne_tracker_init(void)
{ {
// nothing to do // nothing to do
} }
void ksu_throne_tracker_exit() void ksu_throne_tracker_exit(void)
{ {
// nothing to do // nothing to do
} }

View file

@ -5,6 +5,6 @@ void ksu_throne_tracker_init();
void ksu_throne_tracker_exit(); void ksu_throne_tracker_exit();
void track_throne(); void track_throne(bool prune_only);
#endif #endif

242
kernel/umount_manager.c Normal file
View file

@ -0,0 +1,242 @@
#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;
}

63
kernel/umount_manager.h Normal file
View file

@ -0,0 +1,63 @@
#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

View file

@ -2,7 +2,6 @@
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)
@ -17,6 +16,7 @@ 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,15 +51,12 @@ 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
@ -77,7 +74,8 @@ android {
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path("src/main/cpp/CMakeLists.txt") path = file("src/main/cpp/CMakeLists.txt")
version = androidCmakeVersion
} }
} }
@ -125,6 +123,7 @@ 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)

View file

@ -44,3 +44,5 @@
-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.** { *; }

View file

@ -18,31 +18,82 @@
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

View file

@ -0,0 +1,10 @@
// 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.

Binary file not shown.

View file

@ -6,10 +6,11 @@ cmake_minimum_required(VERSION 3.18.1)
project("kernelsu") project("kernelsu")
add_library(zako add_library(kernelsu
SHARED SHARED
jni.c jni.c
ksu.c ksu.c
legacy.c
) )
find_library(log-lib log) find_library(log-lib log)
@ -21,7 +22,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(zako ${log-lib} ${zakosign-lib}) target_link_libraries(kernelsu ${log-lib} ${zakosign-lib})
else() else()
target_link_libraries(zako ${log-lib}) target_link_libraries(kernelsu ${log-lib})
endif() endif()

View file

@ -5,18 +5,16 @@
#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) {
return get_version(); uint32_t version = get_version();
if (version > 0) {
return (jint)version;
}
// try legacy method as fallback
return legacy_get_info().version;
} }
// get VERSION FULL // get VERSION FULL
@ -27,15 +25,18 @@ NativeBridgeNP(getFullVersion, jstring) {
} }
NativeBridgeNP(getAllowList, jintArray) { NativeBridgeNP(getAllowList, jintArray) {
int uids[1024]; struct ksu_get_allow_list_cmd cmd = {};
int size = 0; bool result = get_allow_list(&cmd);
bool result = get_allow_list(uids, &size);
LogDebug("getAllowList: %d, size: %d", result, size);
if (result) { if (result) {
jintArray array = GetEnvironment()->NewIntArray(env, size); jsize array_size = (jsize)cmd.count;
GetEnvironment()->SetIntArrayRegion(env, array, 0, size, uids); if (array_size < 0 || (unsigned int)array_size != cmd.count) {
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;
} }
@ -51,6 +52,10 @@ 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");
@ -124,7 +129,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(key, &profile); bool useDefaultProfile = get_app_profile(&profile) != 0;
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");
@ -298,6 +303,38 @@ 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();
@ -305,42 +342,11 @@ NativeBridgeNP(isKPMEnabled, jboolean) {
// Get HOOK type // Get HOOK type
NativeBridgeNP(getHookType, jstring) { NativeBridgeNP(getHookType, jstring) {
char hook_type[16]; char hook_type[32] = { 0 };
get_hook_type(hook_type, sizeof(hook_type)); get_hook_type((char *) &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) {
@ -432,3 +438,15 @@ 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();
}

View file

@ -2,11 +2,14 @@
// 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"
@ -21,177 +24,328 @@ extern const char* zako_file_verrcidx2str(uint8_t index);
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM #endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM
#define KERNEL_SU_OPTION 0xDEADBEEF static int fd = -1;
#define CMD_GRANT_ROOT 0 static inline int scan_driver_fd() {
const char *kName = "[ksu_driver]";
#define CMD_BECOME_MANAGER 1 DIR *fd_dir = opendir("/proc/self/fd");
#define CMD_GET_VERSION 2 if (!fd_dir) {
#define CMD_ALLOW_SU 3 return -1;
#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;
}
bool become_manager(const char* pkg) {
char param[128];
uid_t uid = getuid();
uint32_t userId = uid / 100000;
if (userId == 0) {
sprintf(param, "/data/data/%s", pkg);
} else {
snprintf(param, sizeof(param), "/data/user/%d/%s", userId, pkg);
} }
return ksuctl(CMD_BECOME_MANAGER, param, NULL); int found = -1;
} struct dirent *de;
char path[64];
char target[PATH_MAX];
// cache the result to avoid unnecessary syscall while ((de = readdir(fd_dir)) != NULL) {
static bool is_lkm; if (de->d_name[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;
} }
return version;
char *endptr = nullptr;
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);
ssize_t n = readlink(path, target, sizeof(target) - 1);
if (n < 0) {
continue;
}
target[n] = '\0';
const char *base = strrchr(target, '/');
base = base ? base + 1 : target;
if (strstr(base, kName)) {
found = (int)fd_long;
break;
}
}
closedir(fd_dir);
return found;
} }
void get_full_version(char* buff) { static int ksuctl(unsigned long op, void* arg) {
ksuctl(CMD_GET_VERSION_FULL, buff, NULL); if (fd < 0) {
fd = scan_driver_fd();
}
return ioctl(fd, op, arg);
} }
bool get_allow_list(int *uids, int *size) { static struct ksu_get_info_cmd g_version = {0};
return ksuctl(CMD_GET_SU_LIST, uids, size);
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() { bool is_safe_mode() {
return ksuctl(CMD_CHECK_SAFEMODE, NULL, NULL); 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() { bool is_lkm_mode() {
// you should call get_version first! auto info = get_info();
return is_lkm; 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) { bool uid_should_umount(int uid) {
int should; struct ksu_uid_should_umount_cmd cmd = {};
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, (void*) ((size_t) uid), &should) && should; 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) { bool set_app_profile(const struct app_profile *profile) {
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, NULL); 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);
} }
bool get_app_profile(char* key, struct app_profile* profile) { int get_app_profile(struct app_profile *profile) {
return ksuctl(CMD_GET_APP_PROFILE, profile, NULL); 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) { bool set_su_enabled(bool enabled) {
return ksuctl(CMD_ENABLE_SU, (void*) enabled, NULL); 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() { bool is_su_enabled() {
int enabled = true; struct ksu_get_feature_cmd cmd = {};
// if ksuctl failed, we assume su is enabled, and it cannot be disabled. cmd.feature_id = KSU_FEATURE_SU_COMPAT;
ksuctl(CMD_IS_SU_ENABLED, &enabled, NULL); if (ksuctl(KSU_IOCTL_GET_FEATURE, &cmd) == 0 && cmd.supported) {
return enabled; return cmd.value != 0;
}
return legacy_is_su_enabled();
} }
bool is_KPM_enable() { static inline bool get_feature(uint32_t feature_id, uint64_t *out_value, bool *out_supported) {
int enabled = false; struct ksu_get_feature_cmd cmd = {};
ksuctl(CMD_ENABLE_KPM, &enabled, NULL); cmd.feature_id = feature_id;
return enabled; if (ksuctl(KSU_IOCTL_GET_FEATURE, &cmd) != 0) {
}
bool get_hook_type(char* hook_type, size_t size) {
if (hook_type == NULL || size == 0) {
return false; return false;
} }
if (out_value) *out_value = cmd.value;
static char cached_hook_type[16] = {0}; if (out_supported) *out_supported = cmd.supported;
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);
hook_type[size - 1] = '\0';
return true; return true;
} }
bool get_susfs_feature_status(struct susfs_feature_status* status) { static inline bool set_feature(uint32_t feature_id, uint64_t value) {
if (status == NULL) { struct ksu_set_feature_cmd cmd = {};
return false; cmd.feature_id = feature_id;
} cmd.value = value;
return ksuctl(KSU_IOCTL_SET_FEATURE, &cmd) == 0;
return ksuctl(CMD_GET_SUSFS_FEATURE_STATUS, status, NULL);
} }
bool set_dynamic_manager(unsigned int size, const char* hash) { bool set_kernel_umount_enabled(bool enabled) {
if (hash == NULL) { return set_feature(KSU_FEATURE_KERNEL_UMOUNT, enabled ? 1 : 0);
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 get_dynamic_manager(struct dynamic_manager_user_config* config) { bool is_kernel_umount_enabled() {
if (config == NULL) { uint64_t value = 0;
bool supported = false;
if (!get_feature(KSU_FEATURE_KERNEL_UMOUNT, &value, &supported)) {
return false; return false;
} }
if (!supported) {
config->operation = DYNAMIC_MANAGER_OP_GET;
return ksuctl(CMD_DYNAMIC_MANAGER, config, NULL);
}
bool clear_dynamic_manager() {
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 false;
} }
return value != 0;
}
return ksuctl(CMD_GET_MANAGERS, info, NULL); 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) {
struct ksu_get_full_version_cmd cmd = {0};
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) {
struct ksu_enable_kpm_cmd cmd = {};
if (ksuctl(KSU_IOCTL_ENABLE_KPM, &cmd) == 0 && cmd.enabled) {
return true;
}
return legacy_is_KPM_enable();
}
void get_hook_type(char *buff) {
struct ksu_hook_type_cmd cmd = {0};
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)
{
struct ksu_dynamic_manager_cmd cmd = {0};
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)
{
if (!cfg)
return false;
struct ksu_dynamic_manager_cmd cmd = {0};
cmd.config.operation = DYNAMIC_MANAGER_OP_GET;
if (ksuctl(KSU_IOCTL_DYNAMIC_MANAGER, &cmd) != 0)
return false;
*cfg = cmd.config;
return true;
}
bool clear_dynamic_manager(void)
{
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;
struct ksu_get_managers_cmd cmd = {0};
if (ksuctl(KSU_IOCTL_GET_MANAGERS, &cmd) != 0)
return false;
*info = cmd.manager_info;
return true;
}
bool is_uid_scanner_enabled(void)
{
bool status = false;
struct ksu_enable_uid_scanner_cmd cmd = {
.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)
{
struct ksu_enable_uid_scanner_cmd cmd = {
.operation = UID_SCANNER_OP_TOGGLE,
.enabled = enabled
};
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
}
bool clear_uid_scanner_environment(void)
{
struct ksu_enable_uid_scanner_cmd cmd = {
.operation = UID_SCANNER_OP_CLEAR_ENV
};
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
} }
bool verify_module_signature(const char* input) { bool verify_module_signature(const char* input) {
@ -201,13 +355,13 @@ bool verify_module_signature(const char* input) {
return false; return false;
} }
int fd = zako_sys_file_open(input); int file_fd = zako_sys_file_open(input);
if (fd < 0) { if (file_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(fd, 0); uint32_t results = zako_file_verify_esig(file_fd, 0);
if (results != 0) { if (results != 0) {
/* If important error occured, verification process should /* If important error occured, verification process should
@ -241,7 +395,7 @@ bool verify_module_signature(const char* input) {
} }
exit: exit:
close(fd); close(file_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;

View file

@ -6,16 +6,15 @@
#define KERNELSU_KSU_H #define KERNELSU_KSU_H
#include "prelude.h" #include "prelude.h"
#include <linux/capability.h> #include <stdint.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
bool become_manager(const char *); #define KSU_FULL_VERSION_STRING 255
void get_full_version(char* buff); uint32_t get_version();
int get_version();
bool get_allow_list(int *uids, int *size);
bool uid_should_umount(int uid); bool uid_should_umount(int uid);
@ -23,6 +22,10 @@ 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.
@ -33,29 +36,16 @@ bool is_lkm_mode();
#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;
@ -115,17 +105,11 @@ struct manager_list_info {
bool set_app_profile(const struct app_profile* profile); bool set_app_profile(const struct app_profile* profile);
bool get_app_profile(char* key, struct app_profile* profile); int get_app_profile(struct app_profile* profile);
bool set_su_enabled(bool enabled);
bool is_su_enabled();
bool is_KPM_enable(); bool is_KPM_enable();
bool get_hook_type(char* hook_type, size_t size); void get_hook_type(char* hook_type);
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);
@ -137,4 +121,180 @@ 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

View file

@ -0,0 +1,163 @@
//
// 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);
}

View file

@ -1,94 +1,40 @@
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.content.Context import android.system.Os
import android.content.res.Configuration import androidx.lifecycle.ViewModelProvider
import android.content.res.Resources import androidx.lifecycle.ViewModelStore
import android.os.Build import androidx.lifecycle.ViewModelStoreOwner
import android.os.Bundle import kotlinx.coroutines.CoroutineScope
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.* import java.util.Locale
@SuppressLint("StaticFieldLeak")
lateinit var ksuApp: KernelSUApplication lateinit var ksuApp: KernelSUApplication
class KernelSUApplication : Application() { class KernelSUApplication : Application(), ViewModelStoreOwner {
private var currentActivity: Activity? = null
private val activityLifecycleCallbacks = object : ActivityLifecycleCallbacks { lateinit var okhttpClient: OkHttpClient
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { private val appViewModelStore by lazy { ViewModelStore() }
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
// 注册Activity生命周期回调 // For faster response when first entering superuser or webui activity
registerActivityLifecycleCallbacks(activityLifecycleCallbacks) val superUserViewModel = ViewModelProvider(this)[SuperUserViewModel::class.java]
CoroutineScope(Dispatchers.Main).launch {
superUserViewModel.fetchAppList()
}
Platform.setHiddenApiExemptions() Platform.setHiddenApiExemptions()
@ -107,45 +53,20 @@ class KernelSUApplication : Application() {
if (!webroot.exists()) { if (!webroot.exists()) {
webroot.mkdir() webroot.mkdir()
} }
}
override fun onConfigurationChanged(newConfig: Configuration) { // Provide working env for rust's temp_dir()
super.onConfigurationChanged(newConfig) Os.setenv("TMPDIR", cacheDir.absolutePath, true)
applyLanguageSetting()
}
@SuppressLint("ObsoleteSdkInt") okhttpClient =
private fun applyLanguageSetting() { OkHttpClient.Builder().cache(Cache(File(cacheDir, "okhttp"), 10 * 1024 * 1024))
val prefs = getSharedPreferences("settings", MODE_PRIVATE) .addInterceptor { block ->
val languageCode = prefs.getString("app_language", "") ?: "" block.proceed(
block.request().newBuilder()
if (languageCode.isNotEmpty()) { .header("User-Agent", "SukiSU/${BuildConfig.VERSION_CODE}")
val locale = Locale.forLanguageTag(languageCode) .header("Accept-Language", Locale.getDefault().toLanguageTag()).build()
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
) )
activity.startActivity(intent, options.toBundle()) }.build()
}
} }
override val viewModelStore: ViewModelStore
get() = appViewModelStore
} }

View file

@ -16,23 +16,22 @@ 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.
const val MINIMAL_SUPPORTED_KERNEL = 11071 // 12143: breaking: new supercall impl
const val MINIMAL_SUPPORTED_KERNEL_FULL = "v3.1.5" const val MINIMAL_SUPPORTED_KERNEL = 12143
// 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
@ -63,11 +62,9 @@ object Natives {
init { init {
System.loadLibrary("zakosign") System.loadLibrary("zakosign")
System.loadLibrary("zako") System.loadLibrary("kernelsu")
} }
// become root manager, return true if success.
external fun becomeManager(pkg: String?): Boolean
val version: Int val version: Int
external get external get
@ -81,6 +78,9 @@ 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,6 +99,34 @@ 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
@ -106,7 +134,6 @@ 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
@ -138,6 +165,28 @@ 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
@ -159,31 +208,10 @@ object Natives {
} }
fun requireNewKernel(): Boolean { fun requireNewKernel(): Boolean {
if (version < MINIMAL_SUPPORTED_KERNEL) return true if (version != -1 && 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

View file

@ -1,137 +1,75 @@
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 rikka.parcelablelist.ParcelableListSlice import com.sukisu.zako.IKsuInterface
import java.lang.reflect.Method
/** /**
* @author ShirkNeko * @author ShirkNeko
* @date 2025/7/2. * @date 2025/10/17.
*/ */
class KsuService : RootService() { class KsuService : RootService() {
companion object { private val TAG = "KsuService"
private const val TAG = "KsuService"
private const val DESCRIPTOR = "com.sukisu.ultra.IKsuInterface" private val cacheLock = Object()
private const val TRANSACTION_GET_PACKAGES = IBinder.FIRST_CALL_TRANSACTION + 0 private var _all: List<PackageInfo>? = null
private val allPackages: List<PackageInfo>
get() = synchronized(cacheLock) {
_all ?: loadAllPackages().also { _all = it }
} }
interface IKsuInterface : IInterface { private fun loadAllPackages(): List<PackageInfo> {
fun getPackages(flags: Int): ParcelableListSlice<PackageInfo> val tmp = arrayListOf<PackageInfo>()
for (user in (getSystemService(USER_SERVICE) as UserManager).userProfiles) {
val userId = user.getUserIdCompat()
tmp += getInstalledPackagesAsUser(userId)
}
return tmp
} }
abstract class Stub : Binder(), IKsuInterface { internal inner class Stub : IKsuInterface.Stub() {
init { override fun getPackageCount(): Int = allPackages.size
attachInterface(this, DESCRIPTOR)
}
companion object { override fun getPackages(start: Int, maxCount: Int): List<PackageInfo> {
fun asInterface(obj: IBinder?): IKsuInterface? { val list = allPackages
if (obj == null) return null val end = (start + maxCount).coerceAtMost(list.size)
val iin = obj.queryLocalInterface(DESCRIPTOR) return if (start >= list.size) emptyList()
return if (iin != null && iin is IKsuInterface) { else list.subList(start, end)
iin
} else {
Proxy(obj)
}
} }
} }
override fun asBinder(): IBinder = this override fun onBind(intent: Intent): IBinder = Stub()
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { @SuppressLint("PrivateApi")
val descriptor = DESCRIPTOR private fun getInstalledPackagesAsUser(userId: Int): List<PackageInfo> {
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 getInstalledPackagesAsUser: Method = pm.javaClass.getDeclaredMethod( val m = pm.javaClass.getDeclaredMethod(
"getInstalledPackagesAsUser", "getInstalledPackagesAsUser",
Int::class.java, Int::class.java,
Int::class.java Int::class.java
) )
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
getInstalledPackagesAsUser.invoke(pm, flags, userId) as List<PackageInfo> m.invoke(pm, 0, userId) as List<PackageInfo>
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e(TAG, "err", e) Log.e(TAG, "getInstalledPackagesAsUser", e)
ArrayList() emptyList()
}
}
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
} }
} }
} }

View file

@ -1,7 +1,8 @@
package com.sukisu.ultra.ui package com.sukisu.ultra.ui
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.Intent
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
@ -9,13 +10,10 @@ 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.WindowInsets import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.filled.*
import androidx.compose.material3.Scaffold import androidx.compose.material3.*
import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.*
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
@ -26,6 +24,8 @@ 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 io.sukisu.ultra.UltraToolInstall import com.sukisu.ultra.ui.component.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import zako.zako.zako.zakoui.activity.component.BottomBar import com.sukisu.ultra.ui.activity.component.BottomBar
import zako.zako.zako.zakoui.activity.util.* import com.sukisu.ultra.ui.activity.util.*
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var superUserViewModel: SuperUserViewModel private lateinit var superUserViewModel: SuperUserViewModel
@ -50,21 +50,18 @@ class MainActivity : ComponentActivity() {
val showKpmInfo: Boolean = false val showKpmInfo: Boolean = false
) )
private lateinit var themeChangeObserver: ThemeChangeContentObserver private var showConfirmationDialog = mutableStateOf(false)
private var pendingZipFiles = mutableStateOf<List<ZipFileInfo>>(emptyList())
// 添加标记避免重复初始化 private lateinit var themeChangeObserver: ThemeChangeContentObserver
private var isInitialized = false private var isInitialized = false
override fun attachBaseContext(newBase: Context) { override fun attachBaseContext(newBase: Context?) {
val context = LocaleUtils.applyLocale(newBase) super.attachBaseContext(newBase?.let { LocaleHelper.applyLanguage(it) })
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)
@ -77,6 +74,11 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val isManager = Natives.isManager
if (isManager && !Natives.requireNewKernel()) {
install()
}
// 使用标记控制初始化流程 // 使用标记控制初始化流程
if (!isInitialized) { if (!isInitialized) {
initializeViewModels() initializeViewModels()
@ -84,6 +86,39 @@ 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()
@ -94,6 +129,38 @@ 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
@ -187,32 +254,17 @@ 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()
// 仅在需要时刷新数据 // 仅在需要时刷新数据
@ -228,7 +280,6 @@ 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()
@ -253,13 +304,4 @@ class MainActivity : ComponentActivity() {
e.printStackTrace() e.printStackTrace()
} }
} }
override fun onConfigurationChanged(newConfig: Configuration) {
try {
super.onConfigurationChanged(newConfig)
LocaleUtils.applyLanguageSetting(this)
} catch (e: Exception) {
e.printStackTrace()
}
}
} }

View file

@ -1,4 +1,4 @@
package zako.zako.zako.zakoui.activity.component package com.sukisu.ultra.ui.activity.component
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@ -15,21 +15,20 @@ import com.ramcosta.composedestinations.spec.RouteOrDirection
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.ui.MainActivity import com.sukisu.ultra.ui.MainActivity
import com.sukisu.ultra.ui.activity.util.*
import com.sukisu.ultra.ui.activity.util.AppData.getKpmVersionUse
import com.sukisu.ultra.ui.screen.BottomBarDestination import com.sukisu.ultra.ui.screen.BottomBarDestination
import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha 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 zako.zako.zako.zakoui.activity.util.AppData import com.sukisu.ultra.ui.util.*
import zako.zako.zako.zakoui.activity.util.AppData.DataRefreshManager
import zako.zako.zako.zakoui.activity.util.AppData.getKpmVersionUse
@SuppressLint("ContextCastToActivity") @SuppressLint("ContextCastToActivity")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BottomBar(navController: NavHostController) { fun BottomBar(navController: NavHostController) {
val navigator = navController.rememberDestinationsNavigator() val navigator = navController.rememberDestinationsNavigator()
val isFullFeatured = AppData.isFullFeatured(ksuApp.packageName) val isFullFeatured = AppData.isFullFeatured()
val kpmVersion = getKpmVersionUse() val kpmVersion = getKpmVersionUse()
val cardColor = MaterialTheme.colorScheme.surfaceContainer val cardColor = MaterialTheme.colorScheme.surfaceContainer
val activity = LocalContext.current as MainActivity val activity = LocalContext.current as MainActivity
@ -40,9 +39,9 @@ fun BottomBar(navController: NavHostController) {
val showKpmInfo = settings.showKpmInfo val showKpmInfo = settings.showKpmInfo
// 收集计数数据 // 收集计数数据
val superuserCount by DataRefreshManager.superuserCount.collectAsState() val superuserCount by AppData.DataRefreshManager.superuserCount.collectAsState()
val moduleCount by DataRefreshManager.moduleCount.collectAsState() val moduleCount by AppData.DataRefreshManager.moduleCount.collectAsState()
val kpmModuleCount by DataRefreshManager.kpmModuleCount.collectAsState() val kpmModuleCount by AppData.DataRefreshManager.kpmModuleCount.collectAsState()
NavigationBar( NavigationBar(

View file

@ -1,8 +1,9 @@
package zako.zako.zako.zakoui.activity.util package com.sukisu.ultra.ui.activity.util
import android.content.Context import android.content.Context
import android.database.ContentObserver import android.database.ContentObserver
import android.os.Handler import android.os.Handler
import android.provider.Settings
import androidx.core.content.edit import androidx.core.content.edit
import com.sukisu.ultra.ui.MainActivity import com.sukisu.ultra.ui.MainActivity
import com.sukisu.ultra.ui.theme.CardConfig import com.sukisu.ultra.ui.theme.CardConfig
@ -56,7 +57,7 @@ object ThemeUtils {
} }
activity.contentResolver.registerContentObserver( activity.contentResolver.registerContentObserver(
android.provider.Settings.System.getUriFor("ui_night_mode"), Settings.System.getUriFor("ui_night_mode"),
false, false,
contentObserver contentObserver
) )

View file

@ -0,0 +1,236 @@
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()
}
}
}
}

View file

@ -8,11 +8,16 @@ 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
@ -428,7 +433,17 @@ 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 {
@ -439,16 +454,15 @@ 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.WRAP_CONTENT ViewGroup.LayoutParams.MATCH_PARENT,
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())
} }
) )
}
} }

View file

@ -1,223 +0,0 @@
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
)
}
}
}
}

View file

@ -0,0 +1,441 @@
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)
)
}
}

View file

@ -8,7 +8,7 @@ import com.sukisu.ultra.ksuApp
fun KsuIsValid( fun KsuIsValid(
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
val isManager = Natives.becomeManager(ksuApp.packageName) val isManager = Natives.isManager
val ksuVersion = if (isManager) Natives.version else null val ksuVersion = if (isManager) Natives.version else null
if (ksuVersion != null) { if (ksuVersion != null) {

View file

@ -0,0 +1,250 @@
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
)
}
}

View file

@ -21,7 +21,6 @@ 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,
@ -29,7 +28,6 @@ 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
@ -53,23 +51,15 @@ 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( animationSpec = tween(animationDurationMs, easing = FastOutSlowInEasing),
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( animationSpec = tween(animationDurationMs, easing = FastOutSlowInEasing),
durationMillis = animationDurationMs,
easing = FastOutSlowInEasing
),
label = "mainButtonScale" label = "mainButtonScale"
) )
@ -77,14 +67,9 @@ 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) { targetValue = if (isExpanded) -(buttonSpacing.value * (index + 1)) else 0f,
-(buttonSpacing.value * (index + 1))
} else {
0f
},
animationSpec = tween( animationSpec = tween(
durationMillis = animationDurationMs, durationMillis = animationDurationMs,
delayMillis = if (isExpanded) { delayMillis = if (isExpanded) {
@ -125,7 +110,6 @@ fun VerticalExpandableFab(
label = "fabAlpha$index" label = "fabAlpha$index"
) )
// 子按钮容器(包含标签)
Row( Row(
modifier = Modifier modifier = Modifier
.offset(y = animatedOffsetY.dp) .offset(y = animatedOffsetY.dp)
@ -134,7 +118,6 @@ 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(
@ -161,7 +144,6 @@ fun VerticalExpandableFab(
} }
} }
// 子按钮
SmallFloatingActionButton( SmallFloatingActionButton(
onClick = { onClick = {
menuItem.onClick() menuItem.onClick()
@ -193,15 +175,12 @@ fun VerticalExpandableFab(
} }
} }
// 主按钮
FloatingActionButton( FloatingActionButton(
onClick = { onClick = {
onMainButtonClick?.invoke() onMainButtonClick?.invoke()
isExpanded = !isExpanded isExpanded = !isExpanded
}, },
modifier = Modifier modifier = Modifier.size(buttonSize).scale(mainButtonScale),
.size(buttonSize)
.scale(mainButtonScale),
elevation = FloatingActionButtonDefaults.elevation( elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 6.dp, defaultElevation = 6.dp,
pressedElevation = 8.dp, pressedElevation = 8.dp,
@ -221,7 +200,6 @@ fun VerticalExpandableFab(
} }
} }
// 预设菜单项
object FabMenuPresets { object FabMenuPresets {
fun getScrollMenuItems( fun getScrollMenuItems(
onScrollToTop: () -> Unit, onScrollToTop: () -> Unit,

View file

@ -43,7 +43,7 @@ fun TemplateConfig(
) { ) {
OutlinedTextField( OutlinedTextField(
modifier = Modifier modifier = Modifier
.menuAnchor(MenuAnchorType.PrimaryNotEditable) .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
.fillMaxWidth(), .fillMaxWidth(),
readOnly = true, readOnly = true,
label = { Text(stringResource(R.string.profile_template)) }, label = { Text(stringResource(R.string.profile_template)) },

View file

@ -248,7 +248,12 @@ 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(profile.copy(rootUseDefault = it == Mode.Default)) onProfileChange(
profile.copy(
rootUseDefault = it == Mode.Default,
rootTemplate = null
)
)
} }
mode = it mode = it
} }
@ -479,7 +484,10 @@ private fun ProfileBox(
@SuppressLint("UnusedBoxWithConstraintsScope") @SuppressLint("UnusedBoxWithConstraintsScope")
@Composable @Composable
private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) { private fun AppMenuBox(
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
@ -499,15 +507,15 @@ private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) {
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),

View file

@ -1,5 +1,7 @@
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
@ -30,6 +32,7 @@ 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
@ -48,6 +51,9 @@ 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
@ -119,6 +125,29 @@ 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() }
@ -203,8 +232,25 @@ 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("")) { // clear command if (tempText.startsWith("")) { // clear command
@ -297,6 +343,26 @@ 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"
@ -319,6 +385,9 @@ 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()
@ -330,6 +399,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
} }
} }
} }
}
BackHandler(enabled = true) { BackHandler(enabled = true) {
onBack() onBack()
@ -619,7 +689,7 @@ private fun TopBar(
) )
} }
suspend fun getModuleNameFromUri(context: android.content.Context, uri: Uri): String { suspend fun getModuleNameFromUri(context: Context, uri: Uri): String {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
try { try {
if (uri == Uri.EMPTY) { if (uri == Uri.EMPTY) {
@ -637,7 +707,7 @@ suspend fun getModuleNameFromUri(context: android.content.Context, uri: Uri): St
@Parcelize @Parcelize
sealed class FlashIt : Parcelable { sealed class FlashIt : Parcelable {
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : FlashIt() data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean, val partition: String? = null) : 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() // 模块更新
@ -666,6 +736,7 @@ fun flashIt(
flashIt.boot, flashIt.boot,
flashIt.lkm, flashIt.lkm,
flashIt.ota, flashIt.ota,
flashIt.partition,
onFinish, onFinish,
onStdout, onStdout,
onStderr onStderr

View file

@ -50,20 +50,21 @@ 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.util.SuSFSManager import com.sukisu.ultra.ui.susfs.util.SuSFSManager
import com.sukisu.ultra.ui.util.checkNewVersion import com.sukisu.ultra.ui.util.checkNewVersion
import com.sukisu.ultra.ui.util.getSuSFS import com.sukisu.ultra.ui.util.getSuSFSVersion
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/5/31. * @date 2025/9/29.
*/ */
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Destination<RootGraph>(start = true) @Destination<RootGraph>(start = true)
@ -73,16 +74,35 @@ 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) {
viewModel.loadUserSettings(context)
coroutineScope.launch { coroutineScope.launch {
viewModel.refreshAllData(context) viewModel.loadCoreData()
delay(100)
viewModel.loadExtendedData(context)
}
// 启动数据变化监听
coroutineScope.launch {
while (true) {
delay(5000) // 每5秒检查一次
viewModel.autoRefreshIfNeeded(context)
}
} }
} }
LaunchedEffect(Unit) { // 监听数据刷新状态流
viewModel.loadUserSettings(context) LaunchedEffect(viewModel.dataRefreshTrigger) {
viewModel.initializeData() viewModel.dataRefreshTrigger.collect { _ ->
viewModel.checkForUpdates(context) // 数据刷新时的额外处理可以在这里添加
}
} }
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
@ -92,22 +112,14 @@ 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)
@ -121,13 +133,16 @@ 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) navigator.navigate(InstallScreenDestination(preselectedKernelUri = null))
} }
) )
// 警告信息
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(
@ -143,12 +158,25 @@ 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,
@ -158,13 +186,25 @@ fun HomeScreen(navigator: DestinationsNavigator) {
lkmMode = viewModel.systemStatus.lkmMode, lkmMode = viewModel.systemStatus.lkmMode,
) )
if (!viewModel.isSimpleMode) { // 链接卡片
if (!viewModel.isHideLinkCard) { if (!viewModel.isSimpleMode && !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))
} }
} }
@ -231,7 +271,8 @@ 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
@ -253,8 +294,10 @@ private fun TopBar(
scrolledContainerColor = cardColor.copy(alpha = cardAlpha) scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
), ),
actions = { actions = {
if (isDataLoaded) {
// SuSFS 配置按钮 // SuSFS 配置按钮
if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) { val susfsVersion = getSuSFSVersion()
if (susfsVersion.isNotEmpty() && !susfsVersion.startsWith("[-]") && SuSFSManager.isBinaryAvailable(context)) {
IconButton(onClick = { IconButton(onClick = {
navigator.navigate(SuSFSConfigScreenDestination) navigator.navigate(SuSFSConfigScreenDestination)
}) { }) {
@ -294,6 +337,7 @@ private fun TopBar(
} }
} }
} }
}
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
@ -741,7 +785,6 @@ 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")) {
@ -785,21 +828,15 @@ 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 {
isSUS_SU && isKprobesHook -> {
append(" (${systemInfo.suSFSVariant})")
if (systemInfo.susSUMode.isNotEmpty()) {
append(" ${stringResource(R.string.sus_su_mode)} ${systemInfo.susSUMode}")
}
}
Natives.getHookType() == "Manual" -> { Natives.getHookType() == "Manual" -> {
append(" (${stringResource(R.string.manual_hook)})") append(" (${stringResource(R.string.manual_hook)})")
} }
Natives.getHookType() == "Inline" -> {
append(" (${stringResource(R.string.inline_hook)})")
}
else -> { else -> {
append(" (${Natives.getHookType()})") append(" (${Natives.getHookType()})")
} }
@ -829,7 +866,7 @@ private fun StatusCardPreview() {
StatusCard( StatusCard(
HomeViewModel.SystemStatus( HomeViewModel.SystemStatus(
isManager = true, isManager = true,
ksuVersion = 20000, ksuVersion = 40000,
lkmMode = true, lkmMode = true,
kernelVersion = KernelVersion(5, 10, 101), kernelVersion = KernelVersion(5, 10, 101),
isRootAvailable = true isRootAvailable = true
@ -858,6 +895,23 @@ 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() {

View file

@ -1,8 +1,10 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.provider.OpenableColumns
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@ -17,7 +19,9 @@ import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Input
import androidx.compose.material.icons.filled.AutoFixHigh import androidx.compose.material.icons.filled.AutoFixHigh
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.FileUpload import androidx.compose.material.icons.filled.FileUpload
import androidx.compose.material.icons.filled.Security import androidx.compose.material.icons.filled.Security
import androidx.compose.material3.* import androidx.compose.material3.*
@ -33,6 +37,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
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.core.net.toUri
import com.maxkeppeker.sheets.core.models.base.Header import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.list.ListDialog import com.maxkeppeler.sheets.list.ListDialog
@ -47,7 +52,7 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.sukisu.ultra.getKernelVersion import com.sukisu.ultra.getKernelVersion
import com.sukisu.ultra.ui.component.DialogHandle import com.sukisu.ultra.ui.component.DialogHandle
import com.sukisu.ultra.ui.component.SlotSelectionDialog import com.sukisu.ultra.ui.component.SuperDropdown
import com.sukisu.ultra.ui.component.rememberConfirmDialog import com.sukisu.ultra.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.component.rememberCustomDialog import com.sukisu.ultra.ui.component.rememberCustomDialog
import com.sukisu.ultra.ui.theme.CardConfig import com.sukisu.ultra.ui.theme.CardConfig
@ -56,6 +61,7 @@ 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.util.* import com.sukisu.ultra.ui.util.*
import zako.zako.zako.zakoui.screen.kernelFlash.component.SlotSelectionDialog
/** /**
* @author ShirkNeko * @author ShirkNeko
@ -71,19 +77,49 @@ enum class KpmPatchOption {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
fun InstallScreen(navigator: DestinationsNavigator) { fun InstallScreen(
navigator: DestinationsNavigator,
preselectedKernelUri: String? = null
) {
val context = LocalContext.current
var installMethod by remember { mutableStateOf<InstallMethod?>(null) } var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) } var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
var kpmPatchOption by remember { mutableStateOf(KpmPatchOption.FOLLOW_KERNEL) } var kpmPatchOption by remember { mutableStateOf(KpmPatchOption.FOLLOW_KERNEL) }
val context = LocalContext.current
var showRebootDialog by remember { mutableStateOf(false) } var showRebootDialog by remember { mutableStateOf(false) }
var showSlotSelectionDialog by remember { mutableStateOf(false) } var showSlotSelectionDialog by remember { mutableStateOf(false) }
var showKpmPatchDialog by remember { mutableStateOf(false) }
var tempKernelUri by remember { mutableStateOf<Uri?>(null) } var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
val kernelVersion = getKernelVersion() val kernelVersion = getKernelVersion()
val isGKI = kernelVersion.isGKI() val isGKI = kernelVersion.isGKI()
val isAbDevice = isAbDevice() val isAbDevice = produceState(initialValue = false) {
value = isAbDevice()
}.value
val summary = stringResource(R.string.horizon_kernel_summary) val summary = stringResource(R.string.horizon_kernel_summary)
// 处理预选的内核文件
LaunchedEffect(preselectedKernelUri) {
preselectedKernelUri?.let { uriString ->
try {
val preselectedUri = uriString.toUri()
val horizonMethod = InstallMethod.HorizonKernel(
uri = preselectedUri,
summary = summary
)
installMethod = horizonMethod
tempKernelUri = preselectedUri
if (isAbDevice) {
showSlotSelectionDialog = true
} else {
showKpmPatchDialog = true
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
if (showRebootDialog) { if (showRebootDialog) {
RebootDialog( RebootDialog(
show = true, show = true,
@ -103,6 +139,10 @@ fun InstallScreen(navigator: DestinationsNavigator) {
) )
} }
var partitionSelectionIndex by remember { mutableIntStateOf(0) }
var partitionsState by remember { mutableStateOf<List<String>>(emptyList()) }
var hasCustomSelected by remember { mutableStateOf(false) }
val onInstall = { val onInstall = {
installMethod?.let { method -> installMethod?.let { method ->
when (method) { when (method) {
@ -119,10 +159,13 @@ fun InstallScreen(navigator: DestinationsNavigator) {
} }
} }
else -> { else -> {
val isOta = method is InstallMethod.DirectInstallToInactiveSlot
val partitionSelection = partitionsState.getOrNull(partitionSelectionIndex)
val flashIt = FlashIt.FlashBoot( val flashIt = FlashIt.FlashBoot(
boot = if (method is InstallMethod.SelectFile) method.uri else null, boot = if (method is InstallMethod.SelectFile) method.uri else null,
lkm = lkmSelection, lkm = lkmSelection,
ota = method is InstallMethod.DirectInstallToInactiveSlot ota = isOta,
partition = partitionSelection
) )
navigator.navigate(FlashScreenDestination(flashIt)) navigator.navigate(FlashScreenDestination(flashIt))
} }
@ -143,6 +186,20 @@ fun InstallScreen(navigator: DestinationsNavigator) {
summary = summary summary = summary
) )
installMethod = horizonMethod installMethod = horizonMethod
if (preselectedKernelUri != null) {
showKpmPatchDialog = true
}
}
)
KpmPatchSelectionDialog(
show = showKpmPatchDialog,
currentOption = kpmPatchOption,
onDismiss = { showKpmPatchDialog = false },
onOptionSelected = { option ->
kpmPatchOption = option
showKpmPatchDialog = false
} }
) )
@ -165,6 +222,32 @@ fun InstallScreen(navigator: DestinationsNavigator) {
} }
} }
val selectLkmLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
val isKo = isKoFile(context, uri)
if (isKo) {
lkmSelection = LkmSelection.LkmUri(uri)
} else {
lkmSelection = LkmSelection.KmiNone
Toast.makeText(
context,
context.getString(R.string.install_only_support_ko_file),
Toast.LENGTH_SHORT
).show()
}
}
}
}
val onLkmUpload = {
selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream"
})
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold( Scaffold(
@ -194,6 +277,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
showSlotSelectionDialog = true showSlotSelectionDialog = true
} else { } else {
installMethod = method installMethod = method
showKpmPatchDialog = true
} }
} else { } else {
installMethod = method installMethod = method
@ -204,32 +288,103 @@ fun InstallScreen(navigator: DestinationsNavigator) {
selectedMethod = installMethod selectedMethod = installMethod
) )
// 选择LKM直接安装分区
AnimatedVisibility(
visible = installMethod is InstallMethod.DirectInstall || installMethod is InstallMethod.DirectInstallToInactiveSlot,
enter = fadeIn() + expandVertically(),
exit = shrinkVertically() + fadeOut()
) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp) .padding(16.dp)
) { ) {
(lkmSelection as? LkmSelection.LkmUri)?.let {
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant), colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
elevation = getCardElevation(), elevation = getCardElevation(),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 12.dp) .padding(bottom = 12.dp),
.clip(MaterialTheme.shapes.medium)
.shadow(
elevation = cardElevation,
shape = MaterialTheme.shapes.medium,
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
)
) { ) {
val isOta = installMethod is InstallMethod.DirectInstallToInactiveSlot
val suffix = produceState(initialValue = "", isOta) {
value = getSlotSuffix(isOta)
}.value
val partitions = produceState(initialValue = emptyList()) {
value = getAvailablePartitions()
}.value
val defaultPartition = produceState(initialValue = "") {
value = getDefaultPartition()
}.value
partitionsState = partitions
val displayPartitions = partitions.map { name ->
if (defaultPartition == name) "$name (default)" else name
}
val defaultIndex = partitions.indexOf(defaultPartition).takeIf { it >= 0 } ?: 0
if (!hasCustomSelected) partitionSelectionIndex = defaultIndex
SuperDropdown(
items = displayPartitions,
selectedIndex = partitionSelectionIndex,
title = "${stringResource(R.string.install_select_partition)} (${suffix})",
onSelectedIndexChange = { index ->
hasCustomSelected = true
partitionSelectionIndex = index
},
leftAction = {
Icon(
Icons.Default.Edit,
tint = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(end = 16.dp),
contentDescription = null
)
}
)
}
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
if (isGKI) {
// 使用本地的LKM文件
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
elevation = getCardElevation(),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp),
) {
ListItem(
headlineContent = {
Text(stringResource(id = R.string.install_upload_lkm_file))
},
supportingContent = {
(lkmSelection as? LkmSelection.LkmUri)?.let {
Text( Text(
text = stringResource( stringResource(
id = R.string.selected_lkm, id = R.string.selected_lkm,
it.uri.lastPathSegment ?: "(file)" it.uri.lastPathSegment ?: "(file)"
), )
style = MaterialTheme.typography.bodyMedium, )
modifier = Modifier.padding(16.dp) }
},
leadingContent = {
Icon(
Icons.AutoMirrored.Filled.Input,
contentDescription = null
)
},
modifier = Modifier
.fillMaxWidth()
.clickable { onLkmUpload() }
) )
} }
} }
@ -242,12 +397,6 @@ fun InstallScreen(navigator: DestinationsNavigator) {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 12.dp) .padding(bottom = 12.dp)
.clip(MaterialTheme.shapes.medium)
.shadow(
elevation = cardElevation,
shape = MaterialTheme.shapes.medium,
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
)
) { ) {
Text( Text(
text = stringResource( text = stringResource(
@ -269,12 +418,6 @@ fun InstallScreen(navigator: DestinationsNavigator) {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 12.dp) .padding(bottom = 12.dp)
.clip(MaterialTheme.shapes.medium)
.shadow(
elevation = cardElevation,
shape = MaterialTheme.shapes.medium,
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
)
) { ) {
Text( Text(
text = when (kpmPatchOption) { text = when (kpmPatchOption) {
@ -316,6 +459,47 @@ fun InstallScreen(navigator: DestinationsNavigator) {
} }
} }
@Composable
private fun KpmPatchSelectionDialog(
show: Boolean,
currentOption: KpmPatchOption,
onDismiss: () -> Unit,
onOptionSelected: (KpmPatchOption) -> Unit
) {
if (show) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.kpm_patch_options)) },
text = {
Column {
Text(
text = stringResource(R.string.kpm_patch_description),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
KpmPatchOptionGroup(
selectedOption = currentOption,
onOptionChanged = onOptionSelected
)
}
},
confirmButton = {
TextButton(
onClick = { onOptionSelected(currentOption) }
) {
Text(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(android.R.string.cancel))
}
}
)
}
}
@Composable @Composable
private fun RebootDialog( private fun RebootDialog(
show: Boolean, show: Boolean,
@ -378,15 +562,15 @@ private fun SelectInstallMethod(
selectedMethod: InstallMethod? = null selectedMethod: InstallMethod? = null
) { ) {
val rootAvailable = rootAvailable() val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice() val isAbDevice = produceState(initialValue = false) {
value = isAbDevice()
}.value
val defaultPartitionName = produceState(initialValue = "boot") {
value = getDefaultPartition()
}.value
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary) val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
val selectFileTip = stringResource( val selectFileTip = stringResource(
id = R.string.select_file_tip, id = R.string.select_file_tip, defaultPartitionName
if (isInitBoot()) {
"init_boot / vendor_boot ${stringResource(R.string.select_file_tip_vendor)}"
} else {
"boot"
}
) )
val radioOptions = mutableListOf<InstallMethod>( val radioOptions = mutableListOf<InstallMethod>(
@ -404,6 +588,10 @@ private fun SelectInstallMethod(
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) } var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
var currentSelectingMethod by remember { mutableStateOf<InstallMethod?>(null) } var currentSelectingMethod by remember { mutableStateOf<InstallMethod?>(null) }
LaunchedEffect(selectedMethod) {
selectedOption = selectedMethod
}
val selectImageLauncher = rememberLauncherForActivityResult( val selectImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult() contract = ActivityResultContracts.StartActivityForResult()
) { ) {
@ -479,7 +667,6 @@ private fun SelectInstallMethod(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 16.dp) .padding(bottom = 16.dp)
.clip(MaterialTheme.shapes.large)
) { ) {
MaterialTheme( MaterialTheme(
colorScheme = MaterialTheme.colorScheme.copy( colorScheme = MaterialTheme.colorScheme.copy(
@ -518,7 +705,7 @@ private fun SelectInstallMethod(
bottom = 16.dp bottom = 16.dp
) )
) { ) {
radioOptions.take(3).forEach { option -> radioOptions.filter { it !is InstallMethod.HorizonKernel }.forEach { option ->
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
Surface( Surface(
color = if (option.javaClass == selectedOption?.javaClass) color = if (option.javaClass == selectedOption?.javaClass)
@ -586,7 +773,6 @@ private fun SelectInstallMethod(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 12.dp) .padding(bottom = 12.dp)
.clip(MaterialTheme.shapes.large)
) { ) {
MaterialTheme( MaterialTheme(
colorScheme = MaterialTheme.colorScheme.copy( colorScheme = MaterialTheme.colorScheme.copy(
@ -884,6 +1070,31 @@ private fun TopBar(
) )
} }
private fun isKoFile(context: Context, uri: Uri): Boolean {
val seg = uri.lastPathSegment ?: ""
if (seg.endsWith(".ko", ignoreCase = true)) return true
return try {
context.contentResolver.query(
uri,
arrayOf(OpenableColumns.DISPLAY_NAME),
null,
null,
null
)?.use { cursor ->
val idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (idx != -1 && cursor.moveToFirst()) {
val name = cursor.getString(idx)
name?.endsWith(".ko", ignoreCase = true) == true
} else {
false
}
} ?: false
} catch (_: Throwable) {
false
}
}
@Preview @Preview
@Composable @Composable
fun SelectInstallPreview() { fun SelectInstallPreview() {

Some files were not shown because too many files have changed in this diff Show more