1
mirror of https://github.com/topjohnwu/Magisk synced 2025-10-29 07:20:52 +01:00

Compare commits

..

123 Commits
v30.0 ... v30.3

Author SHA1 Message Date
topjohnwu
f16e93c7db Release Magisk v30.3
[skip ci]
2025-09-29 21:35:45 -07:00
Thonsi
1b0ddec66e Remove unused code 2025-09-29 02:33:02 -07:00
topjohnwu
cd8820f563 Refactor code for more readability 2025-09-29 01:41:55 -07:00
topjohnwu
b70192ca3e Sync libsepol with upstream AOSP 2025-09-29 01:18:52 -07:00
LoveSy
d42ec5da9a Fix pattern matching for CANARY version 2025-09-29 01:18:52 -07:00
topjohnwu
742913ebcb Support installing Magisk on vendor_boot
Close #9238, fix #8835
2025-09-28 01:10:11 -07:00
topjohnwu
ed206c6480 Upgrade cargo dependencies 2025-09-26 23:37:45 -07:00
topjohnwu
f9a8052583 Improve build.py
Close #8988
2025-09-26 17:00:58 -07:00
topjohnwu
f4fdd516f9 Upgrade gradle dependencies 2025-09-24 03:18:35 -07:00
topjohnwu
5925a71f94 Upgrade cargo dependencies 2025-09-24 03:05:18 -07:00
topjohnwu
3cda9beb93 Cleanup unused bindings 2025-09-24 02:38:18 -07:00
topjohnwu
8b7d1ffcdd Migrate magisk_main to Rust 2025-09-18 03:22:44 -07:00
topjohnwu
8d02d0632e Fix comments 2025-09-18 03:22:44 -07:00
topjohnwu
dd743f6f7e Improve Encodable/Decodable impls 2025-09-18 01:17:28 -07:00
topjohnwu
cf483ad4d2 Migrate connect_daemon to Rust 2025-09-15 14:25:18 -07:00
topjohnwu
4aed644e08 Directly accept RequestCode for connect_daemon 2025-09-15 14:25:18 -07:00
topjohnwu
0acc39cec0 Use bitflags to implement BootState 2025-09-15 14:25:18 -07:00
topjohnwu
8b3a44344f Move bootstages into its own module 2025-09-15 14:25:18 -07:00
topjohnwu
8b49eda85a Migrate daemon_entry to Rust 2025-09-15 14:25:18 -07:00
topjohnwu
7057d4c7f1 Migrate setup_magisk_env to Rust 2025-09-15 14:25:18 -07:00
Radoš Milićev
aab8344058 Update Serbian 2025-09-14 18:42:22 -07:00
topjohnwu
7cccf83b37 Remove unused poll_ctrl implementation 2025-09-14 01:59:04 -07:00
topjohnwu
f10ad93c4e Move more code of daemon_entry into Rust 2025-09-13 01:21:33 -07:00
topjohnwu
f143b5df15 Do not mount directories as mirror
Mounting real directories into worker will cause init to start tracking
the mount point through dev.mnt. This causes issues, so we are forced
to recursively reconstruct the mirror directory structure from scratch.

Fix #9316
2025-09-12 22:01:08 -07:00
topjohnwu
71213cc6f4 Fix path tracking in module.rs 2025-09-12 22:01:08 -07:00
topjohnwu
e2a1774e5b Make logging.rs use nix 2025-09-11 01:17:34 -07:00
topjohnwu
0222527a1e Use bitflags macro 2025-09-11 01:17:34 -07:00
topjohnwu
312bfe1bab Do not leak base::ffi to external crates 2025-09-11 01:17:34 -07:00
topjohnwu
48c62a1dae Disable exit on error for cmdline_logging 2025-09-11 01:17:34 -07:00
rikka
cfc2bcb665 Fix zygisk native bridge library name concatenation order 2025-09-11 01:16:54 -07:00
topjohnwu
94b1ff674f Allow calling remove_all on non-existence file 2025-09-10 03:44:39 -07:00
topjohnwu
111136733a Migrate away from unsafe set_len of Utf8CStr 2025-09-09 22:19:05 -07:00
topjohnwu
c8caaa98f5 Enable mount for nix 2025-09-09 20:17:09 -07:00
topjohnwu
8d28f10a3f Enable zerocopy for nix 2025-09-09 12:04:46 -07:00
topjohnwu
177a456d8b Enable term for nix 2025-09-09 12:04:31 -07:00
topjohnwu
ef4e230258 Use nix for libc functions 2025-09-08 23:59:29 -07:00
topjohnwu
17082af438 Simplify OsError 2025-09-08 11:25:20 -07:00
topjohnwu
1df5b34175 Stop differentiate Error vs ErrorCxx 2025-09-08 11:25:18 -07:00
topjohnwu
ea5fe7525d Simplify LibcReturn 2025-09-08 10:55:57 -07:00
topjohnwu
a75c335261 Update cargo dependencies 2025-09-08 02:24:01 -07:00
topjohnwu
3903f42cf6 Support specify ABI for clippy 2025-09-08 02:23:49 -07:00
topjohnwu
fb0c4ea838 Fallback to userspace copy if splice failed
Fix #9032
2025-09-03 16:10:18 -07:00
topjohnwu
bc89c60977 Run cargo fmt 2025-09-02 22:06:08 -07:00
topjohnwu
bd657c354c Reduce FFI across C++/Rust 2025-09-02 22:06:08 -07:00
MONA
675b5f9565 feat(i18n): Add Hinglish translation 2025-09-02 01:27:58 -07:00
MONA
1b2c43268e feat(i18n): Add Hinglish translation 2025-09-02 01:27:58 -07:00
topjohnwu
653730d75e Make cxx binding generate less code 2025-08-29 01:44:06 -07:00
topjohnwu
d472e9c36e Update cargo dependencies 2025-08-28 22:01:35 -07:00
topjohnwu
484d53ef7e Update to ONDK r29.2 2025-08-28 16:15:59 -07:00
topjohnwu
c4e2985677 Migrate resetprop to Rust 2025-08-27 22:48:48 -07:00
topjohnwu
42d9f87bc9 Cleanup resetprop code 2025-08-27 22:48:48 -07:00
topjohnwu
2e4fa6864c Make Utf8CStr a first class citizen in C++ codebase
Utf8CStr is in many cases a better string view class than
std::string_view, because it provides "view" access to a string buffer
that is guaranteed to be null terminated. It also has the additional
benefit of being UTF-8 verified and can seemlessly cross FFI boundaries.

