Compare commits

...

366 Commits

Author SHA1 Message Date
weishu
b52bf53d01 kernel: Fix compile err 2023-08-09 18:51:03 +08:00
weishu
7bdb885816 kernel: fix probe_kernel_read failed to read user addr 2023-08-09 18:37:30 +08:00
weishu
cde3e95180 kernel: Fix manager recognize err 2023-08-09 16:28:22 +08:00
Weblate (bot)
7ecb4b03ee Translations update from Hosted Weblate (#832)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for
[KernelSU/Manager](https://hosted.weblate.org/projects/kernelsu/manager/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widgets/kernelsu/-/manager/horizontal-auto.svg)

---------

Co-authored-by: weishu tian <twsxtd@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: cachiusa <nhat.dogpro@outlook.com>
Co-authored-by: dabao1955 <dabao1955@163.com>
2023-08-09 15:00:16 +08:00
weishu
e8a90aadb2 ci: Fix No space left on device (#833)
For android13 gki kernels, Google puts lots of unused file to kernel
source tree, while the Github Action only has 14G disk size and the ci
may fail because of "No space left on device". 

The `repo` tool will try pull all the refs and tags with depth=INT_MAX if it found the SHA1 hash doesn't exist on remote server even if you force it to fetch with `depth=1` and `--no-tags`. So we use the version `v2.16` which doesn't check it.

And also, using a fixed repo version can avoid randomly failure of building.
2023-08-09 13:32:48 +08:00
weishu
beaa048be3 kernel: don't trigger page fault when become manager. fix #836 2023-08-09 12:33:05 +08:00
Akari
936b650f12 update README_PT-BR.md (#835) 2023-08-09 11:16:56 +08:00
weishu
747c91d5c8 manager: remove require_kernel_version for re-translation. 2023-08-08 11:25:53 +08:00
Aquarius223
5023d0ab1d manager: Corrected "Kernel version" and "KernelSU version" string (#825)
* Avoid misunderstanding "The current kernel version %d is too low" Many
people misunderstand that the version of the Linux kernel is too low, so
the version of KernelSU is low and they go looking for a kernel with an
upgraded version of the Linux kernel.

 Correct the string of require_kernel_version to avoid misunderstanding,
 Now let them look for the updated KernelSU version of the kernel.

(cherry picked from commit dcc4ad10f81b1531b551b2b44a3b9cbdc0e489e9)
Change-Id: I4373b006d45ed98e02effa4556e8a9e8c0b70f14

Co-authored-by: admin <paper@localhost>
2023-08-08 11:21:40 +08:00
Weblate (bot)
5b638c876e Translations update from Hosted Weblate (#829)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for
[KernelSU/Manager](https://hosted.weblate.org/projects/kernelsu/manager/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widgets/kernelsu/-/manager/horizontal-auto.svg)

---------

Co-authored-by: Pierre GRASSER <piorrro33@outlook.com>
Co-authored-by: Kazuki Nakashima <flukfik41@gmail.com>
2023-08-08 10:32:34 +08:00
weishu
d6a7231fae manager: module description and name is optional. 2023-08-03 23:15:21 +08:00
weishu
06681a2490 ksud: restore selinux context for unlabeled module files on boot. fix #817 2023-08-03 12:00:28 +08:00
weishu
278cbef3ec ksud: don't follow link when restore file context 2023-08-03 12:00:28 +08:00
The_second_Tom
a83390b0ec Kernel: fix filp_open in kernel below 4.9 (#822)
Use current_cred()->session_keyring to check whether session_keyring
installed or not. close #814
2023-08-01 20:51:32 +08:00
weishu
907bcad1a7 kernel: use ordered work queue to avoid timing issues 2023-08-01 20:47:43 +08:00
Weblate (bot)
4c0a36785b Translations update from Hosted Weblate (#805)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for
[KernelSU/Manager](https://hosted.weblate.org/projects/kernelsu/manager/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widgets/kernelsu/-/manager/horizontal-auto.svg)

---------

Co-authored-by: Ali Beyaz <alipolatbeyaz@gmail.com>
Co-authored-by: dabao1955 <dabao1955@163.com>
Co-authored-by: Tian xiang <minepzh@outlook.com>
2023-08-01 17:02:43 +08:00
AzukiAtsui
983ad2c1fd Fix the directory pathname while moving directories (#816)
Fix the duplicate directory name error that occurs when the destination
directory already exists while moving folders.
2023-08-01 13:40:56 +08:00
hopez13
a3590b767e Docs: Fixed Word Repetition (#818) 2023-08-01 10:06:56 +08:00
DawfukFR
12e00dc717 [add device]: Oneplus8/8T/8P/9R (opkona) (#819)
Custom kernel for Oneplus SM8250 devices with support of KernelSU (using
the command : curl -LSs
"https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh"
| bash -)

Precompiled kernel can be found here : 
https://t.me/StellarisRelease (stable)
https://t.me/DawfukSpace (testing)

I have tested the KernelSU feature on this kernel, everything works
fine.
Current version used : 11165
KernelSU Manager signature size: 0x033b
KernelSU Manager signature hash: 0xb0b91415
2023-08-01 10:06:26 +08:00
weishu
e00a355fa8 website: Add docs for boot-completed stage 2023-07-31 19:58:45 +08:00
weishu
e95c5a9675 ksud: support common & module boot-completed.sh 2023-07-31 19:50:53 +08:00
weishu
d0b8144b96 manager: refresh module screen if it comes from install screen. fix #758 2023-07-31 16:15:42 +08:00
weishu
d16d3f87a6 manager: block vol up & down for module installation. fix #815 2023-07-31 15:27:53 +08:00
weishu
76decba8d9 manager: auto scroll for module installation. #815 2023-07-31 15:06:00 +08:00
dabao1955
d75678fca0 Update Custom issue temple (#811) 2023-07-28 22:27:52 +08:00
TinyHai
abe1fa471d manager: fix alignment and padding of some text in ModuleScreen (#812) 2023-07-28 22:27:03 +08:00
weishu
07a430aa5b ksud: re-enable lto 2023-07-28 22:23:03 +08:00
weishu
df9cf61575 ksud: fix su args parse error. close #736 2023-07-28 22:21:12 +08:00
TinyHai
bfe8c2eecd manager: a small fix to WarningCard (#809) 2023-07-28 17:34:57 +08:00
TinyHai
b732765811 manager: fix the text 'No module installed' is not centered (#803) 2023-07-25 18:47:09 +08:00
Weblate (bot)
a966252fa5 Translations update from Hosted Weblate (#783)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for
[KernelSU/Manager](https://hosted.weblate.org/projects/kernelsu/manager/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widgets/kernelsu/-/manager/horizontal-auto.svg)

---------

Co-authored-by: dabao1955 <dabao1955@163.com>
Co-authored-by: $D&fX!a&#YA6ZbJkZN$3 <mariusnnnn@gmail.com>
Co-authored-by: ia. Shovon <tecifac284@kameili.com>
Co-authored-by: Ruben Guerra <rubenguerrasr5870@gmail.com>
Co-authored-by: Fiqri Ardyansyah <fiqri0927936@gmail.com>
Co-authored-by: Kazuki Nakashima <flukfik41@gmail.com>
Co-authored-by: TheNoFace <fprhqkrtk303@naver.com>
Co-authored-by: Ali Beyaz <alipolatbeyaz@gmail.com>
Co-authored-by: Kirill Azimov <azimxv@yandex.ru>
2023-07-25 09:21:43 +08:00
weishu
3664003260 ksud: fix common post-fs-data.d may not execute if no modules are enabled 2023-07-24 13:39:51 +08:00
Flame
978178afc0 [add device]: POCO F3/Redmi K40/Mi 11X (alioth) (#799)
This pull request includes a modified LineageOS kernel with the
implementation of KernelSU for POCO F3/Redmi K40/Mi 11X (alioth), adding
them to the list of unofficially supported devices.
2023-07-23 10:44:30 +08:00
Asriadi Rahim
16f6f30eae Again add unofficial support Google Pixel 2/2XL (#797)
* i dont have any clue what i am doing...sory tian sur...hhh

Signed-off-by: Asriadi Rahim <asriadirahim03@gmail.com>
2023-07-22 00:22:11 +08:00
Akari
63851f8c88 Update README_PT-BR.md (#793)
Added information that was updated from
[README.md](https://github.com/tiann/KernelSU/blob/main/README.md) to
Brazilian Portuguese. some of this information:
- fixed some lines
- added information about Translate
- added information about the App Profile
2023-07-21 15:54:14 +08:00
Giovany
34c6765752 Support m30s (#791)
add , missing
2023-07-21 15:53:01 +08:00
Giovany
c23d1bcf58 Support Galaxy a50 (#790) 2023-07-21 10:08:23 +08:00
weishu
6ae7e1624f Merge pull request #788 from Sanju0910/patch-1
avicii: Update repository link
2023-07-21 00:10:07 +08:00
Sreeshankar K
ba4b014a99 Merge branch 'main' into patch-1 2023-07-20 19:42:16 +05:30
weishu
f192638943 Merge pull request #785 from dabao1955/main
README:Update Readme for Japanese Language
2023-07-20 22:05:23 +08:00
Sreeshankar K
0323ee7958 avicii: Update repository link 2023-07-20 19:34:30 +05:30
dabao1955
021ef521cb Update README_JP.md 2023-07-19 21:29:04 +08:00
dabao1955
34086cd445 Update README_JP.md 2023-07-19 21:26:26 +08:00
dabao1955
c306eddee8 Update README_JP.md 2023-07-19 21:21:07 +08:00
dabao1955
c72f7d750e Update README_JP.md 2023-07-19 21:19:37 +08:00
weishu
f9a91848ad Merge pull request #774 from tiann/translation
Translations update from Hosted Weblate
2023-07-19 09:40:08 +08:00
Mantas
cd8013a616 Translated using Weblate (Lithuanian)
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/lt/
2023-07-18 15:30:09 +02:00
Rəşad Qasımlı
dc536d652a Translated using Weblate (Azerbaijani)
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/az/
2023-07-18 15:30:08 +02:00
cachiusa
3114f6d7f6 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/vi/
2023-07-18 13:18:01 +02:00
Hosted Weblate
e2f5015107 Merge remote-tracking branch 'origin/main' 2023-07-18 11:46:39 +02:00
Ali Beyaz
884dd606eb Update README_TR.md (#779) 2023-07-18 17:46:33 +08:00
Hosted Weblate
8d246a6b9d Merge remote-tracking branch 'origin/main' 2023-07-18 10:08:32 +02:00
ia. Shovon
2ee3d55c88 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 50.6% (41 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/bn_BD/
2023-07-18 10:08:32 +02:00
dabao1955
55b540bf4c repos.json:Update repo info (#778)
Some changes to repos.json:
- Redirect android_kernel_xiaomi_surya to kernel_xiaomi_surya: Because
the original warehouse has changed android_kernel_xiaomi_surya to
kernel_xiaomi_surya, this time the original address is 404
- Removed kernel repository information maintained by AkariOficial #754
2023-07-18 16:08:26 +08:00
Hosted Weblate
c0d147dcad Merge remote-tracking branch 'origin/main' 2023-07-18 06:38:50 +02:00
セリカ・シルフィル
be413fd147 CI: Support set manager signature size and hash (#757)
close #739, close #745, close #752
2023-07-18 12:38:44 +08:00
weishu tian
786f3d6441 Added translation using Weblate (Bengali (Bangladesh)) 2023-07-18 06:34:32 +02:00
Hosted Weblate
8f50b5f6e5 Merge remote-tracking branch 'origin/main' 2023-07-18 04:16:19 +02:00
セリカ・シルフィル
90639fad6c Translated using Weblate (Russian)
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/ru/
2023-07-18 04:16:19 +02:00
セリカ・シルフィル
2979434e2a Translated using Weblate (Indonesian)
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/id/
2023-07-18 04:16:18 +02:00
weishu
56d145666c ksud: fix path env, close #775 2023-07-18 10:02:06 +08:00
weishu
db5f77aa96 docs: Update README 2023-07-18 09:45:42 +08:00
weishu
5fbce8ef07 docs: Add weblate to README 2023-07-18 09:39:37 +08:00
Hosted Weblate
4cc3644416 Merge remote-tracking branch 'origin/main' 2023-07-18 03:29:36 +02:00
Jen Kung-chih
01a7678a26 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/zh_Hant/
2023-07-18 03:29:35 +02:00
Jen Kung-chih
95d22d2bb4 Translated using Weblate (Chinese (Traditional, Hong Kong))
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/zh_Hant_HK/
2023-07-18 03:29:35 +02:00
Sreeshankar K
3efb2be456 avicii: Define Kernel Name & Update repository link (#777) 2023-07-18 09:29:28 +08:00
Rəşad Qasımlı
3b7ca2d7e8 Translated using Weblate (Azerbaijani)
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/az/
2023-07-17 21:37:33 +02:00
cachiusa
7b45bc5aad Translated using Weblate (Vietnamese)
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/vi/
2023-07-17 14:32:11 +02:00
ekotin
ed1e892600 Translated using Weblate (Turkish)
Currently translated at 98.7% (80 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/tr/
2023-07-17 14:32:10 +02:00
Igor Sorocean
f188802044 Translated using Weblate (Romanian)
Currently translated at 100.0% (81 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/ro/
2023-07-17 12:21:02 +02:00
Tian xiang
965c23867f Translated using Weblate (Japanese)
Currently translated at 86.4% (70 of 81 strings)

Translation: KernelSU/Manager
Translate-URL: https://hosted.weblate.org/projects/kernelsu/manager/ja/
2023-07-17 08:43:35 +02:00
Weblate (bot)
b271b2f587 Translations update from Hosted Weblate (#772)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for
[KernelSU/Manager](https://hosted.weblate.org/projects/kernelsu/manager/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widgets/kernelsu/-/manager/horizontal-auto.svg)

---------

Co-authored-by: Navin Holkar <fakeuser1693@gmail.com>
Co-authored-by: Cabrito <zrenzo81@gmail.com>
Co-authored-by: Fiqri Ardyansyah <fiqri0927936@gmail.com>
Co-authored-by: Yuttapong Paengsai <nuppyy400@gmail.com>
Co-authored-by: Rəşad Qasımlı <rashadgasimly2005@gmail.com>
Co-authored-by: Kazuki Nakashima <flukfik41@gmail.com>
Co-authored-by: Marvin Grasberger <marvingrasberger14@gmail.com>
Co-authored-by: Mantas <mantukasjolanta@gmail.com>
Co-authored-by: Sergio <sergta@live.com>
Co-authored-by: Melo Longo <melolongo87@gmail.com>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: cachiusa <nhat.dogpro@outlook.com>
Co-authored-by: CakesTwix <cakestwix1@gmail.com>
Co-authored-by: SchweGELBin <abramjannikmichael06@gmail.com>
2023-07-17 08:57:39 +08:00
Weblate (bot)
0953f50e0c Translations update from Hosted Weblate (#771)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for
[KernelSU/Manager](https://hosted.weblate.org/projects/kernelsu/manager/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widgets/kernelsu/-/manager/horizontal-auto.svg)

---------

Co-authored-by: Karl Schrader <pokemetti@gmail.com>
Co-authored-by: weishu tian <twsxtd@gmail.com>
Co-authored-by: tcheral <lcockx@protonmail.com>
Co-authored-by: Тимур Муллаяров (Timur_23_1337) <mullaiarov2002@gmail.com>
2023-07-16 23:46:31 +08:00
Kirill
77ac974ce8 website: updated index.ts, ru_RU.ts, as well as some links (#763)
On the web page there is no link to the translation itself, I
familiarized myself with vitepress and edited the necessary configs
2023-07-13 20:59:38 +08:00
セリカ・シルフィル
d714ab0c5d CI: Add ARCVM release build (#762) 2023-07-13 20:58:45 +08:00
weishu
38eb93d5ca website: fix deploy 2023-07-13 20:11:37 +08:00
Kirill
080d5bd9e8 Translation of instructions into Russian (#761)
This project is quite extensive, decided to translate it into Russian
(further adjustments may be required)
2023-07-13 19:26:15 +08:00
weishu
d05ec41379 manager: show loading dialog when uninstall/enable/disable module. 2023-07-12 13:00:40 +08:00
weishu
d07956ead6 ksud: fix prune module 2023-07-11 22:00:50 +08:00
weishu
4e3af6dab3 ksud: refactor module iteration 2023-07-11 21:45:45 +08:00
weishu
45d96b98c5 ksud: remove update flag file before mount. if module installation is failed, the module.img will be reverted and the update flag file may exist 2023-07-11 21:28:56 +08:00
weishu
b554c66b46 ksud: uninstall module at next boot. close #740 2023-07-11 21:25:51 +08:00
Muhammed Ali
fb87d0f0f5 Support Xiaomi Redmi Note 8 and 8T (#750)
Updated

Signed-off-by: Muhammed Ali Simsek <malisimsek17@gmail.com>
2023-07-11 21:08:16 +08:00
weishu
37abe48702 manager: save stderr of installation to log file. fix #723 2023-07-11 20:10:12 +08:00
weishu
ec9babea76 ksud: Specify the block size of the ext4 image as 1K, because our subsequent size calculations are based on this; some phones such as the Samsung S23, if no default value is specified, will cause unexpected errors. close #721 2023-07-11 18:30:33 +08:00
Zackptg5
8a464ac7b2 Add boot script directory vars (#747)
Add back variable names for post-fs-data.d and service.d directories

Co-authored-by: weishu <twsxtd@gmail.com>
2023-07-11 12:39:56 +08:00
weishu
eb5a99e4b6 ci: fix rustfmt failed (#748) 2023-07-11 12:23:56 +08:00
weishu
047312e0e5 ci: fix rustfmt failed duplicated toml 2023-07-11 12:12:53 +08:00
weishu
45a25eda50 ci: fix rustfmt failed typo 2023-07-11 12:00:42 +08:00
weishu
120c2f43de ci: fix rustfmt failed 2023-07-11 11:59:58 +08:00
Gustavo Mendes
9deb820923 Update Portuguese brazilian translation (#741)
Signed-off-by: Gustavo Mendes <gusttavo.me@outlook.com>
2023-07-10 08:49:36 +08:00
Amicia De Rune
ae21d4c9fd manager: update translation Indonesian (#734)
Signed-off-by: RooGhz720 <rooghz720@gmail.com>
2023-07-09 17:02:00 +08:00
shìwēi nguyen
5381ceabae ksud: symlink by relative path when handle partitions (#737)
This allows the Magisk design-compliant module boot script to access
`$MODDIR/system/$part` while the mounted overlay still points to
`/$part`
Fix https://github.com/tiann/KernelSU/issues/726
2023-07-09 17:01:00 +08:00
Syuugo
5d07e1d392 Add WSA Kernel 5.15.104.1 (#704)
- Add `5.15.104.1`
- Delete `5.15.78.1`
2023-07-08 12:20:27 +08:00
weishu
d78f24098a Revert "manager: don't remember state when process died." close #728
This reverts commit 12761ee167.
2023-07-08 01:22:21 +08:00
dabao1955
cd952f5e45 kernel/ksu.c:Fix word mistakes when enabling CONFIG_KSU_DEBUG (#724)
According to the instructions in Kconfig, when the `CONFIG_KSU_DEBUG`
option is turned on, KernelSU will run in `debug mode` instead of the
`debug version` mentioned in kernel/ksu.c.
2023-07-08 01:22:06 +08:00
awakened
156b17f69d Support Samsung S10/N10 and Oneplus 9/9Pro (#725)
Co-authored-by: Phạm Tiên Sinh <song.long.dai.duong@gmail.com>
2023-07-06 13:25:28 +08:00
4qwerty7
f4d2b0feab Distinguish different PT_REGS_PARM4 under x86 (#711)
1. `PT_REGS_CCALL_PARM4` 表示存放C调用约定的第4个参数的寄存器
2. `PT_REGS_SYSCALL_PARM4` 表示存放linux syscall调用约定的第4个参数的寄存器
3. 将原有 `PT_REGS_PARM4` 改为上述之一
4. 将原有 `ksu_handle_execveat_ksud` 和 `ksu_handle_execveat_sucompat` 可能被
kprobe 传递错误实参、且不使用的形参标记为 never_used 并传递 `NULL`
5. 为 `ksu_handle_execveat_ksud` 提供正确的 argv 参数用以在 x86 下也能正确识别 `init
second_stage`

---------

Co-authored-by: weishu <twsxtd@gmail.com>
2023-07-06 09:01:35 +08:00
th1nhhdk
2c0a9cd64c repos.json: Updated repo link for Sony Xperia 1 II & Sony Xperia 5 II (#722)
The old one is outdated and unmaintained
2023-07-05 21:00:49 +08:00
Howard Wu
134507b928 ci: Fix checkout branch (#720)
Don't set ref branch for properly check PR
2023-07-05 08:03:27 +08:00
Howard Wu
2b42d14ef8 ci: Fix WSA build output path (#717) 2023-07-04 12:41:14 +08:00
Jakub Skorłutowski
e6071b5247 Add Polish language (#716) 2023-07-04 12:37:06 +08:00
dabao1955
f288cfccae update featrue_request issue temple (#715)
Compared with markdown, yaml may be more focused on the required
information, and will not easily destroy the content in the template.
And I think the "feature request" might not require users to upload
their logs.

It looks like this:


![Screenshot_2023-07-03-22-53-28-88_a252b927494330cdc2c8ba3b3f952e5e](https://github.com/tiann/KernelSU/assets/79307765/ef15e532-2d69-4dea-ba7a-326b7fa2e0ef)

Co-authored-by: dabao1955 <195328750@qq.com>
2023-07-03 23:05:19 +08:00
naxitoo
1cc9da5efe Update Spanish translations + Spanish README (#710) 2023-07-03 09:52:17 +08:00
longhuan1999
0aee64f339 Update app-profile.md and strings.xml (#712)
1. website: Correcting several errors in docs of App Profile
2. manager: Remove the 'app name' field except for the default
strings.xml file
2023-07-03 09:51:54 +08:00
Ali Beyaz
cbbdc665c8 Changed some Turkish translations (#707) 2023-07-02 19:42:58 +08:00
weishu
1b2635784f website: Add docs for App Profile 2023-07-02 19:07:09 +08:00
weishu
ee5d2f8c84 kernel: fix compile err in old kernel 2023-07-02 19:06:31 +08:00
weishu
dfc2a86e70 kernel: fix compile err and format code. close #706 2023-07-02 14:51:14 +08:00
4qwerty7
c0066b68f5 kernel: support the case that init_task.mnt_ns != zygote.mnt_ns(WSA) (#698)
Basic support for the case that init_task.mnt_ns != zygote.mnt_ns(WSA),
just copy nsproxy and fs pointers for solve #276.

Note the copy in `apk_sign.c` is not required but suggested for
secure(ensure the checked mnt_ns is what ns android running, not created
by user, although many distributions does not have user ns.).

Tested with latest release on Win10 19045.3086(with WSAPatch).

Further review required for:
- [x] Security of this operation (without locking).
- [x] The impact of these modifications on other Android distributions.
2023-07-02 00:20:01 +08:00
Ali Beyaz
6ef5e4ef76 Update Turkish strings.xml (#703) 2023-07-01 21:22:50 +08:00
weishu
e3e77fde78 kernel: authorize the newly type for all roles. refer: http://aospxref.com/kernel-android12-5.10-lts/xref/security/selinux/ss/policydb.c#950 2023-07-01 20:54:13 +08:00
weishu
8ea55c7f2f manager: bump minimal kernel version 2023-07-01 20:46:20 +08:00
raystef66
d2a976b3cc Update Flemish/Dutch translation (#702) 2023-07-01 20:14:51 +08:00
weishu
d675662862 kernel: add some log 2023-07-01 19:35:28 +08:00
weishu
839b318785 ksud: don't create dir when read 2023-07-01 19:01:09 +08:00
weishu
971f59c11e ksud: fix fmt 2023-07-01 18:59:41 +08:00
weishu
ab58808b64 ksud: fix compile err 2023-07-01 18:55:01 +08:00
weishu
9cbb7cb10e ksud: apply selinux rule when profile is set 2023-07-01 18:46:21 +08:00
weishu
70f2df11d1 manager: support setting selinux rules profile 2023-07-01 18:44:56 +08:00
weishu
827a2f2901 ksud: don't apply rule when check grammer 2023-07-01 17:43:40 +08:00
weishu
a9c33f6940 ksud: load profile sepolicy rules when boot 2023-07-01 16:50:10 +08:00
weishu
2bb73a2a92 ksud: support root profile's sepolicy 2023-07-01 16:34:43 +08:00
weishu
90407986be ksud: remove unused command 2023-07-01 15:31:53 +08:00
weishu
b85ece440b manager: show context menu on touch position 2023-07-01 12:13:30 +08:00
weishu
a10d2651c1 manager: Add context menu for app profile 2023-07-01 12:13:30 +08:00
SirRGB
b308a368d3 misc grammar and typo fixes (#699) 2023-06-30 23:54:15 +08:00
weishu
e6fea652de manager: only check update for enabled module 2023-06-30 23:49:11 +08:00
Aquarius223
0856b718de kernel: Modify KERNEL_SU_VERSION logic and behavior (#696)
This patch modifies the following:
- Move the version addition logic in ksu.h to Makefile processing
- Print the current version number of KernelSU during make build

build test (normal):
 buildlog:
HOSTCC scripts/basic/bin2c Using .. as source for kernel -- KernelSU
version: 11055
  CHK     include/generated/utsrelease.h
  UPD     include/generated/utsrelease.h

 KernelSU software:
  KernelSU Working Version: 11055 (v) Superusers: 0 Modules: 0


![11055](https://github.com/tiann/KernelSU/assets/47409494/10506289-04d2-41c0-bd48-bad9f0dbdec5)

build test (missing .git file):
 buildlog:
HOSTCC scripts/basic/bin2c Using .. as source for kernel
../drivers/kernelsu/Makefile:23: "KSU_GIT_VERSION not defined! It is
better to make KernelSU a git submodule!"
  CHK     include/generated/utsrelease.h
  CHK     scripts/mod/devicetable-offsets.h

 KernelSU software:
  KernelSU Working Version: 16 (v) Superusers: 0 Modules: 0

The current kernel version 16 is too low for the manager to function
properly. Please upgrade to version 10977 or higher!


![16](https://github.com/tiann/KernelSU/assets/47409494/9ea877a7-7b91-4a27-b6ab-58ce6e1b386f)

(cherry picked from commit 0c38a1614a77d80de752aba20908e3f9d21660a8)
Change-Id: I570f2ee33db224e1a36770f847137f290ba9bcfd

Co-authored-by: stic-server-open <1138705738@qq.com>
2023-06-28 21:45:36 +08:00
Zillion
6f1ccc5b3c Remove fontFamily from the Unsupported Warning (#694)
Just a small design fix, to make the Unsupported Warning text have the
same font as the Working text.

## Before

![before](https://github.com/tiann/KernelSU/assets/77107077/7e3f2382-446a-4039-8bec-d72c7e4471b3)

## After

![after](https://github.com/tiann/KernelSU/assets/77107077/31905aa3-a274-4289-b51b-0c45bbeee90e)
2023-06-27 21:17:48 +08:00
weishu
da959b4e17 kernel: fix compile err on lower kernel 2023-06-27 20:46:09 +08:00
likkai
0bfd6d9e30 Add device (lisa) (#691)
Co-authored-by: weishu <twsxtd@gmail.com>
2023-06-27 10:55:05 +08:00
weishu
980f1d09bc kernel: allow kernel to mount loop devices. close #514 2023-06-26 19:29:29 +08:00
weishu
b644c124e3 kernel: copy filename ourself instead of getname 2023-06-26 19:28:40 +08:00
weishu
65005131bd Create FUNDING.yml 2023-06-26 18:53:27 +08:00
syntaxticsugr
18aa7f2a17 Fixed Banner Art (#687)
Minor change (added spaces) to fix KSU Banner Art.
2023-06-26 18:22:56 +08:00
Zillion
cd5bc2efa9 Add Spanish Translation (#689) 2023-06-26 10:45:24 +08:00
Pegioner
477361f119 Update Russian translation (#681) 2023-06-24 20:17:51 +08:00
Gustavo Mendes
d3632e4b3b Update Portuguese brazilian translation (#682)
Signed-off-by: Gustavo Mendes <gusttavo.me@outlook.com>
2023-06-24 20:17:15 +08:00
SoDebug
0c2f90123b repos.json: Update the link of the KernelSU kernel release repo of the device I maintain (#686)
Update the link of the KernelSU kernel release repo of the device I
maintain
2023-06-24 20:16:25 +08:00
Howard Wu
09d90e1a0a ci: update gki version (#679)
Fix the version name of android13-5.15.74
Add android12-5.10.117
2023-06-23 17:48:18 +08:00
Trịnh Văn Lợi
4fe167c361 Update Vietnamese strings (#678) 2023-06-23 17:30:04 +08:00
raystef66
58ffaeb4e2 Update Flemish/Dutch translation (#677) 2023-06-23 09:31:58 +08:00
Ali Beyaz
76499ee629 Translated latest strings to Turkish (#676) 2023-06-23 09:31:23 +08:00
weishu
fedfa3e3fd manager: update card color 2023-06-23 00:35:35 +08:00
Igor Sorocean
2902e42f91 manager: update ro translation (#674) 2023-06-23 00:32:26 +08:00
weishu
37f4045499 manager: add a simple manager updater, close #627 2023-06-23 00:31:36 +08:00
weishu
12761ee167 manager: don't remember state when process died. 2023-06-22 23:24:35 +08:00
weishu
0d254237a4 manager: fix module install 2023-06-22 23:20:13 +08:00
weishu
f5bb24676e manager: fix download state 2023-06-22 19:46:26 +08:00
weishu
303a3a8482 manager: fix update button 2023-06-22 19:29:37 +08:00
weishu
07273b6971 manager: support module update online 2023-06-22 18:40:28 +08:00
weishu
c7c9e9c3ed ksud: respect the skip_mount flag of module 2023-06-22 17:00:02 +08:00
weishu
c3c990c50a ksud: increase reserved size to 256M 2023-06-22 16:48:13 +08:00
weishu
6942fe12b5 manager: set keyboard options for inputtext 2023-06-22 16:46:41 +08:00
weishu
f5cfb32882 kernel: fix incorrect umount for apps 2023-06-22 15:17:32 +08:00
weishu
e17f3eab96 Revert "kernel: use vfs_fstatat on kernel 5.10+, vfs_statx may have cfi."
This reverts commit cd3e292489.
2023-06-22 14:37:17 +08:00
weishu
08884da423 kernel: don't alloc groups for default groups 2023-06-22 13:42:28 +08:00
weishu
5f1d70dabb Revert "kernel: getname might sleep in kprobe handler (#670)"
This reverts commit 79bb9813ef.
2023-06-22 12:54:30 +08:00
weishu
79bb9813ef kernel: getname might sleep in kprobe handler (#670) 2023-06-22 10:54:50 +08:00
Ali Beyaz
1cda4ba245 Update latest strings to Turkish (#662) 2023-06-20 18:45:24 +08:00
raystef66
1cc678d20b Update Flemish/Dutch translation (#665) 2023-06-20 18:44:04 +08:00
weishu
cd3e292489 kernel: use vfs_fstatat on kernel 5.10+, vfs_statx may have cfi. 2023-06-20 18:42:41 +08:00
Howard Wu
40ea27a59c ci: Fix kernel version (#666) 2023-06-20 18:10:07 +08:00
Azeroth
e95ca9316c Fix typo (#667)
Fixed typo in this section
https://kernelsu.org/guide/installation.html#patch-boot-img-manully
2023-06-20 17:58:05 +08:00
Ikko Eltociear Ashimine
9b2f907a48 kernel: fix typo in allowlist.c (#663)
creat -> create
2023-06-20 11:03:09 +08:00
Coconut
90299ad0bb kernel:Fix the issue of incompatible __maybe_unused in the GCC compiler kernel used in versions 4.4. x to 4.9. x. (#660) 2023-06-20 10:35:07 +08:00
weishu
22d084f89b manager: Add selinux rules UI 2023-06-19 22:20:40 +08:00
Muhammad Fadlyas
99770a7362 Update Indonesian translation (#659) 2023-06-19 20:07:47 +08:00
weishu
bbc7ebe5b1 kernel: Enforcement of Manager Signature Verification 2023-06-19 17:57:19 +08:00
exer
d131b7561d [add device]: Sony Tama (XZ2/c/p, XZ3) (#656) 2023-06-18 17:06:41 +08:00
weishu
ff8c614737 kernel: allow uid 1000(system_uid) to grant root. close #645 2023-06-18 13:00:37 +08:00
weishu
c12ad9d1c7 kernel: fix compile err. close #647 2023-06-18 12:51:34 +08:00
weishu
1703c16948 ci: support deprecated kernel versions (#648) 2023-06-18 12:47:29 +08:00
github-actions[bot]
a48d7b112f [add device]: (#650)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/644

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-06-17 23:11:41 +08:00
Ylarod
f2d5e57f01 fix add-device (#649) 2023-06-17 22:07:39 +08:00
Gustavo Mendes
4111bbf720 Update Portuguese brazilian translation (#643)
Signed-off-by: Gustavo Mendes <gusttavo.me@outlook.com>
2023-06-17 21:14:36 +08:00
Ali Beyaz
cd32ad8751 Fixed some Turkish strings again (#646) 2023-06-17 21:14:09 +08:00
Rem01Gaming
fefb826177 Update Indonesian translation (#634) 2023-06-16 20:50:31 +08:00
Ali Beyaz
e27fc04b5b Fixed some Turkish strings (#640) 2023-06-16 20:50:13 +08:00
SupeChicken666
168f412324 Add CI workflow for ChromeOS ARCVM (#641)
All changes are tested on my fork (the Telegram error was fixed in
9b16150):
https://github.com/supechicken/KernelSU/actions/runs/5287864543

---------

Co-authored-by: weishu <twsxtd@gmail.com>
2023-06-16 20:49:51 +08:00
Juhyung Park
bd8434f4f4 Hook improvements (take 2) (#563)
Hi @tiann.

Thanks for the great project, I had great fun playing around with it.

This PR mainly tries to further minimize the possible delays caused by
KernelSU hooking.

There are 3 major changes:
- Processes with 0 < UID < 2000 are blocked straight-up before going
through the allow_list.
I don't see any need for such processes to be interested in root, and
this allows returning early before going through a more expensive
lookup.
If there's an expected breakage due to this change, I'll remove it. Let
me know.
- A page-sized (4K) bitmap is added.
This allows O(1) lookup for UID <= 32767.
This speeds up `ksu_is_allow_uid()` by about 4.8x by sacrificing a 4K
memory. IMHO, a good trade-off.
Most notably, this reduces the 99.999% result previously from worrying
milliseconds scale to microseconds scale.
For UID > 32767, another page-sized (4K) sequential array is used to
cache allow_list.

Compared to the previous PR #557, this new approach gives another nice
25% performance boost in average, 63-96% boost in worst cases.

Benchmark results are available at
https://docs.google.com/spreadsheets/d/1w_tO1zRLPNMFRer49pL1TQfL6ndEhilRrDU1XFIcWXY/edit?usp=sharing

Thanks!

---------

Signed-off-by: Juhyung Park <qkrwngud825@gmail.com>
2023-06-16 19:53:15 +08:00
weishu
c697398893 kernel: fix warning on x86_64, close #637 2023-06-16 19:32:48 +08:00
Ali Beyaz
1e0176242a Translated latest 3 strings to Turkish (#630) 2023-06-14 11:03:42 +08:00
Pegioner
0e5e12b331 Update Russian translation and typo fix (#631) 2023-06-14 11:03:27 +08:00
Igor Sorocean
a2b240121a Update ro translation (#629) 2023-06-13 19:23:48 +08:00
raystef66
246dadea05 Update Flemish/Dutch translation (#626) 2023-06-13 14:22:45 +08:00
Hikari-no-Tenshi
b5291077a9 manager: Make more strings translatable (#622)
Co-authored-by: weishu <twsxtd@gmail.com>
2023-06-13 12:51:03 +08:00
Ali Beyaz
ee548d513a Turkish translation (#618) 2023-06-13 12:11:12 +08:00
YuKongA
bdd269ea22 ci: upgrade kernel branches (#614)
* Add android12-5.10.177 & android13-5.10.177
2023-06-10 20:40:50 +08:00
weishu
e1d7bb91e8 ci: Fix incorrect patch level for a13 kernel. close #601 2023-06-10 20:12:15 +08:00
weishu
593bc06302 ci: fix clippy (#616) 2023-06-10 20:04:12 +08:00
weishu
8b6204a534 ksud: Fix su parameter parsing. close #530, close #305, close #330, close #305 2023-06-10 15:23:45 +08:00
weishu
01ea9c9dce manager: Add title to profile list dialog 2023-06-09 20:43:44 +08:00
weishu
12e664b0a4 manager: Add desc to profile list item 2023-06-09 20:04:23 +08:00
weishu
0ca92b9de9 manager: minor fixes for uid/gid input 2023-06-09 19:59:11 +08:00
Pegioner
2fc9e03d86 Update Russian translation (#611) 2023-06-09 11:06:50 +08:00
Fiqri Ardyansyah
4218aa488f Fix error build when using GCC 12.0.0 (#609)
Signed-off-by: Fiqri Ardyansyah <fiqri0927936@gmail.com>
2023-06-09 10:35:16 +08:00
Gustavo Mendes
d95ede1f76 Update Portuguese brazilian translation (#608)
Signed-off-by: Gustavo Mendes <gusttavo.me@outlook.com>
2023-06-08 10:48:02 +08:00
Rushi Ranpise
56f72e8702 Update English translation (#605)
Fixes https://github.com/tiann/KernelSU/issues/602
2023-06-07 16:37:08 +08:00
raystef66
964bf34325 Update Flemish/Dutch translation (#604) 2023-06-07 11:13:04 +08:00
WeeAris
3191165ba2 website: Update unofficial device maintainer information (#603)
RealKing kernel has included kernelsu officially, so I don't need to
keep maintaining this fork. With the consent of the author of the
RealKing kernel, the maintainer information here is updated to his.
2023-06-07 10:37:16 +08:00
weishu
3ad27c5999 kernel: use macro for default domain 2023-06-06 21:07:25 +08:00
weishu
5b27f6c010 kernel: Fix shell permission under KSU_DEBUG 2023-06-06 21:05:37 +08:00
weishu
be44fad288 ci: android13-5.15-2022-11 is deprecated: https://android.googlesource.com/kernel/common/+refs 2023-06-06 20:59:15 +08:00
weishu
2a5fb76f95 manager: sync kernel struct and bump profile version 2023-06-06 20:56:27 +08:00
weishu
5db7075432 kernel: bump format version to force clear allowlist, prepare to release a new version 2023-06-06 20:49:39 +08:00
weishu
1408175a35 kernel: fix set groups for kernel 4.9- 2023-06-06 20:45:42 +08:00
weishu
ab1dc894e1 manager: disable template ui temporarily 2023-06-06 19:06:03 +08:00
weishu
c5bc1c8b15 manager: make template ui work 2023-06-06 17:54:57 +08:00
Soo-Hwan Na
6d1ee60d67 kernel/selinux: Need to force include errno.h sometimes (#572)
- Seen with Linux 4.14 kernel with error message:

In file included from ../drivers/android/kernelsu/selinux/sepolicy.c:1:
In file included from ../drivers/android/kernelsu/selinux/sepolicy.h:6:
In file included from ../security/selinux/ss/policydb.h:30: In file
included from ../security/selinux/ss/avtab.h:26:
../security/selinux/include/security.h:240:10: error: use of undeclared
identifier 'EIDRM'
        return -EIDRM;
                ^
  CC      drivers/base/transport_class.o
  CC      kernel/rcu/update.o
../security/selinux/include/security.h:246:10: error: use of undeclared
identifier 'ENOENT'
        return -ENOENT;
                ^
2023-06-06 16:42:45 +08:00
weishu
a58e929205 manager: Default selinux context should be "u:r:su:s0" 2023-06-06 16:37:14 +08:00
weishu
53be8612c8 kernel: support setting selinux context for profile 2023-06-06 16:35:25 +08:00
weishu
c7f6a7d11b kernel: support settings supplementary groups for profile 2023-06-06 13:19:11 +08:00
weishu
e9011041c5 manager: Fix groups overflow, close #598 2023-06-06 12:40:07 +08:00
weishu
9803371fdb ci: respect the CONFIG_KSU options (#599)
close #596, #597
2023-06-06 12:12:27 +08:00
weishu
710edb72fa kernel: prevent root process to exec su, which makes app can escape root profile by exec it twice 2023-06-06 11:08:57 +08:00
Igor Sorocean
a4ddf59562 Update ro translation (#595) 2023-06-05 22:40:44 +08:00
weishu
37dc9a27a7 kernel: fix incorrect prune uid for work profile app, close #587 2023-06-05 17:41:41 +08:00
weishu
27ccfa6395 manager: change minimal CAP to DAC_READ_SEARCH 2023-06-05 10:17:10 +08:00
weishu
116bc56cfe manager: Fix set profile failed. 2023-06-05 10:10:16 +08:00
weishu
9ae0126be5 manager: Add default minimal caps for default root profile 2023-06-05 09:08:41 +08:00
weishu
e8755f8ae0 manager: Fix groups and caps selection, close #592 2023-06-05 08:31:15 +08:00
Gustavo Mendes
6ba2bd3af9 Add Portuguese brazilian translation (#593)
Signed-off-by: Gustavo Mendes <gusttavo.me@outlook.com>
2023-06-05 08:24:41 +08:00
raystef66
4c5e485e76 Update Flemish/Dutch translation (#590) 2023-06-05 05:48:10 +08:00
Womsxd
05c6892f43 ksubot.py: Increase timeout (#589)
Increase the timeout duration and try to resolve the
'telegram.error.TimedOut: Timed out' issue when sending messages in a
Telegram group.
2023-06-04 23:31:37 +08:00
weishu
670a20c37f manager: Fix selection 2023-06-04 23:24:19 +08:00
weishu
75509aba72 ksud: fix gid is override by uid 2023-06-04 23:14:44 +08:00
weishu
887f02e742 manager: optimize uid/gid panel 2023-06-04 23:14:44 +08:00
Hikari-no-Tenshi
ef8da39d34 manager: Update Ukrainian translation (#584) 2023-06-04 21:41:46 +08:00
Jia-Bin
b7c759ece1 Update Traditional Chinese (#586)
Update Traditional Chinese
Modify the translation to meet localization and usage habits
2023-06-04 21:36:32 +08:00
weishu
019da4a634 ksud: respect the kernel default uid 2023-06-04 18:09:58 +08:00
weishu
33aa6de50b manager: Add desc for umount modules. 2023-06-04 17:42:58 +08:00
weishu
0b3688c3b1 manager: unify kernel and user structs 2023-06-04 17:29:31 +08:00
weishu
076e5d3655 kernel: make the profile uid, gid, capabilities really work 2023-06-04 17:29:12 +08:00
weishu
c9608af0c8 manager: fix capabilities and namespace save/load 2023-06-04 16:12:03 +08:00
weishu
908fbadaf5 manager: remove unused resources 2023-06-04 16:12:02 +08:00
weishu
ee97fdfc56 kernel: add more capailities 2023-06-04 16:09:27 +08:00
weishu
4ac137313f manager: rename "umount modules" -> "Hide modules" 2023-06-04 14:25:50 +08:00
weishu
11d0029a4b manager: Add default groups when empty 2023-06-04 14:25:50 +08:00
weishu
8af5a9038b kernel: fix set app profile may override other user's 2023-06-04 13:59:22 +08:00
weishu
5d449988fb manager: Fix app profile umount default 2023-06-04 09:58:02 +08:00
weishu
e969563df0 manager: Add UMOUNT badge 2023-06-04 09:52:51 +08:00
weishu
990626cf7d kernel: Fixing the issue of a delay in updating the default profile. 2023-06-04 09:47:31 +08:00
weishu
fc77ca989f kernel: use uid instead of package to compare allowlist, fix #580 2023-06-04 09:18:39 +08:00
diphons
2ce3976023 Fixup: build under linux 4.14 (#581)
Split compiler_types.h from compiler.h only available for linux 4.14
2023-06-04 09:15:03 +08:00
weishu
63ec531814 kernel: dont prune uid used for app profile 2023-06-04 01:01:39 +08:00
weishu
a4b55b30ca manager: Add log for fallbacked app profile 2023-06-04 00:59:59 +08:00
weishu
807556f361 manager: show default value for app profile. 2023-06-04 00:22:36 +08:00
weishu
2d854f2f37 manager: the default value of umountModules is actually "true" in kernel 2023-06-04 00:05:19 +08:00
weishu
5980c113fe kernel: respect the app profile's umount modules field for kernel umounting 2023-06-04 00:03:56 +08:00
weishu
1491465b55 manager: require kernel 10931 2023-06-03 23:58:23 +08:00
weishu
728380222a kernel: Add version field for struct app_profile 2023-06-03 23:57:05 +08:00
weishu
005404f552 kernel: support load/restore default profiles 2023-06-03 23:57:05 +08:00
weishu
b55c229038 manager: add support for setting default non root profile. 2023-06-03 23:54:15 +08:00
weishu
32538c9833 manager: remove unused 2023-06-03 23:54:15 +08:00
weishu
b31fc47197 kernel: support CMD_IS_UID_GRANTED_ROOT and CMD_IS_UID_SHOULD_UMOUNT 2023-06-03 22:44:01 +08:00
s1204IT
5003824fa8 Update WSA Kernel (#575)
Remove: 5.10.117.2
Change: 5.15.98.1 -> 5.15.98.**4**
2023-06-03 19:29:04 +08:00
Hikari-no-Tenshi
eea75b72a4 manager: Update Ukrainian translation (#579) 2023-06-03 19:28:38 +08:00
weishu
82d5ec3cc1 kernel: fix CMD_GET_ALLOW_LIST error 2023-06-03 19:23:51 +08:00
weishu
75721be8c0 manager: use default profile when kernel return null. 2023-06-03 17:26:38 +08:00
weishu
de72eedb46 kernel: don't copy profile when not found 2023-06-03 17:26:00 +08:00
weishu
66827ab7de manager: show profile info in superuser list 2023-06-03 17:17:19 +08:00
weishu
2a33433272 manager: don't obfuscate Profile 2023-06-03 16:20:13 +08:00
weishu
581dff8a5f manager: only show warning when installed. 2023-06-03 15:30:51 +08:00
weishu
fc425cbba2 manager: remove semicolon for require new version 2023-06-03 15:25:54 +08:00
weishu
87f55c1acb manager: Add require new kernel version. 2023-06-03 15:23:35 +08:00
weishu
68d639e325 manager: Add groups and caps for profile 2023-06-03 15:01:57 +08:00
weishu
89f6cd044e kernel: minor fixes for profiles 2023-06-03 10:18:11 +08:00
weishu
bd3a1291da kernel: don't fill default value for app profile 2023-06-03 00:06:58 +08:00
weishu
3abb7e4ca2 kernel: baby version of profile 2023-06-03 00:01:00 +08:00
weishu
41265b0203 manager: implement app profile api call 2023-06-02 23:58:07 +08:00
sajadasadollahi83
f2cb841b8a Update Persian Translation. (#569) 2023-06-02 16:01:46 +08:00
dabao1955
c69da29081 Kconfig:Update text information (#567)
![Screenshot_2023-05-31-12-56-27-57_84d3000e3f4017145260f7618db1d683](https://github.com/tiann/KernelSU/assets/79307765/84ab8d8e-c034-4567-b815-ebeccde1fdbc)

![Screenshot_2023-05-31-12-56-09-68_84d3000e3f4017145260f7618db1d683](https://github.com/tiann/KernelSU/assets/79307765/a4209212-997f-4e2f-bbcb-2c5136ab6e71)
2023-06-02 10:41:40 +08:00
github-actions[bot]
e304ef8cfb [add device]: Kernel 4.14.194 exynos9610 Non-GKI Device, Added KernelSu using manual method (#574)
Kernel 4.14.194 exynos9610 Non-GKI Device, Added KernelSu using manual
method has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/573

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-06-02 10:31:41 +08:00
Hikari-no-Tenshi
2ce76351da manager: Scroll app profile view (#565)
close #536
2023-05-31 13:04:22 +08:00
sajadasadollahi83
66cbd931a7 Create strings.xml (#561)
add translation for persian.
2023-05-31 12:44:06 +08:00
s1204IT
07bc28e386 Fix README languages (#564) 2023-05-31 12:08:58 +08:00
AndroPlus
2ef4ffe5eb Update Japanese readme (#560)
Changed the installation link to Japanese one (build page is English)
2023-05-29 20:17:43 +08:00
Murat Kozan
128e7e394e Update Turkish translation (#553) 2023-05-29 10:30:59 +08:00
Akari
998dc9b94b added support(README.md) for Brazilian Portuguese (#558)
added README_PT-BR.md support as mentioned before, for Brazilian
Portuguese.
2023-05-29 10:30:23 +08:00
Hikari-no-Tenshi
c6dafb1333 manager: Shorten long Ukrainian strings (#550) 2023-05-25 11:46:47 +08:00
Hikari-no-Tenshi
8ae7299d59 manager: Update Ukrainian translation (#540) 2023-05-23 10:49:39 +08:00
raystef66
8f1b9c579b Update Flemish/Dutch translation (#541) 2023-05-23 10:43:33 +08:00
ahmeda9a
4b6573b521 Fine tuning for Arabic translation. (#543)
Done some changes to make the translation Arabic clearer
2023-05-23 10:43:03 +08:00
Eduardo Novella
325e843569 (scripts) check_v2.c -add missing header (#548) 2023-05-23 10:24:42 +08:00
dabao1955
d014947a54 change warning about disable kprobes (#549)
#479


Co-authored-by: dabao1955 <195328750@qq.com>
2023-05-23 10:24:18 +08:00
weishu
8858cc899e kernel: remove no-macro-redefined 2023-05-22 10:50:04 +08:00
weishu
8f3e59803f kernel: fix format warnning 2023-05-22 10:39:54 +08:00
weishu
95044bb551 kernel: fix incorrect prune uid in allowlist for work profile app. close #535 2023-05-22 10:32:20 +08:00
Aquarius223
b56448a929 kernel: Modify and adjust KernelSU Kconfig (#538)
1. List KSU as a "KernelSU" menu
2. Overlayfs[1] may be disabled for some early kernels(4.4.y), so
choosing depends on OVERLAY_FS here is useless.
3. KSU_DEBUG is a bool option, it is not a module, fixed from tristate
to bool.
4. Adjust the config order

[1]: github.com/LineageOS/android_kernel_xiaomi_msm8998/commit/dcc27641

Change-Id: I49c77ba30f0a8ed00efc44afef8a12f9ad039d7e

Co-authored-by: admin <paper@localhost>
2023-05-21 20:28:59 +08:00
Mohamed Elsayed
c2b981dbdb Update Arabic translation (#534) 2023-05-20 11:36:29 +08:00
Muhammad Fadlyas
28fb482720 Update Indonesian translation (#532) 2023-05-20 11:28:37 +08:00
Muhammad Fadlyas
d744a705a8 Fix little typo in README.md (#531) 2023-05-20 09:53:38 +08:00
Nipin NA
c62b89f02b [add device]: Xiaomi Redmi Note 7 Pro (violet) (#524) 2023-05-17 17:17:00 +08:00
AndroPlus
f876b0114e Update Japanese translation (#518) 2023-05-17 17:16:24 +08:00
diphons
9965988f26 [add device]: Xiaomi Poco F1 (#520)
Also update link device Xiaomi Poco X3 Pro
2023-05-17 12:43:39 +08:00
github-actions[bot]
828bb6fbb4 [add device]: OPPO Reno6 on ColorOS11.x/ColorOS12.x,kernel version4.14.186+ (#522)
OPPO Reno6 on ColorOS11.x/ColorOS12.x,kernel version4.14.186+ has been
added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/521

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-17 12:43:06 +08:00
Howard Wu
10d8d9efcd ci: upgrade android13 kernel version (#516)
+ android13-5.10.168
+ android13-5.15.94
2023-05-17 10:48:13 +08:00
AndroPlus
e59f3333cb Update and fix Japanese translation (#517) 2023-05-17 10:00:46 +08:00
Nullptr
31a9189d80 manager: several UI improvements (#515) 2023-05-17 09:34:08 +08:00
dabao1955
d162221fac a little change (#513)
Though it is a little not suitable?
2023-05-17 09:31:38 +08:00
weishu
0cdca0d053 website: fix build error 2023-05-16 23:54:36 +08:00
AndroPlus
4c934d460b Update Japanese translation (#508)
I'm not familiar with vitepress, so there may be some mistakes in the
settings.
2023-05-16 23:40:54 +08:00
Nullptr
76612b9cf7 manager: several updates (#510)
+ update deps
+ update app profile page
+ don't show su and module page if no root
2023-05-16 22:32:48 +08:00
github-actions[bot]
9cf8ac9c51 [add device]: (#512)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/511

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-16 22:31:19 +08:00
tiann
b80e06256d Revert "Add option to enable global namespace (#497)"
This reverts commit f81caf75a9.
2023-05-16 22:27:01 +08:00
tiann
08d9e5d6bc manager: add app_profile set/get 2023-05-16 20:33:09 +08:00
tiann
ed0cfd231e ci: fix debug build 2023-05-16 19:46:49 +08:00
weishu
538d3f06f4 ci: Add debug kernel build (#506) 2023-05-16 18:30:33 +08:00
Vayruz Rafli
f5d3fb6217 Fix little typo in setup script. (#505) 2023-05-16 18:12:16 +08:00
tiann
ffa3579e6f manager: minor fixes for app profile 2023-05-16 17:31:57 +08:00
tiann
c7adb8e3b1 manager: Add app profile implementation 2023-05-16 17:15:01 +08:00
tiann
c1427f658a manager: Add working mode UI 2023-05-16 16:31:56 +08:00
tiann
eccce7b31f manager: Add app profile UI 2023-05-16 15:07:26 +08:00
Hikari-no-Tenshi
f81caf75a9 Add option to enable global namespace (#497)
Disabled by default.
To enable/disable use terminal with root.

To enable:
echo 1 > /sys/module/ksu/parameters/global_namespace_enable

To disable:
echo 0 > /sys/module/ksu/parameters/global_namespace_enable

Value will be reset to 0 (disabled) on reboot.
2023-05-16 11:39:48 +08:00
dabao1955
d4680c6de7 Add Japanese language for Readme (#501)
Co-authored-by: dabao1955 <195328750@qq.com>
2023-05-16 10:40:39 +08:00
浅秋枫影
2716ec58a0 Revert 67667b6 (#500)
确实会导致卡住问题,一旦卡住,其他程序就无法正确卸载。
可能是,卸载目录那里一直在等待前面的空闲后再卸载,然后把后面的事件全部阻塞了?
2023-05-15 23:18:34 +08:00
TheNoFace
f25dbd8fed [Add Device]: OnePlus 5/5T (cheeseburger/dumpling) (#499)
Issue https://github.com/tiann/KernelSU/issues/498 closed without any
explicit reason, so made a pull request.
2023-05-15 18:13:58 +08:00
weishu
e35180c919 Update build-manager.yml 2023-05-15 12:14:30 +08:00
Howard Wu
2f54ceb7c4 ci: android12-5.10 drop deprecated tag (#495) 2023-05-15 09:58:25 +08:00
Mohamed Elsayed
6506ef468d Add Arabic translation (#491) 2023-05-13 16:25:10 +08:00
5ec1cff
da46dfbde1 ksud: use MS_RDONLY when mounting overlayfs (#490)
fix https://github.com/tiann/KernelSU/issues/489
2023-05-13 16:17:22 +08:00
Alessandro Paluzzi
09ecc2c9b5 Add italian translation (#486) 2023-05-13 07:07:34 +08:00
github-actions[bot]
0b2899a961 [add device]: (#485)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/484

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-13 06:03:40 +08:00
dabao1955
c6ed3fa27f Change the device supported by the kernel source code I maintain. (#483) 2023-05-12 23:34:25 +08:00
TheNoFace
43cd3b9cad Add Korean translation (#480) 2023-05-12 20:27:43 +08:00
tiann
294d6fa05e kernel: don't umount for process in global namespace 2023-05-12 12:32:35 +08:00
Syuugo
009a479c17 WSA: 5.15.94.1 (#475)
![](https://github.com/tiann/KernelSU/assets/52069677/99edfd12-c0a1-4430-bf2f-b17b76dbc44f)

Co-authored-by: weishu <twsxtd@gmail.com>
2023-05-11 18:45:15 +08:00
tiann
72ee14e6be kernel: revert init stage for x86_64 temporarily 2023-05-11 17:20:55 +08:00
Murat Kozan
2b01a1c395 Add Turkish translation (#474) 2023-05-11 11:14:38 +08:00
tiann
f69793d38e ksud: REPLACE must be directory, if you need to replace normal file, just put it to module's system corresponding location, don't need extra steps. fix #466 2023-05-10 14:36:02 +08:00
github-actions[bot]
eda0d6d23b [add device]: OPPO Reno 6 (#473)
OPPO Reno 6 has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/472

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-10 07:12:39 +08:00
S M Mahbub Hossain
abba36d786 Add Bangla/Bengali translation (#471)
Signed-off-by: S M Mahbub Hossain  <118460787+smmahbubhossain@users.noreply.github.com>
2023-05-09 19:03:58 +08:00
raystef66
33ea9a6f33 Add Flemish/Dutch translation (#470) 2023-05-08 23:21:20 +08:00
Kurisu Cat
9a0da7270b document: fix typo (#467)
修正文档中的错别字
2023-05-08 00:12:38 +08:00
github-actions[bot]
9caf440200 [add device]: (#462)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/461

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-05 18:57:39 +08:00
github-actions[bot]
c9e53cf355 [add device]: (#459)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/458

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-05 10:42:42 +08:00
RyuujiX
432eb318ac kernel: Kconfig: Remove KPROBES dependancy (#453)
For those who want to Implement KernelSU Manually with KPROBES disabled.

When KPROBES Broken and Still enabled, this will causing Loop at splash
logo even Already Manually Imported because this
842c0b674f/kernel/ksu.c (L57).
And when KPROBES is disabled, KSU also will not compiled (I Know it'll
still build if obj-y is set for KSU, but it's better to remove, who
knows if someone set as obj-$(CONFIG_KSU)).

Signed-off-by: RyuujiX <saputradenny712@gmail.com>
2023-05-04 22:57:49 +08:00
セリカ・シルフィル
cea0db4e0d manager: Open guide in the current language if it exist (#452)
If not present, the English page will be opened by default.
2023-05-04 21:10:33 +08:00
weishu
842c0b674f ci: Fix setlocalversion patch failed (#451) 2023-05-04 15:37:06 +08:00
Huy Minh
36ed99d1f6 ksud.c : Include compat.h (#450)
Fix compiling on Android-x86

Signed-off-by: hmtheboy154 <buingoc67@gmail.com>
2023-05-04 11:49:21 +08:00
shygos
86d0f37b37 kernel: Use a better way to link submodule directory (#449)
Use ../ instead of absolute path
2023-05-03 22:05:36 +08:00
github-actions[bot]
5303c9c461 [add device]: (#448)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/447

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-02 06:56:35 +08:00
tiann
67667b6df2 kernel: umount by lazy 2023-05-02 00:12:01 +08:00
Lvc Revincx
d9d27b4229 readme: fix credits link title of genuine project (#446)
The credit link title of [genuine](https://github.com/brevent/genuine)
in `README_CN.md` and `README_TW.md` is `true`.
2023-05-01 16:56:31 +08:00
Sreeshankar K
a2a1b19758 Add OnePlus Nord (avicii) (#444) 2023-04-29 23:45:07 +08:00
github-actions[bot]
fb9d39d6d5 [add device]: (#443)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/442

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-29 12:03:07 +08:00
github-actions[bot]
0f3d425f64 [add device]: (#439)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/438

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-28 08:35:02 +08:00
Abdelhay-Ali
08e3580646 make huawei hi6250 4.9.319 kernel compile (#431) 2023-04-27 13:30:04 +08:00
Igor Sorocean
0fa4a4c6db Update ro strings (#434) 2023-04-26 17:22:53 +08:00
diphons
d452e01a3d [add device]: Xiaomi Poco X3 Pro | Vayu - Bhima (#433) 2023-04-26 11:45:50 +08:00
diphons
f7da373f8b [add device]: Xiaomi Poco F3 - F4 - MI10T/Pro (#432) 2023-04-25 14:55:32 +08:00
Koinu
b80cf7ba15 Update repos.json (#430)
他的内核已经不支持KernelSU了
2023-04-24 11:22:48 +08:00
Kung-chih
fe1cd4b27a website: add Traditional Chinese (#429)
Also README_TW added, hope it works :)
2023-04-23 18:54:33 +08:00
Hikari-no-Tenshi
925206f9c8 Add Ukrainian translation (#427) 2023-04-23 13:55:43 +08:00
Abdelhay-Ali
f24a317e2d Update repos.json (#428)
add Huawei P20 lite (hi6250) (Linux 4.9)
2023-04-23 10:28:29 +08:00
TireX228
aeaa3ce982 manager: Update Russian language (#423) 2023-04-22 23:26:53 +08:00
Kung-chih
8c6913a9af Update zh-rHK and zh-rTW (#425)
@cracky5322 For zh-rTW with Android Style: 

> Log: 記錄(檔)
Developer: 開發人員
Access: 存取(權)
Support: 支援(For users) 支持(For devs)
2023-04-22 23:26:37 +08:00
TinyHai
7795232a58 manager: fix ConfirmDialog (#426) 2023-04-22 23:26:21 +08:00
TinyHai
057330c68f manager: refine dialog component and make a small fix in AboutCard (#422)
Co-authored-by: weishu <twsxtd@gmail.com>
2023-04-22 18:40:11 +08:00
github-actions[bot]
91c80279bd [add device]: (#421)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/420

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-22 15:25:07 +08:00
Nullptr
5715df0b10 manager: small fix for navigation (#416) 2023-04-21 23:52:46 +08:00
github-actions[bot]
9f0c540fba [add device]: (#418)
has been added to the website.
Related issue: https://github.com/tiann/KernelSU/issues/417

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-21 23:52:16 +08:00
Nullptr
f9d19a957a manager: refactor to use AGP 8.0.0 (#414) 2023-04-21 16:19:49 +08:00
202 changed files with 10646 additions and 2114 deletions

5
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
# These are supported funding model platforms
github: tiann
patreon: weishu
custom: https://vxposed.com/donate.html

View File

@@ -1,10 +0,0 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---

11
.github/ISSUE_TEMPLATE/custom.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: Custom issue template
description: Ask questions about other aspects of the project
title: '[Custom]'
body:
- type: textarea
id: description
attributes:
label: "Describe your problem."
validations:
required: true

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,40 @@
name: Feature Request
description: "Suggest an idea for this project"
title: "[Feature]"
labels: "feature"
assignees: tiann
body:
- type: markdown
id: feature-info
attributes:
value: "## Feature Infomation"
- type: textarea
id: feature-main
validations:
required: true
attributes:
label: "Is your feature request related to a problem? Please describe."
description: "A clear and concise description of what the problem is."
placeholder: "I'm always frustrated when [...]"
- type: textarea
id: feature-solution
validations:
required: true
attributes:
label: "Describe the solution you'd like."
description: "A clear and concise description of what you want to happen."
- type: textarea
id: feature-describe
validations:
required: true
attributes:
label: "Describe alternatives you've considered."
description: "A clear and concise description of any alternative solutions or features you've considered."
- type: textarea
id: feature-extra
validations:
required: false
attributes:
label: "Additional context"
description: "Add any other context or screenshots about the feature request here."

View File

@@ -1,43 +0,0 @@
From dbdd2906c0b3a967ca28c6b870b46f905c170661 Mon Sep 17 00:00:00 2001
From: Park Ju Hyung <qkrwngud825@gmail.com>
Date: Wed, 13 Mar 2019 13:36:37 +0900
Subject: [PATCH] setlocalversion: don't check for uncommitted changes
I ofter push after the build is done and I hate seeing "-dirty"
Signed-off-by: Park Ju Hyung <qkrwngud825@gmail.com>
Signed-off-by: Danny Lin <danny@kdrag0n.dev>
Signed-off-by: Divyanshu-Modi <divyan.m05@gmail.com>
Change-Id: I240c516520879da680794fd144b1f273f9e21e13
Signed-off-by: Divyanshu-Modi <divyan.m05@gmail.com>
---
scripts/setlocalversion | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/scripts/setlocalversion b/scripts/setlocalversion
index 842936656b84..ef27a273ebf5 100755
--- a/scripts/setlocalversion
+++ b/scripts/setlocalversion
@@ -107,19 +107,6 @@ scm_version()
printf -- '-svn%s' "$(git svn find-rev $head)"
fi
- # Check for uncommitted changes.
- # First, with git-status, but --no-optional-locks is only
- # supported in git >= 2.14, so fall back to git-diff-index if
- # it fails. Note that git-diff-index does not refresh the
- # index, so it may give misleading results. See
- # git-update-index(1), git-diff-index(1), and git-status(1).
- if {
- git --no-optional-locks status -uno --porcelain 2>/dev/null ||
- git diff-index --name-only HEAD
- } | grep -qvE '^(.. )?scripts/package'; then
- printf '%s' -dirty
- fi
-
# All done with git
return
fi
--
2.37.2

View File

@@ -1,46 +0,0 @@
From bbb9e7fb1ccadac47b58ba615e6874ddeaa9e628 Mon Sep 17 00:00:00 2001
From: Park Ju Hyung <qkrwngud825@gmail.com>
Date: Wed, 13 Mar 2019 13:36:37 +0900
Subject: [PATCH] setlocalversion: don't check for uncommitted changes
I ofter push after the build is done and I hate seeing "-dirty"
Signed-off-by: Park Ju Hyung <qkrwngud825@gmail.com>
Signed-off-by: Danny Lin <danny@kdrag0n.dev>
Signed-off-by: Divyanshu-Modi <divyan.m05@gmail.com>
Change-Id: I240c516520879da680794fd144b1f273f9e21e13
Signed-off-by: Divyanshu-Modi <divyan.m05@gmail.com>
---
scripts/setlocalversion | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/scripts/setlocalversion b/scripts/setlocalversion
index 1b733ae4c..2a3ea7684 100755
--- a/scripts/setlocalversion
+++ b/scripts/setlocalversion
@@ -90,22 +90,6 @@ scm_version()
printf '%s%s' -g "$(echo $head | cut -c1-12)"
fi
- # Check for uncommitted changes.
- # This script must avoid any write attempt to the source tree,
- # which might be read-only.
- # You cannot use 'git describe --dirty' because it tries to
- # create .git/index.lock .
- # First, with git-status, but --no-optional-locks is only
- # supported in git >= 2.14, so fall back to git-diff-index if
- # it fails. Note that git-diff-index does not refresh the
- # index, so it may give misleading results. See
- # git-update-index(1), git-diff-index(1), and git-status(1).
- if {
- git --no-optional-locks status -uno --porcelain 2>/dev/null ||
- git diff-index --name-only HEAD
- } | read dummy; then
- printf '%s' -dirty
- fi
fi
}
--
2.37.2

View File

@@ -11,7 +11,15 @@ build_from_image() {
echo "[+] patch level: $PATCH_LEVEL" echo "[+] patch level: $PATCH_LEVEL"
echo '[+] Download prebuilt ramdisk' echo '[+] Download prebuilt ramdisk'
curl -Lo gki-kernel.zip https://dl.google.com/android/gki/gki-certified-boot-android12-5.10-"${PATCH_LEVEL}"_r1.zip GKI_URL=https://dl.google.com/android/gki/gki-certified-boot-android12-5.10-"${PATCH_LEVEL}"_r1.zip
FALLBACK_URL=https://dl.google.com/android/gki/gki-certified-boot-android12-5.10-2023-01_r1.zip
status=$(curl -sL -w "%{http_code}" "$GKI_URL" -o /dev/null)
if [ "$status" = "200" ]; then
curl -Lo gki-kernel.zip "$GKI_URL"
else
echo "[+] $GKI_URL not found, using $FALLBACK_URL"
curl -Lo gki-kernel.zip "$FALLBACK_URL"
fi
unzip gki-kernel.zip && rm gki-kernel.zip unzip gki-kernel.zip && rm gki-kernel.zip
echo '[+] Unpack prebuilt boot.img' echo '[+] Unpack prebuilt boot.img'

View File

@@ -42,12 +42,12 @@ jobs:
run: | run: |
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
- uses: ben-z/actions-comment-on-issue@1.0.2 - uses: Kernel-SU/actions-comment-on-issue@master
if: ${{ steps.cpr.outputs.pull-request-number }} if: ${{ steps.cpr.outputs.pull-request-number }}
with: with:
message: "Automatically created pull request: ${{ steps.cpr.outputs.pull-request-url }}" message: "Automatically created pull request: ${{ steps.cpr.outputs.pull-request-url }}"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: ben-z/actions-comment-on-issue@1.0.2 - uses: Kernel-SU/actions-comment-on-issue@master
if: steps.handle-add-device.outputs.success != 'true' if: steps.handle-add-device.outputs.success != 'true'
with: with:
message: "Cannot create pull request. Please check the issue content. Or you can create a pull request manually." message: "Cannot create pull request. Please check the issue content. Or you can create a pull request manually."
@@ -56,4 +56,4 @@ jobs:
uses: peter-evans/close-issue@v1 uses: peter-evans/close-issue@v1
with: with:
issue-number: ${{ github.event.issue.number }} issue-number: ${{ github.event.issue.number }}
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,31 @@
name: Build debug kernel
on:
workflow_dispatch:
jobs:
build-debug-kernel-a12:
uses: ./.github/workflows/gki-kernel.yml
with:
version: android12-5.10
version_name: android12-5.10.177
tag: android12-5.10-2023-06
os_patch_level: 2023-06
patch_path: "5.10"
debug: true
build-debug-kernel-a13:
strategy:
matrix:
include:
- version: "5.10"
sub_level: 177
os_patch_level: 2023-06
- version: "5.15"
sub_level: 94
os_patch_level: 2023-06
uses: ./.github/workflows/gki-kernel.yml
with:
version: android13-${{ matrix.version }}
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }}
debug: true

View File

@@ -22,19 +22,25 @@ jobs:
matrix: matrix:
include: include:
- sub_level: 66 - sub_level: 66
os_patch_level: 2021-11 os_patch_level: 2022-01
- sub_level: 81 - sub_level: 81
os_patch_level: 2022-03 os_patch_level: 2022-03
- sub_level: 101 - sub_level: 101
os_patch_level: 2022-05 os_patch_level: 2022-05
- sub_level: 110 - sub_level: 110
os_patch_level: 2022-07 os_patch_level: 2022-07
- sub_level: 117
os_patch_level: 2022-09
- sub_level: 136 - sub_level: 136
os_patch_level: 2022-11 os_patch_level: 2022-11
- sub_level: 149 - sub_level: 149
os_patch_level: 2023-01 os_patch_level: 2023-01
- sub_level: 160 - sub_level: 160
os_patch_level: 2023-02 os_patch_level: 2023-03
- sub_level: 168
os_patch_level: 2023-05
- sub_level: 177
os_patch_level: 2023-06
uses: ./.github/workflows/gki-kernel.yml uses: ./.github/workflows/gki-kernel.yml
secrets: inherit secrets: inherit
with: with:
@@ -115,7 +121,7 @@ jobs:
uses: ./.github/workflows/gki-kernel.yml uses: ./.github/workflows/gki-kernel.yml
with: with:
version: android12-5.10 version: android12-5.10
version_name: android12-5.10.160 version_name: android12-5.10.177
tag: android12-5.10-2023-02 tag: android12-5.10-2023-06
os_patch_level: 2023-02 os_patch_level: 2023-06
patch_path: "5.10" patch_path: "5.10"

View File

@@ -29,7 +29,13 @@ jobs:
os_patch_level: 2023-01 os_patch_level: 2023-01
- version: "5.10" - version: "5.10"
sub_level: 157 sub_level: 157
os_patch_level: 2023-02 os_patch_level: 2023-03
- version: "5.10"
sub_level: 168
os_patch_level: 2023-05
- version: "5.10"
sub_level: 177
os_patch_level: 2023-06
- version: "5.15" - version: "5.15"
sub_level: 41 sub_level: 41
os_patch_level: 2022-11 os_patch_level: 2022-11
@@ -38,13 +44,20 @@ jobs:
os_patch_level: 2023-01 os_patch_level: 2023-01
- version: "5.15" - version: "5.15"
sub_level: 78 sub_level: 78
os_patch_level: 2023-02 os_patch_level: 2023-03
- version: "5.15"
sub_level: 94
os_patch_level: 2023-05
- version: "5.15"
sub_level: 104
os_patch_level: 2023-06
uses: ./.github/workflows/gki-kernel.yml uses: ./.github/workflows/gki-kernel.yml
secrets: inherit secrets: inherit
with: with:
version: android13-${{ matrix.version }} version: android13-${{ matrix.version }}
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }} version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }} tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }} patch_path: ${{ matrix.version }}
upload-artifacts: upload-artifacts:
needs: build-kernel needs: build-kernel
@@ -119,14 +132,14 @@ jobs:
matrix: matrix:
include: include:
- version: "5.10" - version: "5.10"
sub_level: 149 sub_level: 177
os_patch_level: 2022-11 os_patch_level: 2023-06
- version: "5.15" - version: "5.15"
sub_level: 74 sub_level: 104
os_patch_level: 2023-01 os_patch_level: 2023-06
uses: ./.github/workflows/gki-kernel.yml uses: ./.github/workflows/gki-kernel.yml
with: with:
version: android13-${{ matrix.version }} version: android13-${{ matrix.version }}
version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }} version_name: android13-${{ matrix.version }}.${{ matrix.sub_level }}
tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }} tag: android13-${{ matrix.version }}-${{ matrix.os_patch_level }}
patch_path: ${{ matrix.version }} patch_path: ${{ matrix.version }}

140
.github/workflows/build-kernel-arcvm.yml vendored Normal file
View File

@@ -0,0 +1,140 @@
name: Build Kernel - ChromeOS ARCVM
on:
push:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-arcvm.yml"
- "kernel/**"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/build-kernel-arcvm.yml"
- "kernel/**"
workflow_call:
workflow_dispatch:
jobs:
build:
strategy:
matrix:
arch: [x86_64]
version: ["5.10.178"]
include:
- arch: x86_64
git_tag: chromeos-5.10-arcvm
file_name: "bzImage"
name: Build ChromeOS ARCVM kernel
runs-on: ubuntu-20.04
env:
LTO: thin
ROOT_DIR: /
KERNEL_DIR: ${{ github.workspace }}/kernel
steps:
- name: Install Build Tools
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends bc \
bison build-essential ca-certificates flex git gnupg \
libelf-dev libssl-dev lsb-release software-properties-common wget \
libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget gzip \
rsync python3 device-tree-compiler
sudo ln -s --force python3 /usr/bin/python
export LLVM_VERSION=12
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh $LLVM_VERSION
rm ./llvm.sh
sudo ln -s --force /usr/bin/clang-$LLVM_VERSION /usr/bin/clang
sudo ln -s --force /usr/bin/ld.lld-$LLVM_VERSION /usr/bin/ld.lld
sudo ln -s --force /usr/bin/llvm-objdump-$LLVM_VERSION /usr/bin/llvm-objdump
sudo ln -s --force /usr/bin/llvm-ar-$LLVM_VERSION /usr/bin/llvm-ar
sudo ln -s --force /usr/bin/llvm-nm-$LLVM_VERSION /usr/bin/llvm-nm
sudo ln -s --force /usr/bin/llvm-strip-$LLVM_VERSION /usr/bin/llvm-strip
sudo ln -s --force /usr/bin/llvm-objcopy-$LLVM_VERSION /usr/bin/llvm-objcopy
sudo ln -s --force /usr/bin/llvm-readelf-$LLVM_VERSION /usr/bin/llvm-readelf
sudo ln -s --force /usr/bin/clang++-$LLVM_VERSION /usr/bin/clang++
- name: Checkout KernelSU
uses: actions/checkout@v3
with:
path: KernelSU
fetch-depth: 0
- name: Setup kernel source
run: git clone https://chromium.googlesource.com/chromiumos/third_party/kernel.git -b ${{ matrix.git_tag }} --depth=1
- name: Setup KernelSU
working-directory: kernel
run: |
echo "[+] KernelSU setup"
KERNEL_ROOT=$GITHUB_WORKSPACE/kernel
echo "[+] KERNEL_ROOT: $KERNEL_ROOT"
echo "[+] Copy KernelSU driver to $KERNEL_ROOT/drivers"
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $KERNEL_ROOT/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$KERNEL_ROOT/drivers/Makefile
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
echo "[+] Apply KernelSU patches"
cd $KERNEL_ROOT && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/5.10/*.patch
echo "[+] Patch script/setlocalversion"
sed -i 's/-dirty//g' $KERNEL_ROOT/scripts/setlocalversion
echo "[+] KernelSU setup done."
cd $GITHUB_WORKSPACE/KernelSU
VERSION=$(($(git rev-list --count HEAD) + 10200))
echo "VERSION: $VERSION"
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
- name: Build Kernel
working-directory: kernel
run: |
set -a && . build.config.gki.x86_64; set +a
export DEFCONFIG=x86_64_arcvm_defconfig
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
fi
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} mrproper
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} ${DEFCONFIG} < /dev/null
scripts/config --file .config -e LTO_CLANG -d LTO_NONE -e LTO_CLANG_THIN -d LTO_CLANG_FULL -e THINLTO
make LLVM=1 LLVM_IAS=1 DEPMOD=depmod DTC=dtc O=${PWD} -j$(nproc) bzImage modules prepare-objtool
echo "file_path=${PWD}/arch/x86/boot/bzImage" >> $GITHUB_ENV
- name: Upload kernel-ARCVM-${{ matrix.arch }}-${{ matrix.version }}
uses: actions/upload-artifact@v3
with:
name: kernel-ARCVM-${{ matrix.arch }}-${{ matrix.version }}
path: "${{ env.file_path }}"
- name: Post to Telegram
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
env:
CHAT_ID: ${{ secrets.CHAT_ID }}
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
COMMIT_URL: ${{ github.event.head_commit.url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
TITLE=kernel-ARCVM-${{ matrix.arch }}-${{ matrix.version }}
echo "[+] title: $TITLE"
export TITLE
export VERSION="${{ env.kernelsu_version }}"
echo "[+] Compress images"
gzip -n -f -9 "${{ env.file_path }}"
echo "[+] Image to upload"
ls -l "${{ env.file_path }}.gz"
if [ -n "${{ secrets.BOT_TOKEN }}" ]; then
pip3 install python-telegram-bot
python3 "$GITHUB_WORKSPACE/KernelSU/scripts/ksubot.py" "${{ env.file_path }}.gz"
fi

View File

@@ -18,29 +18,29 @@ jobs:
strategy: strategy:
matrix: matrix:
arch: [x86_64, arm64] arch: [x86_64, arm64]
version: ["5.10.117.2", "5.15.78.1"] version: ["5.15.94.4", "5.15.104.1"]
include: include:
- arch: x86_64 - arch: x86_64
file_name: "bzImage" file_name: "bzImage"
- arch: arm64 - arch: arm64
file_name: "Image" file_name: "Image"
cross_compile: "aarch64-linux-gnu" cross_compile: "aarch64-linux-gnu"
- version: "5.10.117.2" - version: "5.15.94.4"
arch: x86_64
make_config: config-wsa-5.10
- version: "5.10.117.2"
arch: arm64
make_config: config-wsa-arm64-5.10
- version: "5.15.78.1"
arch: x86_64 arch: x86_64
make_config: config-wsa-x64 make_config: config-wsa-x64
- version: "5.15.78.1" - version: "5.15.94.4"
arch: arm64 arch: arm64
make_config: config-wsa-arm64 make_config: config-wsa-arm64
- version: "5.10.117.2" - version: "5.15.104.1"
device_code: latte arch: x86_64
kernel_version: "5.10" make_config: config-wsa-x64
- version: "5.15.78.1" - version: "5.15.104.1"
arch: arm64
make_config: config-wsa-arm64
- version: "5.15.94.4"
device_code: latte-2
kernel_version: "5.15"
- version: "5.15.104.1"
device_code: latte-2 device_code: latte-2
kernel_version: "5.15" kernel_version: "5.15"
@@ -54,8 +54,8 @@ jobs:
steps: steps:
- name: Install Build Tools - name: Install Build Tools
run: | run: |
sudo apt update sudo apt-get update
sudo apt install -y --no-install-recommends bc bison build-essential ca-certificates flex git gnupg libelf-dev libssl-dev lsb-release software-properties-common wget libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget gzip sudo apt-get install -y --no-install-recommends bc bison build-essential ca-certificates flex git gnupg libelf-dev libssl-dev lsb-release software-properties-common wget libncurses-dev binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu nuget gzip
export LLVM_VERSION=12 export LLVM_VERSION=12
wget https://apt.llvm.org/llvm.sh wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh chmod +x llvm.sh
@@ -75,7 +75,6 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
path: KernelSU path: KernelSU
ref: main
fetch-depth: 0 fetch-depth: 0
- name: Setup kernel source - name: Setup kernel source
@@ -116,8 +115,13 @@ jobs:
run: | run: |
cp configs/wsa/${{ matrix.make_config }} .config cp configs/wsa/${{ matrix.make_config }} .config
make olddefconfig make olddefconfig
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
fi
make -j`nproc` LLVM=1 ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cross_compile }} ${{ matrix.file_name }} CCACHE="/usr/bin/ccache" make -j`nproc` LLVM=1 ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cross_compile }} ${{ matrix.file_name }} CCACHE="/usr/bin/ccache"
echo "file_path=WSA-Linux-Kernel/arch/${{ matrix.arch }}/boot/${{ matrix.file_name }}" >> $GITHUB_ENV declare -A ARCH_MAP=(["x86_64"]="x86" ["arm64"]="arm64")
echo "file_path=WSA-Linux-Kernel/arch/${ARCH_MAP[${{ matrix.arch }}]}/boot/${{ matrix.file_name }}" >> $GITHUB_ENV
- name: Upload kernel-${{ matrix.arch }}-${{ matrix.version }} - name: Upload kernel-${{ matrix.arch }}-${{ matrix.version }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@@ -1,16 +1,18 @@
name: Build Manager name: Build Manager
on: on:
push: push:
branches: [ "main" ] branches: [ "main" ]
paths: paths:
- '.github/workflows/build-manager.yml' - '.github/workflows/build-manager.yml'
- 'manager/**' - 'manager/**'
- 'userspace/ksud/**' - 'userspace/ksud/**'
pull_request: pull_request:
branches: [ "main" ] branches: [ "main" ]
paths: paths:
- 'manager/**' - 'manager/**'
workflow_call: workflow_call:
jobs: jobs:
build-ksud: build-ksud:
strategy: strategy:
@@ -21,87 +23,104 @@ jobs:
uses: ./.github/workflows/ksud.yml uses: ./.github/workflows/ksud.yml
with: with:
target: ${{ matrix.target }} target: ${{ matrix.target }}
build-manager: build-manager:
needs: build-ksud needs: build-ksud
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
working-directory: ./manager working-directory: ./manager
steps: steps:
- uses: actions/checkout@v3 - name: Checkout
with: uses: actions/checkout@v3
fetch-depth: 0 with:
- name: Setup need_upload fetch-depth: 0
id: need_upload
run: | - name: Setup need_upload
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then id: need_upload
echo "UPLOAD=true" >> $GITHUB_OUTPUT run: |
else if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
echo "UPLOAD=false" >> $GITHUB_OUTPUT echo "UPLOAD=true" >> $GITHUB_OUTPUT
fi else
- name: set up JDK 11 echo "UPLOAD=false" >> $GITHUB_OUTPUT
uses: actions/setup-java@v3 fi
with:
java-version: '11' - name: Write key
distribution: 'temurin' if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }}
cache: gradle run: |
- uses: nttld/setup-ndk@v1 if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
with: echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}' >> gradle.properties
ndk-version: r25b echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}' >> gradle.properties
local-cache: true echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}' >> gradle.properties
- name: Extract keystore echo KEYSTORE_FILE='../key.jks' >> gradle.properties
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/main' ) || github.ref_type == 'tag' }} echo ${{ secrets.KEYSTORE }} | base64 --decode > key.jks
run: | fi
if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
echo KEYSTORE_PASSWORD='${{ secrets.KEYSTORE_PASSWORD }}' >> sign.properties - name: Setup Java
echo KEY_ALIAS='${{ secrets.KEY_ALIAS }}' >> sign.properties uses: actions/setup-java@v3
echo KEY_PASSWORD='${{ secrets.KEY_PASSWORD }}' >> sign.properties with:
echo KEYSTORE_FILE='../key.jks' >> sign.properties distribution: "temurin"
echo ${{ secrets.KEYSTORE }} | base64 --decode > key.jks java-version: "17"
fi
- name: Download arm64 ksud - name: Setup Gradle
uses: actions/download-artifact@v3 uses: gradle/gradle-build-action@v2
with: with:
name: ksud-aarch64-linux-android gradle-home-cache-cleanup: true
path: .
- name: Download x86_64 ksud - name: Download arm64 ksud
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: ksud-x86_64-linux-android name: ksud-aarch64-linux-android
path: . path: .
- name: Copy ksud to app jniLibs
run: | - name: Download x86_64 ksud
mkdir -p app/src/main/jniLibs/arm64-v8a uses: actions/download-artifact@v3
mkdir -p app/src/main/jniLibs/x86_64 with:
cp -f ../aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud.so name: ksud-x86_64-linux-android
cp -f ../x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud.so path: .
- name: Grant execute permission for gradlew
run: chmod +x gradlew - name: Copy ksud to app jniLibs
- name: Build with Gradle run: |
run: ./gradlew clean assembleRelease mkdir -p app/src/main/jniLibs/arm64-v8a
- name: Upload build artifact mkdir -p app/src/main/jniLibs/x86_64
uses: actions/upload-artifact@v3 cp -f ../aarch64-linux-android/release/ksud ../manager/app/src/main/jniLibs/arm64-v8a/libksud.so
with: cp -f ../x86_64-linux-android/release/ksud ../manager/app/src/main/jniLibs/x86_64/libksud.so
name: manager
path: manager/app/build/outputs/apk/release/*.apk - name: Build with Gradle
- name: Setup mutex for uploading run: |
uses: ben-z/gh-action-mutex@v1.0-alpha-7 echo 'org.gradle.parallel=true' >> gradle.properties
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true' echo 'org.gradle.vfs.watch=true' >> gradle.properties
- name: Upload to telegram echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true' echo 'android.native.buildOutput=verbose' >> gradle.properties
env: sed -i 's/org.gradle.configuration-cache=true//g' gradle.properties
CHAT_ID: ${{ secrets.CHAT_ID }} ./gradlew clean assembleRelease
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }} - name: Upload build artifact
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }} uses: actions/upload-artifact@v3
COMMIT_MESSAGE: ${{ github.event.head_commit.message }} with:
COMMIT_URL: ${{ github.event.head_commit.url }} name: manager
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} path: manager/app/build/outputs/apk/release/*.apk
TITLE: Manager
run: | - name: Setup mutex for uploading
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then uses: ben-z/gh-action-mutex@v1.0-alpha-7
export VERSION=$(git rev-list --count HEAD) if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
pip3 install python-telegram-bot - name: Upload to telegram
python3 $GITHUB_WORKSPACE/scripts/ksubot.py $APK if: github.event_name != 'pull_request' && steps.need_upload.outputs.UPLOAD == 'true'
fi env:
CHAT_ID: ${{ secrets.CHAT_ID }}
CACHE_CHAT_ID: ${{ secrets.CACHE_CHAT_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
MESSAGE_THREAD_ID: ${{ secrets.MESSAGE_THREAD_ID }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
COMMIT_URL: ${{ github.event.head_commit.url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
TITLE: Manager
run: |
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
export VERSION=$(git rev-list --count HEAD)
APK=$(find ./app/build/outputs/apk/release -name "*.apk")
pip3 install python-telegram-bot
python3 $GITHUB_WORKSPACE/scripts/ksubot.py $APK
fi

View File

@@ -22,7 +22,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
# cross build failed after Rust 1.68, see https://github.com/cross-rs/cross/issues/1222
- run: rustup default 1.67.0
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
workspaces: userspace/ksud workspaces: userspace/ksud

View File

@@ -45,6 +45,10 @@ on:
description: > description: >
Artifact name of prebuilt ksud to be embedded Artifact name of prebuilt ksud to be embedded
for example: ksud-aarch64-linux-android for example: ksud-aarch64-linux-android
debug:
required: false
type: boolean
default: false
secrets: secrets:
BOOT_SIGN_KEY: BOOT_SIGN_KEY:
required: false required: false
@@ -82,15 +86,31 @@ jobs:
- name: Setup kernel source - name: Setup kernel source
run: | run: |
echo "Free space:"
df -h
cd $GITHUB_WORKSPACE cd $GITHUB_WORKSPACE
git clone https://gerrit.googlesource.com/git-repo sudo apt-get install repo -y
mkdir android-kernel && cd android-kernel mkdir android-kernel && cd android-kernel
../git-repo/repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }} repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }} --repo-rev=v2.16
../git-repo/repo sync -j$(nproc --all) REMOTE_BRANCH=$(git ls-remote https://android.googlesource.com/kernel/common ${{ inputs.tag }})
DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml
if grep -q deprecated <<< $REMOTE_BRANCH; then
echo "Found deprecated branch: ${{ inputs.tag }}"
sed -i 's/"${{ inputs.tag }}"/"deprecated\/${{ inputs.tag }}"/g' $DEFAULT_MANIFEST_PATH
cat $DEFAULT_MANIFEST_PATH
fi
repo --version
repo --trace sync -q -c -j$(nproc --all) --no-tags
if [ -d prebuilts/clang/host/linux-x86 ] && [ -f common/build.config.constants ]; then
ls -d prebuilts/clang/host/linux-x86/clang-r* | grep -v $(sed -n 's/^CLANG_VERSION=//p' common/build.config.constants)
fi
rm -rf .repo
df -h
- name: Setup KernelSU - name: Setup KernelSU
env: env:
PATCH_PATH: ${{ inputs.patch_path }} PATCH_PATH: ${{ inputs.patch_path }}
IS_DEBUG_KERNEL: ${{ inputs.debug }}
run: | run: |
cd $GITHUB_WORKSPACE/android-kernel cd $GITHUB_WORKSPACE/android-kernel
echo "[+] KernelSU setup" echo "[+] KernelSU setup"
@@ -103,6 +123,13 @@ jobs:
grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE grep -q "kernelsu" $DRIVER_MAKEFILE || echo "obj-y += kernelsu/" >> $DRIVER_MAKEFILE
echo "[+] Apply KernelSU patches" echo "[+] Apply KernelSU patches"
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch
echo "[+] Patch script/setlocalversion"
sed -i 's/-dirty//g' $GKI_ROOT/common/scripts/setlocalversion
if [ "$IS_DEBUG_KERNEL" = "true" ]; then
echo "[+] Enable debug features for kernel"
echo "ccflags-y += -DCONFIG_KSU_DEBUG" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
fi
echo "[+] KernelSU setup done." echo "[+] KernelSU setup done."
- name: Symbol magic - name: Symbol magic
@@ -126,7 +153,12 @@ jobs:
- name: Build boot.img - name: Build boot.img
working-directory: android-kernel working-directory: android-kernel
run: CCACHE="/usr/bin/ccache" LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh run: |
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
fi
CCACHE="/usr/bin/ccache" LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
- name: Prepare artifacts - name: Prepare artifacts
id: prepareArtifacts id: prepareArtifacts

View File

@@ -15,12 +15,15 @@ jobs:
uses: ./.github/workflows/build-kernel-a13.yml uses: ./.github/workflows/build-kernel-a13.yml
build-wsa-kernel: build-wsa-kernel:
uses: ./.github/workflows/build-kernel-wsa.yml uses: ./.github/workflows/build-kernel-wsa.yml
build-arcvm-kernel:
uses: ./.github/workflows/build-kernel-arcvm.yml
release: release:
needs: needs:
- build-manager - build-manager
- build-a12-kernel - build-a12-kernel
- build-a13-kernel - build-a13-kernel
- build-wsa-kernel - build-wsa-kernel
- build-arcvm-kernel
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download artifacts - name: Download artifacts
@@ -43,6 +46,15 @@ jobs:
fi fi
done done
- name: Zip ChromeOS ARCVM kernel
run: |
for dir in kernel-ARCVM-*; do
if [ -d "$dir" ]; then
echo "------ Zip $dir ----------"
(cd $dir && zip -r9 "$dir".zip ./* -x .git .gitignore ./*.zip && mv *.zip ..)
fi
done
- name: Display structure of downloaded files - name: Display structure of downloaded files
run: ls -R run: ls -R
@@ -54,3 +66,4 @@ jobs:
AnyKernel3-*.zip AnyKernel3-*.zip
boot-images-*/Image-*/*.img.gz boot-images-*/Image-*/*.img.gz
kernel-WSA*.zip kernel-WSA*.zip
kernel-ARCVM*.zip

View File

@@ -27,7 +27,7 @@ jobs:
with: with:
components: rustfmt components: rustfmt
- uses: LoliGothick/rustfmt-check@v0.3.1 - uses: LoliGothick/rustfmt-check@master
with: with:
token: ${{ github.token }} token: ${{ github.token }}
options: --manifest-path userspace/ksud/Cargo.toml working-directory: userspace/ksud

3
.gitignore vendored
View File

@@ -1 +1,2 @@
.vscode/ .idea
.vscode

View File

@@ -1,4 +1,5 @@
**English** | [中文](README_CN.md)
**English** | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md)
# KernelSU # KernelSU
@@ -8,22 +9,25 @@ A Kernel based root solution for Android devices.
1. Kernel-based `su` and root access management. 1. Kernel-based `su` and root access management.
2. Module system based on overlayfs. 2. Module system based on overlayfs.
3. [App Profile](https://kernelsu.org/guide/app-profile.html): Lock up the root power in a cage.
## Compatibility State ## Compatibility State
KernelSU officially supports Android GKI 2.0 devices(with kernel 5.10+), old kernels(4.14+) is also compatiable, but you need to build kernel yourself. KernelSU officially supports Android GKI 2.0 devices(with kernel 5.10+), old kernels(4.14+) is also compatible, but you need to build kernel yourself.
WSA and containter-based Android should also work with KernelSU integrated. WSA, ChromeOS and containter-based Android can also work with KernelSU integrated.
And the current supported ABIs are : `arm64-v8a` and `x86_64` And the current supported ABIs are : `arm64-v8a` and `x86_64`
## Usage ## Usage
[Installation](https://kernelsu.org/guide/installation.html) - [Installation Instruction](https://kernelsu.org/guide/installation.html)
- [How to build?](https://kernelsu.org/guide/how-to-build.html)
- [Official Website](https://kernelsu.org/)
## Build ## Translation
[How to build?](https://kernelsu.org/guide/how-to-build.html) To translate KernelSU into your language, or to improve an existing translation, use [Weblate](https://hosted.weblate.org/engage/kernelsu/) please.
### Discussion ### Discussion
@@ -37,6 +41,6 @@ And the current supported ABIs are : `arm64-v8a` and `x86_64`
## Credits ## Credits
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): the KernelSU idea. - [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): the KernelSU idea.
- [Magisk](https://github.com/topjohnwu/Magisk): the powerful root tool.
- [genuine](https://github.com/brevent/genuine/): apk v2 signature validation. - [genuine](https://github.com/brevent/genuine/): apk v2 signature validation.
- [Diamorphine](https://github.com/m0nad/Diamorphine): some rootkit skills. - [Diamorphine](https://github.com/m0nad/Diamorphine): some rootkit skills.
- [Magisk](https://github.com/topjohnwu/Magisk): the sepolicy implementation.

View File

@@ -1,4 +1,4 @@
[English](README.md) | **中文** [English](README.md) | [Español](README_ES.md) | **简体中文** | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md)
# KernelSU # KernelSU
@@ -8,24 +8,26 @@
- 基于内核的 su 和权限管理。 - 基于内核的 su 和权限管理。
- 基于 overlayfs 的模块系统。 - 基于 overlayfs 的模块系统。
- [App Profile](https://kernelsu.org/guide/app-profile.html): 把 Root 权限关进笼子里。
## 兼容状态 ## 兼容状态
KernelSU 官方支持 GKI 2.0 的设备内核版本5.10以上旧内核也是兼容的最低4.14+),不过需要自己编译内核。 KernelSU 官方支持 GKI 2.0 的设备内核版本5.10以上旧内核也是兼容的最低4.14+),不过需要自己编译内核。
WSA 和运行在容器上的 Android 也可以与 KernelSU 一起工作。 WSA, ChromeOS 和运行在容器上的 Android 也可以与 KernelSU 一起工作。
目前支持架构 : `arm64-v8a``x86_64` 目前支持架构 : `arm64-v8a``x86_64`
## 使用方法 ## 使用方法
[安装教程](https://kernelsu.org/zh_CN/guide/installation.html) - [安装教程](https://kernelsu.org/zh_CN/guide/installation.html)
- [如何构建?](https://kernelsu.org/zh_CN/guide/how-to-build.html)
## 构建 ## 参与翻译
[如何构建?](https://kernelsu.org/zh_CN/guide/how-to-build.html) 要将 KernelSU 翻译成您的语言,或完善现有的翻译,请使用 [Weblate](https://hosted.weblate.org/engage/kernelsu/)
### 讨论 ## 讨论
- Telegram: [@KernelSU](https://t.me/KernelSU) - Telegram: [@KernelSU](https://t.me/KernelSU)
@@ -37,6 +39,6 @@ WSA 和运行在容器上的 Android 也可以与 KernelSU 一起工作。
## 鸣谢 ## 鸣谢
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/)KernelSU 的灵感。 - [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/)KernelSU 的灵感。
- [true](https://github.com/brevent/genuine/)apk v2 签名验证 - [Magisk](https://github.com/topjohnwu/Magisk):强大的 root 工具箱
- [genuine](https://github.com/brevent/genuine/)apk v2 签名验证。
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。 - [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。
- [Magisk](https://github.com/topjohnwu/Magisk)sepolicy 的实现。

47
README_ES.md Normal file
View File

@@ -0,0 +1,47 @@
[ 🇬🇧 English](README.md) | 🇪🇸 **Español** | [🇨🇳 简体中文](README_CN.md) | [🇹🇼 繁體中文](README_TW.md) | [ 🇯🇵 日本語](README_JP.md) | [🇵🇱 Polski](README_PL.md) | [🇧🇷 Portuguese-Brazil](README_PT-BR.md) | [🇹🇷 Türkçe](README_TR.md) | [Русский](README_RU.md)
<div style="display: flex; align-items: center;">
<img src="https://kernelsu.org/logo.png" style="width: 96px;" alt="">
<div style="margin-left: 20px;">
<span style="font-size: large; "><b>KernelSU</b></span>
<br>
<span style="font-size: medium; "><i>Una solución root basada en el kernel para dispositivos Android.</i></span>
</div>
</div>
## 🚀 Características
**1.** Binario `su` basado en el kernel y gestión de acceso root.<br/>
**2.** Sistema de módulos basado en **OverlayFS**.
## ✅ Estado de compatibilidad
**KernelSU** soporta de forma oficial dispositivos Android con **GKI 2.0** (a partir de la versión **5.10** del kernel). Los kernels antiguos (a partir de la versión **4.14**) también son compatibles, pero necesitas compilarlos por tu cuenta.
El **Subsistema de Windows para Android (WSA)** e implementaciones de Android basadas en contenedores, como **Waydroid**, también deberían funcionar con **KernelSU** integrado.
Actualmente se soportan las siguientes **ABIs**: `arm64-v8a`; `x86_64`.
## 📖 Uso
[¿Cómo instalarlo?](https://kernelsu.org/guide/installation.html)
## 🔨 Compilación
[¿Cómo compilarlo?](https://kernelsu.org/guide/how-to-build.html)
## 💬 Discusión
- Telegram: [@KernelSU](https://t.me/KernelSU)
## ⚖️ Licencia
- Los archivos bajo el directorio `kernel` están licenciados bajo [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
- Todas las demás partes, a excepción del directorio `kernel`, están licenciados bajo [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html).
## 👥 Créditos
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): la idea de **KernelSU**.
- [genuine](https://github.com/brevent/genuine/): la validación del **esquema de firmas APK v2**.
- [Diamorphine](https://github.com/m0nad/Diamorphine): algunas habilidades de rootkit.
- [Magisk](https://github.com/topjohnwu/Magisk): la implementación de la **política de SELinux (SEPolicy)**.

47
README_JP.md Normal file
View File

@@ -0,0 +1,47 @@
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | **日本語** | [Polski](README_PL.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md)
# KernelSU
Android におけるカーネルベースの root ソリューションです。
## 特徴
1. カーネルベースの `su` と権限管理
2. OverlayFS に基づくモジュールシステム
3. [アプリのプロファイル](https://kernelsu.org/guide/app-profile.html): root の権限をケージ内に閉じ込めます。
## 対応状況
KernelSU は GKI 2.0 デバイス(カーネルバージョン 5.10 以上を公式にサポートしています。古いカーネル4.14以上)とも互換性がありますが、自分でカーネルをビルドする必要があります。
WSA 、ChromeOS とコンテナ上で動作する Android でも KernelSU を統合して動かせます。
現在サポートしているアーキテクチャは `arm64-v8a` および `x86_64` です。
## 使用方法
- [インストール方法はこちら](https://kernelsu.org/ja_JP/guide/installation.html)
- [ビルド方法はこちら](https://kernelsu.org/guide/how-to-build.html)
- [公式サイト](https://kernelsu.org)
## 翻訳
KernelSU をあなたの言語に翻訳するか、既存の翻訳を改善するには、[Weblate](https://hosted.weblate.org/engage/kernelsu/) を使用してください。
## ディスカッション
- Telegram: [@KernelSU](https://t.me/KernelSU)
## ライセンス
- `kernel` ディレクトリの下にあるすべてのファイル: [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
- `kernel` ディレクトリ以外のすべてのファイル: [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
## クレジット
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/)KernelSU のアイデア元
- [Magisk](https://github.com/topjohnwu/Magisk):強力な root ツール
- [genuine](https://github.com/brevent/genuine/)apk v2 の署名検証
- [Diamorphine](https://github.com/m0nad/Diamorphine): rootkit のスキル

42
README_PL.md Normal file
View File

@@ -0,0 +1,42 @@
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | **Polski** | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md)
# KernelSU
Rozwiązanie root oparte na jądrze dla urządzeń z systemem Android.
## Cechy
1. Oparte na jądrze `su` i zarządzanie dostępem roota.
2. System modułów oparty na overlayfs.
## Kompatybilność
KernelSU oficjalnie obsługuje urządzenia z Androidem GKI 2.0 (z jądrem 5.10+), starsze jądra (4.14+) są również kompatybilne, ale musisz sam skompilować jądro.
WSA i Android oparty na kontenerach również powinny działać ze zintegrowanym KernelSU.
Aktualnie obsługiwane ABI to : `arm64-v8a` i `x86_64`.
## Użycie
[Instalacja](https://kernelsu.org/guide/installation.html)
## Kompilacja
[Jak skompilować?](https://kernelsu.org/guide/how-to-build.html)
### Dyskusja
- Telegram: [@KernelSU](https://t.me/KernelSU)
## Licencja
- Pliki w katalogu `kernel` są na licencji [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
- Wszystkie inne części poza katalogiem `kernel` są na licencji [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
## Podziękowania
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): pomysłodawca KernelSU.
- [genuine](https://github.com/brevent/genuine/): walidacja podpisu apk v2.
- [Diamorphine](https://github.com/m0nad/Diamorphine): cenna znajomość rootkitów.
- [Magisk](https://github.com/topjohnwu/Magisk): implementacja sepolicy.

46
README_PT-BR.md Normal file
View File

@@ -0,0 +1,46 @@
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | **Portuguese-Brazil** | [Türkçe](README_TR.md) | [Русский](README_RU.md)
# KernelSU
Uma solução raiz baseada em Kernel para dispositivos Android.
## Características
1. `su` baseado em kernel e gerenciamento de acesso root.
2. Sistema modular baseado em overlayfs.
3. [App Perfil](https://kernelsu.org/guide/app-profile.html): Tranque o poder raiz em uma gaiola.
## Estado de compatibilidade
O KernelSU suporta oficialmente dispositivos Android GKI 2.0 (com kernel 5.10+), kernels antigos (4.14+) também são compatíveis, mas você mesmo precisa construir o kernel.
WSA, ChromeOS e Android baseado em contêiner também deve funcionar com o KernelSU integrado.
E os ABIs atualmente suportados são: `arm64-v8a` e `x86_64`
## Uso
- [Instalação](https://kernelsu.org/guide/installation.html)
- [Como construir?](https://kernelsu.org/guide/how-to-build.html)
- [Site Oficial](https://kernelsu.org/)
## Tradução
Para traduzir o KernelSU para o seu idioma, ou para melhorar uma tradução existente, use o [Weblate](https://hosted.weblate.org/engage/kernelsu/), por favor.
### Discussão
- Telegram: [@KernelSU](https://t.me/KernelSU)
## Licença
- Os arquivos no diretório `kernel` são [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
- Todas as outras partes, exceto o diretório `kernel`, são [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
## Créditos
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): a ideia do KernelSU.
- [Magisk](https://github.com/topjohnwu/Magisk): a implementação da sepolicy.
- [genuine](https://github.com/brevent/genuine/): validação de assinatura apk v2.
- [Diamorphine](https://github.com/m0nad/Diamorphine): algumas habilidades de rootkit.

42
README_RU.md Normal file
View File

@@ -0,0 +1,42 @@
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md) | **Русский**
# KernelSU
Решение на основе ядра root для Android-устройств.
## Особенности
1. Управление `su` и root-доступом на основе ядра.
2. Система модулей на основе overlayfs.
## Совместимость
KernelSU официально поддерживает устройства на базе Android GKI 2.0 (с ядром 5.10+), старые ядра (4.14+) также совместимы, но для этого необходимо собрать ядро самостоятельно.
WSA и Android на основе контейнеров также должны работать с интегрированным KernelSU.
В настоящее время поддерживаются следующие ABI: `arm64-v8a` и `x86_64`.
## Использование
[Установка](https://kernelsu.org/ru_RU/guide/installation.html)
## Сборка
[Как собрать?](https://kernelsu.org/ru_RU/guide/how-to-build.html)
## Обсуждение
- Telegram: [@KernelSU](https://t.me/KernelSU)
## Лицензия
- Файлы в директории `kernel` - [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
- Все остальные части, кроме директории `kernel` - [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
## Благодарности
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): идея KernelSU.
- [genuine](https://github.com/brevent/genuine/): проверка подписи apk v2.
- [Diamorphine](https://github.com/m0nad/Diamorphine): некоторые навыки руткита.
- [Magisk](https://github.com/topjohnwu/Magisk): реализация sepolicy.

45
README_TR.md Normal file
View File

@@ -0,0 +1,45 @@
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | [繁體中文](README_TW.md) | [日本語](README_JP.md) | [Polski](README_PL.md) | [Portuguese-Brazil](README_PT-BR.md) | **Türkçe** | [Русский](README_RU.md)
# KernelSU
Android cihazlar için kernel tabanlı bir root çözümü.
## Özellikler
1. Kernel-tabanlı `su` ve root erişimi yönetimi.
2. Overlayfs'ye dayalı modül sistemi.
3. [Uygulama profili](https://kernelsu.org/guide/app-profile.html): Root gücünü bir kafese kapatın.
## Uyumluluk Durumu
KernelSU resmi olarak Android GKI 2.0 cihazlarını ( 5.10+ kernelli) destekler, eski kernellerle de (4.14+) uyumludur, ancak kerneli kendinizin inşaa etmesi gerekir.
WSA ve konteyner tabanlı Android'in de, KernelSU ile entegre olarak da çalışması gerekmektedir.
Ve desteklenen mevcut ABI'ler : `arm64-v8a` ve `x86_64`
## Kullanım
- [Yükleme](https://kernelsu.org/guide/installation.html)
- [Nasıl inşa edilir?](https://kernelsu.org/guide/how-to-build.html)
- [Resmi WEB sitesi](https://kernelsu.org/)
## Çeviri
KernelSU'yu kendi dilinize çevirmek veya varolan bir çeviriyi geliştirmek istiyorsanız, lütfen [Weblate](https://hosted.weblate.org/engage/kernelsu/)'i kullanın.
### Tartışma
- Telegram: [@KernelSU](https://t.me/KernelSU)
## Lisans
- `kernel` klasöründeki dosyalar [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) lisansı altındadır.
- `kernel` klasörü dışındaki bütün diğer bölümler [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html) lisansı altındadır.
## Krediler
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/): KernelSU fikri.
- [Magisk](https://github.com/topjohnwu/Magisk): güçlü root aracı.
- [genuine](https://github.com/brevent/genuine/): apk v2 imza doğrulama.
- [Diamorphine](https://github.com/m0nad/Diamorphine): bazı rootkit becerileri.

42
README_TW.md Normal file
View File

@@ -0,0 +1,42 @@
[English](README.md) | [Español](README_ES.md) | [简体中文](README_CN.md) | **繁體中文** | [日本語](README_JP.md) | [Polski](README_PL.md) | [Portuguese-Brazil](README_PT-BR.md) | [Türkçe](README_TR.md) | [Русский](README_RU.md)
# KernelSU
一個基於核心的 Android 裝置 Root 解決方案
## 功能
- 基於核心的 Su 和 Root 存取權管理。
- 基於 Overlayfs 的模組系統。
## 相容性狀態
KernelSU 官方支援 Android GKI 2.0 的裝置 (核心版本 5.10+);舊版核心同樣相容 (最低 4.14+),但需要自行編譯核心。
WSA 和執行在容器中的 Android 也可以與 KernelSU 一同運作。
目前支援架構:`arm64-v8a``x86_64`
## 使用方法
[安裝教學](https://kernelsu.org/zh_TW/guide/installation.html)
## 建置
[如何建置?](https://kernelsu.org/zh_TW/guide/how-to-build.html)
### 討論
- Telegram[@KernelSU](https://t.me/KernelSU)
## 授權
- 目錄 `kernel` 下所有檔案為 [GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
-`kernel` 目錄的其他部分均為 [GPL-3](https://www.gnu.org/licenses/gpl-3.0.html)
## 致謝
- [kernel-assisted-superuser](https://git.zx2c4.com/kernel-assisted-superuser/about/)KernelSU 的靈感。
- [genuine](https://github.com/brevent/genuine/)apk v2 簽章驗證。
- [Diamorphine](https://github.com/m0nad/Diamorphine):一些 rootkit 技巧。
- [Magisk](https://github.com/topjohnwu/Magisk)sepolicy 實作。

View File

@@ -1,4 +1,4 @@
Diagnostics: Diagnostics:
UnusedIncludes: Strict UnusedIncludes: Strict
ClangTidy: ClangTidy:
Remove: bugprone-sizeof-expression Remove: bugprone-sizeof-expression

View File

@@ -1,14 +1,17 @@
menu "KernelSU"
config KSU config KSU
tristate "KernelSU module" tristate "KernelSU function support"
select OVERLAY_FS
default y default y
depends on KPROBES
depends on OVERLAY_FS
help help
This is the KSU privilege driver for android system. Enable kernel-level root privileges on Android System.
config KSU_DEBUG config KSU_DEBUG
tristate "KernelSU module debug mode" bool "KernelSU debug mode"
default n
depends on KSU depends on KSU
default n
help help
This enables debug mode for KSU Enable KernelSU debug mode
endmenu

View File

@@ -15,18 +15,27 @@ obj-y += selinux/
# .git is a text file while the module is imported by 'git submodule add'. # .git is a text file while the module is imported by 'git submodule add'.
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0) ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD) KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD)
ccflags-y += -DKSU_GIT_VERSION=$(KSU_GIT_VERSION) # ksu_version: major * 10000 + git version + 200 for historical reasons
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 200))
$(info -- KernelSU version: $(KSU_VERSION))
ccflags-y += -DKSU_VERSION=$(KSU_VERSION)
else # If there is no .git file, the default version will be passed.
$(warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git submodule!")
ccflags-y += -DKSU_VERSION=16
endif endif
ifndef EXPECTED_SIZE ifndef KSU_EXPECTED_SIZE
EXPECTED_SIZE := 0x033b KSU_EXPECTED_SIZE := 0x033b
endif endif
ifndef EXPECTED_HASH ifndef KSU_EXPECTED_HASH
EXPECTED_HASH := 0xb0b91415 KSU_EXPECTED_HASH := 0xb0b91415
endif endif
ccflags-y += -DEXPECTED_SIZE=$(EXPECTED_SIZE) $(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
ccflags-y += -DEXPECTED_HASH=$(EXPECTED_HASH) $(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
ccflags-y += -DEXPECTED_SIZE=$(KSU_EXPECTED_SIZE)
ccflags-y += -DEXPECTED_HASH=$(KSU_EXPECTED_HASH)
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion -Wno-gcc-compat
ccflags-y += -Wno-macro-redefined -Wno-declaration-after-statement ccflags-y += -Wno-declaration-after-statement

View File

@@ -1,27 +1,91 @@
#include "linux/delay.h" #include "ksu.h"
#include "linux/compiler.h"
#include "linux/fs.h" #include "linux/fs.h"
#include "linux/gfp.h"
#include "linux/kernel.h" #include "linux/kernel.h"
#include "linux/list.h" #include "linux/list.h"
#include "linux/printk.h" #include "linux/printk.h"
#include "linux/slab.h" #include "linux/slab.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"
#endif
#include "klog.h" // IWYU pragma: keep #include "klog.h" // IWYU pragma: keep
#include "selinux/selinux.h" #include "selinux/selinux.h"
#include "kernel_compat.h" #include "kernel_compat.h"
#include "allowlist.h"
#define FILE_MAGIC 0x7f4b5355 // ' KSU', u32 #define FILE_MAGIC 0x7f4b5355 // ' KSU', u32
#define FILE_FORMAT_VERSION 1 // u32 #define FILE_FORMAT_VERSION 3 // u32
#define KSU_APP_PROFILE_PRESERVE_UID 9999 // NOBODY_UID
#define KSU_DEFAULT_SELINUX_DOMAIN "u:r:su:s0"
static DEFINE_MUTEX(allowlist_mutex); static DEFINE_MUTEX(allowlist_mutex);
// default profiles, these may be used frequently, so we cache it
static struct root_profile default_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);
static int allow_list_pointer __read_mostly = 0;
static void remove_uid_from_arr(uid_t uid)
{
int *temp_arr;
int i, j;
if (allow_list_pointer == 0)
return;
temp_arr = kmalloc(sizeof(allow_list_arr), GFP_KERNEL);
if (temp_arr == NULL) {
pr_err("%s: unable to allocate memory\n", __func__);
return;
}
for (i = j = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid)
continue;
temp_arr[j++] = allow_list_arr[i];
}
allow_list_pointer = j;
for (; j < ARRAY_SIZE(allow_list_arr); j++)
temp_arr[j] = -1;
memcpy(&allow_list_arr, temp_arr, PAGE_SIZE);
kfree(temp_arr);
}
static void init_default_profiles()
{
default_root_profile.uid = 0;
default_root_profile.gid = 0;
default_root_profile.groups_count = 1;
default_root_profile.groups[0] = 0;
memset(&default_root_profile.capabilities, 0xff,
sizeof(default_root_profile.capabilities));
default_root_profile.namespaces = 0;
strcpy(default_root_profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
// This means that we will umount modules by default!
default_non_root_profile.umount_modules = true;
}
struct perm_data { struct perm_data {
struct list_head list; struct list_head list;
uid_t uid; struct app_profile profile;
bool allow;
}; };
static struct list_head allow_list; static struct list_head allow_list;
static uint8_t allow_list_bitmap[PAGE_SIZE] __read_mostly __aligned(PAGE_SIZE);
#define BITMAP_UID_MAX ((sizeof(allow_list_bitmap) * BITS_PER_BYTE) - 1)
#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_save_work;
@@ -36,67 +100,231 @@ void ksu_show_allow_list(void)
pr_info("ksu_show_allow_list"); pr_info("ksu_show_allow_list");
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("uid :%d, allow: %d\n", p->uid, p->allow); pr_info("uid :%d, allow: %d\n", p->profile.current_uid,
p->profile.allow_su);
} }
} }
bool ksu_allow_uid(uid_t uid, bool allow, bool persist) #ifdef CONFIG_KSU_DEBUG
static void ksu_grant_root_to_shell()
{
struct app_profile profile = {
.allow_su = true,
.current_uid = 2000,
};
strcpy(profile.key, "com.android.shell");
strcpy(profile.rp_config.profile.selinux_domain, KSU_DEFAULT_SELINUX_DOMAIN);
ksu_set_app_profile(&profile, false);
}
#endif
bool ksu_get_app_profile(struct app_profile *profile)
{
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);
bool uid_match = profile->current_uid == p->profile.current_uid;
if (uid_match) {
// found it, override it with ours
memcpy(profile, &p->profile, sizeof(*profile));
found = true;
goto exit;
}
}
exit:
return found;
}
static inline bool forbid_system_uid(uid_t uid) {
#define SHELL_UID 2000
#define SYSTEM_UID 1000
return uid < SHELL_UID && uid != SYSTEM_UID;
}
static bool profile_valid(struct app_profile *profile)
{
if (!profile) {
return false;
}
if (forbid_system_uid(profile->current_uid)) {
pr_err("uid lower than 2000 is unsupported: %d\n", profile->current_uid);
return false;
}
if (profile->version < KSU_APP_PROFILE_VER) {
pr_info("Unsupported profile version: %d\n", profile->version);
return false;
}
if (profile->allow_su) {
if (profile->rp_config.profile.groups_count > KSU_MAX_GROUPS) {
return false;
}
if (strlen(profile->rp_config.profile.selinux_domain) == 0) {
return false;
}
}
return true;
}
bool ksu_set_app_profile(struct app_profile *profile, bool persist)
{ {
// find the node first!
struct perm_data *p = NULL; struct perm_data *p = NULL;
struct list_head *pos = NULL; struct list_head *pos = NULL;
bool result = false; bool result = false;
if (!profile_valid(profile)) {
pr_err("Failed to set app profile: invalid profile!\n");
return false;
}
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);
if (uid == p->uid) { // both uid and package must match, otherwise it will break multiple package with different user id
p->allow = allow; if (profile->current_uid == p->profile.current_uid &&
!strcmp(profile->key, p->profile.key)) {
// found it, just override it all!
memcpy(&p->profile, profile, sizeof(*profile));
result = true; result = true;
goto exit; goto out;
} }
} }
// 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 *)kmalloc(sizeof(struct perm_data), GFP_KERNEL);
if (!p) { if (!p) {
pr_err("alloc allow node failed.\n"); pr_err("ksu_set_app_profile alloc failed\n");
return false; return false;
} }
p->uid = uid;
p->allow = allow;
pr_info("allow_uid: %d, allow: %d", uid, allow);
memcpy(&p->profile, profile, sizeof(*profile));
if (profile->allow_su) {
pr_info("set root profile, key: %s, uid: %d, gid: %d, context: %s\n",
profile->key, profile->current_uid,
profile->rp_config.profile.gid,
profile->rp_config.profile.selinux_domain);
} else {
pr_info("set app profile, key: %s, uid: %d, umount modules: %d\n",
profile->key, profile->current_uid,
profile->nrp_config.profile.umount_modules);
}
list_add_tail(&p->list, &allow_list); list_add_tail(&p->list, &allow_list);
out:
if (profile->current_uid <= BITMAP_UID_MAX) {
if (profile->allow_su)
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] |= 1 << (profile->current_uid % BITS_PER_BYTE);
else
allow_list_bitmap[profile->current_uid / BITS_PER_BYTE] &= ~(1 << (profile->current_uid % BITS_PER_BYTE));
} else {
if (profile->allow_su) {
/*
* 1024 apps with uid higher than BITMAP_UID_MAX
* registered to request superuser?
*/
if (allow_list_pointer >= ARRAY_SIZE(allow_list_arr)) {
pr_err("too many apps registered\n");
WARN_ON(1);
return false;
}
allow_list_arr[allow_list_pointer++] = profile->current_uid;
} else {
remove_uid_from_arr(profile->current_uid);
}
}
result = true; result = true;
exit: // check if the default profiles is changed, cache it to a single struct to accelerate access.
if (unlikely(!strcmp(profile->key, "$"))) {
// set default non root profile
memcpy(&default_non_root_profile, &profile->nrp_config.profile,
sizeof(default_non_root_profile));
}
if (unlikely(!strcmp(profile->key, "#"))) {
// set default root profile
memcpy(&default_root_profile, &profile->rp_config.profile,
sizeof(default_root_profile));
}
if (persist) if (persist)
persistent_allow_list(); persistent_allow_list();
return result; return result;
} }
bool ksu_is_allow_uid(uid_t uid) bool __ksu_is_allow_uid(uid_t uid)
{ {
struct perm_data *p = NULL; int i;
struct list_head *pos = NULL;
if (uid == 0) { if (unlikely(uid == 0)) {
// already root, but only allow our domain. // already root, but only allow our domain.
return is_ksu_domain(); return is_ksu_domain();
} }
list_for_each (pos, &allow_list) { if (forbid_system_uid(uid)) {
p = list_entry(pos, struct perm_data, list); // do not bother going through the list if it's system
// pr_info("is_allow_uid uid :%d, allow: %d\n", p->uid, p->allow); return false;
if (uid == p->uid) { }
return p->allow;
if (likely(uid <= BITMAP_UID_MAX)) {
return !!(allow_list_bitmap[uid / BITS_PER_BYTE] & (1 << (uid % BITS_PER_BYTE)));
} else {
for (i = 0; i < allow_list_pointer; i++) {
if (allow_list_arr[i] == uid)
return true;
} }
} }
return false; return false;
} }
bool ksu_uid_should_umount(uid_t uid)
{
struct app_profile profile = { .current_uid = uid };
bool found = ksu_get_app_profile(&profile);
if (!found) {
// no app profile found, it must be non root app
return default_non_root_profile.umount_modules;
}
if (profile.allow_su) {
// if found and it is granted to su, we shouldn't umount for it
return false;
} else {
// found an app profile
if (profile.nrp_config.use_default) {
return default_non_root_profile.umount_modules;
} else {
return profile.nrp_config.profile.umount_modules;
}
}
}
struct root_profile *ksu_get_root_profile(uid_t uid)
{
struct perm_data *p = NULL;
struct list_head *pos = NULL;
list_for_each (pos, &allow_list) {
p = list_entry(pos, struct perm_data, list);
if (uid == p->profile.current_uid && p->profile.allow_su) {
if (!p->profile.rp_config.use_default) {
return &p->profile.rp_config.profile;
}
}
}
// use default profile
return &default_root_profile;
}
bool ksu_get_allow_list(int *array, int *length, bool allow) bool ksu_get_allow_list(int *array, int *length, bool allow)
{ {
struct perm_data *p = NULL; struct perm_data *p = NULL;
@@ -105,8 +333,8 @@ bool ksu_get_allow_list(int *array, int *length, bool allow)
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("get_allow_list uid: %d allow: %d\n", p->uid, p->allow); // pr_info("get_allow_list uid: %d allow: %d\n", p->uid, p->allow);
if (p->allow == allow) { if (p->profile.allow_su == allow) {
array[i++] = p->uid; array[i++] = p->profile.current_uid;
} }
} }
*length = i; *length = i;
@@ -114,24 +342,24 @@ bool ksu_get_allow_list(int *array, int *length, bool allow)
return true; return true;
} }
void do_persistent_allow_list(struct work_struct *work) void do_save_allow_list(struct work_struct *work)
{ {
u32 magic = FILE_MAGIC; u32 magic = FILE_MAGIC;
u32 version = FILE_FORMAT_VERSION; u32 version = FILE_FORMAT_VERSION;
struct perm_data *p = NULL; struct perm_data *p = NULL;
struct list_head *pos = NULL; struct list_head *pos = NULL;
loff_t off = 0; loff_t off = 0;
KWORKER_INSTALL_KEYRING();
struct file *fp =
filp_open(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT, 0644);
struct file *fp =
ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_WRONLY | O_CREAT, 0644);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
pr_err("save_allow_list creat file failed: %d\n", PTR_ERR(fp)); pr_err("save_allow_list create file failed: %ld\n", PTR_ERR(fp));
return; return;
} }
// store magic and version // store magic and version
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) { if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) !=
sizeof(magic)) {
pr_err("save_allow_list write magic failed.\n"); pr_err("save_allow_list write magic failed.\n");
goto exit; goto exit;
} }
@@ -144,10 +372,12 @@ void do_persistent_allow_list(struct work_struct *work)
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 uid :%d, allow: %d\n", p->uid, pr_info("save allow list, name: %s uid :%d, allow: %d\n",
p->allow); p->profile.key, p->profile.current_uid,
ksu_kernel_write_compat(fp, &p->uid, sizeof(p->uid), &off); p->profile.allow_su);
ksu_kernel_write_compat(fp, &p->allow, sizeof(p->allow), &off);
ksu_kernel_write_compat(fp, &p->profile, sizeof(p->profile),
&off);
} }
exit: exit:
@@ -161,29 +391,22 @@ void do_load_allow_list(struct work_struct *work)
struct file *fp = NULL; struct file *fp = NULL;
u32 magic; u32 magic;
u32 version; u32 version;
KWORKER_INSTALL_KEYRING();
#ifdef CONFIG_KSU_DEBUG
// always allow adb shell by default
ksu_grant_root_to_shell();
#endif
// load allowlist now! // load allowlist now!
fp = filp_open(KERNEL_SU_ALLOWLIST, O_RDONLY, 0); fp = ksu_filp_open_compat(KERNEL_SU_ALLOWLIST, O_RDONLY, 0);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
#ifdef CONFIG_KSU_DEBUG pr_err("load_allow_list open file failed: %ld\n", PTR_ERR(fp));
int errno = PTR_ERR(fp);
if (errno == -ENOENT) {
ksu_allow_uid(2000, true,
true); // allow adb shell by default
} else {
pr_err("load_allow_list open file failed: %d\n",
PTR_ERR(fp));
}
#else
pr_err("load_allow_list open file failed: %d\n", PTR_ERR(fp));
#endif
return; return;
} }
// verify magic // verify magic
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) || if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) !=
sizeof(magic) ||
magic != FILE_MAGIC) { magic != FILE_MAGIC) {
pr_err("allowlist file invalid: %d!\n", magic); pr_err("allowlist file invalid: %d!\n", magic);
goto exit; goto exit;
@@ -198,18 +421,19 @@ void do_load_allow_list(struct work_struct *work)
pr_info("allowlist version: %d\n", version); pr_info("allowlist version: %d\n", version);
while (true) { while (true) {
u32 uid; struct app_profile profile;
bool allow = false;
ret = ksu_kernel_read_compat(fp, &uid, sizeof(uid), &off); ret = ksu_kernel_read_compat(fp, &profile, sizeof(profile),
&off);
if (ret <= 0) { if (ret <= 0) {
pr_info("load_allow_list read err: %d\n", ret); pr_info("load_allow_list read err: %zd\n", ret);
break; break;
} }
ret = ksu_kernel_read_compat(fp, &allow, sizeof(allow), &off);
pr_info("load_allow_uid: %d, allow: %d\n", uid, allow); pr_info("load_allow_uid, name: %s, uid: %d, allow: %d\n",
profile.key, profile.current_uid, profile.allow_su);
ksu_allow_uid(uid, allow, false); ksu_set_app_profile(&profile, false);
} }
exit: exit:
@@ -226,11 +450,16 @@ void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data)
// TODO: use RCU! // TODO: use RCU!
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) {
uid_t uid = np->uid; uid_t uid = np->profile.current_uid;
if (!is_uid_exist(uid, data)) { // we use this uid for special cases, don't prune it!
bool is_preserved_uid = uid == KSU_APP_PROFILE_PRESERVE_UID;
if (!is_preserved_uid && !is_uid_exist(uid, data)) {
modified = true; modified = true;
pr_info("prune uid: %d\n", uid); pr_info("prune uid: %d\n", uid);
list_del(&np->list); list_del(&np->list);
allow_list_bitmap[uid / BITS_PER_BYTE] &= ~(1 << (uid % BITS_PER_BYTE));
remove_uid_from_arr(uid);
smp_mb();
kfree(np); kfree(np);
} }
} }
@@ -254,10 +483,20 @@ bool ksu_load_allow_list(void)
void ksu_allowlist_init(void) void ksu_allowlist_init(void)
{ {
int i;
BUILD_BUG_ON(sizeof(allow_list_bitmap) != PAGE_SIZE);
BUILD_BUG_ON(sizeof(allow_list_arr) != PAGE_SIZE);
for (i = 0; i < ARRAY_SIZE(allow_list_arr); i++)
allow_list_arr[i] = -1;
INIT_LIST_HEAD(&allow_list); INIT_LIST_HEAD(&allow_list);
INIT_WORK(&ksu_save_work, do_persistent_allow_list); INIT_WORK(&ksu_save_work, do_save_allow_list);
INIT_WORK(&ksu_load_work, do_load_allow_list); INIT_WORK(&ksu_load_work, do_load_allow_list);
init_default_profiles();
} }
void ksu_allowlist_exit(void) void ksu_allowlist_exit(void)
@@ -265,7 +504,7 @@ 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_persistent_allow_list(NULL); do_save_allow_list(NULL);
// free allowlist // free allowlist
mutex_lock(&allowlist_mutex); mutex_lock(&allowlist_mutex);
@@ -274,4 +513,4 @@ void ksu_allowlist_exit(void)
kfree(np); kfree(np);
} }
mutex_unlock(&allowlist_mutex); mutex_unlock(&allowlist_mutex);
} }

View File

@@ -2,6 +2,7 @@
#define __KSU_H_ALLOWLIST #define __KSU_H_ALLOWLIST
#include "linux/types.h" #include "linux/types.h"
#include "ksu.h"
void ksu_allowlist_init(void); void ksu_allowlist_init(void);
@@ -11,12 +12,16 @@ bool ksu_load_allow_list(void);
void ksu_show_allow_list(void); void ksu_show_allow_list(void);
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))
bool ksu_allow_uid(uid_t uid, bool allow, bool persist);
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, void *), void *data); void ksu_prune_allowlist(bool (*is_uid_exist)(uid_t, void *), void *data);
#endif bool ksu_get_app_profile(struct app_profile *);
bool ksu_set_app_profile(struct app_profile *, bool persist);
bool ksu_uid_should_umount(uid_t uid);
struct root_profile *ksu_get_root_profile(uid_t uid);
#endif

View File

@@ -16,7 +16,7 @@ check_v2_signature(char *path, unsigned expected_size, unsigned expected_hash)
int sign = -1; int sign = -1;
int i; int i;
struct file *fp = filp_open(path, O_RDONLY, 0); struct file *fp = ksu_filp_open_compat(path, O_RDONLY, 0);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
pr_err("open %s error.", path); pr_err("open %s error.", path);
return PTR_ERR(fp); return PTR_ERR(fp);

View File

@@ -4,4 +4,4 @@
// return 0 if signature match // return 0 if signature match
int is_manager_apk(char *path); int is_manager_apk(char *path);
#endif #endif

View File

@@ -8,7 +8,8 @@
#define __PT_PARM1_REG regs[0] #define __PT_PARM1_REG regs[0]
#define __PT_PARM2_REG regs[1] #define __PT_PARM2_REG regs[1]
#define __PT_PARM3_REG regs[2] #define __PT_PARM3_REG regs[2]
#define __PT_PARM4_REG regs[3] #define __PT_SYSCALL_PARM4_REG regs[3]
#define __PT_CCALL_PARM4_REG regs[3]
#define __PT_PARM5_REG regs[4] #define __PT_PARM5_REG regs[4]
#define __PT_PARM6_REG regs[5] #define __PT_PARM6_REG regs[5]
#define __PT_RET_REG regs[30] #define __PT_RET_REG regs[30]
@@ -29,8 +30,8 @@
#define __PT_PARM2_REG si #define __PT_PARM2_REG si
#define __PT_PARM3_REG dx #define __PT_PARM3_REG dx
/* syscall uses r10 for PARM4 */ /* syscall uses r10 for PARM4 */
#define __PT_PARM4_REG r10 #define __PT_SYSCALL_PARM4_REG r10
// #define __PT_PARM4_REG cx #define __PT_CCALL_PARM4_REG cx
#define __PT_PARM5_REG r8 #define __PT_PARM5_REG r8
#define __PT_PARM6_REG r9 #define __PT_PARM6_REG r9
#define __PT_RET_REG sp #define __PT_RET_REG sp
@@ -56,7 +57,8 @@
#define PT_REGS_PARM1(x) (__PT_REGS_CAST(x)->__PT_PARM1_REG) #define PT_REGS_PARM1(x) (__PT_REGS_CAST(x)->__PT_PARM1_REG)
#define PT_REGS_PARM2(x) (__PT_REGS_CAST(x)->__PT_PARM2_REG) #define PT_REGS_PARM2(x) (__PT_REGS_CAST(x)->__PT_PARM2_REG)
#define PT_REGS_PARM3(x) (__PT_REGS_CAST(x)->__PT_PARM3_REG) #define PT_REGS_PARM3(x) (__PT_REGS_CAST(x)->__PT_PARM3_REG)
#define PT_REGS_PARM4(x) (__PT_REGS_CAST(x)->__PT_PARM4_REG) #define PT_REGS_SYSCALL_PARM4(x) (__PT_REGS_CAST(x)->__PT_SYSCALL_PARM4_REG)
#define PT_REGS_CCALL_PARM4(x) (__PT_REGS_CAST(x)->__PT_CCALL_PARM4_REG)
#define PT_REGS_PARM5(x) (__PT_REGS_CAST(x)->__PT_PARM5_REG) #define PT_REGS_PARM5(x) (__PT_REGS_CAST(x)->__PT_PARM5_REG)
#define PT_REGS_PARM6(x) (__PT_REGS_CAST(x)->__PT_PARM6_REG) #define PT_REGS_PARM6(x) (__PT_REGS_CAST(x)->__PT_PARM6_REG)
#define PT_REGS_RET(x) (__PT_REGS_CAST(x)->__PT_RET_REG) #define PT_REGS_RET(x) (__PT_REGS_CAST(x)->__PT_RET_REG)

View File

@@ -1,3 +1,4 @@
#include "linux/capability.h"
#include "linux/cred.h" #include "linux/cred.h"
#include "linux/dcache.h" #include "linux/dcache.h"
#include "linux/err.h" #include "linux/err.h"
@@ -5,6 +6,7 @@
#include "linux/kernel.h" #include "linux/kernel.h"
#include "linux/kprobes.h" #include "linux/kprobes.h"
#include "linux/lsm_hooks.h" #include "linux/lsm_hooks.h"
#include "linux/nsproxy.h"
#include "linux/path.h" #include "linux/path.h"
#include "linux/printk.h" #include "linux/printk.h"
#include "linux/uaccess.h" #include "linux/uaccess.h"
@@ -52,24 +54,85 @@ static inline bool is_isolated_uid(uid_t uid)
static struct group_info root_groups = { .usage = ATOMIC_INIT(2) }; static struct group_info root_groups = { .usage = ATOMIC_INIT(2) };
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;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
group_info->gid[i] = kgid;
#else
GROUP_AT(group_info, i) = kgid;
#endif
}
groups_sort(group_info);
set_groups(cred, group_info);
}
void escape_to_root(void) void escape_to_root(void)
{ {
struct cred *cred; struct cred *cred;
cred = (struct cred *)__task_cred(current); cred = (struct cred *)__task_cred(current);
memset(&cred->uid, 0, sizeof(cred->uid)); if (cred->euid.val == 0) {
memset(&cred->gid, 0, sizeof(cred->gid)); pr_warn("Already root, don't escape!\n");
memset(&cred->suid, 0, sizeof(cred->suid)); return;
memset(&cred->euid, 0, sizeof(cred->euid)); }
memset(&cred->egid, 0, sizeof(cred->egid)); struct root_profile *profile = ksu_get_root_profile(cred->uid.val);
memset(&cred->fsuid, 0, sizeof(cred->fsuid));
memset(&cred->fsgid, 0, sizeof(cred->fsgid)); cred->uid.val = profile->uid;
memset(&cred->cap_inheritable, 0xff, sizeof(cred->cap_inheritable)); cred->suid.val = profile->uid;
memset(&cred->cap_permitted, 0xff, sizeof(cred->cap_permitted)); cred->euid.val = profile->uid;
memset(&cred->cap_effective, 0xff, sizeof(cred->cap_effective)); cred->fsuid.val = profile->uid;
memset(&cred->cap_bset, 0xff, sizeof(cred->cap_bset));
memset(&cred->cap_ambient, 0xff, sizeof(cred->cap_ambient)); cred->gid.val = profile->gid;
cred->fsgid.val = profile->gid;
cred->sgid.val = profile->gid;
cred->egid.val = profile->gid;
BUILD_BUG_ON(sizeof(profile->capabilities.effective) !=
sizeof(kernel_cap_t));
// capabilities
memcpy(&cred->cap_effective, &profile->capabilities.effective,
sizeof(cred->cap_effective));
memcpy(&cred->cap_inheritable, &profile->capabilities.effective,
sizeof(cred->cap_inheritable));
memcpy(&cred->cap_permitted, &profile->capabilities.effective,
sizeof(cred->cap_permitted));
memcpy(&cred->cap_bset, &profile->capabilities.effective,
sizeof(cred->cap_bset));
memcpy(&cred->cap_ambient, &profile->capabilities.effective,
sizeof(cred->cap_ambient));
// disable seccomp // disable seccomp
#if defined(CONFIG_GENERIC_ENTRY) && \ #if defined(CONFIG_GENERIC_ENTRY) && \
@@ -85,12 +148,9 @@ void escape_to_root(void)
#else #else
#endif #endif
// setgroup to root setup_groups(profile, cred);
if (cred->group_info)
put_group_info(cred->group_info);
cred->group_info = get_group_info(&root_groups);
setup_selinux(); setup_selinux(profile->selinux_domain);
} }
int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry) int ksu_handle_rename(struct dentry *old_dentry, struct dentry *new_dentry)
@@ -172,8 +232,10 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
// someone wants to be root manager, just check it! // someone wants to be root manager, just check it!
// arg3 should be `/data/user/<userId>/<manager_package_name>` // arg3 should be `/data/user/<userId>/<manager_package_name>`
char param[128]; char param[128];
if (copy_from_user(param, arg3, sizeof(param))) { if (ksu_strncpy_from_user_nofault(param, arg3, sizeof(param)) == -EFAULT) {
#ifdef CONFIG_KSU_DEBUG
pr_err("become_manager: copy param err\n"); pr_err("become_manager: copy param err\n");
#endif
return 0; return 0;
} }
@@ -185,7 +247,8 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
if (userId == 0) { if (userId == 0) {
prefix = "/data/data"; prefix = "/data/data";
} else { } else {
snprintf(prefixTmp, sizeof(prefixTmp), "/data/user/%d", userId); snprintf(prefixTmp, sizeof(prefixTmp), "/data/user/%d",
userId);
prefix = prefixTmp; prefix = prefixTmp;
} }
@@ -226,10 +289,6 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
pr_err("grant_root: prctl reply error\n"); pr_err("grant_root: prctl reply error\n");
} }
} else {
pr_info("deny root for: %d\n", current_uid());
// add it to deny list!
ksu_allow_uid(current_uid().val, false, true);
} }
return 0; return 0;
} }
@@ -324,6 +383,30 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
return 0; return 0;
} }
if (arg2 == CMD_UID_GRANTED_ROOT || arg2 == CMD_UID_SHOULD_UMOUNT) {
if (is_manager() || 0 == current_uid().val) {
uid_t target_uid = (uid_t)arg3;
bool allow = false;
if (arg2 == CMD_UID_GRANTED_ROOT) {
allow = ksu_is_allow_uid(target_uid);
} else if (arg2 == CMD_UID_SHOULD_UMOUNT) {
allow = ksu_uid_should_umount(target_uid);
} else {
pr_err("unknown cmd: %d\n", arg2);
}
if (!copy_to_user(arg4, &allow, sizeof(allow))) {
if (copy_to_user(result, &reply_ok,
sizeof(reply_ok))) {
pr_err("prctl reply error, cmd: %d\n",
arg2);
}
} else {
pr_err("prctl copy err, cmd: %d\n", arg2);
}
}
return 0;
}
// all other cmds are for 'root manager' // all other cmds are for 'root manager'
if (!is_manager()) { if (!is_manager()) {
last_failed_uid = current_uid().val; last_failed_uid = current_uid().val;
@@ -331,17 +414,39 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
} }
// we are already manager // we are already manager
if (arg2 == CMD_ALLOW_SU || arg2 == CMD_DENY_SU) { if (arg2 == CMD_GET_APP_PROFILE) {
bool allow = arg2 == CMD_ALLOW_SU; struct app_profile profile;
bool success = false; if (copy_from_user(&profile, arg3, sizeof(profile))) {
uid_t uid = (uid_t)arg3; pr_err("copy profile failed\n");
success = ksu_allow_uid(uid, allow, true); return 0;
}
bool success = ksu_get_app_profile(&profile);
if (success) { if (success) {
if (copy_to_user(arg3, &profile, sizeof(profile))) {
pr_err("copy profile failed\n");
return 0;
}
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
pr_err("prctl reply error, cmd: %d\n", arg2);
}
}
return 0;
}
if (arg2 == CMD_SET_APP_PROFILE) {
struct app_profile profile;
if (copy_from_user(&profile, arg3, sizeof(profile))) {
pr_err("copy profile failed\n");
return 0;
}
// todo: validate the params
if (ksu_set_app_profile(&profile, true)) {
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) { if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
pr_err("prctl reply error, cmd: %d\n", arg2); pr_err("prctl reply error, cmd: %d\n", arg2);
} }
} }
ksu_show_allow_list();
return 0; return 0;
} }
@@ -364,6 +469,12 @@ static bool should_umount(struct path *path)
return false; return false;
} }
if (current->nsproxy->mnt_ns == init_nsproxy.mnt_ns) {
pr_info("ignore global mnt namespace process: %d\n",
current_uid().val);
return false;
}
if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) { if (path->mnt && path->mnt->mnt_sb && path->mnt->mnt_sb->s_type) {
const char *fstype = path->mnt->mnt_sb->s_type->name; const char *fstype = path->mnt->mnt_sb->s_type->name;
return strcmp(fstype, "overlay") == 0; return strcmp(fstype, "overlay") == 0;
@@ -418,6 +529,14 @@ int ksu_handle_setuid(struct cred *new, const struct cred *old)
return 0; return 0;
} }
if (!ksu_uid_should_umount(new_uid.val)) {
return 0;
} else {
#ifdef CONFIG_KSU_DEBUG
pr_info("uid: %d should not umount!\n", current_uid().val);
#endif
}
// umount the target mnt // umount the target mnt
pr_info("handle umount for uid: %d\n", new_uid.val); pr_info("handle umount for uid: %d\n", new_uid.val);
@@ -442,7 +561,14 @@ static int handler_pre(struct kprobe *p, struct pt_regs *regs)
int option = (int)PT_REGS_PARM1(real_regs); int option = (int)PT_REGS_PARM1(real_regs);
unsigned long arg2 = (unsigned long)PT_REGS_PARM2(real_regs); unsigned long arg2 = (unsigned long)PT_REGS_PARM2(real_regs);
unsigned long arg3 = (unsigned long)PT_REGS_PARM3(real_regs); unsigned long arg3 = (unsigned long)PT_REGS_PARM3(real_regs);
unsigned long arg4 = (unsigned long)PT_REGS_PARM4(real_regs); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)
// PRCTL_SYMBOL is the arch-specificed one, which receive raw pt_regs from syscall
unsigned long arg4 = (unsigned long)PT_REGS_SYSCALL_PARM4(real_regs);
#else
// PRCTL_SYMBOL is the common one, called by C convention in do_syscall_64
// https://elixir.bootlin.com/linux/v4.15.18/source/arch/x86/entry/common.c#L287
unsigned long arg4 = (unsigned long)PT_REGS_CCALL_PARM4(real_regs);
#endif
unsigned long arg5 = (unsigned long)PT_REGS_PARM5(real_regs); unsigned long arg5 = (unsigned long)PT_REGS_PARM5(real_regs);
return ksu_handle_prctl(option, arg2, arg3, arg4, arg5); return ksu_handle_prctl(option, arg2, arg3, arg4, arg5);
@@ -462,7 +588,7 @@ static int renameat_handler_pre(struct kprobe *p, struct pt_regs *regs)
struct dentry *new_entry = rd->new_dentry; struct dentry *new_entry = rd->new_dentry;
#else #else
struct dentry *old_entry = (struct dentry *)PT_REGS_PARM2(regs); struct dentry *old_entry = (struct dentry *)PT_REGS_PARM2(regs);
struct dentry *new_entry = (struct dentry *)PT_REGS_PARM4(regs); struct dentry *new_entry = (struct dentry *)PT_REGS_CCALL_PARM4(regs);
#endif #endif
return ksu_handle_rename(old_entry, new_entry); return ksu_handle_rename(old_entry, new_entry);

View File

@@ -2,4 +2,4 @@
// This file will be regenerated by CI // This file will be regenerated by CI
unsigned int ksud_size = 0; unsigned int ksud_size = 0;
const char ksud[0] = {}; const char ksud[0] = {};

View File

@@ -1,2 +1,2 @@
register_kprobe register_kprobe
unregister_kprobe unregister_kprobe

View File

@@ -1,34 +1,174 @@
#include "linux/version.h" #include "linux/version.h"
#include "linux/fs.h" #include "linux/fs.h"
#include "linux/nsproxy.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
#include "linux/sched/task.h"
#include "linux/uaccess.h"
#else
#include "linux/sched.h"
#endif
#include "klog.h" // IWYU pragma: keep
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
#include "linux/key.h" #include "linux/key.h"
#include "linux/errno.h" #include "linux/errno.h"
struct key *init_session_keyring = NULL; struct key *init_session_keyring = NULL;
static inline int install_session_keyring(struct key *keyring)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
ret = install_session_keyring_to_cred(new, keyring);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
#endif #endif
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, loff_t *pos){
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;
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);
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)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
if (init_session_keyring != NULL && !current_cred()->session_keyring &&
(current->flags & PF_WQ_WORKER)) {
pr_info("installing init session keyring for older kernel\n");
install_session_keyring(init_session_keyring);
}
#endif
// 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) {
pr_info("start switch current nsproxy and fs to android context\n");
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);
pr_info("switch current nsproxy and fs back to saved successfully\n");
}
return fp;
}
ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count,
loff_t *pos)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
return kernel_read(p, buf, count, pos); return kernel_read(p, buf, count, pos);
#else #else
loff_t offset = pos ? *pos : 0; loff_t offset = pos ? *pos : 0;
ssize_t result = kernel_read(p, offset, (char *)buf, count); ssize_t result = kernel_read(p, offset, (char *)buf, count);
if (pos && result > 0) if (pos && result > 0) {
{ *pos = offset + result;
*pos = offset + result; }
} return result;
return result;
#endif #endif
} }
ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, loff_t *pos){ ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count,
loff_t *pos)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
return kernel_write(p, buf, count, pos); return kernel_write(p, buf, count, pos);
#else #else
loff_t offset = pos ? *pos : 0; loff_t offset = pos ? *pos : 0;
ssize_t result = kernel_write(p, buf, count, offset); ssize_t result = kernel_write(p, buf, count, offset);
if (pos && result > 0) if (pos && result > 0) {
{ *pos = offset + result;
*pos = offset + result; }
} return result;
return result;
#endif #endif
} }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
long count)
{
return strncpy_from_user_nofault(dst, unsafe_addr, count);
}
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
long count)
{
return strncpy_from_unsafe_user(dst, unsafe_addr, count);
}
#else
// Copied from: https://elixir.bootlin.com/linux/v4.9.337/source/mm/maccess.c#L201
long ksu_strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,
long count)
{
mm_segment_t old_fs = get_fs();
long ret;
if (unlikely(count <= 0))
return 0;
set_fs(USER_DS);
pagefault_disable();
ret = strncpy_from_user(dst, unsafe_addr, count);
pagefault_enable();
set_fs(old_fs);
if (ret >= count) {
ret = count;
dst[ret - 1] = '\0';
} else if (ret > 0) {
ret++;
}
return ret;
}
#endif

View File

@@ -5,38 +5,20 @@
#include "linux/key.h" #include "linux/key.h"
#include "linux/version.h" #include "linux/version.h"
extern struct key *init_session_keyring; extern long ksu_strncpy_from_user_nofault(char *dst,
const void __user *unsafe_addr,
extern ssize_t ksu_kernel_read_compat(struct file *p, void *buf, size_t count, loff_t *pos); long count);
extern ssize_t ksu_kernel_write_compat(struct file *p, const void *buf, size_t count, loff_t *pos);
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
static inline int install_session_keyring(struct key *keyring) extern struct key *init_session_keyring;
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
ret = install_session_keyring_to_cred(new, keyring);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
#define KWORKER_INSTALL_KEYRING() \
static bool keyring_installed = false; \
if (init_session_keyring != NULL && !keyring_installed) \
{ \
install_session_keyring(init_session_keyring); \
keyring_installed = true; \
}
#else
#define KWORKER_INSTALL_KEYRING()
#endif #endif
#endif 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);
#endif

View File

@@ -8,4 +8,4 @@
#define pr_fmt(fmt) "KernelSU: " fmt #define pr_fmt(fmt) "KernelSU: " fmt
#endif #endif
#endif #endif

View File

@@ -39,7 +39,7 @@ int __init kernelsu_init(void)
pr_alert("*************************************************************"); pr_alert("*************************************************************");
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
pr_alert("** **"); pr_alert("** **");
pr_alert("** You are running DEBUG version of KernelSU **"); pr_alert("** You are running KernelSU in DEBUG mode **");
pr_alert("** **"); pr_alert("** **");
pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **"); pr_alert("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **");
pr_alert("*************************************************************"); pr_alert("*************************************************************");
@@ -47,7 +47,7 @@ int __init kernelsu_init(void)
ksu_core_init(); ksu_core_init();
ksu_workqueue = alloc_workqueue("kernelsu_work_queue", 0, 0); ksu_workqueue = alloc_ordered_workqueue("kernelsu_work_queue", 0);
ksu_allowlist_init(); ksu_allowlist_init();
@@ -57,7 +57,7 @@ int __init kernelsu_init(void)
ksu_enable_sucompat(); ksu_enable_sucompat();
ksu_enable_ksud(); ksu_enable_ksud();
#else #else
#warning("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
return 0; return 0;

View File

@@ -1,15 +1,10 @@
#ifndef __KSU_H_KSU #ifndef __KSU_H_KSU
#define __KSU_H_KSU #define __KSU_H_KSU
#include "linux/types.h"
#include "linux/workqueue.h" #include "linux/workqueue.h"
#ifndef KSU_GIT_VERSION #define KERNEL_SU_VERSION KSU_VERSION
#warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git submodule!"
#define KERNEL_SU_VERSION (16)
#else
#define KERNEL_SU_VERSION (10000 + KSU_GIT_VERSION + 200) // major * 10000 + git version + 200 for historical reasons
#endif
#define KERNEL_SU_OPTION 0xDEADBEEF #define KERNEL_SU_OPTION 0xDEADBEEF
#define CMD_GRANT_ROOT 0 #define CMD_GRANT_ROOT 0
@@ -22,10 +17,68 @@
#define CMD_REPORT_EVENT 7 #define CMD_REPORT_EVENT 7
#define CMD_SET_SEPOLICY 8 #define CMD_SET_SEPOLICY 8
#define CMD_CHECK_SAFEMODE 9 #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 EVENT_POST_FS_DATA 1 #define EVENT_POST_FS_DATA 1
#define EVENT_BOOT_COMPLETED 2 #define EVENT_BOOT_COMPLETED 2
#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;
};
};
bool ksu_queue_work(struct work_struct *work); bool ksu_queue_work(struct work_struct *work);
static inline int startswith(char *s, char *prefix) static inline int startswith(char *s, char *prefix)

View File

@@ -1,6 +1,5 @@
#include "asm/current.h" #include "asm/current.h"
#include "linux/string.h" #include "linux/compat.h"
#include "linux/cred.h"
#include "linux/dcache.h" #include "linux/dcache.h"
#include "linux/err.h" #include "linux/err.h"
#include "linux/fs.h" #include "linux/fs.h"
@@ -11,12 +10,12 @@
#include "linux/uaccess.h" #include "linux/uaccess.h"
#include "linux/version.h" #include "linux/version.h"
#include "linux/workqueue.h" #include "linux/workqueue.h"
#include "linux/input.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"
static const char KERNEL_SU_RC[] = static const char KERNEL_SU_RC[] =
@@ -51,9 +50,9 @@ 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
static bool vfs_read_hook = true; bool ksu_vfs_read_hook __read_mostly = true;
static bool execveat_hook = true; bool ksu_execveat_hook __read_mostly = true;
static bool input_hook = true; bool ksu_input_hook __read_mostly = true;
#endif #endif
void on_post_fs_data(void) void on_post_fs_data(void)
@@ -107,7 +106,13 @@ static const char __user *get_user_arg_ptr(struct user_arg_ptr argv, int nr)
/* /*
* count() counts the number of strings in array ARGV. * count() counts the number of strings in array ARGV.
*/ */
static int count(struct user_arg_ptr argv, int max)
/*
* 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; int i = 0;
@@ -133,11 +138,12 @@ static int count(struct user_arg_ptr argv, int max)
return i; return i;
} }
// 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_ksud(int *fd, struct filename **filename_ptr, int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
void *argv, void *envp, int *flags) struct user_arg_ptr *argv, void *__never_use_envp, int *__never_use_flags)
{ {
#ifndef CONFIG_KPROBES #ifndef CONFIG_KPROBES
if (!execveat_hook) { if (!ksu_execveat_hook) {
return 0; return 0;
} }
#endif #endif
@@ -156,28 +162,22 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
return 0; return 0;
} }
if (!memcmp(filename->name, system_bin_init, if (unlikely(!memcmp(filename->name, system_bin_init,
sizeof(system_bin_init) - 1)) { sizeof(system_bin_init) - 1))) {
// /system/bin/init executed // /system/bin/init executed
struct user_arg_ptr *ptr = (struct user_arg_ptr*) argv; int argc = count(*argv, MAX_ARG_STRINGS);
int argc = count(*ptr, MAX_ARG_STRINGS);
pr_info("/system/bin/init argc: %d\n", argc); pr_info("/system/bin/init argc: %d\n", argc);
if (argc > 1 && !init_second_stage_executed) { if (argc > 1 && !init_second_stage_executed) {
const char __user *p = get_user_arg_ptr(*ptr, 1); const char __user *p = get_user_arg_ptr(*argv, 1);
if (p && !IS_ERR(p)) { if (p && !IS_ERR(p)) {
char first_arg[16]; char first_arg[16];
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) ksu_strncpy_from_user_nofault(first_arg, p, sizeof(first_arg));
strncpy_from_user_nofault(first_arg, p, sizeof(first_arg));
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
strncpy_from_unsafe_user(first_arg, p, sizeof(first_arg));
#else
strncpy_from_user(first_arg, p, sizeof(first_arg));
#endif
pr_info("first arg: %s\n", first_arg); pr_info("first arg: %s\n", first_arg);
if (!strcmp(first_arg, "second_stage")) { if (!strcmp(first_arg, "second_stage")) {
pr_info("/system/bin/init second_stage executed\n"); pr_info("/system/bin/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 { } else {
pr_err("/system/bin/init parse args err!\n"); pr_err("/system/bin/init parse args err!\n");
@@ -185,8 +185,8 @@ int ksu_handle_execveat_ksud(int *fd, struct filename **filename_ptr,
} }
} }
if (first_app_process && if (unlikely(first_app_process &&
!memcmp(filename->name, app_process, sizeof(app_process) - 1)) { !memcmp(filename->name, app_process, sizeof(app_process) - 1))) {
first_app_process = false; first_app_process = false;
pr_info("exec app_process, /data prepared, second_stage: %d\n", init_second_stage_executed); pr_info("exec app_process, /data prepared, second_stage: %d\n", init_second_stage_executed);
on_post_fs_data(); // we keep this for old ksud on_post_fs_data(); // we keep this for old ksud
@@ -229,7 +229,7 @@ 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_KPROBES #ifndef CONFIG_KPROBES
if (!vfs_read_hook) { if (!ksu_vfs_read_hook) {
return 0; return 0;
} }
#endif #endif
@@ -330,7 +330,7 @@ int ksu_handle_input_handle_event(unsigned int *type, unsigned int *code,
int *value) int *value)
{ {
#ifndef CONFIG_KPROBES #ifndef CONFIG_KPROBES
if (!input_hook) { if (!ksu_input_hook) {
return 0; return 0;
} }
#endif #endif
@@ -379,11 +379,19 @@ static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
int *fd = (int *)&PT_REGS_PARM1(regs); int *fd = (int *)&PT_REGS_PARM1(regs);
struct filename **filename_ptr = struct filename **filename_ptr =
(struct filename **)&PT_REGS_PARM2(regs); (struct filename **)&PT_REGS_PARM2(regs);
void *argv = (void *)&PT_REGS_PARM3(regs); struct user_arg_ptr argv;
void *envp = (void *)&PT_REGS_PARM4(regs); #ifdef CONFIG_COMPAT
int *flags = (int *)&PT_REGS_PARM5(regs); argv.is_compat = PT_REGS_PARM3(regs);
if (unlikely(argv.is_compat)) {
argv.ptr.compat = PT_REGS_CCALL_PARM4(regs);
} else {
argv.ptr.native = PT_REGS_CCALL_PARM4(regs);
}
#else
argv.ptr.native = PT_REGS_PARM3(regs);
#endif
return ksu_handle_execveat_ksud(fd, filename_ptr, argv, envp, flags); return ksu_handle_execveat_ksud(fd, filename_ptr, &argv, NULL, NULL);
} }
static int read_handler_pre(struct kprobe *p, struct pt_regs *regs) static int read_handler_pre(struct kprobe *p, struct pt_regs *regs)
@@ -391,7 +399,7 @@ static int read_handler_pre(struct kprobe *p, struct pt_regs *regs)
struct file **file_ptr = (struct file **)&PT_REGS_PARM1(regs); struct file **file_ptr = (struct file **)&PT_REGS_PARM1(regs);
char __user **buf_ptr = (char **)&PT_REGS_PARM2(regs); char __user **buf_ptr = (char **)&PT_REGS_PARM2(regs);
size_t *count_ptr = (size_t *)&PT_REGS_PARM3(regs); size_t *count_ptr = (size_t *)&PT_REGS_PARM3(regs);
loff_t **pos_ptr = (loff_t **)&PT_REGS_PARM4(regs); loff_t **pos_ptr = (loff_t **)&PT_REGS_CCALL_PARM4(regs);
return ksu_handle_vfs_read(file_ptr, buf_ptr, count_ptr, pos_ptr); return ksu_handle_vfs_read(file_ptr, buf_ptr, count_ptr, pos_ptr);
} }
@@ -401,7 +409,7 @@ static int input_handle_event_handler_pre(struct kprobe *p,
{ {
unsigned int *type = (unsigned int *)&PT_REGS_PARM2(regs); unsigned int *type = (unsigned int *)&PT_REGS_PARM2(regs);
unsigned int *code = (unsigned int *)&PT_REGS_PARM3(regs); unsigned int *code = (unsigned int *)&PT_REGS_PARM3(regs);
int *value = (int *)&PT_REGS_PARM4(regs); int *value = (int *)&PT_REGS_CCALL_PARM4(regs);
return ksu_handle_input_handle_event(type, code, value); return ksu_handle_input_handle_event(type, code, value);
} }
@@ -448,7 +456,7 @@ static void stop_vfs_read_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
vfs_read_hook = false; ksu_vfs_read_hook = false;
#endif #endif
} }
@@ -458,7 +466,7 @@ static void stop_execve_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
execveat_hook = false; ksu_execveat_hook = false;
#endif #endif
} }
@@ -473,7 +481,7 @@ static void stop_input_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
input_hook = false; ksu_input_hook = false;
#endif #endif
} }

View File

@@ -7,4 +7,4 @@ void on_post_fs_data(void);
bool ksu_is_safe_mode(void); bool ksu_is_safe_mode(void);
#endif #endif

View File

@@ -13,7 +13,7 @@
#include "ksu.h" #include "ksu.h"
#include "manager.h" #include "manager.h"
uid_t ksu_manager_uid = INVALID_UID; uid_t ksu_manager_uid = KSU_INVALID_UID;
bool become_manager(char *pkg) bool become_manager(char *pkg)
{ {
@@ -39,39 +39,51 @@ bool become_manager(char *pkg)
files_table = files_fdtable(current->files); files_table = files_fdtable(current->files);
int pkg_len = strlen(pkg);
// todo: use iterate_fd // todo: use iterate_fd
while (files_table->fd[i] != NULL) { for (i = 0; files_table->fd[i] != NULL; i++) {
files_path = files_table->fd[i]->f_path; files_path = files_table->fd[i]->f_path;
if (!d_is_reg(files_path.dentry)) { if (!d_is_reg(files_path.dentry)) {
i++;
continue; continue;
} }
cwd = d_path(&files_path, buf, PATH_MAX); cwd = d_path(&files_path, buf, PATH_MAX);
if (startswith(cwd, "/data/app/") == 0 && if (startswith(cwd, "/data/app/") != 0 ||
endswith(cwd, "/base.apk") == 0) { endswith(cwd, "/base.apk") != 0) {
// we have found the apk! continue;
pr_info("found apk: %s", cwd);
if (!strstr(cwd, pkg)) {
pr_info("apk path not match package name!\n");
i++;
continue;
}
if (is_manager_apk(cwd) == 0) {
// check passed
uid_t uid = current_uid().val;
pr_info("manager uid: %d\n", uid);
ksu_set_manager_uid(uid);
result = true;
goto clean;
} else {
pr_info("manager signature invalid!");
}
break;
} }
i++; // we have found the apk!
pr_info("found apk: %s", cwd);
char *pkg_index = strstr(cwd, pkg);
if (!pkg_index) {
pr_info("apk path not match package name!\n");
continue;
}
char *next_char = pkg_index + pkg_len;
// because we ensure the cwd must startswith `/data/app` and endswith `base.apk`
// we don't need to check if the pointer is out of bounds
if (*next_char != '-') {
// from android 8.1: http://aospxref.com/android-8.1.0_r81/xref/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#17612
// to android 13: http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java#1208
// /data/app/~~[randomStringA]/[packageName]-[randomStringB]
// the previous char must be `/` and the next char must be `-`
// because we use strstr instead of equals, this is a strong verfication.
pr_info("invalid pkg: %s\n", pkg);
continue;
}
if (is_manager_apk(cwd) == 0) {
// check passed
uid_t uid = current_uid().val;
pr_info("manager uid: %d\n", uid);
ksu_set_manager_uid(uid);
result = true;
goto clean;
} else {
pr_info("manager signature invalid!");
}
break;
} }
clean: clean:

View File

@@ -4,18 +4,18 @@
#include "linux/cred.h" #include "linux/cred.h"
#include "linux/types.h" #include "linux/types.h"
#define INVALID_UID -1 #define KSU_INVALID_UID -1
extern uid_t ksu_manager_uid; // DO NOT DIRECT USE extern uid_t ksu_manager_uid; // DO NOT DIRECT USE
static inline bool ksu_is_manager_uid_valid() static inline bool ksu_is_manager_uid_valid()
{ {
return ksu_manager_uid != INVALID_UID; return ksu_manager_uid != KSU_INVALID_UID;
} }
static inline bool is_manager() static inline bool is_manager()
{ {
return ksu_manager_uid == current_uid().val; return unlikely(ksu_manager_uid == current_uid().val);
} }
static inline uid_t ksu_get_manager_uid() static inline uid_t ksu_get_manager_uid()
@@ -30,7 +30,7 @@ static inline void ksu_set_manager_uid(uid_t uid)
static inline void ksu_invalidate_manager_uid() static inline void ksu_invalidate_manager_uid()
{ {
ksu_manager_uid = INVALID_UID; ksu_manager_uid = KSU_INVALID_UID;
} }
bool become_manager(char *pkg); bool become_manager(char *pkg);

View File

@@ -30,4 +30,4 @@ RE_EXPORT_SYMBOL1(unsigned long, kallsyms_lookup_name, const char *, name)
// int ksu_register_kretprobe(struct kretprobe *rp); // int ksu_register_kretprobe(struct kretprobe *rp);
// void unregister_kretprobe(struct kretprobe *rp); // void unregister_kretprobe(struct kretprobe *rp);
// int register_kretprobes(struct kretprobe **rps, int num); // int register_kretprobes(struct kretprobe **rps, int num);
// void unregister_kretprobes(struct kretprobe **rps, int num); // void unregister_kretprobes(struct kretprobe **rps, int num);

View File

@@ -11,6 +11,6 @@ ccflags-y += -DKSU_COMPAT_HAS_SELINUX_STATE
endif endif
ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion ccflags-y += -Wno-implicit-function-declaration -Wno-strict-prototypes -Wno-int-conversion
ccflags-y += -Wno-macro-redefined -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 ccflags-y += -I$(objtree)/security/selinux -include $(srctree)/include/uapi/asm-generic/errno.h

View File

@@ -114,6 +114,10 @@ void apply_kernelsu_rules()
ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process", ksu_allow(db, "hwservicemanager", KERNEL_SU_DOMAIN, "process",
"getattr"); "getattr");
// For mounting loop devices, mirrors, tmpfs
ksu_allow(db, "kernel", ALL, "file", "read");
ksu_allow(db, "kernel", ALL, "file", "write");
// Allow all binder transactions // Allow all binder transactions
ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL); ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL);

View File

@@ -26,7 +26,9 @@ static int transive_to_domain(const char *domain)
} }
error = security_secctx_to_secid(domain, strlen(domain), &sid); error = security_secctx_to_secid(domain, strlen(domain), &sid);
pr_info("error: %d, sid: %d\n", error, sid); if (error) {
pr_info("security_secctx_to_secid %s -> sid: %d, error: %d\n", domain, sid, error);
}
if (!error) { if (!error) {
if (!ksu_sid) if (!ksu_sid)
ksu_sid = sid; ksu_sid = sid;
@@ -39,9 +41,9 @@ static int transive_to_domain(const char *domain)
return error; return error;
} }
void setup_selinux() void setup_selinux(const char *domain)
{ {
if (transive_to_domain(KERNEL_SU_DOMAIN)) { if (transive_to_domain(domain)) {
pr_err("transive domain failed."); pr_err("transive domain failed.");
return; return;
} }
@@ -88,7 +90,8 @@ bool getenforce()
#endif #endif
} }
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) && !defined(KSU_COMPAT_HAS_CURRENT_SID) #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)) && \
!defined(KSU_COMPAT_HAS_CURRENT_SID)
/* /*
* get the subjective security ID of the current task * get the subjective security ID of the current task
*/ */

View File

@@ -8,7 +8,7 @@
#define KSU_COMPAT_USE_SELINUX_STATE #define KSU_COMPAT_USE_SELINUX_STATE
#endif #endif
void setup_selinux(); void setup_selinux(const char *);
void setenforce(bool); void setenforce(bool);
@@ -18,4 +18,4 @@ bool is_ksu_domain();
void apply_kernelsu_rules(); void apply_kernelsu_rules();
#endif #endif

View File

@@ -15,7 +15,7 @@
* From ss/ebitmap.h * From ss/ebitmap.h
*/ */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) && \ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) && \
LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 250) LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
#ifdef HISI_SELINUX_EBITMAP_RO #ifdef HISI_SELINUX_EBITMAP_RO
#define CONFIG_IS_HW_HISI #define CONFIG_IS_HW_HISI
#endif #endif
@@ -73,7 +73,7 @@ static bool add_typeattribute(struct policydb *db, const char *type,
// rules // rules
#define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert) #define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert)
#define hash_for_each(node_ptr, n_slot, cur) \ #define ksu_hash_for_each(node_ptr, n_slot, cur) \
int i; \ int i; \
for (i = 0; i < n_slot; ++i) \ for (i = 0; i < n_slot; ++i) \
for (cur = node_ptr[i]; cur; cur = cur->next) for (cur = node_ptr[i]; cur; cur = cur->next)
@@ -81,10 +81,11 @@ static bool add_typeattribute(struct policydb *db, const char *type,
// htable is a struct instead of pointer above 5.8.0: // htable is a struct instead of pointer above 5.8.0:
// https://elixir.bootlin.com/linux/v5.8-rc1/source/security/selinux/ss/symtab.h // https://elixir.bootlin.com/linux/v5.8-rc1/source/security/selinux/ss/symtab.h
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
#define hashtab_for_each(htab, cur) hash_for_each (htab.htable, htab.size, cur) #define ksu_hashtab_for_each(htab, cur) \
ksu_hash_for_each(htab.htable, htab.size, cur)
#else #else
#define hashtab_for_each(htab, cur) \ #define ksu_hashtab_for_each(htab, cur) \
hash_for_each (htab->htable, htab->size, cur) ksu_hash_for_each(htab->htable, htab->size, cur)
#endif #endif
// symtab_search is introduced on 5.9.0: // symtab_search is introduced on 5.9.0:
@@ -95,8 +96,7 @@ static bool add_typeattribute(struct policydb *db, const char *type,
#endif #endif
#define avtab_for_each(avtab, cur) \ #define avtab_for_each(avtab, cur) \
hash_for_each (avtab.htable, avtab.nslot, cur) \ ksu_hash_for_each(avtab.htable, avtab.nslot, cur);
;
static struct avtab_node *get_avtab_node(struct policydb *db, static struct avtab_node *get_avtab_node(struct policydb *db,
struct avtab_key *key, struct avtab_key *key,
@@ -210,14 +210,14 @@ static void add_rule_raw(struct policydb *db, struct type_datum *src,
if (src == NULL) { if (src == NULL) {
struct hashtab_node *node; struct hashtab_node *node;
if (strip_av(effect, invert)) { if (strip_av(effect, invert)) {
hashtab_for_each(db->p_types.table, node) ksu_hashtab_for_each(db->p_types.table, node)
{ {
add_rule_raw(db, add_rule_raw(db,
(struct type_datum *)node->datum, (struct type_datum *)node->datum,
tgt, cls, perm, effect, invert); tgt, cls, perm, effect, invert);
}; };
} else { } else {
hashtab_for_each(db->p_types.table, node) ksu_hashtab_for_each(db->p_types.table, node)
{ {
struct type_datum *type = struct type_datum *type =
(struct type_datum *)(node->datum); (struct type_datum *)(node->datum);
@@ -230,14 +230,14 @@ static void add_rule_raw(struct policydb *db, struct type_datum *src,
} else if (tgt == NULL) { } else if (tgt == NULL) {
struct hashtab_node *node; struct hashtab_node *node;
if (strip_av(effect, invert)) { if (strip_av(effect, invert)) {
hashtab_for_each(db->p_types.table, node) ksu_hashtab_for_each(db->p_types.table, node)
{ {
add_rule_raw(db, src, add_rule_raw(db, src,
(struct type_datum *)node->datum, (struct type_datum *)node->datum,
cls, perm, effect, invert); cls, perm, effect, invert);
}; };
} else { } else {
hashtab_for_each(db->p_types.table, node) ksu_hashtab_for_each(db->p_types.table, node)
{ {
struct type_datum *type = struct type_datum *type =
(struct type_datum *)(node->datum); (struct type_datum *)(node->datum);
@@ -249,7 +249,7 @@ static void add_rule_raw(struct policydb *db, struct type_datum *src,
} }
} else if (cls == NULL) { } else if (cls == NULL) {
struct hashtab_node *node; struct hashtab_node *node;
hashtab_for_each(db->p_classes.table, node) ksu_hashtab_for_each(db->p_classes.table, node)
{ {
add_rule_raw(db, src, tgt, add_rule_raw(db, src, tgt,
(struct class_datum *)node->datum, perm, (struct class_datum *)node->datum, perm,
@@ -292,7 +292,7 @@ static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
{ {
if (src == NULL) { if (src == NULL) {
struct hashtab_node *node; struct hashtab_node *node;
hashtab_for_each(db->p_types.table, node) ksu_hashtab_for_each(db->p_types.table, node)
{ {
struct type_datum *type = struct type_datum *type =
(struct type_datum *)(node->datum); (struct type_datum *)(node->datum);
@@ -303,7 +303,7 @@ static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
}; };
} else if (tgt == NULL) { } else if (tgt == NULL) {
struct hashtab_node *node; struct hashtab_node *node;
hashtab_for_each(db->p_types.table, node) ksu_hashtab_for_each(db->p_types.table, node)
{ {
struct type_datum *type = struct type_datum *type =
(struct type_datum *)(node->datum); (struct type_datum *)(node->datum);
@@ -314,7 +314,7 @@ static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
}; };
} else if (cls == NULL) { } else if (cls == NULL) {
struct hashtab_node *node; struct hashtab_node *node;
hashtab_for_each(db->p_classes.table, node) ksu_hashtab_for_each(db->p_classes.table, node)
{ {
add_xperm_rule_raw(db, src, tgt, add_xperm_rule_raw(db, src, tgt,
(struct class_datum *)(node->datum), (struct class_datum *)(node->datum),
@@ -693,7 +693,7 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
int i; int i;
for (i = 0; i < db->p_roles.nprim; ++i) { for (i = 0; i < db->p_roles.nprim; ++i) {
ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1, ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
0); 1);
} }
return true; return true;
@@ -743,7 +743,7 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
int i; int i;
for (i = 0; i < db->p_roles.nprim; ++i) { for (i = 0; i < db->p_roles.nprim; ++i) {
ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1, ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
0); 1);
} }
return true; return true;
@@ -854,7 +854,7 @@ static bool add_type(struct policydb *db, const char *type_name, bool attr)
int i; int i;
for (i = 0; i < db->p_roles.nprim; ++i) { for (i = 0; i < db->p_roles.nprim; ++i) {
ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1, ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
0); 1);
} }
return true; return true;
#endif #endif
@@ -870,7 +870,7 @@ static bool set_type_state(struct policydb *db, const char *type_name,
struct type_datum *type; struct type_datum *type;
if (type_name == NULL) { if (type_name == NULL) {
struct hashtab_node *node; struct hashtab_node *node;
hashtab_for_each(db->p_types.table, node) ksu_hashtab_for_each(db->p_types.table, node)
{ {
type = (struct type_datum *)(node->datum); type = (struct type_datum *)(node->datum);
if (ebitmap_set_bit(&db->permissive_map, type->value, if (ebitmap_set_bit(&db->permissive_map, type->value,
@@ -913,7 +913,7 @@ static void add_typeattribute_raw(struct policydb *db, struct type_datum *type,
struct hashtab_node *node; struct hashtab_node *node;
struct constraint_node *n; struct constraint_node *n;
struct constraint_expr *e; struct constraint_expr *e;
hashtab_for_each(db->p_classes.table, node) ksu_hashtab_for_each(db->p_classes.table, node)
{ {
struct class_datum *cls = (struct class_datum *)(node->datum); struct class_datum *cls = (struct class_datum *)(node->datum);
for (n = cls->constraints; n; n = n->next) { for (n = cls->constraints; n; n = n->next) {

View File

@@ -11,7 +11,7 @@ elif test -d "$GKI_ROOT/drivers"; then
DRIVER_DIR="$GKI_ROOT/drivers" DRIVER_DIR="$GKI_ROOT/drivers"
else else
echo '[ERROR] "drivers/" directory is not found.' echo '[ERROR] "drivers/" directory is not found.'
echo '[+] You should modify this scrpit by yourself.' echo '[+] You should modify this script by yourself.'
exit 127 exit 127
fi fi
@@ -32,11 +32,19 @@ cd "$GKI_ROOT"
echo "[+] GKI_ROOT: $GKI_ROOT" echo "[+] GKI_ROOT: $GKI_ROOT"
echo "[+] Copy kernel su driver to $DRIVER_DIR" echo "[+] Copy kernel su driver to $DRIVER_DIR"
test -e "$DRIVER_DIR/kernelsu" || ln -sf "$GKI_ROOT/KernelSU/kernel" "$DRIVER_DIR/kernelsu" cd "$DRIVER_DIR"
if test -d "$GKI_ROOT/common/drivers"; then
ln -sf "../../KernelSU/kernel" "kernelsu"
elif test -d "$GKI_ROOT/drivers"; then
ln -sf "../KernelSU/kernel" "kernelsu"
fi
cd "$GKI_ROOT"
echo '[+] Add kernel su driver to Makefile' echo '[+] Add kernel su driver to Makefile'
DRIVER_MAKEFILE=$DRIVER_DIR/Makefile DRIVER_MAKEFILE=$DRIVER_DIR/Makefile
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-y += kernelsu/\n" >> "$DRIVER_MAKEFILE" DRIVER_KCONFIG=$DRIVER_DIR/Kconfig
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "obj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG"
echo '[+] Done.' echo '[+] Done.'

View File

@@ -16,6 +16,7 @@
#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"
#define SU_PATH "/system/bin/su" #define SU_PATH "/system/bin/su"
#define SH_PATH "/system/bin/sh" #define SH_PATH "/system/bin/sh"
@@ -41,65 +42,58 @@ static char __user *sh_user_path(void)
int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode, int ksu_handle_faccessat(int *dfd, const char __user **filename_user, int *mode,
int *flags) int *flags)
{ {
struct filename *filename;
const char su[] = SU_PATH; const char su[] = SU_PATH;
if (!ksu_is_allow_uid(current_uid().val)) { if (!ksu_is_allow_uid(current_uid().val)) {
return 0; return 0;
} }
filename = getname(*filename_user); char path[sizeof(su)];
memset(path, 0, sizeof(path));
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
if (IS_ERR(filename)) { if (unlikely(!memcmp(path, su, sizeof(su)))) {
return 0;
}
if (!memcmp(filename->name, su, sizeof(su))) {
pr_info("faccessat su->sh!\n"); pr_info("faccessat su->sh!\n");
*filename_user = sh_user_path(); *filename_user = sh_user_path();
} }
putname(filename);
return 0; return 0;
} }
int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags) int ksu_handle_stat(int *dfd, const char __user **filename_user, int *flags)
{ {
// const char sh[] = SH_PATH; // const char sh[] = SH_PATH;
struct filename *filename;
const char su[] = SU_PATH; const char su[] = SU_PATH;
if (!ksu_is_allow_uid(current_uid().val)) { if (!ksu_is_allow_uid(current_uid().val)) {
return 0; return 0;
} }
if (!filename_user) { if (unlikely(!filename_user)) {
return 0; return 0;
} }
filename = getname(*filename_user); char path[sizeof(su)];
memset(path, 0, sizeof(path));
ksu_strncpy_from_user_nofault(path, *filename_user, sizeof(path));
if (IS_ERR(filename)) { if (unlikely(!memcmp(path, su, sizeof(su)))) {
return 0;
}
if (!memcmp(filename->name, su, sizeof(su))) {
pr_info("newfstatat su->sh!\n"); pr_info("newfstatat su->sh!\n");
*filename_user = sh_user_path(); *filename_user = sh_user_path();
} }
putname(filename);
return 0; return 0;
} }
// 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, int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
void *argv, void *envp, int *flags) void *__never_use_argv, void *__never_use_envp, int *__never_use_flags)
{ {
struct filename *filename; struct filename *filename;
const char sh[] = KSUD_PATH; const char sh[] = KSUD_PATH;
const char su[] = SU_PATH; const char su[] = SU_PATH;
if (!filename_ptr) if (unlikely(!filename_ptr))
return 0; return 0;
filename = *filename_ptr; filename = *filename_ptr;
@@ -107,16 +101,16 @@ int ksu_handle_execveat_sucompat(int *fd, struct filename **filename_ptr,
return 0; return 0;
} }
if (!ksu_is_allow_uid(current_uid().val)) { if (likely(memcmp(filename->name, su, sizeof(su))))
return 0; return 0;
}
if (!memcmp(filename->name, su, sizeof(su))) { if (!ksu_is_allow_uid(current_uid().val))
pr_info("do_execveat_common su found\n"); return 0;
memcpy((void *)filename->name, sh, sizeof(sh));
escape_to_root(); pr_info("do_execveat_common su found\n");
} memcpy((void *)filename->name, sh, sizeof(sh));
escape_to_root();
return 0; return 0;
} }
@@ -128,7 +122,8 @@ static int faccessat_handler_pre(struct kprobe *p, struct pt_regs *regs)
int *dfd = (int *)PT_REGS_PARM1(regs); int *dfd = (int *)PT_REGS_PARM1(regs);
const char __user **filename_user = (const char **)&PT_REGS_PARM2(regs); const char __user **filename_user = (const char **)&PT_REGS_PARM2(regs);
int *mode = (int *)&PT_REGS_PARM3(regs); int *mode = (int *)&PT_REGS_PARM3(regs);
int *flags = (int *)&PT_REGS_PARM4(regs); // Both sys_ and do_ is C function
int *flags = (int *)&PT_REGS_CCALL_PARM4(regs);
return ksu_handle_faccessat(dfd, filename_user, mode, flags); return ksu_handle_faccessat(dfd, filename_user, mode, flags);
} }
@@ -142,7 +137,7 @@ static int newfstatat_handler_pre(struct kprobe *p, struct pt_regs *regs)
int *flags = (int *)&PT_REGS_PARM3(regs); int *flags = (int *)&PT_REGS_PARM3(regs);
#else #else
// int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,int flag) // int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat,int flag)
int *flags = (int *)&PT_REGS_PARM4(regs); int *flags = (int *)&PT_REGS_CCALL_PARM4(regs);
#endif #endif
return ksu_handle_stat(dfd, filename_user, flags); return ksu_handle_stat(dfd, filename_user, flags);
@@ -154,12 +149,8 @@ static int execve_handler_pre(struct kprobe *p, struct pt_regs *regs)
int *fd = (int *)&PT_REGS_PARM1(regs); int *fd = (int *)&PT_REGS_PARM1(regs);
struct filename **filename_ptr = struct filename **filename_ptr =
(struct filename **)&PT_REGS_PARM2(regs); (struct filename **)&PT_REGS_PARM2(regs);
void *argv = (void *)&PT_REGS_PARM3(regs);
void *envp = (void *)&PT_REGS_PARM4(regs);
int *flags = (int *)&PT_REGS_PARM5(regs);
return ksu_handle_execveat_sucompat(fd, filename_ptr, argv, envp, return ksu_handle_execveat_sucompat(fd, filename_ptr, NULL, NULL, NULL);
flags);
} }
static struct kprobe faccessat_kp = { static struct kprobe faccessat_kp = {

View File

@@ -29,7 +29,7 @@ static bool is_uid_exist(uid_t uid, void *data)
bool exist = false; bool exist = false;
list_for_each_entry (np, list, list) { list_for_each_entry (np, list, list) {
if (np->uid == uid) { if (np->uid == uid % 100000) {
exist = true; exist = true;
break; break;
} }
@@ -39,12 +39,11 @@ static bool is_uid_exist(uid_t uid, void *data)
static void do_update_uid(struct work_struct *work) static void do_update_uid(struct work_struct *work)
{ {
KWORKER_INSTALL_KEYRING(); struct file *fp = ksu_filp_open_compat(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
struct file *fp = filp_open(SYSTEM_PACKAGES_LIST_PATH, O_RDONLY, 0);
if (IS_ERR(fp)) { if (IS_ERR(fp)) {
pr_err("do_update_uid, open " SYSTEM_PACKAGES_LIST_PATH pr_err("do_update_uid, open " SYSTEM_PACKAGES_LIST_PATH
" failed: %d\n", " failed: %d\n",
ERR_PTR(fp)); PTR_ERR(fp));
return; return;
} }
@@ -56,13 +55,15 @@ static void do_update_uid(struct work_struct *work)
loff_t line_start = 0; loff_t line_start = 0;
char buf[128]; char buf[128];
for (;;) { for (;;) {
ssize_t count = ksu_kernel_read_compat(fp, &chr, sizeof(chr), &pos); ssize_t count =
ksu_kernel_read_compat(fp, &chr, sizeof(chr), &pos);
if (count != sizeof(chr)) if (count != sizeof(chr))
break; break;
if (chr != '\n') if (chr != '\n')
continue; continue;
count = ksu_kernel_read_compat(fp, buf, sizeof(buf), &line_start); count = ksu_kernel_read_compat(fp, buf, sizeof(buf),
&line_start);
struct uid_data *data = struct uid_data *data =
kmalloc(sizeof(struct uid_data), GFP_ATOMIC); kmalloc(sizeof(struct uid_data), GFP_ATOMIC);
@@ -136,4 +137,4 @@ int ksu_uid_observer_init()
int ksu_uid_observer_exit() int ksu_uid_observer_exit()
{ {
return 0; return 0;
} }

View File

@@ -7,4 +7,4 @@ int ksu_uid_observer_exit();
void update_uid(); void update_uid();
#endif #endif

18
manager/.gitignore vendored
View File

@@ -1,17 +1,9 @@
*.iml *.iml
.gradle .gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties local.properties
sign.properties .idea
.DS_Store
build
captures
.cxx
key.jks key.jks

3
manager/.idea/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

1
manager/.idea/.name generated
View File

@@ -1 +0,0 @@
KernelSU

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -1,37 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

10
manager/.idea/misc.xml generated
View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

6
manager/.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@@ -1,37 +1,52 @@
import com.android.build.gradle.internal.api.BaseVariantOutputImpl import com.android.build.gradle.internal.api.BaseVariantOutputImpl
plugins { plugins {
id("com.android.application") alias(libs.plugins.agp.app)
id("com.google.devtools.ksp") alias(libs.plugins.kotlin)
kotlin("android") alias(libs.plugins.ksp)
alias(libs.plugins.lsplugin.apksign)
id("kotlin-parcelize")
}
val managerVersionCode: Int by rootProject.extra
val managerVersionName: String by rootProject.extra
apksign {
storeFileProperty = "KEYSTORE_FILE"
storePasswordProperty = "KEYSTORE_PASSWORD"
keyAliasProperty = "KEY_ALIAS"
keyPasswordProperty = "KEY_PASSWORD"
} }
android { android {
namespace = "me.weishu.kernelsu" namespace = "me.weishu.kernelsu"
ndkVersion = "25.1.8937393" buildTypes {
release {
defaultConfig { isMinifyEnabled = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
ndk {
abiFilters += listOf("arm64-v8a", "x86_64")
} }
} }
lint {
checkReleaseBuilds = false
}
buildFeatures { buildFeatures {
aidl = true
buildConfig = true
compose = true compose = true
} }
composeOptions { kotlinOptions {
kotlinCompilerExtensionVersion = "1.3.2" jvmTarget = "17"
} }
packagingOptions { composeOptions {
kotlinCompilerExtensionVersion = "1.4.3"
}
packaging {
jniLibs {
useLegacyPackaging = true
}
resources { resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
} }
@@ -39,15 +54,14 @@ android {
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path(file("src/main/cpp/CMakeLists.txt")) path("src/main/cpp/CMakeLists.txt")
version = "3.18.1"
} }
} }
applicationVariants.all { applicationVariants.all {
outputs.forEach { outputs.forEach {
val output = it as BaseVariantOutputImpl val output = it as BaseVariantOutputImpl
output.outputFileName = "KernelSU_$versionName-${buildType.name}.apk" output.outputFileName = "KernelSU_${managerVersionName}_${managerVersionCode}-$name.apk"
} }
kotlin.sourceSets { kotlin.sourceSets {
@@ -59,40 +73,42 @@ android {
} }
dependencies { dependencies {
val accompanistVersion = "0.28.0" implementation(libs.androidx.activity.compose)
val composeDestinationsVersion = "1.7.27-beta" implementation(libs.androidx.navigation.compose)
implementation(platform("androidx.compose:compose-bom:2023.04.00"))
debugImplementation("androidx.compose.ui:ui-test-manifest")
debugImplementation("androidx.compose.ui:ui-tooling")
implementation("androidx.activity:activity-compose:1.7.0")
implementation("androidx.compose.material:material:1.5.0-alpha01")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
implementation("androidx.navigation:navigation-compose:2.5.3")
implementation("com.google.accompanist:accompanist-drawablepainter:$accompanistVersion")
implementation("com.google.accompanist:accompanist-navigation-animation:$accompanistVersion")
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
implementation("io.github.raamcosta.compose-destinations:animations-core:$composeDestinationsVersion")
implementation("io.coil-kt:coil-compose:2.3.0") implementation(platform(libs.androidx.compose.bom))
implementation("me.zhanghai.android.appiconloader:appiconloader-coil:1.5.0") implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.tooling.preview)
val libsuVersion = "5.0.5" debugImplementation(libs.androidx.compose.ui.test.manifest)
// change to official build(com.github.topjohnwu.libsu) when this pr is merged: debugImplementation(libs.androidx.compose.ui.tooling)
// https://github.com/topjohnwu/libsu/pull/151
implementation("com.github.tiann.libsu:core:$libsuVersion")
implementation("com.github.tiann.libsu:service:$libsuVersion")
implementation("dev.rikka.rikkax.parcelablelist:parcelablelist:2.0.0")
implementation("com.github.alorma:compose-settings-ui-m3:0.22.0") implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose)
ksp("io.github.raamcosta.compose-destinations:ksp:$composeDestinationsVersion") implementation(libs.com.google.accompanist.drawablepainter)
implementation(libs.com.google.accompanist.navigation.animation)
implementation(libs.com.google.accompanist.systemuicontroller)
testImplementation("junit:junit:4.13.2") implementation(libs.compose.destinations.animations.core)
androidTestImplementation("androidx.test.ext:junit:1.1.4") ksp(libs.compose.destinations.ksp)
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
implementation(libs.com.github.topjohnwu.libsu.core)
implementation(libs.com.github.topjohnwu.libsu.service)
implementation(libs.dev.rikka.rikkax.parcelablelist)
implementation(libs.io.coil.kt.coil.compose)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.me.zhanghai.android.appiconloader.coil)
implementation(libs.sheet.compose.dialogs.core)
implementation(libs.sheet.compose.dialogs.list)
implementation(libs.sheet.compose.dialogs.input)
} }

View File

@@ -1,21 +1,9 @@
# Add project specific ProGuard rules here. -dontwarn org.bouncycastle.jsse.BCSSLParameters
# You can control the set of applied configuration files using the -dontwarn org.bouncycastle.jsse.BCSSLSocket
# proguardFiles setting in build.gradle. -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
# -dontwarn org.conscrypt.Conscrypt$Version
# For more details, see -dontwarn org.conscrypt.Conscrypt
# http://developer.android.com/guide/developing/tools/proguard.html -dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
# If your project uses WebView with JS, uncomment the following -dontwarn org.openjsse.javax.net.ssl.SSLSocket
# and specify the fully qualified class name to the JavaScript interface -dontwarn org.openjsse.net.ssl.OpenJSSE
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -1,24 +0,0 @@
package me.weishu.kernelsu
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("me.weishu.kernelsu", appContext.packageName)
}
}

View File

@@ -2,18 +2,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".KernelSUApplication" android:name=".KernelSUApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"
android:extractNativeLibs="true" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.KernelSU" android:theme="@style/Theme.KernelSU"
tools:targetApi="31"> tools:targetApi="33">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:exported="true" android:exported="true"

View File

@@ -3,15 +3,16 @@
#include <sys/prctl.h> #include <sys/prctl.h>
#include <android/log.h> #include <android/log.h>
#include <cstring>
#include "ksu.h" #include "ksu.h"
#define LOG_TAG "KernelSu" #define LOG_TAG "KernelSU"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jclass clazz, jstring pkg) { Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
auto cpkg = env->GetStringUTFChars(pkg, nullptr); auto cpkg = env->GetStringUTFChars(pkg, nullptr);
auto result = become_manager(cpkg); auto result = become_manager(cpkg);
env->ReleaseStringUTFChars(pkg, cpkg); env->ReleaseStringUTFChars(pkg, cpkg);
@@ -20,13 +21,13 @@ Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jclass clazz, jstring
extern "C" extern "C"
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jclass clazz) { Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jobject) {
return get_version(); return get_version();
} }
extern "C" extern "C"
JNIEXPORT jintArray JNICALL JNIEXPORT jintArray JNICALL
Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jclass clazz) { Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) {
int uids[1024]; int uids[1024];
int size = 0; int size = 0;
bool result = get_allow_list(uids, &size); bool result = get_allow_list(uids, &size);
@@ -39,31 +40,260 @@ Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jclass clazz) {
return env->NewIntArray(0); return env->NewIntArray(0);
} }
extern "C"
JNIEXPORT jintArray JNICALL
Java_me_weishu_kernelsu_Natives_getDenyList(JNIEnv *env, jclass clazz) {
int uids[1024];
int size = 0;
bool result = get_deny_list(uids, &size);
if (result) {
// success!
auto array = env->NewIntArray(size);
env->SetIntArrayRegion(array, 0, size, uids);
return array;
}
return env->NewIntArray(0);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_allowRoot(JNIEnv *env, jclass clazz, jint uid, jboolean allow) {
return allow_su(uid, allow);
}
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) { Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
return is_safe_mode(); return is_safe_mode();
}
static void fillIntArray(JNIEnv *env, jobject list, int *data, int count) {
auto cls = env->GetObjectClass(list);
auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z");
auto integerCls = env->FindClass("java/lang/Integer");
auto constructor = env->GetMethodID(integerCls, "<init>", "(I)V");
for (int i = 0; i < count; ++i) {
auto integer = env->NewObject(integerCls, constructor, data[i]);
env->CallBooleanMethod(list, add, integer);
}
}
static void addIntToList(JNIEnv *env, jobject list, int ele) {
auto cls = env->GetObjectClass(list);
auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z");
auto integerCls = env->FindClass("java/lang/Integer");
auto constructor = env->GetMethodID(integerCls, "<init>", "(I)V");
auto integer = env->NewObject(integerCls, constructor, ele);
env->CallBooleanMethod(list, add, integer);
}
static uint64_t capListToBits(JNIEnv *env, jobject list) {
auto cls = env->GetObjectClass(list);
auto get = env->GetMethodID(cls, "get", "(I)Ljava/lang/Object;");
auto size = env->GetMethodID(cls, "size", "()I");
auto listSize = env->CallIntMethod(list, size);
auto integerCls = env->FindClass("java/lang/Integer");
auto intValue = env->GetMethodID(integerCls, "intValue", "()I");
uint64_t result = 0;
for (int i = 0; i < listSize; ++i) {
auto integer = env->CallObjectMethod(list, get, i);
int data = env->CallIntMethod(integer, intValue);
if (cap_valid(data)) {
result |= (1ULL << data);
}
}
return result;
}
static int getListSize(JNIEnv *env, jobject list) {
auto cls = env->GetObjectClass(list);
auto size = env->GetMethodID(cls, "size", "()I");
return env->CallIntMethod(list, size);
}
static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
auto cls = env->GetObjectClass(list);
auto get = env->GetMethodID(cls, "get", "(I)Ljava/lang/Object;");
auto integerCls = env->FindClass("java/lang/Integer");
auto intValue = env->GetMethodID(integerCls, "intValue", "()I");
for (int i = 0; i < count; ++i) {
auto integer = env->CallObjectMethod(list, get, i);
data[i] = env->CallIntMethod(integer, intValue);
}
}
extern "C"
JNIEXPORT jobject JNICALL
Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
return nullptr;
}
p_key_t key = {};
auto cpkg = env->GetStringUTFChars(pkg, nullptr);
strcpy(key, cpkg);
env->ReleaseStringUTFChars(pkg, cpkg);
app_profile profile = {};
profile.version = KSU_APP_PROFILE_VER;
strcpy(profile.key, key);
profile.current_uid = uid;
bool useDefaultProfile = !get_app_profile(key, &profile);
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile");
auto constructor = env->GetMethodID(cls, "<init>", "()V");
auto obj = env->NewObject(cls, constructor);
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
auto allowSuField = env->GetFieldID(cls, "allowSu", "Z");
auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z");
auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;");
auto uidField = env->GetFieldID(cls, "uid", "I");
auto gidField = env->GetFieldID(cls, "gid", "I");
auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;");
auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;");
auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;");
auto namespacesField = env->GetFieldID(cls, "namespace", "I");
auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z");
auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z");
env->SetObjectField(obj, keyField, env->NewStringUTF(profile.key));
env->SetIntField(obj, currentUidField, profile.current_uid);
if (useDefaultProfile) {
// no profile found, so just use default profile:
// don't allow root and use default profile!
LOGD("use default profile for: %s, %d", key, uid);
// allow_su = false
// non root use default = true
env->SetBooleanField(obj, allowSuField, false);
env->SetBooleanField(obj, nonRootUseDefaultField, true);
jobject capList = env->GetObjectField(obj, capabilitiesField);
int DEFAULT_CAPS[] = {CAP_DAC_READ_SEARCH};
for (auto i: DEFAULT_CAPS) {
addIntToList(env, capList, i);
}
return obj;
}
auto allowSu = profile.allow_su;
if (allowSu) {
env->SetBooleanField(obj, rootUseDefaultField, (jboolean) profile.rp_config.use_default);
if (strlen(profile.rp_config.template_name) > 0) {
env->SetObjectField(obj, rootTemplateField,
env->NewStringUTF(profile.rp_config.template_name));
}
env->SetIntField(obj, uidField, profile.rp_config.profile.uid);
env->SetIntField(obj, gidField, profile.rp_config.profile.gid);
jobject groupList = env->GetObjectField(obj, groupsField);
int groupCount = profile.rp_config.profile.groups_count;
if (groupCount > KSU_MAX_GROUPS) {
LOGD("kernel group count too large: %d???", groupCount);
groupCount = KSU_MAX_GROUPS;
}
fillIntArray(env, groupList, profile.rp_config.profile.groups, groupCount);
jobject capList = env->GetObjectField(obj, capabilitiesField);
for (int i = 0; i <= CAP_LAST_CAP; i++) {
if (profile.rp_config.profile.capabilities.effective & (1ULL << i)) {
addIntToList(env, capList, i);
}
}
env->SetObjectField(obj, domainField,
env->NewStringUTF(profile.rp_config.profile.selinux_domain));
env->SetIntField(obj, namespacesField, profile.rp_config.profile.namespaces);
env->SetBooleanField(obj, allowSuField, profile.allow_su);
} else {
env->SetBooleanField(obj, nonRootUseDefaultField,
(jboolean) profile.nrp_config.use_default);
env->SetBooleanField(obj, umountModulesField, profile.nrp_config.profile.umount_modules);
}
return obj;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile");
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
auto allowSuField = env->GetFieldID(cls, "allowSu", "Z");
auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z");
auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;");
auto uidField = env->GetFieldID(cls, "uid", "I");
auto gidField = env->GetFieldID(cls, "gid", "I");
auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;");
auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;");
auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;");
auto namespacesField = env->GetFieldID(cls, "namespace", "I");
auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z");
auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z");
auto key = env->GetObjectField(profile, keyField);
if (!key) {
return false;
}
if (env->GetStringLength((jstring) key) > KSU_MAX_PACKAGE_NAME) {
return false;
}
auto cpkg = env->GetStringUTFChars((jstring) key, nullptr);
p_key_t p_key = {};
strcpy(p_key, cpkg);
env->ReleaseStringUTFChars((jstring) key, cpkg);
auto currentUid = env->GetIntField(profile, currentUidField);
auto uid = env->GetIntField(profile, uidField);
auto gid = env->GetIntField(profile, gidField);
auto groups = env->GetObjectField(profile, groupsField);
auto capabilities = env->GetObjectField(profile, capabilitiesField);
auto domain = env->GetObjectField(profile, domainField);
auto allowSu = env->GetBooleanField(profile, allowSuField);
auto umountModules = env->GetBooleanField(profile, umountModulesField);
app_profile p = {};
p.version = KSU_APP_PROFILE_VER;
strcpy(p.key, p_key);
p.allow_su = allowSu;
p.current_uid = currentUid;
if (allowSu) {
p.rp_config.use_default = env->GetBooleanField(profile, rootUseDefaultField);
auto templateName = env->GetObjectField(profile, rootTemplateField);
if (templateName) {
auto ctemplateName = env->GetStringUTFChars((jstring) templateName, nullptr);
strcpy(p.rp_config.template_name, ctemplateName);
env->ReleaseStringUTFChars((jstring) templateName, ctemplateName);
}
p.rp_config.profile.uid = uid;
p.rp_config.profile.gid = gid;
int groups_count = getListSize(env, groups);
if (groups_count > KSU_MAX_GROUPS) {
LOGD("groups count too large: %d", groups_count);
return false;
}
p.rp_config.profile.groups_count = groups_count;
fillArrayWithList(env, groups, p.rp_config.profile.groups, groups_count);
p.rp_config.profile.capabilities.effective = capListToBits(env, capabilities);
auto cdomain = env->GetStringUTFChars((jstring) domain, nullptr);
strcpy(p.rp_config.profile.selinux_domain, cdomain);
env->ReleaseStringUTFChars((jstring) domain, cdomain);
p.rp_config.profile.namespaces = env->GetIntField(profile, namespacesField);
} else {
p.nrp_config.use_default = env->GetBooleanField(profile, nonRootUseDefaultField);
p.nrp_config.profile.umount_modules = umountModules;
}
return set_app_profile(&p);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
return uid_should_umount(uid);
} }

View File

@@ -18,10 +18,16 @@
#define CMD_GET_VERSION 2 #define CMD_GET_VERSION 2
#define CMD_ALLOW_SU 3 #define CMD_ALLOW_SU 3
#define CMD_DENY_SU 4 #define CMD_DENY_SU 4
#define CMD_GET_ALLOW_LIST 5 #define CMD_GET_SU_LIST 5
#define CMD_GET_DENY_LIST 6 #define CMD_GET_DENY_LIST 6
#define CMD_CHECK_SAFEMODE 9 #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
static bool ksuctl(int cmd, void* arg1, void* arg2) { static bool ksuctl(int cmd, void* arg1, void* arg2) {
int32_t result = 0; int32_t result = 0;
prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result); prctl(KERNEL_SU_OPTION, cmd, arg1, arg2, &result);
@@ -49,19 +55,23 @@ int get_version() {
return version; return version;
} }
bool allow_su(int uid, bool allow) {
int cmd = allow ? CMD_ALLOW_SU : CMD_DENY_SU;
return ksuctl(cmd, (void*) uid, nullptr);
}
bool get_allow_list(int *uids, int *size) { bool get_allow_list(int *uids, int *size) {
return ksuctl(CMD_GET_ALLOW_LIST, uids, size); return ksuctl(CMD_GET_SU_LIST, uids, size);
}
bool get_deny_list(int *uids, int *size) {
return ksuctl(CMD_GET_DENY_LIST, uids, size);
} }
bool is_safe_mode() { bool is_safe_mode() {
return ksuctl(CMD_CHECK_SAFEMODE, nullptr, nullptr); return ksuctl(CMD_CHECK_SAFEMODE, nullptr, nullptr);
} }
bool uid_should_umount(int uid) {
bool should;
return ksuctl(CMD_IS_UID_SHOULD_UMOUNT, reinterpret_cast<void*>(uid), &should) && should;
}
bool set_app_profile(const app_profile *profile) {
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, nullptr);
}
bool get_app_profile(p_key_t key, app_profile *profile) {
return ksuctl(CMD_GET_APP_PROFILE, (void*) profile, nullptr);
}

View File

@@ -5,16 +5,76 @@
#ifndef KERNELSU_KSU_H #ifndef KERNELSU_KSU_H
#define KERNELSU_KSU_H #define KERNELSU_KSU_H
bool become_manager(const char*); #include <linux/capability.h>
bool become_manager(const char *);
int get_version(); int get_version();
bool allow_su(int uid, bool allow);
bool get_allow_list(int *uids, int *size); bool get_allow_list(int *uids, int *size);
bool get_deny_list(int *uids, int *size); bool uid_should_umount(int uid);
bool is_safe_mode(); bool is_safe_mode();
#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
using p_key_t = char[KSU_MAX_PACKAGE_NAME];
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 {
uint64_t effective;
uint64_t permitted;
uint64_t 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.
uint32_t 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 set_app_profile(const app_profile *profile);
bool get_app_profile(p_key_t key, app_profile *profile);
#endif //KERNELSU_KSU_H #endif //KERNELSU_KSU_H

View File

@@ -1,26 +0,0 @@
package me.weishu.kernelsu;
/**
* @author weishu
* @date 2022/12/8.
*/
public final class Natives {
static {
System.loadLibrary("kernelsu");
}
// become root manager, return true if success.
public static native boolean becomeManager(String pkg);
public static native int getVersion();
// get the uid list of allowed su processes.
public static native int[] getAllowList();
public static native int[] getDenyList();
public static native boolean allowRoot(int uid, boolean allow);
public static native boolean isSafeMode();
}

View File

@@ -0,0 +1,107 @@
package me.weishu.kernelsu
import android.os.Parcelable
import androidx.annotation.Keep
import androidx.compose.runtime.Immutable
import kotlinx.parcelize.Parcelize
/**
* @author weishu
* @date 2022/12/8.
*/
object Natives {
// minimal supported kernel version
// 10915: allowlist breaking change, add app profile
// 10931: app profile struct add 'version' field
// 10946: add capabilities
// 10977: change groups_count and groups to avoid overflow write
// 11071: Fix the issue of failing to set a custom SELinux type.
const val MINIMAL_SUPPORTED_KERNEL = 11071
init {
System.loadLibrary("kernelsu")
}
// become root manager, return true if success.
external fun becomeManager(pkg: String?): Boolean
val version: Int
external get
// get the uid list of allowed su processes.
val allowList: IntArray
external get
val isSafeMode: Boolean
external get
external fun uidShouldUmount(uid: Int): Boolean
/**
* Get the profile of the given package.
* @param key usually the package name
* @return return null if failed.
*/
external fun getAppProfile(key: String?, uid: Int): Profile
external fun setAppProfile(profile: Profile?): Boolean
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
private const val ROOT_DEFAULT_PROFILE_KEY = "#"
private const val NOBODY_UID = 9999
fun setDefaultUmountModules(umountModules: Boolean): Boolean {
Profile(
NON_ROOT_DEFAULT_PROFILE_KEY,
NOBODY_UID,
false,
umountModules = umountModules
).let {
return setAppProfile(it)
}
}
fun isDefaultUmountModules(): Boolean {
getAppProfile(NON_ROOT_DEFAULT_PROFILE_KEY, NOBODY_UID).let {
return it.umountModules
}
}
fun requireNewKernel(): Boolean {
return version < MINIMAL_SUPPORTED_KERNEL
}
@Immutable
@Parcelize
@Keep
data class Profile(
// and there is a default profile for root and non-root
val name: String,
// current uid for the package, this is convivent for kernel to check
// if the package name doesn't match uid, then it should be invalidated.
val currentUid: Int = 0,
// if this is true, kernel will grant root permission to this package
val allowSu: Boolean = false,
// these are used for root profile
val rootUseDefault: Boolean = true,
val rootTemplate: String? = null,
val uid: Int = 0,
val gid: Int = 0,
val groups: List<Int> = mutableListOf(),
val capabilities: List<Int> = mutableListOf(),
val context: String = "u:r:su:s0",
val namespace: Int = Namespace.Inherited.ordinal,
val nonRootUseDefault: Boolean = true,
val umountModules: Boolean = true,
var rules: String = "", // this field is save in ksud!!
) : Parcelable {
enum class Namespace {
Inherited,
Global,
Individual,
}
constructor() : this("")
}
}

View File

@@ -0,0 +1,49 @@
package me.weishu.kernelsu.profile
/**
* @author weishu
* @date 2023/6/3.
*/
enum class Capabilities(val cap: Int, val display: String, val desc: String) {
CAP_CHOWN(0, "CHOWN", "Make arbitrary changes to file UIDs and GIDs (see chown(2))"),
CAP_DAC_OVERRIDE(1, "DAC_OVERRIDE", "Bypass file read, write, and execute permission checks"),
CAP_DAC_READ_SEARCH(2, "DAC_READ_SEARCH", "Bypass file read permission checks and directory read and execute permission checks"),
CAP_FOWNER(3, "FOWNER", "Bypass permission checks on operations that normally require the filesystem UID of the process to match the UID of the file (e.g., chmod(2), utime(2)), excluding those operations covered by CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH"),
CAP_FSETID(4, "FSETID", "Dont clear set-user-ID and set-group-ID permission bits when a file is modified; set the set-group-ID bit for a file whose GID does not match the filesystem or any of the supplementary GIDs of the calling process"),
CAP_KILL(5, "KILL", "Bypass permission checks for sending signals (see kill(2))."),
CAP_SETGID(6, "SETGID", "Make arbitrary manipulations of process GIDs and supplementary GID list; allow setgid(2) manipulation of the callers effective and real group IDs"),
CAP_SETUID(7, "SETUID", "Make arbitrary manipulations of process UIDs (setuid(2), setreuid(2), setresuid(2), setfsuid(2)); allow changing the current process user IDs; allow changing of the current process group ID to any value in the systems range of legal group IDs"),
CAP_SETPCAP(8, "SETPCAP", "If file capabilities are supported: grant or remove any capability in the callers permitted capability set to or from any other process. (This property supersedes the obsolete notion of giving a process all capabilities by granting all capabilities in its permitted set, and of removing all capabilities from a process by granting no capabilities in its permitted set. It does not permit any actions that were not permitted before.)"),
CAP_LINUX_IMMUTABLE(9, "LINUX_IMMUTABLE", "Set the FS_APPEND_FL and FS_IMMUTABLE_FL inode flags (see chattr(1))."),
CAP_NET_BIND_SERVICE(10, "NET_BIND_SERVICE", "Bind a socket to Internet domain"),
CAP_NET_BROADCAST(11, "NET_BROADCAST", "Make socket broadcasts, and listen to multicasts"),
CAP_NET_ADMIN(12, "NET_ADMIN", "Perform various network-related operations: interface configuration, administration of IP firewall, masquerading, and accounting, modify routing tables, bind to any address for transparent proxying, set type-of-service (TOS), clear driver statistics, set promiscuous mode, enabling multicasting, use setsockopt(2) to set the following socket options: SO_DEBUG, SO_MARK, SO_PRIORITY (for a priority outside the range 0 to 6), SO_RCVBUFFORCE, and SO_SNDBUFFORCE"),
CAP_NET_RAW(13, "NET_RAW", "Use RAW and PACKET sockets"),
CAP_IPC_LOCK(14, "IPC_LOCK", "Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2))"),
CAP_IPC_OWNER(15, "IPC_OWNER", "Bypass permission checks for operations on System V IPC objects"),
CAP_SYS_MODULE(16, "SYS_MODULE", "Load and unload kernel modules (see init_module(2) and delete_module(2)); in kernels before 2.6.25, this also granted rights for various other operations related to kernel modules"),
CAP_SYS_RAWIO(17, "SYS_RAWIO", "Perform I/O port operations (iopl(2) and ioperm(2)); access /proc/kcore"),
CAP_SYS_CHROOT(18, "SYS_CHROOT", "Use chroot(2)"),
CAP_SYS_PTRACE(19, "SYS_PTRACE", "Trace arbitrary processes using ptrace(2)"),
CAP_SYS_PACCT(20, "SYS_PACCT", "Use acct(2)"),
CAP_SYS_ADMIN(21, "SYS_ADMIN", "Perform a range of system administration operations including: quotactl(2), mount(2), umount(2), swapon(2), swapoff(2), sethostname(2), and setdomainname(2); set and modify process resource limits (setrlimit(2)); perform various network-related operations (e.g., setting privileged socket options, enabling multicasting, interface configuration); perform various IPC operations (e.g., SysV semaphores, POSIX message queues, System V shared memory); allow reboot and kexec_load(2); override /proc/sys kernel tunables; perform ptrace(2) PTRACE_SECCOMP_GET_FILTER operation; perform some tracing and debugging operations (see ptrace(2)); administer the lifetime of kernel tracepoints (tracefs(5)); perform the KEYCTL_CHOWN and KEYCTL_SETPERM keyctl(2) operations; perform the following keyctl(2) operations: KEYCTL_CAPABILITIES, KEYCTL_CAPSQUASH, and KEYCTL_PKEY_ OPERATIONS; set state for the Extensible Authentication Protocol (EAP) kernel module; and override the RLIMIT_NPROC resource limit; allow ioperm/iopl access to I/O ports"),
CAP_SYS_BOOT(22, "SYS_BOOT", "Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution"),
CAP_SYS_NICE(23, "SYS_NICE", "Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes; set real-time scheduling policies for calling process, and set scheduling policies and priorities for arbitrary processes (sched_setscheduler(2), sched_setparam(2)"),
CAP_SYS_RESOURCE(24, "SYS_RESOURCE", "Override resource Limits. Set resource limits (setrlimit(2), prlimit(2)), override quota limits (quota(2), quotactl(2)), override reserved space on ext2 filesystem (ext2_ioctl(2)), override size restrictions on IPC message queues (msg(2)) and system V shared memory segments (shmget(2)), and override the /proc/sys/fs/pipe-size-max limit"),
CAP_SYS_TIME(25, "SYS_TIME", "Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock"),
CAP_SYS_TTY_CONFIG(26, "SYS_TTY_CONFIG", "Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals"),
CAP_MKNOD(27, "MKNOD", "Create special files using mknod(2)"),
CAP_LEASE(28, "LEASE", "Establish leases on arbitrary files (see fcntl(2))"),
CAP_AUDIT_WRITE(29, "AUDIT_WRITE", "Write records to kernel auditing log"),
CAP_AUDIT_CONTROL(30, "AUDIT_CONTROL", "Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules"),
CAP_SETFCAP(31, "SETFCAP", "If file capabilities are supported: grant or remove any capability in any capability set to any file"),
CAP_MAC_OVERRIDE(32, "MAC_OVERRIDE", "Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM)"),
CAP_MAC_ADMIN(33, "MAC_ADMIN", "Allow MAC configuration or state changes. Implemented for the Smack LSM"),
CAP_SYSLOG(34, "SYSLOG", "Perform privileged syslog(2) operations. See syslog(2) for information on which operations require privilege"),
CAP_WAKE_ALARM(35, "WAKE_ALARM", "Trigger something that will wake up the system"),
CAP_BLOCK_SUSPEND(36, "BLOCK_SUSPEND", "Employ features that can block system suspend"),
CAP_AUDIT_READ(37, "AUDIT_READ", "Allow reading the audit log via a multicast netlink socket"),
CAP_PERFMON(38, "PERFMON", "Allow performance monitoring via perf_event_open(2)"),
CAP_BPF(39, "BPF", "Allow BPF operations via bpf(2)"),
CAP_CHECKPOINT_RESTORE(40, "CHECKPOINT_RESTORE", "Allow processes to be checkpointed via checkpoint/restore in user namespace(2)"),
}

View File

@@ -0,0 +1,87 @@
package me.weishu.kernelsu.profile
/**
* @author weishu
* @date 2023/6/3.
*/
enum class Groups(val gid: Int, val display: String, val desc: String) {
ROOT(0, "root", "traditional unix root user"),
DAEMON(1, "daemon", "Traditional unix daemon owner."),
BIN(2, "bin", "Traditional unix binaries owner."),
SYS(3, "sys", "A group with the same gid on Linux/macOS/Android."),
SYSTEM(1000, "system", "system server"),
RADIO(1001, "radio", "telephony subsystem, RIL"),
BLUETOOTH(1002, "bluetooth", "bluetooth subsystem"),
GRAPHICS(1003, "graphics", "graphics devices"),
INPUT(1004, "input", "input devices"),
AUDIO(1005, "audio", "audio devices"),
CAMERA(1006, "camera", "camera devices"),
LOG(1007, "log", "log devices"),
COMPASS(1008, "compass", "compass device"),
MOUNT(1009, "mount", "mountd socket"),
WIFI(1010, "wifi", "wifi subsystem"),
ADB(1011, "adb", "android debug bridge (adbd)"),
INSTALL(1012, "install", "group for installing packages"),
MEDIA(1013, "media", "mediaserver process"),
DHCP(1014, "dhcp", "dhcp client"),
SDCARD_RW(1015, "sdcard_rw", "external storage write access"),
VPN(1016, "vpn", "vpn system"),
KEYSTORE(1017, "keystore", "keystore subsystem"),
USB(1018, "usb", "USB devices"),
DRM(1019, "drm", "DRM server"),
MDNSR(1020, "mdnsr", "MulticastDNSResponder (service discovery)"),
GPS(1021, "gps", "GPS daemon"),
UNUSED1(1022, "unused1", "deprecated, DO NOT USE"),
MEDIA_RW(1023, "media_rw", "internal media storage write access"),
MTP(1024, "mtp", "MTP USB driver access"),
UNUSED2(1025, "unused2", "deprecated, DO NOT USE"),
DRMRPC(1026, "drmrpc", "group for drm rpc"),
NFC(1027, "nfc", "nfc subsystem"),
SDCARD_R(1028, "sdcard_r", "external storage read access"),
CLAT(1029, "clat", "clat part of nat464"),
LOOP_RADIO(1030, "loop_radio", "loop radio devices"),
MEDIA_DRM(1031, "media_drm", "MediaDrm plugins"),
PACKAGE_INFO(1032, "package_info", "access to installed package details"),
SDCARD_PICS(1033, "sdcard_pics", "external storage photos access"),
SDCARD_AV(1034, "sdcard_av", "external storage audio/video access"),
SDCARD_ALL(1035, "sdcard_all", "access all users external storage"),
LOGD(1036, "logd", "log daemon"),
SHARED_RELRO(1037, "shared_relro", "creator of shared GNU RELRO files"),
DBUS(1038, "dbus", "dbus-daemon IPC broker process"),
TLSDATE(1039, "tlsdate", "tlsdate unprivileged user"),
MEDIA_EX(1040, "media_ex", "mediaextractor process"),
AUDIOSERVER(1041, "audioserver", "audioserver process"),
METRICS_COLL(1042, "metrics_coll", "metrics_collector process"),
METRICSD(1043, "metricsd", "metricsd process"),
WEBSERV(1044, "webserv", "webservd process"),
DEBUGGERD(1045, "debuggerd", "debuggerd unprivileged user"),
MEDIA_CODEC(1046, "media_codec", "media_codec process"),
CAMERASERVER(1047, "cameraserver", "cameraserver process"),
FIREWALL(1048, "firewall", "firewall process"),
TRUNKS(1049, "trunks", "trunksd process"),
NVRAM(1050, "nvram", "nvram daemon"),
DNS_TETHER(1051, "dns_tether", "dns_tether device"),
DNS_TETHER_RESERVED(1052, "dns_tether_reserved", "Reserved range for dns_tether"),
WEBVIEW_ZYGOTE(1053, "webview_zygote", "zygote process"),
WEBVIEW_USER(1054, "webview_user", "webview chromium user"),
ETHERNET(1055, "ethernet", "Ethernet"),
TOMBSTONED(1056, "tombstoned", "tombstoned process"),
GRAPHICS_RW(1057, "graphics_rw", "graphics devices"),
SHELL(2000, "shell", "adb and debug shell user"),
CACHE(2001, "cache", "cache access"),
DIAG(2002, "diag", "diagnostics"),
NET_BT_ADMIN(3001, "net_bt_admin", "bluetooth: create any socket"),
NET_BT(3002, "net_bt", "bluetooth: create sco, rfcomm or l2cap sockets"),
INET(3003, "inet", "can create AF_INET and AF_INET6 sockets"),
NET_RAW(3004, "net_raw", "can create raw INET sockets"),
NET_ADMIN(3005, "net_admin", "can configure interfaces and routing tables."),
NET_BW_STATS(3006, "net_bw_stats", "read bandwidth statistics"),
NET_BW_ACCT(3007, "net_bw_acct", "change bandwidth statistics accounting"),
NET_BT_STACK(3008, "net_bt_stack", "access to various bluetooth management functions"),
QCOM_DIAG(3009, "qcom_diag", "allow msm specific diag commands"),
EVERYBODY(9997, "everybody", "Shared external storage read/write"),
MISC(9998, "misc", "Access to misc storage"),
NOBODY(9999, "nobody", "Reserved"),
APP(10000, "app", "Access to app data"),
}

View File

@@ -5,30 +5,37 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.* import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.google.accompanist.navigation.animation.rememberAnimatedNavController
import com.ramcosta.composedestinations.DestinationsNavHost import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.navigation.popBackStack
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.component.rememberDialogHostState import me.weishu.kernelsu.ui.component.rememberDialogHostState
import me.weishu.kernelsu.ui.screen.BottomBarDestination import me.weishu.kernelsu.ui.screen.BottomBarDestination
import me.weishu.kernelsu.ui.screen.NavGraphs import me.weishu.kernelsu.ui.screen.NavGraphs
import me.weishu.kernelsu.ui.screen.appCurrentDestinationAsState
import me.weishu.kernelsu.ui.screen.destinations.Destination
import me.weishu.kernelsu.ui.screen.startAppDestination
import me.weishu.kernelsu.ui.theme.KernelSUTheme import me.weishu.kernelsu.ui.theme.KernelSUTheme
import me.weishu.kernelsu.ui.util.LocalDialogHost import me.weishu.kernelsu.ui.util.LocalDialogHost
import me.weishu.kernelsu.ui.util.LocalSnackbarHost import me.weishu.kernelsu.ui.util.LocalSnackbarHost
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalAnimationApi::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -58,31 +65,29 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
private fun BottomBar(navController: NavHostController) { private fun BottomBar(navController: NavHostController) {
val topDestination: Destination = navController.appCurrentDestinationAsState().value val isManager = Natives.becomeManager(ksuApp.packageName)
?: NavGraphs.root.startAppDestination val fullFeatured = isManager && !Natives.requireNewKernel()
val bottomBarRoutes = remember {
BottomBarDestination.values().map { it.direction.route }
}
NavigationBar(tonalElevation = 8.dp) { NavigationBar(tonalElevation = 8.dp) {
BottomBarDestination.values().forEach { destination -> BottomBarDestination.values().forEach { destination ->
if (!fullFeatured && destination.rootRequired) return@forEach
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
NavigationBarItem( NavigationBarItem(
selected = topDestination.route == destination.direction.route, selected = isCurrentDestOnBackStack,
onClick = { onClick = {
val firstRoute = navController.backQueue.reversed().first { if (isCurrentDestOnBackStack) {
it.destination.route in bottomBarRoutes navController.popBackStack(destination.direction, false)
}.destination.route }
navController.navigate(destination.direction.route) { navController.navigate(destination.direction.route) {
popUpTo(navController.graph.findStartDestination().id) { popUpTo(NavGraphs.root.route) {
saveState = firstRoute != destination.direction.route saveState = true
} }
launchSingleTop = true launchSingleTop = true
restoreState = true restoreState = true
} }
}, },
icon = { icon = {
if (topDestination.route == destination.direction.route) { if (isCurrentDestOnBackStack) {
Icon(destination.iconSelected, stringResource(destination.label)) Icon(destination.iconSelected, stringResource(destination.label))
} else { } else {
Icon(destination.iconNotSelected, stringResource(destination.label)) Icon(destination.iconNotSelected, stringResource(destination.label))

View File

@@ -3,16 +3,23 @@ package me.weishu.kernelsu.ui.component
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.widget.TextView import android.widget.TextView
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -59,46 +66,43 @@ private fun AboutCardContent() {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.bodyMedium) { val drawable = ResourcesCompat.getDrawable(
LocalContext.current.resources,
R.mipmap.ic_launcher,
LocalContext.current.theme
)
val drawable = ResourcesCompat.getDrawable( Row {
LocalContext.current.resources, Image(
R.mipmap.ic_launcher, painter = rememberDrawablePainter(drawable),
LocalContext.current.theme contentDescription = "icon",
modifier = Modifier.size(40.dp)
) )
Row { Spacer(modifier = Modifier.width(12.dp))
Image(
painter = rememberDrawablePainter(drawable), Column {
contentDescription = "icon",
modifier = Modifier.size(40.dp) Text(
stringResource(id = R.string.app_name),
style = MaterialTheme.typography.titleSmall,
fontSize = 18.sp
)
Text(
BuildConfig.VERSION_NAME,
style = MaterialTheme.typography.bodySmall,
fontSize = 14.sp
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.height(8.dp))
Column { HtmlText(
html = stringResource(
Text( id = R.string.about_source_code,
stringResource(id = R.string.app_name), "<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>",
style = MaterialTheme.typography.titleSmall, "<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>"
fontSize = 18.sp
) )
Text( )
BuildConfig.VERSION_NAME,
style = MaterialTheme.typography.bodySmall,
fontSize = 14.sp
)
Spacer(modifier = Modifier.height(8.dp))
HtmlText(
html = stringResource(
id = R.string.about_source_code,
"<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>"
)
)
}
} }
} }
} }
@@ -106,6 +110,7 @@ private fun AboutCardContent() {
@Composable @Composable
fun HtmlText(html: String, modifier: Modifier = Modifier) { fun HtmlText(html: String, modifier: Modifier = Modifier) {
val contentColor = LocalContentColor.current
AndroidView( AndroidView(
modifier = modifier, modifier = modifier,
factory = { context -> factory = { context ->
@@ -113,6 +118,9 @@ fun HtmlText(html: String, modifier: Modifier = Modifier) {
it.movementMethod = LinkMovementMethod.getInstance() it.movementMethod = LinkMovementMethod.getInstance()
} }
}, },
update = { it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT) } update = {
it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
it.setTextColor(contentColor.toArgb())
}
) )
} }

View File

@@ -1,56 +1,113 @@
package me.weishu.kernelsu.ui.component package me.weishu.kernelsu.ui.component
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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 kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import me.weishu.kernelsu.ui.util.LocalDialogHost import me.weishu.kernelsu.ui.util.LocalDialogHost
import kotlin.coroutines.resume import kotlin.coroutines.resume
sealed interface DialogResult { interface DialogVisuals
object Confirmed : DialogResult
object Dismissed : DialogResult
}
interface DialogVisuals { interface LoadingDialogVisuals : DialogVisuals
interface PromptDialogVisuals : DialogVisuals {
val title: String val title: String
val content: String val content: String
}
interface ConfirmDialogVisuals : PromptDialogVisuals {
val confirm: String? val confirm: String?
val dismiss: String? val dismiss: String?
} }
interface DialogData {
sealed interface DialogData {
val visuals: DialogVisuals val visuals: DialogVisuals
}
fun confirm() interface LoadingDialogData : DialogData {
override val visuals: LoadingDialogVisuals
fun dismiss() fun dismiss()
} }
interface PromptDialogData : DialogData {
override val visuals: PromptDialogVisuals
fun dismiss()
}
interface ConfirmDialogData : PromptDialogData {
override val visuals: ConfirmDialogVisuals
fun confirm()
}
sealed interface ConfirmResult {
object Confirmed : ConfirmResult
object Canceled : ConfirmResult
}
class DialogHostState { class DialogHostState {
private data class DialogVisualsImpl( private object LoadingDialogVisualsImpl : LoadingDialogVisuals
private data class PromptDialogVisualsImpl(
override val title: String,
override val content: String
) : PromptDialogVisuals
private data class ConfirmDialogVisualsImpl(
override val title: String, override val title: String,
override val content: String, override val content: String,
override val confirm: String?, override val confirm: String?,
override val dismiss: String? override val dismiss: String?
) : DialogVisuals ) : ConfirmDialogVisuals
private data class DialogDataImpl( private data class LoadingDialogDataImpl(
override val visuals: DialogVisuals, override val visuals: LoadingDialogVisuals,
val continuation: CancellableContinuation<DialogResult> private val continuation: CancellableContinuation<Unit>,
) : DialogData { ) : LoadingDialogData {
override fun dismiss() {
if (continuation.isActive) continuation.resume(Unit)
}
}
private data class PromptDialogDataImpl(
override val visuals: PromptDialogVisuals,
private val continuation: CancellableContinuation<Unit>,
) : PromptDialogData {
override fun dismiss() {
if (continuation.isActive) continuation.resume(Unit)
}
}
private data class ConfirmDialogDataImpl(
override val visuals: ConfirmDialogVisuals,
private val continuation: CancellableContinuation<ConfirmResult>
) : ConfirmDialogData {
override fun confirm() { override fun confirm() {
if (continuation.isActive) continuation.resume(DialogResult.Confirmed) if (continuation.isActive) continuation.resume(ConfirmResult.Confirmed)
} }
override fun dismiss() { override fun dismiss() {
if (continuation.isActive) continuation.resume(DialogResult.Dismissed) if (continuation.isActive) continuation.resume(ConfirmResult.Canceled)
} }
} }
@@ -59,16 +116,58 @@ class DialogHostState {
var currentDialogData by mutableStateOf<DialogData?>(null) var currentDialogData by mutableStateOf<DialogData?>(null)
private set private set
suspend fun showDialog( suspend fun showLoading() {
try {
mutex.withLock {
suspendCancellableCoroutine { continuation ->
currentDialogData = LoadingDialogDataImpl(
visuals = LoadingDialogVisualsImpl,
continuation = continuation
)
}
}
} finally {
currentDialogData = null
}
}
suspend fun <R> withLoading(block: suspend () -> R) = coroutineScope {
val showLoading = launch {
showLoading()
}
val result = block()
showLoading.cancel()
result
}
suspend fun showPrompt(title: String, content: String) {
try {
mutex.withLock {
suspendCancellableCoroutine { continuation ->
currentDialogData = PromptDialogDataImpl(
visuals = PromptDialogVisualsImpl(title, content),
continuation = continuation
)
}
}
} finally {
currentDialogData = null
}
}
suspend fun showConfirm(
title: String, title: String,
content: String, content: String,
confirm: String? = null, confirm: String? = null,
dismiss: String? = null dismiss: String? = null
): DialogResult = mutex.withLock { ): ConfirmResult = mutex.withLock {
try { try {
return@withLock suspendCancellableCoroutine { continuation -> return@withLock suspendCancellableCoroutine { continuation ->
currentDialogData = DialogDataImpl( currentDialogData = ConfirmDialogDataImpl(
visuals = DialogVisualsImpl(title, content, confirm, dismiss), visuals = ConfirmDialogVisualsImpl(title, content, confirm, dismiss),
continuation = continuation continuation = continuation
) )
} }
@@ -85,82 +184,85 @@ fun rememberDialogHostState(): DialogHostState {
} }
} }
@Composable private inline fun <reified T : DialogData> DialogData?.tryInto(): T? {
fun BaseDialog( return when (this) {
state: DialogHostState = LocalDialogHost.current, is T -> this
title: @Composable (String) -> Unit, else -> null
confirmButton: @Composable (String?, () -> Unit) -> Unit, }
dismissButton: @Composable (String?, () -> Unit) -> Unit,
content: @Composable (String) -> Unit = { Text(text = it) },
) {
val currentDialogData = state.currentDialogData ?: return
val visuals = currentDialogData.visuals
AlertDialog(
onDismissRequest = {
currentDialogData.dismiss()
},
title = {
title(visuals.title)
},
text = {
content(visuals.content)
},
confirmButton = {
confirmButton(visuals.confirm, currentDialogData::confirm)
},
dismissButton = {
dismissButton(visuals.dismiss, currentDialogData::dismiss)
}
)
} }
@Composable @Composable
fun SimpleDialog( fun LoadingDialog(
state: DialogHostState = LocalDialogHost.current, state: DialogHostState = LocalDialogHost.current,
content: @Composable (String) -> Unit
) { ) {
BaseDialog( state.currentDialogData.tryInto<LoadingDialogData>() ?: return
state = state, val dialogProperties = remember {
DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)
}
Dialog(onDismissRequest = {}, properties = dialogProperties) {
Surface(
modifier = Modifier
.size(100.dp),
shape = RoundedCornerShape(8.dp)
) {
Box(
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
}
}
}
@Composable
fun PromptDialog(
state: DialogHostState = LocalDialogHost.current,
) {
val promptDialogData = state.currentDialogData.tryInto<PromptDialogData>() ?: return
val visuals = promptDialogData.visuals
AlertDialog(
onDismissRequest = {
promptDialogData.dismiss()
},
title = { title = {
Text(text = it) Text(text = visuals.title)
}, },
confirmButton = { text, confirm -> text = {
text?.let { Text(text = visuals.content)
TextButton(onClick = confirm) { },
Text(text = it) confirmButton = {
} TextButton(onClick = { promptDialogData.dismiss() }) {
Text(text = stringResource(id = android.R.string.ok))
} }
}, },
dismissButton = { text, dismiss -> dismissButton = null,
text?.let {
TextButton(onClick = dismiss) {
Text(text = it)
}
}
},
content = content
) )
} }
@Composable @Composable
fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) { fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
BaseDialog( val confirmDialogData = state.currentDialogData.tryInto<ConfirmDialogData>() ?: return
state = state,
title = { val visuals = confirmDialogData.visuals
Text(text = it) AlertDialog(
onDismissRequest = {
confirmDialogData.dismiss()
}, },
confirmButton = { text, confirm -> title = {
text?.let { Text(text = visuals.title)
TextButton(onClick = confirm) { },
Text(text = it) text = {
} Text(text = visuals.content)
},
confirmButton = {
TextButton(onClick = { confirmDialogData.confirm() }) {
Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok))
} }
}, },
dismissButton = { text, dismiss -> dismissButton = {
text?.let { TextButton(onClick = { confirmDialogData.dismiss() }) {
TextButton(onClick = dismiss) { Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel))
Text(text = it)
}
} }
}, },
) )

View File

@@ -0,0 +1,28 @@
package me.weishu.kernelsu.ui.component
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.onKeyEvent
@Composable
fun KeyEventBlocker(predicate: (KeyEvent) -> Boolean) {
val requester = remember { FocusRequester() }
Box(
Modifier
.onKeyEvent {
predicate(it)
}
.focusRequester(requester)
.focusable()
)
LaunchedEffect(Unit) {
requester.requestFocus()
}
}

View File

@@ -10,10 +10,22 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.* import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -21,11 +33,9 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
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 me.weishu.kernelsu.R
private const val TAG = "SearchBar" private const val TAG = "SearchBar"

View File

@@ -0,0 +1,52 @@
package me.weishu.kernelsu.ui.component
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
@Composable
fun SwitchItem(
icon: ImageVector? = null,
title: String,
summary: String? = null,
checked: Boolean,
enabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit
) {
ListItem(
headlineContent = {
Text(title)
},
leadingContent = icon?.let {
{ Icon(icon, title) }
},
trailingContent = {
Switch(checked = checked, enabled = enabled, onCheckedChange = onCheckedChange)
},
supportingContent = {
if (summary != null) {
Text(summary)
}
}
)
}
@Composable
fun RadioItem(
title: String,
selected: Boolean,
onClick: () -> Unit,
) {
ListItem(
headlineContent = {
Text(title)
},
leadingContent = {
RadioButton(selected = selected, onClick = onClick)
},
)
}

View File

@@ -0,0 +1,63 @@
package me.weishu.kernelsu.ui.component.profile
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SwitchItem
@Composable
fun AppProfileConfig(
modifier: Modifier = Modifier,
fixedName: Boolean,
enabled: Boolean,
profile: Natives.Profile,
onProfileChange: (Natives.Profile) -> Unit,
) {
Column(modifier = modifier) {
if (!fixedName) {
OutlinedTextField(
label = { Text(stringResource(R.string.profile_name)) },
value = profile.name,
onValueChange = { onProfileChange(profile.copy(name = it)) }
)
}
SwitchItem(
title = stringResource(R.string.profile_umount_modules),
summary = stringResource(R.string.profile_umount_modules_summary),
checked = if (enabled) {
profile.umountModules
} else {
Natives.isDefaultUmountModules()
},
enabled = enabled,
onCheckedChange = {
onProfileChange(
profile.copy(
umountModules = it,
nonRootUseDefault = false
)
)
}
)
}
}
@Preview
@Composable
private fun AppProfileConfigPreview() {
var profile by remember { mutableStateOf(Natives.Profile("")) }
AppProfileConfig(fixedName = true, enabled = false, profile = profile) {
profile = it
}
}

View File

@@ -0,0 +1,464 @@
@file:OptIn(ExperimentalMaterial3Api::class)
package me.weishu.kernelsu.ui.component.profile
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material3.AssistChip
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.text.isDigitsOnly
import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.input.InputDialog
import com.maxkeppeler.sheets.input.models.InputHeader
import com.maxkeppeler.sheets.input.models.InputSelection
import com.maxkeppeler.sheets.input.models.InputTextField
import com.maxkeppeler.sheets.input.models.InputTextFieldType
import com.maxkeppeler.sheets.input.models.ValidationResult
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.profile.Capabilities
import me.weishu.kernelsu.profile.Groups
import me.weishu.kernelsu.ui.util.isSepolicyValid
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RootProfileConfig(
modifier: Modifier = Modifier,
fixedName: Boolean,
profile: Natives.Profile,
onProfileChange: (Natives.Profile) -> Unit,
) {
Column(modifier = modifier) {
if (!fixedName) {
OutlinedTextField(
label = { Text(stringResource(R.string.profile_name)) },
value = profile.name,
onValueChange = { onProfileChange(profile.copy(name = it)) }
)
}
var expanded by remember { mutableStateOf(false) }
val currentNamespace = when (profile.namespace) {
Natives.Profile.Namespace.Inherited.ordinal -> stringResource(R.string.profile_namespace_inherited)
Natives.Profile.Namespace.Global.ordinal -> stringResource(R.string.profile_namespace_global)
Natives.Profile.Namespace.Individual.ordinal -> stringResource(R.string.profile_namespace_individual)
else -> stringResource(R.string.profile_namespace_inherited)
}
ListItem(headlineContent = {
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded }
) {
OutlinedTextField(
modifier = Modifier
.menuAnchor()
.fillMaxWidth(),
readOnly = true,
label = { Text(stringResource(R.string.profile_namespace)) },
value = currentNamespace,
onValueChange = {},
trailingIcon = {
if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
else Icon(Icons.Filled.ArrowDropDown, null)
},
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.profile_namespace_inherited)) },
onClick = {
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Inherited.ordinal))
expanded = false
},
)
DropdownMenuItem(
text = { Text(stringResource(R.string.profile_namespace_global)) },
onClick = {
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Global.ordinal))
expanded = false
},
)
DropdownMenuItem(
text = { Text(stringResource(R.string.profile_namespace_individual)) },
onClick = {
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Individual.ordinal))
expanded = false
},
)
}
}
})
UidPanel(uid = profile.uid, label = "uid", onUidChange = {
onProfileChange(
profile.copy(
uid = it,
rootUseDefault = false
)
)
})
UidPanel(uid = profile.gid, label = "gid", onUidChange = {
onProfileChange(
profile.copy(
gid = it,
rootUseDefault = false
)
)
})
val selectedGroups = profile.groups.ifEmpty { listOf(0) }.let { e ->
e.mapNotNull { g ->
Groups.values().find { it.gid == g }
}
}
GroupsPanel(selectedGroups) {
onProfileChange(
profile.copy(
groups = it.map { group -> group.gid }.ifEmpty { listOf(0) },
rootUseDefault = false
)
)
}
val selectedCaps = profile.capabilities.mapNotNull { e ->
Capabilities.values().find { it.cap == e }
}
CapsPanel(selectedCaps) {
onProfileChange(
profile.copy(
capabilities = it.map { cap -> cap.cap },
rootUseDefault = false
)
)
}
SELinuxPanel(profile = profile, onSELinuxChange = { domain, rules ->
onProfileChange(
profile.copy(
context = domain,
rules = rules,
rootUseDefault = false
)
)
})
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>) -> Unit) {
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
val groups = Groups.values()
val options = groups.map { value ->
ListOption(
titleText = value.display,
subtitleText = value.desc,
selected = selected.contains(value),
)
}
val selection = HashSet(selected)
ListDialog(
state = rememberUseCaseState(visible = true, onFinishedRequest = {
closeSelection(selection)
}, onCloseRequest = {
showDialog = false
}),
header = Header.Default(
title = stringResource(R.string.profile_groups),
),
selection = ListSelection.Multiple(
showCheckBoxes = true,
options = options,
maxChoices = 32, // Kernel only supports 32 groups at most
) { indecies, _ ->
// Handle selection
selection.clear()
indecies.forEach { index ->
val group = groups[index]
selection.add(group)
}
}
)
}
OutlinedCard(modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable {
showDialog = true
}) {
Column(modifier = Modifier.padding(16.dp)) {
Text(stringResource(R.string.profile_groups))
FlowRow {
selected.forEach { group ->
AssistChip(
modifier = Modifier.padding(3.dp),
onClick = { /*TODO*/ },
label = { Text(group.display) })
}
}
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun CapsPanel(
selected: Collection<Capabilities>,
closeSelection: (selection: Set<Capabilities>) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
val caps = Capabilities.values()
val options = caps.map { value ->
ListOption(
titleText = value.display,
subtitleText = value.desc,
selected = selected.contains(value),
)
}
val selection = HashSet(selected)
ListDialog(
state = rememberUseCaseState(visible = true, onFinishedRequest = {
closeSelection(selection)
}, onCloseRequest = {
showDialog = false
}),
header = Header.Default(
title = stringResource(R.string.profile_capabilities),
),
selection = ListSelection.Multiple(
showCheckBoxes = true,
options = options
) { indecies, _ ->
// Handle selection
selection.clear()
indecies.forEach { index ->
val group = caps[index]
selection.add(group)
}
}
)
}
OutlinedCard(modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable {
showDialog = true
}) {
Column(modifier = Modifier.padding(16.dp)) {
Text(stringResource(R.string.profile_capabilities))
FlowRow {
selected.forEach { group ->
AssistChip(
modifier = Modifier.padding(3.dp),
onClick = { /*TODO*/ },
label = { Text(group.display) })
}
}
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun UidPanel(uid: Int, label: String, onUidChange: (Int) -> Unit) {
ListItem(headlineContent = {
var isError by remember {
mutableStateOf(false)
}
var lastValidUid by remember {
mutableStateOf(uid)
}
val keyboardController = LocalSoftwareKeyboardController.current
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
label = { Text(label) },
value = uid.toString(),
isError = isError,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(onDone = {
keyboardController?.hide()
}),
onValueChange = {
if (it.isEmpty()) {
onUidChange(0)
return@OutlinedTextField
}
val valid = isTextValidUid(it)
val targetUid = if (valid) it.toInt() else lastValidUid
if (valid) {
lastValidUid = it.toInt()
}
onUidChange(targetUid)
isError = !valid
}
)
})
}
@Composable
private fun SELinuxPanel(
profile: Natives.Profile,
onSELinuxChange: (domain: String, rules: String) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
var domain by remember { mutableStateOf(profile.context) }
var rules by remember { mutableStateOf(profile.rules) }
val inputOptions = listOf(
InputTextField(
text = domain,
header = InputHeader(
title = stringResource(id = R.string.profile_selinux_domain),
),
type = InputTextFieldType.OUTLINED,
required = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Ascii,
imeAction = ImeAction.Next
),
resultListener = {
domain = it ?: ""
},
validationListener = { value ->
// value can be a-zA-Z0-9_
val regex = Regex("^[a-z_]+:[a-z0-9_]+:[a-z0-9_]+(:[a-z0-9_]+)?$")
if (value?.matches(regex) == true) ValidationResult.Valid
else ValidationResult.Invalid("Domain must be in the format of \"user:role:type:level\"")
}
),
InputTextField(
text = rules,
header = InputHeader(
title = stringResource(id = R.string.profile_selinux_rules),
),
type = InputTextFieldType.OUTLINED,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Ascii,
),
singleLine = false,
resultListener = {
rules = it ?: ""
},
validationListener = { value ->
if (isSepolicyValid(value)) ValidationResult.Valid
else ValidationResult.Invalid("SELinux rules is invalid!")
}
)
)
InputDialog(
state = rememberUseCaseState(visible = true,
onFinishedRequest = {
onSELinuxChange(domain, rules)
},
onCloseRequest = {
showDialog = false
}),
header = Header.Default(
title = stringResource(R.string.profile_selinux_context),
),
selection = InputSelection(
input = inputOptions,
onPositiveClick = { result ->
// Handle selection
},
)
)
}
ListItem(headlineContent = {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.clickable {
showDialog = true
},
enabled = false,
colors = TextFieldDefaults.outlinedTextFieldColors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledBorderColor = MaterialTheme.colorScheme.outline,
disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant
),
label = { Text(text = stringResource(R.string.profile_selinux_context)) },
value = profile.context,
onValueChange = { },
)
})
}
@Preview
@Composable
private fun RootProfileConfigPreview() {
var profile by remember { mutableStateOf(Natives.Profile("")) }
RootProfileConfig(fixedName = true, profile = profile) {
profile = it
}
}
private fun isTextValidUid(text: String): Boolean {
return text.isNotEmpty() && text.isDigitsOnly() && text.toInt() >= 0 && text.toInt() <= Int.MAX_VALUE
}

View File

@@ -0,0 +1,391 @@
package me.weishu.kernelsu.ui.screen
import androidx.annotation.StringRes
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Android
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material.icons.filled.Security
import androidx.compose.material3.Divider
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.forceStopApp
import me.weishu.kernelsu.ui.util.getSepolicy
import me.weishu.kernelsu.ui.util.launchApp
import me.weishu.kernelsu.ui.util.restartApp
import me.weishu.kernelsu.ui.util.setSepolicy
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
/**
* @author weishu
* @date 2023/5/16.
*/
@Destination
@Composable
fun AppProfileScreen(
navigator: DestinationsNavigator,
appInfo: SuperUserViewModel.AppInfo,
) {
val context = LocalContext.current
val snackbarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope()
val failToUpdateAppProfile =
stringResource(R.string.failed_to_update_app_profile).format(appInfo.label)
val failToUpdateSepolicy =
stringResource(R.string.failed_to_update_sepolicy).format(appInfo.label)
val packageName = appInfo.packageName
val initialProfile = Natives.getAppProfile(packageName, appInfo.uid)
if (initialProfile.allowSu) {
initialProfile.rules = getSepolicy(packageName)
}
var profile by rememberSaveable {
mutableStateOf(initialProfile)
}
Scaffold(
topBar = { TopBar { navigator.popBackStack() } },
) { paddingValues ->
AppProfileInner(
modifier = Modifier
.padding(paddingValues)
.verticalScroll(rememberScrollState()),
packageName = appInfo.packageName,
appLabel = appInfo.label,
appIcon = {
AsyncImage(
model = ImageRequest.Builder(context)
.data(appInfo.packageInfo)
.crossfade(true)
.build(),
contentDescription = appInfo.label,
modifier = Modifier
.padding(4.dp)
.width(48.dp)
.height(48.dp)
)
},
profile = profile,
onProfileChange = {
scope.launch {
if (it.allowSu && !it.rootUseDefault && it.rules.isNotEmpty()) {
if (!setSepolicy(profile.name, it.rules)) {
snackbarHost.showSnackbar(failToUpdateSepolicy)
return@launch
}
}
if (!Natives.setAppProfile(it)) {
snackbarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
} else {
profile = it
}
}
},
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AppProfileInner(
modifier: Modifier = Modifier,
packageName: String,
appLabel: String,
appIcon: @Composable () -> Unit,
profile: Natives.Profile,
onProfileChange: (Natives.Profile) -> Unit,
) {
val isRootGranted = profile.allowSu
Column(modifier = modifier) {
AppMenuBox(packageName) {
ListItem(
headlineContent = { Text(appLabel) },
supportingContent = { Text(packageName) },
leadingContent = appIcon,
)
}
SwitchItem(
icon = Icons.Filled.Security,
title = stringResource(id = R.string.superuser),
checked = isRootGranted,
onCheckedChange = { onProfileChange(profile.copy(allowSu = it)) },
)
Crossfade(targetState = isRootGranted, label = "") { current ->
Column {
if (current) {
val initialMode = if (profile.rootUseDefault) {
Mode.Default
} else if (profile.rootTemplate != null) {
Mode.Template
} else {
Mode.Custom
}
var mode by remember {
mutableStateOf(initialMode)
}
ProfileBox(mode, false) {
// template mode shouldn't change profile here!
if (it == Mode.Default || it == Mode.Custom) {
onProfileChange(profile.copy(rootUseDefault = it == Mode.Default))
}
mode = it
}
Crossfade(targetState = mode, label = "") { currentMode ->
if (currentMode == Mode.Template) {
var expanded by remember { mutableStateOf(false) }
val templateNone = "None"
var template by rememberSaveable {
mutableStateOf(
profile.rootTemplate
?: templateNone
)
}
ListItem(headlineContent = {
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
) {
OutlinedTextField(
modifier = Modifier.menuAnchor(),
readOnly = true,
label = { Text(stringResource(R.string.profile_template)) },
value = template,
onValueChange = {
if (template != templateNone) {
onProfileChange(
profile.copy(
rootTemplate = it,
rootUseDefault = false
)
)
template = it
}
},
trailingIcon = {
if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
else Icon(Icons.Filled.ArrowDropDown, null)
},
)
// TODO: Template
}
})
} else if (mode == Mode.Custom) {
RootProfileConfig(
fixedName = true,
profile = profile,
onProfileChange = onProfileChange
)
}
}
} else {
val mode = if (profile.nonRootUseDefault) Mode.Default else Mode.Custom
ProfileBox(mode, false) {
onProfileChange(profile.copy(nonRootUseDefault = (it == Mode.Default)))
}
Crossfade(targetState = mode, label = "") { currentMode ->
val modifyEnabled = currentMode == Mode.Custom
AppProfileConfig(
fixedName = true,
profile = profile,
enabled = modifyEnabled,
onProfileChange = onProfileChange
)
}
}
}
}
}
}
private enum class Mode(@StringRes private val res: Int) {
Default(R.string.profile_default),
Template(R.string.profile_template),
Custom(R.string.profile_custom);
val text: String
@Composable get() = stringResource(res)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(onBack: () -> Unit) {
TopAppBar(
title = {
Text(stringResource(R.string.profile))
},
navigationIcon = {
IconButton(
onClick = onBack
) { Icon(Icons.Filled.ArrowBack, contentDescription = null) }
},
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ProfileBox(
mode: Mode,
hasTemplate: Boolean,
onModeChange: (Mode) -> Unit,
) {
ListItem(
headlineContent = { Text(stringResource(R.string.profile)) },
supportingContent = { Text(mode.text) },
leadingContent = { Icon(Icons.Filled.AccountCircle, null) },
)
Divider(thickness = Dp.Hairline)
ListItem(headlineContent = {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
FilterChip(
selected = mode == Mode.Default,
label = { Text(stringResource(R.string.profile_default)) },
onClick = { onModeChange(Mode.Default) },
)
if (hasTemplate) {
FilterChip(
selected = mode == Mode.Template,
label = { Text(stringResource(R.string.profile_template)) },
onClick = { onModeChange(Mode.Template) },
)
}
FilterChip(
selected = mode == Mode.Custom,
label = { Text(stringResource(R.string.profile_custom)) },
onClick = { onModeChange(Mode.Custom) },
)
}
})
}
@Composable
private fun AppMenuBox(packageName: String, content: @Composable () -> Unit) {
var expanded by remember { mutableStateOf(false) }
var touchPoint: Offset by remember { mutableStateOf(Offset.Zero) }
val density = LocalDensity.current
BoxWithConstraints(
Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures {
touchPoint = it
expanded = true
}
}
) {
content()
val (offsetX, offsetY) = with(density) {
(touchPoint.x.toDp()) to (touchPoint.y.toDp())
}
DropdownMenu(
expanded = expanded,
offset = DpOffset(offsetX, -offsetY),
onDismissRequest = {
expanded = false
},
) {
DropdownMenuItem(
text = { Text(stringResource(id = R.string.launch_app)) },
onClick = {
expanded = false
launchApp(packageName)
},
)
DropdownMenuItem(
text = { Text(stringResource(id = R.string.force_stop_app)) },
onClick = {
expanded = false
forceStopApp(packageName)
},
)
DropdownMenuItem(
text = { Text(stringResource(id = R.string.restart_app)) },
onClick = {
expanded = false
restartApp(packageName)
},
)
}
}
}
@Preview
@Composable
private fun AppProfilePreview() {
var profile by remember { mutableStateOf(Natives.Profile("")) }
AppProfileInner(
packageName = "icu.nullptr.test",
appLabel = "Test",
appIcon = { Icon(Icons.Filled.Android, null) },
profile = profile,
onProfileChange = {
profile = it
},
)
}

View File

@@ -15,9 +15,10 @@ enum class BottomBarDestination(
val direction: DirectionDestinationSpec, val direction: DirectionDestinationSpec,
@StringRes val label: Int, @StringRes val label: Int,
val iconSelected: ImageVector, val iconSelected: ImageVector,
val iconNotSelected: ImageVector val iconNotSelected: ImageVector,
val rootRequired: Boolean,
) { ) {
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home), Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false),
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security), SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true),
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps) Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true)
} }

View File

@@ -19,32 +19,31 @@ import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
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 com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootNavGraph import com.ramcosta.composedestinations.annotation.RootNavGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.* import me.weishu.kernelsu.*
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
import me.weishu.kernelsu.ui.util.* import me.weishu.kernelsu.ui.util.*
@OptIn(ExperimentalMaterial3Api::class)
@RootNavGraph(start = true) @RootNavGraph(start = true)
@Destination @Destination
@Composable @Composable
fun HomeScreen(navigator: DestinationsNavigator) { fun HomeScreen(navigator: DestinationsNavigator) {
Scaffold( Scaffold(topBar = {
topBar = { TopBar(onSettingsClick = {
TopBar(onSettingsClick = { navigator.navigate(SettingScreenDestination)
navigator.navigate(SettingScreenDestination) })
}) }) { innerPadding ->
}
) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(innerPadding) .padding(innerPadding)
@@ -57,9 +56,17 @@ fun HomeScreen(navigator: DestinationsNavigator) {
SideEffect { SideEffect {
if (isManager) install() if (isManager) install()
} }
val ksuVersion = if (isManager) Natives.getVersion() else null val ksuVersion = if (isManager) Natives.version else null
StatusCard(kernelVersion, ksuVersion) StatusCard(kernelVersion, ksuVersion)
if (isManager && Natives.requireNewKernel()) {
WarningCard(
stringResource(id = R.string.require_kernel_version).format(
ksuVersion, Natives.MINIMAL_SUPPORTED_KERNEL
)
)
}
UpdateCard()
InfoCard() InfoCard()
DonateCard() DonateCard()
LearnMoreCard() LearnMoreCard()
@@ -68,6 +75,28 @@ fun HomeScreen(navigator: DestinationsNavigator) {
} }
} }
@Composable
fun UpdateCard() {
val context = LocalContext.current
val newVersion by produceState(initialValue = 0 to "") {
value = withContext(Dispatchers.IO) { checkNewVersion() }
}
val currentVersionCode = getManagerVersion(context).second
val newVersionCode = newVersion.first
val newVersionUrl = newVersion.second
if (newVersionCode <= currentVersionCode) {
return
}
val uriHandler = LocalUriHandler.current
WarningCard(
message = stringResource(id = R.string.new_version_available).format(newVersionCode),
MaterialTheme.colorScheme.outlineVariant
) {
uriHandler.openUri(newVersionUrl)
}
}
@Composable @Composable
fun RebootDropdownItem(@StringRes id: Int, reason: String = "") { fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
DropdownMenuItem(text = { DropdownMenuItem(text = {
@@ -80,44 +109,41 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TopBar(onSettingsClick: () -> Unit) { private fun TopBar(onSettingsClick: () -> Unit) {
TopAppBar( TopAppBar(title = { Text(stringResource(R.string.app_name)) }, actions = {
title = { Text(stringResource(R.string.app_name)) }, var showDropdown by remember { mutableStateOf(false) }
actions = { IconButton(onClick = {
var showDropdown by remember { mutableStateOf(false) } showDropdown = true
IconButton(onClick = { }) {
showDropdown = true Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = stringResource(id = R.string.reboot)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) { }) {
Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = stringResource(id = R.string.reboot)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = { RebootDropdownItem(id = R.string.reboot)
showDropdown = false
}) {
RebootDropdownItem(id = R.string.reboot) val pm =
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
val pm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager? RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
}
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
} }
} RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
IconButton(onClick = onSettingsClick) { RebootDropdownItem(id = R.string.reboot_download, reason = "download")
Icon( RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
imageVector = Icons.Filled.Settings,
contentDescription = stringResource(id = R.string.settings)
)
} }
} }
)
IconButton(onClick = onSettingsClick) {
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = stringResource(id = R.string.settings)
)
}
})
} }
@Composable @Composable
@@ -129,20 +155,17 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
}) })
) { ) {
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
Row( Row(modifier = Modifier
modifier = Modifier .fillMaxWidth()
.fillMaxWidth() .clickable {
.clickable { if (kernelVersion.isGKI() && ksuVersion == null) {
if (kernelVersion.isGKI() && ksuVersion == null) { uriHandler.openUri("https://kernelsu.org/guide/installation.html")
uriHandler.openUri("https://kernelsu.org/guide/installation.html")
}
} }
.padding(24.dp), }
verticalAlignment = Alignment.CenterVertically .padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
) {
when { when {
ksuVersion != null -> { ksuVersion != null -> {
val appendText = if (Natives.isSafeMode()) { val appendText = if (Natives.isSafeMode) {
" [${stringResource(id = R.string.safe_mode)}]" " [${stringResource(id = R.string.safe_mode)}]"
} else { } else {
"" ""
@@ -160,8 +183,9 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_superuser_count, getSuperuserCount()), text = stringResource(
style = MaterialTheme.typography.bodyMedium R.string.home_superuser_count, getSuperuserCount()
), style = MaterialTheme.typography.bodyMedium
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
@@ -170,6 +194,7 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
) )
} }
} }
kernelVersion.isGKI() -> { kernelVersion.isGKI() -> {
Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed)) Icon(Icons.Outlined.Warning, stringResource(R.string.home_not_installed))
Column(Modifier.padding(start = 20.dp)) { Column(Modifier.padding(start = 20.dp)) {
@@ -184,12 +209,12 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
) )
} }
} }
else -> { else -> {
Icon(Icons.Outlined.Block, stringResource(R.string.home_unsupported)) Icon(Icons.Outlined.Block, stringResource(R.string.home_unsupported))
Column(Modifier.padding(start = 20.dp)) { Column(Modifier.padding(start = 20.dp)) {
Text( Text(
text = stringResource(R.string.home_unsupported), text = stringResource(R.string.home_unsupported),
fontFamily = FontFamily.Serif,
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
@@ -205,20 +230,40 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
} }
@Composable @Composable
fun LearnMoreCard() { fun WarningCard(
val uriHandler = LocalUriHandler.current message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null
) {
ElevatedCard { ElevatedCard(
colors = CardDefaults.elevatedCardColors(
containerColor = color
)
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .then(onClick?.let { Modifier.clickable { it() } } ?: Modifier)
uriHandler.openUri("https://kernelsu.org/guide/what-is-kernelsu.html") .padding(24.dp)
}
.padding(24.dp),
verticalAlignment = Alignment.CenterVertically
) { ) {
Text(
text = message, style = MaterialTheme.typography.bodyMedium
)
}
}
}
@Composable
fun LearnMoreCard() {
val uriHandler = LocalUriHandler.current
val url = stringResource(R.string.home_learn_kernelsu_url)
ElevatedCard {
Row(modifier = Modifier
.fillMaxWidth()
.clickable {
uriHandler.openUri(url)
}
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
Column() { Column() {
Text( Text(
text = stringResource(R.string.home_learn_kernelsu), text = stringResource(R.string.home_learn_kernelsu),
@@ -240,15 +285,12 @@ fun DonateCard() {
ElevatedCard { ElevatedCard {
Row( Row(modifier = Modifier
modifier = Modifier .fillMaxWidth()
.fillMaxWidth() .clickable {
.clickable { uriHandler.openUri("https://patreon.com/weishu")
uriHandler.openUri("https://patreon.com/weishu") }
} .padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
.padding(24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column() { Column() {
Text( Text(
text = stringResource(R.string.home_support_title), text = stringResource(R.string.home_support_title),
@@ -287,7 +329,11 @@ private fun InfoCard() {
InfoCardItem(stringResource(R.string.home_kernel), uname.release) InfoCardItem(stringResource(R.string.home_kernel), uname.release)
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_manager_version), getManagerVersion(context)) val managerVersion = getManagerVersion(context)
InfoCardItem(
stringResource(R.string.home_manager_version),
"${managerVersion.first} (${managerVersion.second})"
)
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT) InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT)
@@ -298,9 +344,9 @@ private fun InfoCard() {
} }
} }
fun getManagerVersion(context: Context): String { fun getManagerVersion(context: Context): Pair<String, Int> {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
return "${packageInfo.versionName} (${packageInfo.versionCode})" return Pair(packageInfo.versionName, packageInfo.versionCode)
} }
@Preview @Preview
@@ -312,3 +358,12 @@ private fun StatusCardPreview() {
StatusCard(KernelVersion(4, 10, 101), null) StatusCard(KernelVersion(4, 10, 101), null)
} }
} }
@Preview
@Composable
private fun WarningCardPreview() {
Column {
WarningCard(message = "Warning message")
WarningCard(message = "Warning message ", MaterialTheme.colorScheme.outlineVariant, onClick = {})
}
}

View File

@@ -14,7 +14,10 @@ import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
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
@@ -24,6 +27,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.KeyEventBlocker
import me.weishu.kernelsu.ui.util.LocalSnackbarHost import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.installModule import me.weishu.kernelsu.ui.util.installModule
import me.weishu.kernelsu.ui.util.reboot import me.weishu.kernelsu.ui.util.reboot
@@ -35,16 +39,18 @@ import java.util.*
* @author weishu * @author weishu
* @date 2023/1/1. * @date 2023/1/1.
*/ */
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
@Destination @Destination
fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) { fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
var text by rememberSaveable { mutableStateOf("") } var text by rememberSaveable { mutableStateOf("") }
val logContent = StringBuilder()
var showFloatAction by rememberSaveable { mutableStateOf(false) } var showFloatAction by rememberSaveable { mutableStateOf(false) }
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
@@ -55,9 +61,15 @@ fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
if (success) { if (success) {
showFloatAction = true showFloatAction = true
} }
}) { }, onStdout = {
text += "$it\n" text += "$it\n"
} scope.launch {
scrollState.animateScrollTo(scrollState.maxValue)
}
logContent.append(it).append("\n")
}, onStderr = {
logContent.append(it).append("\n")
});
} }
} }
@@ -75,7 +87,7 @@ fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"KernelSU_install_log_${date}.log" "KernelSU_install_log_${date}.log"
) )
file.writeText(text) file.writeText(logContent.toString())
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}") snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
} }
} }
@@ -99,11 +111,14 @@ fun InstallScreen(navigator: DestinationsNavigator, uri: Uri) {
} }
) { innerPadding -> ) { innerPadding ->
KeyEventBlocker {
it.key == Key.VolumeDown || it.key == Key.VolumeUp
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize(1f) .fillMaxSize(1f)
.padding(innerPadding) .padding(innerPadding)
.verticalScroll(rememberScrollState()), .verticalScroll(scrollState),
) { ) {
Text( Text(
modifier = Modifier.padding(8.dp), modifier = Modifier.padding(8.dp),

View File

@@ -2,12 +2,15 @@ package me.weishu.kernelsu.ui.screen
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.util.Log import android.util.Log
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
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
@@ -19,6 +22,7 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -30,32 +34,33 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.Natives import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.ConfirmDialog import me.weishu.kernelsu.ui.component.ConfirmDialog
import me.weishu.kernelsu.ui.component.DialogResult import me.weishu.kernelsu.ui.component.ConfirmResult
import me.weishu.kernelsu.ui.component.LoadingDialog
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
import me.weishu.kernelsu.ui.util.* import me.weishu.kernelsu.ui.util.*
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Destination @Destination
@Composable @Composable
fun ModuleScreen(navigator: DestinationsNavigator) { fun ModuleScreen(navigator: DestinationsNavigator) {
val viewModel = viewModel<ModuleViewModel>() val viewModel = viewModel<ModuleViewModel>()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (viewModel.moduleList.isEmpty()) { if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) {
viewModel.fetchModuleList() viewModel.fetchModuleList()
} }
} }
val isSafeMode = Natives.isSafeMode() val isSafeMode = Natives.isSafeMode
val isKSUVersionInvalid = Natives.getVersion() < 0
val hasMagisk = hasMagisk() val hasMagisk = hasMagisk()
val hideInstallButton = isSafeMode || isKSUVersionInvalid || hasMagisk val hideInstallButton = isSafeMode || hasMagisk
Scaffold(topBar = { Scaffold(topBar = {
TopBar() TopBar()
@@ -75,6 +80,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
navigator.navigate(InstallScreenDestination(uri)) navigator.navigate(InstallScreenDestination(uri))
viewModel.markNeedRefresh()
Log.i("ModuleScreen", "select zip result: ${it.data}") Log.i("ModuleScreen", "select zip result: ${it.data}")
} }
@@ -93,12 +100,9 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
ConfirmDialog() ConfirmDialog()
LoadingDialog()
when { when {
isKSUVersionInvalid -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(stringResource(R.string.require_kernel_version_8))
}
}
hasMagisk -> { hasMagisk -> {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -112,13 +116,15 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
) )
} }
} }
else -> { else -> {
ModuleList( ModuleList(
viewModel = viewModel, viewModel = viewModel, modifier = Modifier
modifier = Modifier
.padding(innerPadding) .padding(innerPadding)
.fillMaxSize() .fillMaxSize()
) ) {
navigator.navigate(InstallScreenDestination(it))
}
} }
} }
} }
@@ -126,7 +132,9 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier) { private fun ModuleList(
viewModel: ModuleViewModel, modifier: Modifier = Modifier, onInstallModule: (Uri) -> Unit
) {
val failedEnable = stringResource(R.string.module_failed_to_enable) val failedEnable = stringResource(R.string.module_failed_to_enable)
val failedDisable = stringResource(R.string.module_failed_to_disable) val failedDisable = stringResource(R.string.module_failed_to_disable)
val failedUninstall = stringResource(R.string.module_uninstall_failed) val failedUninstall = stringResource(R.string.module_uninstall_failed)
@@ -136,24 +144,28 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier
val moduleStr = stringResource(id = R.string.module) val moduleStr = stringResource(id = R.string.module)
val uninstall = stringResource(id = R.string.uninstall) val uninstall = stringResource(id = R.string.uninstall)
val cancel = stringResource(id = android.R.string.cancel) val cancel = stringResource(id = android.R.string.cancel)
val moduleUninstallConfirm = val moduleUninstallConfirm = stringResource(id = R.string.module_uninstall_confirm)
stringResource(id = R.string.module_uninstall_confirm)
val dialogHost = LocalDialogHost.current val dialogHost = LocalDialogHost.current
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) { suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) {
val dialogResult = dialogHost.showDialog( val confirmResult = dialogHost.showConfirm(
moduleStr, moduleStr,
content = moduleUninstallConfirm.format(module.name), content = moduleUninstallConfirm.format(module.name),
confirm = uninstall, confirm = uninstall,
dismiss = cancel dismiss = cancel
) )
if (dialogResult != DialogResult.Confirmed) { if (confirmResult != ConfirmResult.Confirmed) {
return return
} }
val success = uninstallModule(module.id) val success = dialogHost.withLoading {
withContext(Dispatchers.IO) {
uninstallModule(module.id)
}
}
if (success) { if (success) {
viewModel.fetchModuleList() viewModel.fetchModuleList()
} }
@@ -173,46 +185,66 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier
} }
} }
val refreshState = rememberPullRefreshState( val refreshState = rememberPullRefreshState(refreshing = viewModel.isRefreshing,
refreshing = viewModel.isRefreshing, onRefresh = { viewModel.fetchModuleList() })
onRefresh = { viewModel.fetchModuleList() }
)
Box(modifier.pullRefresh(refreshState)) { Box(modifier.pullRefresh(refreshState)) {
if (viewModel.isOverlayAvailable) { val context = LocalContext.current
LazyColumn(
modifier = Modifier.fillMaxSize(), LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxSize(),
contentPadding = remember { verticalArrangement = Arrangement.spacedBy(16.dp),
PaddingValues( contentPadding = remember {
start = 16.dp, PaddingValues(
top = 16.dp, start = 16.dp,
end = 16.dp, top = 16.dp,
bottom = 16.dp end = 16.dp,
+ 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */ bottom = 16.dp + 16.dp + 56.dp /* Scaffold Fab Spacing + Fab container height */
) )
}, },
) { ) {
val isEmpty = viewModel.moduleList.isEmpty() when {
if (isEmpty) { !viewModel.isOverlayAvailable -> {
item { item {
Box( Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillParentMaxSize(),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text(stringResource(R.string.module_empty)) Text(stringResource(R.string.module_overlay_fs_not_available), textAlign = TextAlign.Center)
} }
} }
} else { }
viewModel.moduleList.isEmpty() -> {
item {
Box(
modifier = Modifier.fillParentMaxSize(),
contentAlignment = Alignment.Center
) {
Text(stringResource(R.string.module_empty), textAlign = TextAlign.Center)
}
}
}
else -> {
items(viewModel.moduleList) { module -> items(viewModel.moduleList) { module ->
var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) } var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
ModuleItem(module, isChecked, onUninstall = { val updateUrl by produceState(initialValue = "") {
viewModel.checkUpdate(module) { value = it.orEmpty() }
}
val downloadingText = stringResource(R.string.module_downloading)
val startDownloadingText = stringResource(R.string.module_start_downloading)
ModuleItem(module, isChecked, updateUrl, onUninstall = {
scope.launch { onModuleUninstall(module) } scope.launch { onModuleUninstall(module) }
}, onCheckChanged = { }, onCheckChanged = {
val success = toggleModule(module.id, !isChecked) scope.launch {
if (success) { val success = dialogHost.withLoading {
isChecked = it withContext(Dispatchers.IO) {
scope.launch { toggleModule(module.id, !isChecked)
}
}
if (success) {
isChecked = it
viewModel.fetchModuleList() viewModel.fetchModuleList()
val result = snackBarHost.showSnackbar( val result = snackBarHost.showSnackbar(
@@ -221,27 +253,45 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier
if (result == SnackbarResult.ActionPerformed) { if (result == SnackbarResult.ActionPerformed) {
reboot() reboot()
} }
} else {
val message = if (isChecked) failedDisable else failedEnable
snackBarHost.showSnackbar(message.format(module.name))
} }
} else scope.launch {
val message = if (isChecked) failedDisable else failedEnable
snackBarHost.showSnackbar(message.format(module.name))
} }
}, onUpdate = {
scope.launch {
Toast.makeText(
context,
startDownloadingText.format(module.name),
Toast.LENGTH_SHORT
).show()
}
val downloading = downloadingText.format(module.name)
download(
context,
updateUrl,
"${module.name}-${module.version}.zip",
downloading,
onDownloaded = onInstallModule,
onDownloading = {
Toast.makeText(context, downloading, Toast.LENGTH_SHORT).show()
}
)
}) })
// fix last item shadow incomplete in LazyColumn // fix last item shadow incomplete in LazyColumn
Spacer(Modifier.height(1.dp)) Spacer(Modifier.height(1.dp))
} }
} }
} }
} else {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(stringResource(R.string.module_overlay_fs_not_available))
}
} }
DownloadListener(context, onInstallModule)
PullRefreshIndicator( PullRefreshIndicator(
refreshing = viewModel.isRefreshing, refreshing = viewModel.isRefreshing, state = refreshState, modifier = Modifier.align(
state = refreshState,
modifier = Modifier.align(
Alignment.TopCenter Alignment.TopCenter
) )
) )
@@ -258,8 +308,10 @@ private fun TopBar() {
private fun ModuleItem( private fun ModuleItem(
module: ModuleViewModel.ModuleInfo, module: ModuleViewModel.ModuleInfo,
isChecked: Boolean, isChecked: Boolean,
updateUrl: String,
onUninstall: (ModuleViewModel.ModuleInfo) -> Unit, onUninstall: (ModuleViewModel.ModuleInfo) -> Unit,
onCheckChanged: (Boolean) -> Unit onCheckChanged: (Boolean) -> Unit,
onUpdate: (ModuleViewModel.ModuleInfo) -> Unit,
) { ) {
ElevatedCard( ElevatedCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -341,6 +393,23 @@ private fun ModuleItem(
) { ) {
Spacer(modifier = Modifier.weight(1f, true)) Spacer(modifier = Modifier.weight(1f, true))
if (updateUrl.isNotEmpty()) {
Button(
modifier = Modifier
.padding(0.dp)
.defaultMinSize(52.dp, 32.dp),
onClick = { onUpdate(module) },
shape = RoundedCornerShape(6.dp),
contentPadding = PaddingValues(0.dp)
) {
Text(
fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
fontSize = MaterialTheme.typography.labelMedium.fontSize,
text = stringResource(R.string.module_update),
)
}
}
TextButton( TextButton(
enabled = !module.remove, enabled = !module.remove,
onClick = { onUninstall(module) }, onClick = { onUninstall(module) },
@@ -369,6 +438,7 @@ fun ModuleItemPreview() {
enabled = true, enabled = true,
update = true, update = true,
remove = true, remove = true,
updateJson = ""
) )
ModuleItem(module, true, {}, {}) ModuleItem(module, true, "", {}, {}, {})
} }

View File

@@ -2,31 +2,31 @@ package me.weishu.kernelsu.ui.screen
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.compose.foundation.background import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.ContactPage
import androidx.compose.material.icons.filled.RemoveModerator
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color.Companion.White
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource 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 androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.alorma.compose.settings.ui.*
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.weishu.kernelsu.BuildConfig import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.AboutDialog import me.weishu.kernelsu.ui.component.AboutDialog
import me.weishu.kernelsu.ui.component.LoadingDialog
import me.weishu.kernelsu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.util.LocalDialogHost import me.weishu.kernelsu.ui.util.LocalDialogHost
import me.weishu.kernelsu.ui.util.getBugreportFile import me.weishu.kernelsu.ui.util.getBugreportFile
@@ -34,7 +34,6 @@ import me.weishu.kernelsu.ui.util.getBugreportFile
* @author weishu * @author weishu
* @date 2023/1/1. * @date 2023/1/1.
*/ */
@OptIn(ExperimentalMaterial3Api::class)
@Destination @Destination
@Composable @Composable
fun SettingScreen(navigator: DestinationsNavigator) { fun SettingScreen(navigator: DestinationsNavigator) {
@@ -46,32 +45,42 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}) })
} }
) { paddingValues -> ) { paddingValues ->
LoadingDialog()
val showAboutDialog = remember { mutableStateOf(false) } val showAboutDialog = remember { mutableStateOf(false) }
AboutDialog(showAboutDialog) AboutDialog(showAboutDialog)
var showLoadingDialog by remember { mutableStateOf(false) }
LoadingDialog(showLoadingDialog)
Column(modifier = Modifier.padding(paddingValues)) { Column(modifier = Modifier.padding(paddingValues)) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val dialogHost = LocalDialogHost.current val dialogHost = LocalDialogHost.current
SettingsMenuLink(
title = { var umountChecked by rememberSaveable {
Text(stringResource(id = R.string.send_log)) mutableStateOf(Natives.isDefaultUmountModules())
}, }
onClick = { SwitchItem(
icon = Icons.Filled.RemoveModerator,
title = stringResource(id = R.string.settings_umount_modules_default),
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
checked = umountChecked
) {
if (Natives.setDefaultUmountModules(it)) {
umountChecked = it
}
}
ListItem(
leadingContent = { Icon(Icons.Filled.BugReport, stringResource(id = R.string.send_log)) },
headlineContent = { Text(stringResource(id = R.string.send_log)) },
modifier = Modifier.clickable {
scope.launch { scope.launch {
showLoadingDialog = true val bugreport = dialogHost.withLoading {
withContext(Dispatchers.IO) {
val bugreport = withContext(Dispatchers.IO) { getBugreportFile(context)
getBugreportFile(context) }
} }
showLoadingDialog = false
val uri: Uri = val uri: Uri =
FileProvider.getUriForFile( FileProvider.getUriForFile(
context, context,
@@ -95,11 +104,10 @@ fun SettingScreen(navigator: DestinationsNavigator) {
) )
val about = stringResource(id = R.string.about) val about = stringResource(id = R.string.about)
SettingsMenuLink( ListItem(
title = { leadingContent = { Icon(Icons.Filled.ContactPage, stringResource(id = R.string.about)) },
Text(about) headlineContent = { Text(about) },
}, modifier = Modifier.clickable {
onClick = {
showAboutDialog.value = true showAboutDialog.value = true
} }
) )
@@ -119,24 +127,3 @@ private fun TopBar(onBack: () -> Unit = {}) {
}, },
) )
} }
@Composable
fun LoadingDialog(showLoadingDialog: Boolean) {
if (!showLoadingDialog) {
return
}
Dialog(
onDismissRequest = { },
DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(100.dp)
.background(White, shape = RoundedCornerShape(8.dp))
) {
CircularProgressIndicator()
}
}
}

View File

@@ -1,8 +1,11 @@
package me.weishu.kernelsu.ui.screen package me.weishu.kernelsu.ui.screen
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
@@ -11,33 +14,32 @@ import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.ConfirmDialog import me.weishu.kernelsu.ui.component.ConfirmDialog
import me.weishu.kernelsu.ui.component.DialogResult
import me.weishu.kernelsu.ui.component.SearchAppBar import me.weishu.kernelsu.ui.component.SearchAppBar
import me.weishu.kernelsu.ui.util.LocalDialogHost import me.weishu.kernelsu.ui.screen.destinations.AppProfileScreenDestination
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
import java.util.*
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Destination @Destination
@Composable @Composable
fun SuperUserScreen() { fun SuperUserScreen(navigator: DestinationsNavigator) {
val viewModel = viewModel<SuperUserViewModel>() val viewModel = viewModel<SuperUserViewModel>()
val snackbarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -105,39 +107,12 @@ fun SuperUserScreen() {
.padding(innerPadding) .padding(innerPadding)
.pullRefresh(refreshState) .pullRefresh(refreshState)
) { ) {
val failMessage = stringResource(R.string.superuser_failed_to_grant_root)
LazyColumn(Modifier.fillMaxSize()) { LazyColumn(Modifier.fillMaxSize()) {
items(viewModel.appList, key = { it.packageName + it.uid }) { app -> items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
var isChecked by rememberSaveable(app) { mutableStateOf(app.onAllowList) } AppItem(app) {
val dialogHost = LocalDialogHost.current navigator.navigate(AppProfileScreenDestination(app))
val content =
stringResource(id = R.string.superuser_allow_root_confirm, app.label)
val confirm = stringResource(id = android.R.string.ok)
val cancel = stringResource(id = android.R.string.cancel)
AppItem(app, isChecked) { checked ->
scope.launch {
if (checked) {
val dialogResult = dialogHost.showDialog(
app.label,
content = content,
confirm = confirm,
dismiss = cancel
)
if (dialogResult != DialogResult.Confirmed) {
return@launch
}
}
val success = Natives.allowRoot(app.uid, checked)
if (success) {
isChecked = checked
} else {
snackbarHost.showSnackbar(failMessage.format(app.uid))
}
}
} }
} }
} }
@@ -150,20 +125,36 @@ fun SuperUserScreen() {
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
private fun AppItem( private fun AppItem(
app: SuperUserViewModel.AppInfo, app: SuperUserViewModel.AppInfo,
isChecked: Boolean, onClickListener: () -> Unit,
onCheckedChange: (Boolean) -> Unit
) { ) {
ListItem( ListItem(
headlineText = { Text(app.label) }, modifier = Modifier.clickable(onClick = onClickListener),
supportingText = { Text(app.packageName) }, headlineContent = { Text(app.label) },
supportingContent = {
Column {
Text(app.packageName)
FlowRow {
if (app.allowSu) {
LabelText(label = "ROOT")
} else {
if (Natives.uidShouldUmount(app.uid)) {
LabelText(label = "UMOUNT")
}
}
if (app.hasCustomProfile) {
LabelText(label = "CUSTOM")
}
}
}
},
leadingContent = { leadingContent = {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(LocalContext.current) model = ImageRequest.Builder(LocalContext.current)
.data(app.icon) .data(app.packageInfo)
.crossfade(true) .crossfade(true)
.build(), .build(),
contentDescription = app.label, contentDescription = app.label,
@@ -173,12 +164,26 @@ private fun AppItem(
.height(48.dp) .height(48.dp)
) )
}, },
trailingContent = {
Switch(
checked = isChecked,
onCheckedChange = onCheckedChange,
modifier = Modifier.padding(4.dp)
)
}
) )
} }
@Composable
fun LabelText(label: String) {
Box(
modifier = Modifier
.padding(top = 4.dp, end = 4.dp)
.background(
Color.Black,
shape = RoundedCornerShape(4.dp)
)
) {
Text(
text = label,
modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp),
style = TextStyle(
fontSize = 8.sp,
color = Color.White,
)
)
}
}

View File

@@ -0,0 +1,133 @@
package me.weishu.kernelsu.ui.util
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Environment
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
/**
* @author weishu
* @date 2023/6/22.
*/
@SuppressLint("Range")
fun download(
context: Context,
url: String,
fileName: String,
description: String,
onDownloaded: (Uri) -> Unit = {},
onDownloading: () -> Unit = {}
) {
val downloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val query = DownloadManager.Query()
query.setFilterByStatus(DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_PAUSED or DownloadManager.STATUS_PENDING)
downloadManager.query(query).use { cursor ->
while (cursor.moveToNext()) {
val uri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI))
val localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
val columnTitle = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE))
if (url == uri || fileName == columnTitle) {
if (status == DownloadManager.STATUS_RUNNING || status == DownloadManager.STATUS_PENDING) {
onDownloading()
return
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
onDownloaded(Uri.parse(localUri))
return
}
}
}
}
val request = DownloadManager.Request(Uri.parse(url))
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
fileName
)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setMimeType("application/zip")
.setTitle(fileName)
.setDescription(description)
downloadManager.enqueue(request)
}
fun checkNewVersion(): Pair<Int, String> {
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest"
val defaultValue = 0 to ""
runCatching {
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
.use { response ->
if (!response.isSuccessful) {
return defaultValue
}
val body = response.body?.string() ?: return defaultValue
val json = org.json.JSONObject(body)
val assets = json.getJSONArray("assets")
for (i in 0 until assets.length()) {
val asset = assets.getJSONObject(i)
val name = asset.getString("name")
if (!name.endsWith(".apk")) {
continue
}
val regex = Regex("v(.+?)_(\\d+)-")
val matchResult = regex.find(name) ?: continue
val versionName = matchResult.groupValues[1]
val versionCode = matchResult.groupValues[2].toInt()
val downloadUrl = asset.getString("browser_download_url")
return versionCode to downloadUrl
}
}
}
return defaultValue
}
@Composable
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
DisposableEffect(context) {
val receiver = object : BroadcastReceiver() {
@SuppressLint("Range")
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
val id = intent.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, -1
)
val query = DownloadManager.Query().setFilterById(id)
val downloadManager =
context?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val cursor = downloadManager.query(query)
if (cursor.moveToFirst()) {
val status = cursor.getInt(
cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
)
if (status == DownloadManager.STATUS_SUCCESSFUL) {
val uri = cursor.getString(
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
)
onDownloaded(Uri.parse(uri))
}
}
}
}
}
context.registerReceiver(
receiver,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
)
onDispose {
context.unregisterReceiver(receiver)
}
}
}

View File

@@ -9,7 +9,6 @@ import com.topjohnwu.superuser.ShellUtils
import me.weishu.kernelsu.BuildConfig import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.Natives import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
import org.json.JSONArray import org.json.JSONArray
import java.io.File import java.io.File
@@ -71,7 +70,7 @@ fun getModuleCount(): Int {
} }
fun getSuperuserCount(): Int { fun getSuperuserCount(): Int {
return Natives.getAllowList().size return Natives.allowList.size
} }
fun toggleModule(id: String, enable: Boolean): Boolean { fun toggleModule(id: String, enable: Boolean): Boolean {
@@ -92,7 +91,7 @@ fun uninstallModule(id: String): Boolean {
return result return result
} }
fun installModule(uri: Uri, onFinish: (Boolean) -> Unit, onOutput: (String) -> Unit): Boolean { fun installModule(uri: Uri, onFinish: (Boolean) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit): Boolean {
val resolver = ksuApp.contentResolver val resolver = ksuApp.contentResolver
with(resolver.openInputStream(uri)) { with(resolver.openInputStream(uri)) {
val file = File(ksuApp.cacheDir, "module.zip") val file = File(ksuApp.cacheDir, "module.zip")
@@ -103,14 +102,20 @@ fun installModule(uri: Uri, onFinish: (Boolean) -> Unit, onOutput: (String) -> U
val shell = getRootShell() val shell = getRootShell()
val callbackList: CallbackList<String?> = object : CallbackList<String?>() { val stdoutCallback: CallbackList<String?> = object : CallbackList<String?>() {
override fun onAddElement(s: String?) { override fun onAddElement(s: String?) {
onOutput(s ?: "") onStdout(s ?: "")
}
}
val stderrCallback: CallbackList<String?> = object : CallbackList<String?>() {
override fun onAddElement(s: String?) {
onStderr(s ?: "")
} }
} }
val result = val result =
shell.newJob().add("${getKsuDaemonPath()} $cmd").to(callbackList, callbackList).exec() shell.newJob().add("${getKsuDaemonPath()} $cmd").to(stdoutCallback, stderrCallback).exec()
Log.i("KernelSU", "install module $uri result: $result") Log.i("KernelSU", "install module $uri result: $result")
file.delete() file.delete()
@@ -140,4 +145,48 @@ fun hasMagisk(): Boolean {
val result = shell.newJob().add("nsenter --mount=/proc/1/ns/mnt which magisk").exec() val result = shell.newJob().add("nsenter --mount=/proc/1/ns/mnt which magisk").exec()
Log.i(TAG, "has magisk: ${result.isSuccess}") Log.i(TAG, "has magisk: ${result.isSuccess}")
return result.isSuccess return result.isSuccess
}
fun isSepolicyValid(rules: String?): Boolean {
if (rules == null) {
return true
}
val shell = getRootShell()
val result =
shell.newJob().add("${getKsuDaemonPath()} sepolicy check '$rules'").to(ArrayList(), null).exec()
return result.isSuccess
}
fun getSepolicy(pkg: String): String {
val shell = getRootShell()
val result =
shell.newJob().add("${getKsuDaemonPath()} profile get-sepolicy $pkg").to(ArrayList(), null).exec()
Log.i(TAG, "code: ${result.code}, out: ${result.out}, err: ${result.err}")
return result.out.joinToString("\n")
}
fun setSepolicy(pkg: String, rules: String): Boolean {
val shell = getRootShell()
val result =
shell.newJob().add("${getKsuDaemonPath()} profile set-sepolicy $pkg '$rules'").to(ArrayList(), null).exec()
Log.i(TAG, "set sepolicy result: ${result.code}")
return result.isSuccess
}
fun forceStopApp(packageName: String) {
val shell = getRootShell()
val result = shell.newJob().add("am force-stop $packageName").exec()
Log.i(TAG, "force stop $packageName result: $result")
}
fun launchApp(packageName: String) {
val shell = getRootShell()
val result = shell.newJob().add("monkey -p $packageName -c android.intent.category.LAUNCHER 1").exec()
Log.i(TAG, "launch $packageName result: $result")
}
fun restartApp(packageName: String) {
forceStopApp(packageName)
launchApp(packageName)
} }

View File

@@ -72,9 +72,9 @@ fun getBugreportFile(context: Context): File {
pw.println("Nodename: ${uname.nodename}") pw.println("Nodename: ${uname.nodename}")
pw.println("Sysname: ${uname.sysname}") pw.println("Sysname: ${uname.sysname}")
val ksuKernel = Natives.getVersion() val ksuKernel = Natives.version
pw.println("KernelSU: $ksuKernel") pw.println("KernelSU: $ksuKernel")
val safeMode = Natives.isSafeMode() val safeMode = Natives.isSafeMode
pw.println("SafeMode: $safeMode") pw.println("SafeMode: $safeMode")
} }

View File

@@ -1,5 +1,6 @@
package me.weishu.kernelsu.ui.viewmodel package me.weishu.kernelsu.ui.viewmodel
import android.net.Uri
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@@ -13,6 +14,7 @@ import kotlinx.coroutines.launch
import me.weishu.kernelsu.ui.util.listModules import me.weishu.kernelsu.ui.util.listModules
import me.weishu.kernelsu.ui.util.overlayFsAvailable import me.weishu.kernelsu.ui.util.overlayFsAvailable
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject
import java.text.Collator import java.text.Collator
import java.util.* import java.util.*
@@ -33,6 +35,14 @@ class ModuleViewModel : ViewModel() {
val enabled: Boolean, val enabled: Boolean,
val update: Boolean, val update: Boolean,
val remove: Boolean, val remove: Boolean,
val updateJson: String,
)
data class ModuleUpdateInfo(
val version: String,
val versionCode: Int,
val zipUrl: String,
val changelog: String,
) )
var isRefreshing by mutableStateOf(false) var isRefreshing by mutableStateOf(false)
@@ -48,6 +58,13 @@ class ModuleViewModel : ViewModel() {
} }
} }
var isNeedRefresh by mutableStateOf(false)
private set
fun markNeedRefresh() {
isNeedRefresh = true
}
fun fetchModuleList() { fun fetchModuleList() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
isRefreshing = true isRefreshing = true
@@ -70,16 +87,19 @@ class ModuleViewModel : ViewModel() {
.map { obj -> .map { obj ->
ModuleInfo( ModuleInfo(
obj.getString("id"), obj.getString("id"),
obj.getString("name"),
obj.optString("name"),
obj.optString("author", "Unknown"), obj.optString("author", "Unknown"),
obj.optString("version", "Unknown"), obj.optString("version", "Unknown"),
obj.optInt("versionCode", 0), obj.optInt("versionCode", 0),
obj.getString("description"), obj.optString("description"),
obj.getBoolean("enabled"), obj.getBoolean("enabled"),
obj.getBoolean("update"), obj.getBoolean("update"),
obj.getBoolean("remove"), obj.getBoolean("remove"),
obj.optString("updateJson")
) )
}.toList() }.toList()
isNeedRefresh = false
}.onFailure { e -> }.onFailure { e ->
Log.e(TAG, "fetchModuleList: ", e) Log.e(TAG, "fetchModuleList: ", e)
isRefreshing = false isRefreshing = false
@@ -94,4 +114,57 @@ class ModuleViewModel : ViewModel() {
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules") Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
} }
} }
fun checkUpdate(m: ModuleInfo, callback: (String?) -> Unit) {
if (m.updateJson.isEmpty() || m.remove || m.update || !m.enabled) {
callback(null)
return
}
viewModelScope.launch(Dispatchers.IO) {
// download updateJson
val result = kotlin.runCatching {
val url = m.updateJson
Log.i(TAG, "checkUpdate url: $url")
val response = okhttp3.OkHttpClient()
.newCall(
okhttp3.Request.Builder()
.url(url)
.build()
).execute()
Log.d(TAG, "checkUpdate code: ${response.code}")
if (response.isSuccessful) {
response.body?.string() ?: ""
} else {
""
}
}.getOrDefault("")
Log.i(TAG, "checkUpdate result: $result")
if (result.isEmpty()) {
callback(null)
return@launch
}
val updateJson = kotlin.runCatching {
JSONObject(result)
}.getOrNull()
if (updateJson == null) {
callback(null)
return@launch
}
val version = updateJson.optString("version", "")
val versionCode = updateJson.optInt("versionCode", 0)
val zipUrl = updateJson.optString("zipUrl", "")
val changelog = updateJson.optString("changelog", "")
if (versionCode <= m.versionCode || zipUrl.isEmpty()) {
callback(null)
return@launch
}
callback(zipUrl)
}
}
} }

View File

@@ -6,6 +6,7 @@ import android.content.ServiceConnection
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.os.IBinder import android.os.IBinder
import android.os.Parcelable
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@@ -16,6 +17,7 @@ import androidx.lifecycle.ViewModel
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.IKsuInterface import me.weishu.kernelsu.IKsuInterface
import me.weishu.kernelsu.Natives import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp import me.weishu.kernelsu.ksuApp
@@ -34,14 +36,32 @@ class SuperUserViewModel : ViewModel() {
private var apps by mutableStateOf<List<AppInfo>>(emptyList()) private var apps by mutableStateOf<List<AppInfo>>(emptyList())
} }
class AppInfo( @Parcelize
data class AppInfo(
val label: String, val label: String,
val packageName: String, val packageInfo: PackageInfo,
val icon: PackageInfo, val profile: Natives.Profile?,
val uid: Int, ) : Parcelable {
val onAllowList: Boolean, val packageName: String
val onDenyList: Boolean get() = packageInfo.packageName
) val uid: Int
get() = packageInfo.applicationInfo.uid
val allowSu: Boolean
get() = profile != null && profile.allowSu
val hasCustomProfile: Boolean
get() {
if (profile == null) {
return false
}
return if (profile.allowSu) {
!profile.rootUseDefault
} else {
!profile.nonRootUseDefault
}
}
}
var search by mutableStateOf("") var search by mutableStateOf("")
var showSystemApps by mutableStateOf(false) var showSystemApps by mutableStateOf(false)
@@ -51,8 +71,8 @@ class SuperUserViewModel : ViewModel() {
private val sortedList by derivedStateOf { private val sortedList by derivedStateOf {
val comparator = compareBy<AppInfo> { val comparator = compareBy<AppInfo> {
when { when {
it.onAllowList -> 0 it.allowSu -> 0
it.onDenyList -> 1 it.hasCustomProfile -> 1
else -> 2 else -> 2
} }
}.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label)) }.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))
@@ -67,7 +87,7 @@ class SuperUserViewModel : ViewModel() {
.toPinyinString(it.label).contains(search) .toPinyinString(it.label).contains(search)
}.filter { }.filter {
it.uid == 2000 // Always show shell it.uid == 2000 // Always show shell
|| showSystemApps || it.icon.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0 || showSystemApps || it.packageInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
} }
} }
@@ -107,13 +127,9 @@ class SuperUserViewModel : ViewModel() {
val result = connectKsuService { val result = connectKsuService {
Log.w(TAG, "KsuService disconnected") Log.w(TAG, "KsuService disconnected")
} }
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val pm = ksuApp.packageManager val pm = ksuApp.packageManager
val allowList = Natives.getAllowList().toSet()
val denyList = Natives.getDenyList().toSet()
Log.i(TAG, "allowList: $allowList")
Log.i(TAG, "denyList: $denyList")
val start = SystemClock.elapsedRealtime() val start = SystemClock.elapsedRealtime()
val binder = result.first val binder = result.first
@@ -128,13 +144,11 @@ class SuperUserViewModel : ViewModel() {
apps = packages.map { apps = packages.map {
val appInfo = it.applicationInfo val appInfo = it.applicationInfo
val uid = appInfo.uid val uid = appInfo.uid
val profile = Natives.getAppProfile(it.packageName, uid)
AppInfo( AppInfo(
label = appInfo.loadLabel(pm).toString(), label = appInfo.loadLabel(pm).toString(),
packageName = it.packageName, packageInfo = it,
icon = it, profile = profile,
uid = uid,
onAllowList = uid in allowList,
onDenyList = uid in denyList
) )
}.filter { it.packageName != ksuApp.packageName } }.filter { it.packageName != ksuApp.packageName }
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}") Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}")

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="home">الرئيسية</string>
<string name="home_not_installed">غير مثبت</string>
<string name="home_click_to_install">إضغط للتثبيت</string>
<string name="home_working">يعمل</string>
<string name="home_working_version">الإصدار: %d</string>
<string name="home_superuser_count">مستخدمين الجذر: %d</string>
<string name="home_module_count">الوحدات: %d</string>
<string name="home_unsupported">غير مدعوم</string>
<string name="home_unsupported_reason">KernelSU يدعم GKI kernels فقط</string>
<string name="home_kernel">إصدار النواة</string>
<string name="home_manager_version">إصدار المدير</string>
<string name="home_fingerprint">البصمة</string>
<string name="home_selinux_status">وضع SELinux</string>
<string name="selinux_status_disabled">غير مفعل</string>
<string name="selinux_status_enforcing">مفروض</string>
<string name="selinux_status_permissive">متساهل</string>
<string name="selinux_status_unknown">مجهول</string>
<string name="superuser">مستخدم خارق</string>
<string name="module_failed_to_enable">فشل في تمكين الوحدة: %s</string>
<string name="module_failed_to_disable">فشل تعطيل الوحدة : %s</string>
<string name="module_empty">لا توجد وحدة مثبتة</string>
<string name="module">وحدة</string>
<string name="uninstall">إلغاء التثبيت</string>
<string name="module_install">تثبيت الوحدة</string>
<string name="install">تثبيت</string>
<string name="reboot">إعادة تشغيل</string>
<string name="settings">الإعدادات</string>
<string name="reboot_userspace">إعادة تشغيل سريعة</string>
<string name="reboot_recovery">إعادة تشغيل إلى وضع Recovery</string>
<string name="reboot_bootloader">إعادة تشغيل إلى وضع Bootloader</string>
<string name="reboot_download">إعادة تشغيل إلى وضع Download</string>
<string name="reboot_edl">إعادة تشغيل إلى وضع EDL</string>
<string name="about">من نحن</string>
<string name="module_uninstall_confirm">هل أنت متأكد أنك تريد إلغاء تثبيت الوحدة %s ?</string>
<string name="module_uninstall_success">تم إلغاء التثبيت %s</string>
<string name="module_uninstall_failed">فشل إلغاء التثبيت: %s</string>
<string name="module_version">الإصدار</string>
<string name="module_author">المطور</string>
<string name="module_overlay_fs_not_available">التراكبات غير متوفرة ، لا يمكن للوحدة أن تعمل!</string>
<string name="refresh">إنعاش</string>
<string name="show_system_apps">إظهار تطبيقات النظام</string>
<string name="hide_system_apps">إخفاء تطبيقات النظام</string>
<string name="send_log">إرسال السجلات</string>
<string name="safe_mode">الوضع الآمن</string>
<string name="reboot_to_apply">إعادة التشغيل لتطبيق التغييرات</string>
<string name="module_magisk_conflict">تم تعطيل الوحدات النمطية لأنها تتعارض مع Magisk!</string>
<string name="home_learn_kernelsu">تعلم KernelSU</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_click_to_learn_kernelsu">تعرف على كيفية تثبيت KernelSU واستخدام الوحدات</string>
<string name="home_support_title">إدعمنا</string>
<string name="home_support_content">KernelSU سيظل دائماً مجانياً ومفتوح المصدر. مع ذلك، يمكنك أن تظهر لنا أنك تهتم بالتبرع.</string>
<string name="about_source_code"><![CDATA[أنظر إلى مصدر البرمجة في %1$s<br/>إنضم إلى قناتنا في %2$s ]]></string>
<string name="profile_capabilities">القدرات</string>
<string name="module_update">تحديث</string>
<string name="module_downloading">تحمبل الوحدة : %s</string>
<string name="module_start_downloading">ابدأ التنزيل: %s</string>
<string name="new_version_available">الإصدار الجديد: %s متاح ، انقر للتنزيل</string>
<string name="launch_app">تشغيل</string>
<string name="profile_default">الإفتراضي</string>
<string name="profile_template">نموذج</string>
<string name="profile_namespace_inherited">موروث</string>
<string name="profile_namespace_global">عالمي</string>
<string name="profile_namespace_individual">فردي</string>
<string name="profile_groups">مجموعات</string>
<string name="profile_custom">مُخصّص</string>
<string name="profile_namespace">تركيب مساحة الاسم</string>
<string name="profile_umount_modules">الغاء تحميل الوحدات</string>
<string name="failed_to_update_app_profile">فشل تحديث ملف تعريف التطبيق لـ %s</string>
<string name="profile_selinux_context">سياق SELinux</string>
<string name="force_stop_app">ايقاف إجباري</string>
<string name="settings_umount_modules_default">الغاء تحميل الوحدات بشكل افتراضي</string>
<string name="settings_umount_modules_default_summary">القيمة الافتراضية العامة ل \"إلغاء تحميل الوحدات \" في ملفات تعريف التطبيقات. إذا تم تمكينه ، إزالة جميع تعديلات الوحدة النمطية على النظام للتطبيقات التي لا تحتوي على مجموعة ملف تعريف.</string>
<string name="profile_umount_modules_summary">سيسمح تمكين هذا الخيار ل KernelSU باستعادة أي ملفات معدلة بواسطة الوحدات النمطية لهذا التطبيق.</string>
<string name="profile_selinux_domain">المجال</string>
<string name="profile_selinux_rules">القواعد</string>
<string name="restart_app">إعادة تشغيل التطبيق</string>
<string name="failed_to_update_sepolicy">فشل تحديث قواعد SELinux لما يلي: %s</string>
<string name="profile_name">اسم الملف الشخصي</string>
<string name="require_kernel_version">إصدار KernelSU الحالي %d منخفض جدًا بحيث لا يعمل المدير بشكل صحيح. الرجاء الترقية إلى الإصدار %d أو أعلى!</string>
</resources>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="home">Ana səhifə</string>
<string name="home_superuser_count">Super istifadəçilər: %d</string>
<string name="home_kernel">Nüvə</string>
<string name="home_not_installed">Yüklənmədi</string>
<string name="home_click_to_install">Yükləmək üçün toxunun</string>
<string name="home_working">İşləyir</string>
<string name="home_working_version">Versiya: %d</string>
<string name="home_module_count">Modullar: %d</string>
<string name="home_unsupported_reason">Hal-hazırda KernelSU yalnız GKI nüvələrini dəstəkləyir</string>
<string name="home_unsupported">Dəstəklənmir</string>
<string name="module_install">Yüklə</string>
<string name="install">Yüklə</string>
<string name="selinux_status_unknown">Naməlum</string>
<string name="home_fingerprint">Barmaq izi</string>
<string name="home_manager_version">Menecer versiyası</string>
<string name="selinux_status_disabled">Qeyri-aktiv</string>
<string name="home_selinux_status">SELinux vəziyyəti</string>
<string name="selinux_status_permissive">Sərbəst</string>
<string name="selinux_status_enforcing">Məcburi</string>
<string name="superuser">Super istifadəçi</string>
<string name="uninstall">Sil</string>
<string name="module_failed_to_enable">Modulu aktiv etmək mümkün olmadı: %s</string>
<string name="module_failed_to_disable">Modulu deaktiv etmək mümkün olmadı: %s</string>
<string name="module_empty">Heç bir modul quraşdırılmayıb</string>
<string name="module">Modul</string>
<string name="reboot">Yenidən başlat</string>
<string name="settings">Parametrlər</string>
<string name="reboot_recovery">Bərpa rejimində yenidən başlat</string>
<string name="reboot_userspace">Yüngül vəziyyətdə yenodən başlat</string>
<string name="reboot_bootloader">Bootloader rejimində yenidən başlat</string>
<string name="reboot_download">Yükləmə rejimində yenidən başlat</string>
<string name="module_version">Versiya</string>
<string name="module_author">Sahib</string>
<string name="module_uninstall_confirm">Modulu silmək istədiyinizdən əminsiniz %s\?</string>
<string name="show_system_apps">Sistem proqramlarını göstər</string>
<string name="about">Haqqında</string>
<string name="reboot_edl">EDL rejimində yenidən başlat</string>
<string name="module_uninstall_failed">Silmək mümkün olmadı: %s</string>
<string name="module_uninstall_success">%s silindi</string>
<string name="hide_system_apps">Sistem proqramlarını gizlət</string>
<string name="module_overlay_fs_not_available">overlayfs mövcud deyil,modul işləyə bilməyəcək!</string>
<string name="send_log">Log-u göndər</string>
<string name="refresh">Yenilə</string>
<string name="safe_mode">Təhlükəsiz rejimi</string>
<string name="reboot_to_apply">Qüvvəyə minməsi üçün yenidən başlat</string>
<string name="module_magisk_conflict">Modular deaktiv edilir,çünki o Magisk-in modulları ilə toqquşur!</string>
<string name="home_learn_kernelsu">KernelSU-yu öyrən</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_support_title">Bizi dəstəkləyin</string>
<string name="home_click_to_learn_kernelsu">KernelSU-yu necə quraşdırılacağını və modulların necə istifadə ediləcəyini öyrən</string>
<string name="profile_template">Şablon</string>
<string name="profile_default">Defolt</string>
<string name="profile_custom">Özəl</string>
<string name="home_support_content">KernelSU pulsuz və açıq mənbəlidir,həmişə belə olacaqdır. Bununla belə, ianə etməklə bizə qayğı göstərdiyinizi göstərə bilərsiniz.</string>
<string name="about_source_code">Mənbə kodlarımıza baxın %1$s<br/>Kanalımıza %2$s qoşulun</string>
<string name="profile_name">Profil adı</string>
<string name="profile_capabilities">Bacarıqlar</string>
<string name="profile_umount_modules">Modulları umount et</string>
<string name="profile_namespace_inherited">Miras qalmış</string>
<string name="profile_namespace_global">Qlobal</string>
<string name="profile_namespace">Bölmənin ad sahəsi</string>
<string name="profile_namespace_individual">Fərdi</string>
<string name="profile_groups">Qruplar</string>
<string name="settings_umount_modules_default">Defolt olaraq modulları umount et</string>
<string name="profile_selinux_context">SELinux konteksi</string>
<string name="failed_to_update_app_profile">%s görə tətbiq profillərini güncəlləmək mümkün olmadı</string>
<string name="settings_umount_modules_default_summary">Tətbiq Profillərində \"Umount modulları\" üçün qlobal standart dəyər. Aktivləşdirilərsə, o, Profil dəsti olmayan proqramlar üçün sistemdəki bütün modul dəyişikliklərini siləcək.</string>
<string name="profile_selinux_domain">Domen</string>
<string name="profile_selinux_rules">Qaydalar</string>
<string name="module_update">Güncəllə</string>
<string name="module_start_downloading">Endirməni başlat: %s</string>
<string name="new_version_available">Yeni versiya: %s əlçatandır, endirmək üçün toxunun</string>
<string name="module_downloading">Modul yüklənir: %s</string>
<string name="profile_umount_modules_summary">Bu seçimi aktivləşdirmək KernelSU-ya bu proqram üçün modullar tərəfindən hər hansı dəyişdirilmiş faylları bərpa etməyə imkan verəcək.</string>
<string name="launch_app"></string>
<string name="force_stop_app">Məcburi dayandır</string>
<string name="restart_app">Yenidən başlat</string>
<string name="failed_to_update_sepolicy">%s görə SELinux qaydalarını güncəlləmək mümkün olmadı</string>
</resources>

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