We would want to start use more Utf8CStr in our existing C++ codebase.
2025-08-27 22:48:48 -07:00
topjohnwu
e2abb648ac Update system_properties 2025-08-27 10:12:51 -07:00
topjohnwu
3599dcedfb Make argh directly parse into Utf8CString 2025-08-27 01:26:41 -07:00
topjohnwu
ea72666df8 Only specify ADB port for tests 2025-08-25 15:34:04 -07:00
topjohnwu
bd2a47ba18 Merge libbase cpp files 2025-08-25 01:31:47 -07:00
topjohnwu
b861671391 Cleanup libbase 2025-08-25 01:31:47 -07:00
topjohnwu
e91fc75d86 Consolidate for_each implementation into Rust 2025-08-25 01:31:47 -07:00
LoveSy
78f5cd55c7 Use lzma-rust2 for xz and lzma compression and decompression 2025-08-24 00:23:55 -07:00
topjohnwu
9787a69528 Make all decoders Read instead of Write
Most libraries only implement Read for decoders
2025-08-24 00:23:55 -07:00
topjohnwu
87b8fe374d Fix magiskboot cli parsing 2025-08-23 20:31:15 -07:00
topjohnwu
7b706bb0cb Cleanup and fix compress/decompress command 2025-08-23 20:31:15 -07:00
topjohnwu
c1491b8d2b Fix LoggedResult implementation error 2025-08-23 15:25:52 -07:00
LoveSy
5cbaf2ae11 Use super let to simplify code 2025-08-22 12:05:44 -07:00
topjohnwu
8ebc6207b4 Merge headers 2025-08-22 12:03:47 -07:00
topjohnwu
7848ee616b Cleanup magiskboot main function 2025-08-22 12:03:47 -07:00
topjohnwu
fd193c3cae Simplify ResultExt implementation
Also introduce OptionExt
2025-08-22 12:03:47 -07:00
topjohnwu
36d33c7a85 Make log_err directly return LoggedResult 2025-08-22 12:03:47 -07:00
topjohnwu
5caf28d27c Hide harmless error reporting 2025-08-22 12:03:47 -07:00
topjohnwu
2c39d0234d Fix compression format detection 2025-08-21 12:21:22 -07:00
topjohnwu
c313812129 Simplify magiskboot FFI 2025-08-21 12:21:22 -07:00
topjohnwu
af51880a81 Introduce CmdArgs for argument parsing in Rust 2025-08-21 12:21:22 -07:00
LoveSy
db8d832707 Move magiskboot cli to argh 2025-08-20 21:40:34 -07:00
Wang Han
8dc23d0ead Avoid triggering magisk --zygote-restart twice
We have already used on restart keyword to inject zygote restart, so
triggering it here on prop is not needed.
2025-08-20 12:34:39 -07:00
topjohnwu
b4287700d5 Increase timeout to 15 minutes 2025-08-20 11:23:18 -07:00
topjohnwu
8d10ab89f2 Set zygisk properties in Rust 2025-08-20 11:23:18 -07:00
topjohnwu
49fdc1addb Prevent setting zygisk prop twice 2025-08-20 11:23:18 -07:00
topjohnwu
1333d3b986 Fix canary emulator 2025-08-18 11:25:47 -07:00
残页
335146a6a2 Update supported API levels 2025-08-17 23:58:43 -07:00
topjohnwu
eaf9527971 Use AOSP ATD for API 36
[skip ci]
2025-08-15 17:25:41 -07:00
LoveSy
da937a88c8 if !restore { set_zygisk_prop(); } 2025-08-15 16:45:01 -07:00
topjohnwu
9476e7282d More borrowing, less copying 2025-08-08 21:06:41 -07:00
topjohnwu
251c3c3e0e Remove old ffi data structure 2025-08-08 21:06:41 -07:00
topjohnwu
cd0eca20b0 Migrate connect.cpp to Rust 2025-08-08 21:06:41 -07:00
topjohnwu
6839cb9ab2 Keep /system/xbin/su on emulators 2025-08-08 21:06:41 -07:00
topjohnwu
d11a3397d8 Reduce verbose logging in Zygisk 2025-08-08 21:06:41 -07:00
topjohnwu
975120d6a6 Release Magisk v30.2
[skip ci]
2025-08-06 03:32:32 -07:00
topjohnwu
e489b3b6dd Migrate load_modules to Rust 2025-08-05 11:24:55 -07:00
topjohnwu
589a270b8d Migrate disable/remove modules to Rust 2025-08-05 11:24:55 -07:00
topjohnwu
7961be5cfa Migrate prepare_modules to Rust 2025-08-05 11:24:55 -07:00
topjohnwu
959430e030 Fix systemless hosts installation 2025-08-05 09:44:51 -07:00
topjohnwu
2923c8ccd1 Add module upgrade test 2025-08-05 09:44:51 -07:00
topjohnwu
7df4a9d74f Add uninstaller.sh test 2025-08-05 09:44:51 -07:00
topjohnwu
bf4ed295da Update cargo dependencies 2025-08-02 13:43:27 -07:00
topjohnwu
a5fca960dc Update gradle and dependencies 2025-08-02 02:29:14 -07:00
topjohnwu
f99912b9db Update libsystem_properties 2025-07-21 13:47:30 -07:00
5ec1cff
a54bdb54e4 Skip avb 1,0 verify if tail contains avb 2.0 header
This way, magiskboot will not print "unexpected ASN.1 DER tag: expected SEQUENCE, got APPLICATION [1] (primitive)".
2025-07-21 00:51:14 -07:00
topjohnwu
cd9851a1fe Add regression test for #9179 2025-07-18 17:58:29 -07:00
Wang Han
9ca469898c Use worker for replace feature
This fixes https://github.com/topjohnwu/Magisk/issues/9179.
2025-07-18 16:57:20 -07:00
𝗛𝗼𝗹𝗶
0665549473 Update Turkish
Missing parts were filled in and made better
2025-07-14 10:46:05 -07:00
topjohnwu
9d7a14b335 Remove unnecessary return 2025-07-14 10:03:22 -07:00
Wang Han
62e29fee74 Treat bind mount failure same as C++ implementation
This fixes #9139 and #9174.
2025-07-14 00:27:24 -07:00
igor
e472db552b Update portuguese/english translations 2025-07-11 10:52:21 -07:00
topjohnwu
466e4bd4e1 Update cargo dependencies 2025-07-11 02:04:33 -07:00
topjohnwu
4cf525c588 Add Android canary builds into CI tests 2025-07-11 00:16:39 -07:00
topjohnwu
c8aec2510d Restrict sccache cache size 2025-07-11 00:16:39 -07:00
topjohnwu
ccbfe0e66e Update gradle dependencies 2025-07-10 15:55:14 -07:00
南宫雪珊
23ea28de6f scripts: fix modules_update dir context 2025-07-10 10:59:39 -07:00
topjohnwu
55c3ee3a6f Move Zygisk code out of module.cpp 2025-07-07 13:43:11 -07:00
vvb2060
2a42ca2b8f app: fix time i18n 2025-07-07 11:04:18 -07:00
topjohnwu
a897e82fa4 Remove release notes
They are embedded into GitHub releases
2025-07-07 10:37:45 -07:00
topjohnwu
ffa15831d3 Add release dates 2025-07-07 10:35:22 -07:00
topjohnwu
a344ebf28c Add v30.1 changelog 2025-07-03 18:02:27 -07:00
topjohnwu
78f7fa348e Release Magisk v30.1
[skip ci]
2025-07-03 03:10:10 -07:00
pndwal
d8c448b99d Update faq.md
Add information on restoring Magisk App functionality when stub and full apps conflict.
2025-07-03 02:51:01 -07:00
topjohnwu
d4b83b6a44 Fix app compilation 2025-07-03 02:42:08 -07:00
vvb2060
e5d36d1d24 app: support config restrict policy 2025-07-03 02:42:08 -07:00
vvb2060
ff18cb8e70 su: support drop capabilities 2025-07-03 02:42:08 -07:00
topjohnwu
37a9724a54 Apply clippy fix 2025-07-02 21:20:14 -07:00
topjohnwu
d660401063 Treat magisk symlinks differently 2025-07-02 21:20:14 -07:00
topjohnwu
88541d6f49 Fix file attribute copy in module mounting logic
Due to various reasons, we cannot directly mount module files in /data
into the real paths. Instead we bind mount the module root directory and
remount this mirror with specific mount-point flags. Relevant to this
bug, the module mount is mounted as read-only, which means the file
attribute copy operation could fail in certain configurations.

The fix here is to always copy file attributes into writable locations,
so either in the tmpfs worker directory, or in the module directory
under /data.

A new test case is added to make sure this regression will no longer
happen again in the future.

Fix #9139
2025-07-02 19:23:46 -07:00
topjohnwu
ecd6129fe5 Add systemless hosts test 2025-07-02 19:23:46 -07:00
topjohnwu
6dfe9df9e2 Run cargo fmt 2025-07-02 19:23:46 -07:00
205 changed files with 7701 additions and 8165 deletions

View File

@@ -45,7 +45,7 @@ runs:
env:
SCCACHE_DIRECT: false
SCCACHE_DIR: ${{ github.workspace }}/.sccache
SCCACHE_CACHE_SIZE: 2G
SCCACHE_CACHE_SIZE: ${{ inputs.is-asset-build == 'true' && '2G' || '300M' }}
SCCACHE_IDLE_TIMEOUT: 0
run: |
bash $GITHUB_ACTION_PATH/sccache.sh

View File

@@ -82,12 +82,10 @@ jobs:
strategy:
fail-fast: false
matrix:
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, "CANARY"]
type: [""]
include:
- version: 36
type: "google_apis"
- version: 36
- version: "CANARY"
type: "google_apis_ps16k"
steps:
@@ -107,7 +105,7 @@ jobs:
sudo udevadm trigger --name-match=kvm
- name: Run AVD test
timeout-minutes: 10
timeout-minutes: 15
env:
AVD_TEST_LOG: 1
run: scripts/avd.sh test ${{ matrix.version }} ${{ matrix.type }}
@@ -148,7 +146,7 @@ jobs:
sudo udevadm trigger --name-match=kvm
- name: Run AVD test
timeout-minutes: 10
timeout-minutes: 15
env:
FORCE_32_BIT: 1
AVD_TEST_LOG: 1
@@ -193,7 +191,7 @@ jobs:
scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}
- name: Run Cuttlefish test
timeout-minutes: 10
timeout-minutes: 15
run: sudo -E -u $USER scripts/cuttlefish.sh test
- name: Upload logs on error

3
.gitmodules vendored
View File

@@ -4,9 +4,6 @@
[submodule "lz4"]
path = native/src/external/lz4
url = https://github.com/lz4/lz4.git
[submodule "xz"]
path = native/src/external/xz
url = https://github.com/xz-mirror/xz.git
[submodule "libcxx"]
path = native/src/external/libcxx
url = https://github.com/topjohnwu/libcxx.git

View File

@@ -24,6 +24,7 @@ import androidx.core.widget.ImageViewCompat
import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter
import androidx.databinding.InverseBindingListener
import androidx.databinding.InverseMethod
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
@@ -33,9 +34,11 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.slider.Slider
import com.google.android.material.textfield.TextInputLayout
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.widget.IndeterminateCheckBox
@@ -306,3 +309,38 @@ fun TextView.setText(text: TextHolder) {
fun Spinner.setAdapter(items: Array<Any>, layoutRes: Int) {
adapter = ArrayAdapter(context, layoutRes, items)
}
@BindingAdapter("labelFormatter")
fun Slider.setLabelFormatter(formatter: (Float) -> Int) {
setLabelFormatter { value -> resources.getString(formatter(value)) }
}
@InverseBindingAdapter(attribute = "android:value")
fun Slider.getValueBinding() = value
@BindingAdapter("android:valueAttrChanged")
fun Slider.setListener(attrChange: InverseBindingListener) {
addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
override fun onStartTrackingTouch(slider: Slider) = Unit
override fun onStopTrackingTouch(slider: Slider) = attrChange.onChange()
})
}
@InverseMethod("sliderValueToPolicy")
fun policyToSliderValue(policy: Int): Float {
return when (policy) {
SuPolicy.DENY -> 1f
SuPolicy.RESTRICT -> 2f
SuPolicy.ALLOW -> 3f
else -> 1f
}
}
fun sliderValueToPolicy(value: Float): Int {
return when (value) {
1f -> SuPolicy.DENY
2f -> SuPolicy.RESTRICT
3f -> SuPolicy.ALLOW
else -> SuPolicy.DENY
}
}

View File

@@ -322,6 +322,12 @@ object Reauthenticate : BaseSettingsItem.Toggle() {
override var value by Config::suReAuth
override fun refresh() {
isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O && Info.showSuperUser
isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O
}
}
object Restrict : BaseSettingsItem.Toggle() {
override val title = CoreR.string.settings_su_restrict_title.asText()
override val description = CoreR.string.settings_su_restrict_summary.asText()
override var value by Config::suRestrict
}

View File

@@ -22,11 +22,11 @@ import com.topjohnwu.magisk.core.ktx.activity
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.tasks.AppMigration
import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.events.AddHomeIconEvent
import com.topjohnwu.magisk.events.AuthEvent
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
@@ -83,6 +83,9 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
// Can hide overlay windows on 12.0+
list.remove(Tapjack)
}
if (Const.Version.atLeast_30_1()) {
list.add(Restrict)
}
}
return list
@@ -127,7 +130,8 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
}
private fun createHosts() {
Shell.cmd("add_hosts_module").submit {
viewModelScope.launch {
RootUtils.addSystemlessHosts()
AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
}
}

View File

@@ -4,11 +4,13 @@ import android.graphics.drawable.Drawable
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.core.R as CoreR
class PolicyRvItem(
private val viewModel: SuperuserViewModel,
@@ -33,14 +35,34 @@ class PolicyRvItem(
var isExpanded = false
set(value) = set(value, field, { field = it }, BR.expanded)
val showSlider = Config.suRestrict || item.policy == SuPolicy.RESTRICT
@get:Bindable
var isEnabled
get() = item.policy == SuPolicy.ALLOW
get() = item.policy >= SuPolicy.ALLOW
set(value) = setImpl(value, isEnabled) {
notifyPropertyChanged(BR.enabled)
viewModel.togglePolicy(this, value)
viewModel.updatePolicy(this, if (it) SuPolicy.ALLOW else SuPolicy.DENY)
}
@get:Bindable
var sliderValue
get() = item.policy
set(value) = setImpl(value, sliderValue) {
notifyPropertyChanged(BR.sliderValue)
notifyPropertyChanged(BR.enabled)
viewModel.updatePolicy(this, it)
}
val sliderValueToPolicyString: (Float) -> Int = { value ->
when (value.toInt()) {
1 -> CoreR.string.deny
2 -> CoreR.string.restrict
3 -> CoreR.string.grant
else -> CoreR.string.deny
}
}
@get:Bindable
var shouldNotify
get() = item.notification

View File

@@ -156,15 +156,16 @@ class SuperuserViewModel(
}
}
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
fun updatePolicy(item: PolicyRvItem, policy: Int) {
val items = itemsPolicies.filter { it.item.uid == item.item.uid }
fun updateState() {
viewModelScope.launch {
val res = if (enable) R.string.su_snack_grant else R.string.su_snack_deny
item.item.policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
val res = if (policy >= SuPolicy.ALLOW) R.string.su_snack_grant else R.string.su_snack_deny
item.item.policy = policy
db.update(item.item)
items.forEach {
it.notifyPropertyChanged(BR.enabled)
it.notifyPropertyChanged(BR.sliderValue)
}
SnackbarEvent(res.asText(item.appName)).publish()
}

View File

@@ -43,10 +43,6 @@ enum class Theme(
val isSelected get() = Config.themeOrdinal == ordinal
fun select() {
Config.themeOrdinal = ordinal
}
companion object {
val selected get() = values().getOrNull(Config.themeOrdinal) ?: Piplup
}

View File

@@ -25,7 +25,7 @@
<include
android:id="@+id/log_track_container"
bullet="@{item.log.action == 2 ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}"
bullet="@{item.log.action >= 2 ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}"
isBottom="@{item.isBottom}"
isSelected="@{item.log.action != 2}"
isTop="@{item.isTop}"

View File

@@ -5,6 +5,8 @@
<data>
<import type="com.topjohnwu.magisk.databinding.DataBindingAdaptersKt" />
<variable
name="item"
type="com.topjohnwu.magisk.ui.superuser.PolicyRvItem" />
@@ -85,16 +87,32 @@
app:layout_constraintVertical_bias="0"
tools:text="com.topjohnwu.magisk" />
<com.google.android.material.switchmaterial.SwitchMaterial
<FrameLayout
android:id="@+id/policy_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/l1"
android:checked="@={item.enabled}"
android:nextFocusLeft="@id/policy"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.switchmaterial.SwitchMaterial
gone="@{item.showSlider}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={item.enabled}" />
<com.google.android.material.slider.Slider
goneUnless="@{item.showSlider}"
labelFormatter="@{item.sliderValueToPolicyString}"
android:layout_width="96dp"
android:layout_height="wrap_content"
android:stepSize="1"
android:value="@={DataBindingAdaptersKt.policyToSliderValue(item.sliderValue)}"
android:valueFrom="1"
android:valueTo="3" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -18,12 +18,6 @@ gradlePlugin {
}
}
kotlin {
compilerOptions {
languageVersion = KotlinVersion.KOTLIN_2_0
}
}
dependencies {
implementation(kotlin("gradle-plugin", libs.versions.kotlin.get()))
implementation(libs.android.gradle.plugin)

View File

@@ -55,7 +55,7 @@ fun Project.setupCommon() {
compileSdkVersion(36)
buildToolsVersion = "36.0.0"
ndkPath = "$sdkDirectory/ndk/magisk"
ndkVersion = "28.1.13356709"
ndkVersion = "29.0.13846066"
defaultConfig {
minSdk = 23

View File

@@ -309,9 +309,9 @@ fun Project.setupStubApk() {
outputs.dir(outResDir)
doLast {
val apkTmp = File("${apk}.tmp")
exec {
providers.exec {
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
}
}.result.get()
val bos = ByteArrayOutputStream()
ZipFile(apkTmp).use { src ->

View File

@@ -27,10 +27,15 @@ android {
aidl = true
buildConfig = true
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
api(project(":shared"))
coreLibraryDesugaring(libs.jdk.libs)
api(libs.timber)
api(libs.markwon.core)

View File

@@ -38,3 +38,4 @@
-allowaccessmodification
-dontwarn org.junit.**
-dontwarn org.apache.**

View File

@@ -6,4 +6,5 @@ package com.topjohnwu.magisk.core.utils;
interface IRootUtils {
android.app.ActivityManager.RunningAppProcessInfo getAppProcess(int pid);
IBinder getFileSystem();
boolean addSystemlessHosts();
}

View File

@@ -32,6 +32,7 @@ object Config : PreferenceConfig, DBConfig {
const val SU_NOTIFICATION = "su_notification"
const val SU_REAUTH = "su_reauth"
const val SU_TAPJACK = "su_tapjack"
const val SU_RESTRICT = "su_restrict"
const val CHECK_UPDATES = "check_update"
const val RELEASE_CHANNEL = "release_channel"
const val CUSTOM_CHANNEL = "custom_channel"
@@ -147,6 +148,7 @@ object Config : PreferenceConfig, DBConfig {
}
var suReAuth by preference(Key.SU_REAUTH, false)
var suTapjack by preference(Key.SU_TAPJACK, true)
var suRestrict by preference(Key.SU_RESTRICT, false)
private const val SU_FINGERPRINT = "su_fingerprint"
private const val UPDATE_CHANNEL = "update_channel"

View File

@@ -26,9 +26,11 @@ object Const {
const val MIN_VERSION = "v22.0"
const val MIN_VERCODE = 22000
fun atLeast_24_0() = Info.env.versionCode >= 24000
fun atLeast_25_0() = Info.env.versionCode >= 25000
fun atLeast_28_0() = Info.env.versionCode >= 28000
private fun isCanary() = (Info.env.versionCode % 100) != 0
fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary()
fun atLeast_25_0() = Info.env.versionCode >= 25000 || isCanary()
fun atLeast_28_0() = Info.env.versionCode >= 28000 || isCanary()
fun atLeast_30_1() = Info.env.versionCode >= 30100 || isCanary()
}
object ID {

View File

@@ -47,6 +47,8 @@ object Info {
private set
var slot = ""
private set
var isVendorBoot = false
private set
@JvmField val isZygiskEnabled = System.getenv("ZYGISK_ENABLED") == "1"
@JvmStatic val isFDE get() = crypto == "block"
@JvmStatic var ramdisk = false
@@ -113,6 +115,7 @@ object Info {
crypto = getVar("CRYPTOTYPE")
slot = getVar("SLOT")
legacySAR = getBool("LEGACYSAR")
isVendorBoot = getBool("VENDORBOOT")
// Default presets
Config.recovery = getBool("RECOVERYMODE")

View File

@@ -109,7 +109,7 @@ fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {
return null
}
// Try to find package name from PID
val proc = RootUtils.obj?.getAppProcess(pid)
val proc = RootUtils.getAppProcess(pid)
if (proc == null) {
if (uid == Process.SHELL_UID) {
// It is possible that some apps installed are sharing UID with shell.

View File

@@ -14,10 +14,11 @@ import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Collections
import java.util.Locale
inline fun <In : Closeable, Out : Closeable> withInOut(
input: In,
@@ -83,19 +84,15 @@ inline fun <T, R> Flow<T>.concurrentMap(crossinline transform: suspend (T) -> R)
}
}
fun Long.toTime(format: DateFormat) = format.format(this).orEmpty()
fun Long.toTime(format: DateTimeFormatter): String = format.format(Instant.ofEpochMilli(this))
// Some devices don't allow filenames containing ":"
val timeFormatStandard by lazy {
SimpleDateFormat(
"yyyy-MM-dd'T'HH.mm.ss",
Locale.ROOT
)
val timeFormatStandard: DateTimeFormatter by lazy {
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH.mm.ss").withZone(ZoneId.systemDefault())
}
val timeDateFormat: DateFormat by lazy {
DateFormat.getDateTimeInstance(
DateFormat.DEFAULT,
DateFormat.DEFAULT,
Locale.ROOT
)
val timeDateFormat: DateTimeFormatter by lazy {
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withZone(ZoneId.systemDefault())
}
val dateFormat: DateTimeFormatter by lazy {
DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault())
}

View File

@@ -7,8 +7,7 @@ import com.squareup.moshi.JsonClass
import com.squareup.moshi.JsonQualifier
import com.squareup.moshi.ToJson
import kotlinx.parcelize.Parcelize
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME
import java.time.Instant
@JsonClass(generateAdapter = true)
class UpdateJson(
@@ -40,13 +39,13 @@ data class ReleaseAssets(
class DateTimeAdapter {
@ToJson
fun toJson(date: LocalDateTime): String {
fun toJson(date: Instant): String {
return date.toString()
}
@FromJson
fun fromJson(date: String): LocalDateTime {
return LocalDateTime.parse(date, ISO_OFFSET_DATE_TIME)
fun fromJson(date: String): Instant {
return Instant.parse(date)
}
}
@@ -57,7 +56,7 @@ data class Release(
val prerelease: Boolean,
val assets: List<ReleaseAssets>,
val body: String,
@Json(name = "created_at") val createdTime: LocalDateTime,
@Json(name = "created_at") val createdTime: Instant,
) {
val versionCode: Int get() {
return if (tag[0] == 'v') {

View File

@@ -13,7 +13,7 @@ import java.io.IOException
import java.util.Locale
data class LocalModule(
private val base: ExtendedFile,
val base: ExtendedFile,
) : Module() {
private val svc get() = ServiceLocator.networkService

View File

@@ -4,15 +4,16 @@ import com.topjohnwu.magisk.core.data.magiskdb.MagiskDB
class SuPolicy(
val uid: Int,
var policy: Int = INTERACTIVE,
var policy: Int = QUERY,
var remain: Long = -1L,
var logging: Boolean = true,
var notification: Boolean = true,
) {
companion object {
const val INTERACTIVE = 0
const val QUERY = 0
const val DENY = 1
const val ALLOW = 2
const val RESTRICT = 3
}
fun toMap(): MutableMap<String, Any> {

View File

@@ -10,13 +10,13 @@ import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.data.GithubApiServices
import com.topjohnwu.magisk.core.data.RawUrl
import com.topjohnwu.magisk.core.ktx.dateFormat
import com.topjohnwu.magisk.core.model.Release
import com.topjohnwu.magisk.core.model.ReleaseAssets
import com.topjohnwu.magisk.core.model.UpdateInfo
import retrofit2.HttpException
import timber.log.Timber
import java.io.IOException
import java.time.format.DateTimeFormatter
class NetworkService(
private val raw: RawUrl,
@@ -74,7 +74,7 @@ class NetworkService(
private inline fun Release.asPublicInfo(selector: (ReleaseAssets) -> Boolean): UpdateInfo {
val version = tag.drop(1)
val date = createdTime.format(DateTimeFormatter.ofPattern("yyyy.M.d"))
val date = dateFormat.format(createdTime)
return UpdateInfo(
version = version,
versionCode = versionCode,

View File

@@ -70,7 +70,7 @@ object SuCallbackHandler {
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
if (notify)
notify(context, log.action == SuPolicy.ALLOW, log.appName)
notify(context, log.action >= SuPolicy.ALLOW, log.appName)
runBlocking { ServiceLocator.logRepo.insert(log) }
}
@@ -86,7 +86,7 @@ object SuCallbackHandler {
pm.getPackageInfo(uid, pid)?.applicationInfo?.getLabel(pm)
}.getOrNull() ?: "[UID] $uid"
notify(context, policy == SuPolicy.ALLOW, appName)
notify(context, policy >= SuPolicy.ALLOW, appName)
}
private fun notify(context: Context, granted: Boolean, appName: String) {

View File

@@ -82,7 +82,11 @@ class SuRequestHandler(
}
suspend fun respond(action: Int, time: Long) {
policy.policy = action
if (action == SuPolicy.ALLOW && Config.suRestrict) {
policy.policy = SuPolicy.RESTRICT
} else {
policy.policy = action
}
if (time >= 0) {
policy.remain = TimeUnit.MINUTES.toSeconds(time)
} else {

View File

@@ -81,10 +81,12 @@ abstract class MagiskInstallImpl protected constructor(
}
private fun findImage(slot: String): Boolean {
val bootPath = (
"(RECOVERYMODE=${Config.recovery} " +
"SLOT=$slot find_boot_image; " +
"echo \$BOOTIMAGE)").fsh()
val cmd =
"RECOVERYMODE=${Config.recovery} " +
"VENDORBOOT=${Info.isVendorBoot} " +
"SLOT=$slot " +
"find_boot_image; echo \$BOOTIMAGE"
val bootPath = ("($cmd)").fsh()
if (bootPath.isEmpty()) {
console.add("! Unable to detect target image")
return false

View File

@@ -7,11 +7,14 @@ import android.content.ServiceConnection
import android.os.IBinder
import android.system.Os
import androidx.core.content.getSystemService
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.ipc.RootService
import com.topjohnwu.superuser.nio.FileSystemManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
import java.util.concurrent.locks.AbstractQueuedSynchronizer
@@ -43,16 +46,7 @@ class RootUtils(stub: Any?) : RootService() {
return object : IRootUtils.Stub() {
override fun getAppProcess(pid: Int) = safe(null) { getAppProcessImpl(pid) }
override fun getFileSystem(): IBinder = FileSystemManager.getService()
}
}
private inline fun <T> safe(default: T, block: () -> T): T {
return try {
block()
} catch (e: Throwable) {
// The process died unexpectedly
Timber.e(e)
default
override fun addSystemlessHosts() = safe(false) { addSystemlessHostsImpl() }
}
}
@@ -78,6 +72,26 @@ class RootUtils(stub: Any?) : RootService() {
return null
}
private fun addSystemlessHostsImpl(): Boolean {
val module = File(Const.MODULE_PATH, "hosts")
if (module.exists()) return true
val hosts = File(module, "system/etc/hosts")
if (!hosts.parentFile.mkdirs()) return false
File(module, "module.prop").outputStream().writer().use {
it.write("""
id=hosts
name=Systemless Hosts
version=1.0
versionCode=1
author=Magisk
description=Magisk app built-in systemless hosts module
""".trimIndent())
}
File("/system/etc/hosts").copyTo(hosts)
File(module, "update").createNewFile()
return true
}
object Connection : AbstractQueuedSynchronizer(), ServiceConnection {
init {
state = 1
@@ -131,11 +145,25 @@ class RootUtils(stub: Any?) : RootService() {
return field
}
private set
var obj: IRootUtils? = null
private var obj: IRootUtils? = null
get() {
Connection.await()
return field
}
private set
fun getAppProcess(pid: Int) = safe(null) { obj?.getAppProcess(pid) }
suspend fun addSystemlessHosts() =
withContext(Dispatchers.IO) { safe(false) { obj?.addSystemlessHosts() ?: false } }
private inline fun <T> safe(default: T, block: () -> T): T {
return try {
block()
} catch (e: Throwable) {
// The process died unexpectedly
Timber.e(e)
default
}
}
}
}

View File

@@ -12,9 +12,11 @@ import com.topjohnwu.magisk.test.Environment.Companion.INVALID_ZYGISK
import com.topjohnwu.magisk.test.Environment.Companion.MOUNT_TEST
import com.topjohnwu.magisk.test.Environment.Companion.REMOVE_TEST
import com.topjohnwu.magisk.test.Environment.Companion.SEPOLICY_RULE
import com.topjohnwu.magisk.test.Environment.Companion.UPGRADE_TEST
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
@@ -55,7 +57,7 @@ class AdditionalTest : BaseTest {
@Test
fun testModuleCount() {
var expected = 2
var expected = 4
if (Environment.mount()) expected++
if (Environment.preinit()) expected++
if (Environment.lsposed()) expected++
@@ -90,17 +92,18 @@ class AdditionalTest : BaseTest {
assertNotNull("$MOUNT_TEST is not installed", modules.find { it.id == MOUNT_TEST })
assertTrue(
"/system/etc/newfile should exist",
RootUtils.fs.getFile("/system/etc/newfile").exists()
"/system/fonts/newfile should exist",
RootUtils.fs.getFile("/system/fonts/newfile").exists()
)
assertFalse(
"/system/bin/screenrecord should not exist",
RootUtils.fs.getFile("/system/bin/screenrecord").exists()
)
val egg = RootUtils.fs.getFile("/system/app/EasterEgg").list() ?: arrayOf()
assertTrue(
"/system/app/EasterEgg should be empty",
egg.isEmpty()
assertArrayEquals(
"/system/app/EasterEgg should be replaced",
egg,
arrayOf("newfile")
)
}
@@ -134,5 +137,25 @@ class AdditionalTest : BaseTest {
@Test
fun testRemoveModule() {
assertNull("$REMOVE_TEST should be removed", modules.find { it.id == REMOVE_TEST })
assertTrue(
"Uninstaller of $REMOVE_TEST should be run",
RootUtils.fs.getFile(Environment.REMOVE_TEST_MARKER).exists()
)
}
@Test
fun testModuleUpgrade() {
val module = modules.find { it.id == UPGRADE_TEST }
assertNotNull("$UPGRADE_TEST is not installed", module)
module!!
assertFalse("$UPGRADE_TEST should be disabled", module.enable)
assertTrue(
"$UPGRADE_TEST should be updated",
module.base.getChildFile("post-fs-data.sh").exists()
)
assertFalse(
"$UPGRADE_TEST should be updated",
module.base.getChildFile("service.sh").exists()
)
}
}

View File

@@ -58,12 +58,15 @@ class Environment : BaseTest {
return Build.VERSION.SDK_INT >= 27
}
private const val MODULE_UPDATE_PATH = "/data/adb/modules_update"
private const val MODULE_ERROR = "Module zip processing incorrect"
const val MOUNT_TEST = "mount_test"
const val SEPOLICY_RULE = "sepolicy_rule"
const val INVALID_ZYGISK = "invalid_zygisk"
const val REMOVE_TEST = "remove_test"
const val REMOVE_TEST_MARKER = "/dev/.remove_test_removed"
const val EMPTY_ZYGISK = "empty_zygisk"
const val UPGRADE_TEST = "upgrade_test"
}
object TimberLog : CallbackList<String>(Runnable::run) {
@@ -98,8 +101,8 @@ class Environment : BaseTest {
val error = "$MOUNT_TEST setup failed"
val path = root.getChildFile(MOUNT_TEST)
// Create /system/etc/newfile
val etc = path.getChildFile("system").getChildFile("etc")
// Create /system/fonts/newfile
val etc = path.getChildFile("system").getChildFile("fonts")
assertTrue(error, etc.mkdirs())
assertTrue(error, etc.getChildFile("newfile").createNewFile())
@@ -108,6 +111,9 @@ class Environment : BaseTest {
assertTrue(error, egg.mkdirs())
assertTrue(error, egg.getChildFile(".replace").createNewFile())
// Create /system/app/EasterEgg/newfile
assertTrue(error, egg.getChildFile("newfile").createNewFile())
// Delete /system/bin/screenrecord
val bin = path.getChildFile("system").getChildFile("bin")
assertTrue(error, bin.mkdirs())
@@ -116,6 +122,12 @@ class Environment : BaseTest {
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
}
private fun setupSystemlessHost() {
val error = "hosts setup failed"
assertTrue(error, runBlocking { RootUtils.addSystemlessHosts() })
assertTrue(error, RootUtils.fs.getFile(Const.MODULE_PATH).getChildFile("hosts").exists())
}
private fun setupSepolicyRuleModule(root: ExtendedFile) {
val error = "$SEPOLICY_RULE setup failed"
val path = root.getChildFile(SEPOLICY_RULE)
@@ -163,12 +175,39 @@ class Environment : BaseTest {
// Create a new module but mark is as "remove"
val module = LocalModule(path)
assertTrue(error, path.mkdirs())
// Create uninstaller script
path.getChildFile("uninstall.sh").newOutputStream().writer().use {
it.write("touch $REMOVE_TEST_MARKER")
}
assertTrue(error, path.getChildFile("service.sh").createNewFile())
module.remove = true
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
}
private fun setupUpgradeModule(root: ExtendedFile, update: ExtendedFile) {
val error = "$UPGRADE_TEST setup failed"
val oldPath = root.getChildFile(UPGRADE_TEST)
val newPath = update.getChildFile(UPGRADE_TEST)
// Create an existing module but mark as "disable
val module = LocalModule(oldPath)
assertTrue(error, oldPath.mkdirs())
module.enable = false
// Install service.sh into the old module
assertTrue(error, oldPath.getChildFile("service.sh").createNewFile())
// Create an upgrade module
assertTrue(error, newPath.mkdirs())
// Install post-fs-data.sh into the new module
assertTrue(error, newPath.getChildFile("post-fs-data.sh").createNewFile())
assertTrue(error, Shell.cmd(
"set_default_perm $oldPath",
"set_default_perm $newPath",
).exec().isSuccess)
}
@Test
fun setupEnvironment() {
runBlocking {
@@ -213,11 +252,14 @@ class Environment : BaseTest {
}
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
if (mount()) { setupMountTest(root) }
if (preinit()) { setupSepolicyRuleModule(root) }
setupEmptyZygiskModule(root)
setupInvalidZygiskModule(root)
val update = RootUtils.fs.getFile(MODULE_UPDATE_PATH)
if (mount()) { setupMountTest(update) }
if (preinit()) { setupSepolicyRuleModule(update) }
setupSystemlessHost()
setupEmptyZygiskModule(update)
setupInvalidZygiskModule(update)
setupRemoveModule(root)
setupUpgradeModule(root, update)
}
@Test

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