mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-11 19:03:05 +00:00
Compare commits
99 Commits
canary-280
...
canary-281
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b62835cbeb | ||
|
|
6ea740b5ab | ||
|
|
7ab98dd5ac | ||
|
|
fc8b3400fc | ||
|
|
54428ba415 | ||
|
|
95d1e69d8e | ||
|
|
a0f13ab49f | ||
|
|
c3e8405020 | ||
|
|
a93593ea66 | ||
|
|
23eff70883 | ||
|
|
110dd4a8b9 | ||
|
|
d9c2bffc9f | ||
|
|
049db49dc8 | ||
|
|
7c1d2ec61e | ||
|
|
a1b2830c06 | ||
|
|
82d1d19267 | ||
|
|
4d4195c02d | ||
|
|
5637a258fc | ||
|
|
ee6810f417 | ||
|
|
7098248c64 | ||
|
|
0d31d356ef | ||
|
|
b782e7dcb7 | ||
|
|
a4671b4698 | ||
|
|
7edd8be169 | ||
|
|
24650eefe4 | ||
|
|
8e1a44e7eb | ||
|
|
2722875190 | ||
|
|
3ca6d06f69 | ||
|
|
10e47248de | ||
|
|
e73ff679ac | ||
|
|
53e401fa2d | ||
|
|
d2768357da | ||
|
|
a6c2ba7c1e | ||
|
|
aae5b466fb | ||
|
|
2b7be8b949 | ||
|
|
b6511a510d | ||
|
|
704541aef2 | ||
|
|
005560a4c5 | ||
|
|
231a5d1853 | ||
|
|
9e2b59060d | ||
|
|
08ea937f7c | ||
|
|
2baedf74d1 | ||
|
|
32faa4ced6 | ||
|
|
ccdb0b5d13 | ||
|
|
8506b672ad | ||
|
|
ce2e33bb20 | ||
|
|
6707b72260 | ||
|
|
5885b8c20d | ||
|
|
820710c086 | ||
|
|
51cf196bf7 | ||
|
|
dadba44cf9 | ||
|
|
2ce4a5543b | ||
|
|
9112a3a4f5 | ||
|
|
24615afda1 | ||
|
|
c5778f398b | ||
|
|
4eb4097b9b | ||
|
|
c512496847 | ||
|
|
506961a10d | ||
|
|
3414415907 | ||
|
|
dc2ae7cfd1 | ||
|
|
2e86d21c29 | ||
|
|
2654382c43 | ||
|
|
9e26b73813 | ||
|
|
10cd13bf80 | ||
|
|
f10ee5f887 | ||
|
|
47cc532d96 | ||
|
|
218327f92b | ||
|
|
4eae66a1a7 | ||
|
|
b09ceeb43c | ||
|
|
4fb539c110 | ||
|
|
849b284da5 | ||
|
|
895b5f6cbf | ||
|
|
cb3d4ea514 | ||
|
|
0d89a2a97d | ||
|
|
3ca5913055 | ||
|
|
df6b808f49 | ||
|
|
09c7ac754b | ||
|
|
805da67c23 | ||
|
|
3c6889505b | ||
|
|
c8e9ce7627 | ||
|
|
837c679a31 | ||
|
|
06616659b8 | ||
|
|
a34c04f999 | ||
|
|
da43ac89a0 | ||
|
|
830fc758b9 | ||
|
|
0f3cfef278 | ||
|
|
b32d7bfafd | ||
|
|
c0899f2939 | ||
|
|
082330808f | ||
|
|
024da05888 | ||
|
|
377b6d0cc2 | ||
|
|
c661009b31 | ||
|
|
613f2d31c5 | ||
|
|
7dbb973db5 | ||
|
|
f4502f8be8 | ||
|
|
455b13b83c | ||
|
|
8b98709743 | ||
|
|
1b12f45f39 | ||
|
|
a5cad532ff |
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@@ -6,7 +6,7 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up JDK 17
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
|
||||
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -1,15 +1,6 @@
|
||||
name: Magisk Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- "app/**"
|
||||
- "native/**"
|
||||
- "buildSrc/**"
|
||||
- "build.py"
|
||||
- "gradle.properties"
|
||||
- ".github/workflows/build.yml"
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
@@ -17,7 +8,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build Magisk artifacts
|
||||
runs-on: macos-14
|
||||
runs-on: macos-15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
@@ -60,7 +51,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
os: [windows-2025, ubuntu-24.04]
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
@@ -78,16 +69,18 @@ jobs:
|
||||
|
||||
avd-test:
|
||||
name: Test API ${{ matrix.version }} (x86_64)
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
|
||||
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
|
||||
type: [""]
|
||||
include:
|
||||
- version: 35
|
||||
- version: "Baklava"
|
||||
type: "google_apis"
|
||||
- version: "Baklava"
|
||||
type: "google_apis_ps16k"
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
@@ -122,7 +115,7 @@ jobs:
|
||||
|
||||
avd-test-32:
|
||||
name: Test API ${{ matrix.version }} (x86)
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -161,7 +154,7 @@ jobs:
|
||||
kernel.log
|
||||
logcat.log
|
||||
|
||||
cf_test:
|
||||
cf-test:
|
||||
name: Test ${{ matrix.device }}
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
@@ -173,8 +166,6 @@ jobs:
|
||||
include:
|
||||
- branch: "aosp-main"
|
||||
device: "aosp_cf_x86_64_phone"
|
||||
- branch: "aosp-main-throttled"
|
||||
device: "aosp_cf_x86_64_phone_pgagnostic"
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,6 +14,7 @@ native/out
|
||||
*.iml
|
||||
.gradle
|
||||
.idea
|
||||
.kotlin
|
||||
/local.properties
|
||||
/build
|
||||
/captures
|
||||
|
||||
@@ -20,9 +20,9 @@ Some highlight features:
|
||||
|
||||
Click the icon below to download Magisk apk.
|
||||
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.0)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-28001)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-28102)
|
||||
|
||||
## Useful Links
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
}
|
||||
|
||||
setupAppCommon()
|
||||
setupMainApk()
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
@@ -18,27 +18,6 @@ kapt {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
versionName = Config.version
|
||||
versionCode = Config.versionCode
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
|
||||
debugSymbolLevel = "FULL"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
}
|
||||
@@ -46,6 +25,13 @@ android {
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -71,7 +71,7 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>(), MenuProvider {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
defaultOrientation = activity?.requestedOrientation ?: -1
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
if (savedInstanceState == null) {
|
||||
viewModel.startFlashing()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
@@ -16,8 +17,6 @@ import com.topjohnwu.magisk.arch.BaseFragment
|
||||
import com.topjohnwu.magisk.arch.viewModel
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.databinding.FragmentActionMd2Binding
|
||||
import com.topjohnwu.magisk.ui.flash.FlashViewModel
|
||||
import timber.log.Timber
|
||||
import com.topjohnwu.magisk.core.R as CoreR
|
||||
|
||||
class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
|
||||
@@ -37,39 +36,32 @@ class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
|
||||
super.onStart()
|
||||
activity?.setTitle(viewModel.args.name)
|
||||
binding.closeBtn.setOnClickListener {
|
||||
activity?.onBackPressed();
|
||||
activity?.onBackPressed()
|
||||
}
|
||||
|
||||
viewModel.state.observe(this) {
|
||||
activity?.supportActionBar?.setSubtitle(
|
||||
when (it) {
|
||||
ActionViewModel.State.RUNNING -> CoreR.string.running
|
||||
ActionViewModel.State.SUCCESS -> CoreR.string.done
|
||||
ActionViewModel.State.FAILED -> CoreR.string.failure
|
||||
if (it != ActionViewModel.State.RUNNING) {
|
||||
binding.closeBtn.apply {
|
||||
if (!this.isVisible) this.show()
|
||||
if (!this.isFocused) this.requestFocus()
|
||||
}
|
||||
}
|
||||
if (it != ActionViewModel.State.SUCCESS) return@observe
|
||||
view?.viewTreeObserver?.addOnWindowFocusChangeListener(
|
||||
object : ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
if (hasFocus) return
|
||||
view?.viewTreeObserver?.removeOnWindowFocusChangeListener(this)
|
||||
view?.context?.apply {
|
||||
toast(
|
||||
getString(CoreR.string.done_action, viewModel.args.name),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
}
|
||||
viewModel.back()
|
||||
}
|
||||
}
|
||||
)
|
||||
when (it) {
|
||||
ActionViewModel.State.SUCCESS -> {
|
||||
activity?.apply {
|
||||
toast(
|
||||
getString(
|
||||
com.topjohnwu.magisk.core.R.string.done_action,
|
||||
this@ActionFragment.viewModel.args.name
|
||||
), Toast.LENGTH_LONG
|
||||
)
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
ActionViewModel.State.FAILED -> {
|
||||
binding.closeBtn.apply {
|
||||
if (!this.isVisible) this.show()
|
||||
if (!this.isFocused) this.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +77,7 @@ class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
defaultOrientation = activity?.requestedOrientation ?: -1
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
if (savedInstanceState == null) {
|
||||
viewModel.startRunAction()
|
||||
}
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.view.MenuItem
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.ktx.synchronized
|
||||
import com.topjohnwu.magisk.core.ktx.timeFormatStandard
|
||||
import com.topjohnwu.magisk.core.ktx.toTime
|
||||
import com.topjohnwu.magisk.core.tasks.RunAction
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.ui.flash.ConsoleItem
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
class ActionViewModel : BaseViewModel() {
|
||||
|
||||
@@ -32,7 +30,6 @@ class ActionViewModel : BaseViewModel() {
|
||||
|
||||
private val _state = MutableLiveData(State.RUNNING)
|
||||
val state: LiveData<State> get() = _state
|
||||
val running = state.map { it == State.RUNNING }
|
||||
|
||||
val items = ObservableArrayList<ConsoleItem>()
|
||||
lateinit var args: ActionFragmentArgs
|
||||
@@ -46,10 +43,17 @@ class ActionViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun startRunAction() {
|
||||
viewModelScope.launch {
|
||||
onResult(RunAction(args.id, outItems, logItems).exec())
|
||||
}
|
||||
fun startRunAction() = viewModelScope.launch {
|
||||
onResult(withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Shell.cmd("run_action \'${args.id}\'")
|
||||
.to(outItems, logItems)
|
||||
.exec().isSuccess
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun onResult(success: Boolean) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.R
|
||||
@@ -92,7 +93,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
||||
DownloadPath -> withExternalRW(doAction)
|
||||
UpdateChecker -> withPostNotificationPermission(doAction)
|
||||
Authentication -> AuthEvent(doAction).publish()
|
||||
Hide, Restore -> withInstallPermission(doAction)
|
||||
AutomaticResponse -> if (Config.suAuth) AuthEvent(doAction).publish() else doAction()
|
||||
else -> doAction()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ android {
|
||||
buildConfigField("int", "APP_VERSION_CODE", "${Config.versionCode}")
|
||||
buildConfigField("String", "APP_VERSION_NAME", "\"${Config.version}\"")
|
||||
buildConfigField("int", "STUB_VERSION", Config.stubVersion)
|
||||
consumerProguardFile("proguard-rules.pro")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
@@ -59,5 +60,10 @@ dependencies {
|
||||
implementation(libs.activity)
|
||||
implementation(libs.collection.ktx)
|
||||
implementation(libs.profileinstaller)
|
||||
implementation(libs.lifecycle.process)
|
||||
|
||||
// We also implement all our tests in this module.
|
||||
// However, we don't want to bundle test dependencies.
|
||||
// That's why we make it compileOnly.
|
||||
compileOnly(libs.test.junit)
|
||||
compileOnly(libs.test.uiautomator)
|
||||
}
|
||||
|
||||
@@ -22,42 +22,19 @@
|
||||
int mActivityHandlesConfigFlags;
|
||||
}
|
||||
|
||||
# main
|
||||
-keep,allowoptimization public class com.topjohnwu.magisk.signing.SignBoot {
|
||||
public static void main(java.lang.String[]);
|
||||
}
|
||||
|
||||
# Strip Timber verbose and debug logging
|
||||
-assumenosideeffects class timber.log.Timber$Tree {
|
||||
public void v(**);
|
||||
public void d(**);
|
||||
}
|
||||
|
||||
# https://github.com/square/retrofit/issues/3751#issuecomment-1192043644
|
||||
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
|
||||
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
|
||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
||||
|
||||
# With R8 full mode generic signatures are stripped for classes that are not
|
||||
# kept. Suspend functions are wrapped in continuations where the type argument
|
||||
# is used.
|
||||
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
|
||||
|
||||
|
||||
# Excessive obfuscation
|
||||
-repackageclasses 'a'
|
||||
-flattenpackagehierarchy
|
||||
-allowaccessmodification
|
||||
|
||||
-obfuscationdictionary ../dict.txt
|
||||
-classobfuscationdictionary ../dict.txt
|
||||
-packageobfuscationdictionary ../dict.txt
|
||||
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
-dontwarn org.commonmark.ext.gfm.strikethrough.Strikethrough
|
||||
-dontwarn org.conscrypt.Conscrypt*
|
||||
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||
-dontwarn org.junit.**
|
||||
@@ -16,7 +16,6 @@ import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.base.UntrackedActivity
|
||||
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||
import com.topjohnwu.magisk.core.utils.NetworkObserver
|
||||
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.core.utils.ShellInit
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@@ -40,6 +39,7 @@ object AppContext : ContextWrapper(null),
|
||||
|
||||
private var ref = WeakReference<Activity>(null)
|
||||
private lateinit var application: Application
|
||||
private lateinit var networkObserver: NetworkObserver
|
||||
|
||||
init {
|
||||
// Always log full stack trace with Timber
|
||||
@@ -56,6 +56,10 @@ object AppContext : ContextWrapper(null),
|
||||
LocaleSetting.instance.updateResource(resources)
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
networkObserver.postCurrentState()
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
if (activity is UntrackedActivity) return
|
||||
ref = WeakReference(activity)
|
||||
@@ -102,8 +106,7 @@ object AppContext : ContextWrapper(null),
|
||||
val lm = getSystemService(LocaleManager::class.java)
|
||||
lm.overrideLocaleConfig = LocaleSetting.localeConfig
|
||||
}
|
||||
ProcessLifecycle.init(this)
|
||||
NetworkObserver.init(this)
|
||||
networkObserver = NetworkObserver.init(this)
|
||||
if (!BuildConfig.DEBUG && !isRunningAsStub) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
ProfileInstaller.writeProfile(this@AppContext)
|
||||
@@ -120,7 +123,6 @@ object AppContext : ContextWrapper(null),
|
||||
}
|
||||
|
||||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
||||
override fun onActivityStarted(activity: Activity) {}
|
||||
override fun onActivityStopped(activity: Activity) {}
|
||||
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
||||
override fun onActivityDestroyed(activity: Activity) {}
|
||||
|
||||
@@ -83,7 +83,7 @@ object Config : PreferenceConfig, DBConfig {
|
||||
const val SU_AUTO_ALLOW = 2
|
||||
|
||||
// su timeout
|
||||
val TIMEOUT_LIST = intArrayOf(0, -1, 10, 20, 30, 60)
|
||||
val TIMEOUT_LIST = longArrayOf(0, -1, 10, 20, 30, 60)
|
||||
}
|
||||
|
||||
private val defaultChannel =
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.core.content.getSystemService
|
||||
import com.topjohnwu.magisk.core.base.BaseJobService
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.DownloadSession
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -25,7 +26,7 @@ class JobService : BaseJobService() {
|
||||
@TargetApi(value = 34)
|
||||
inner class Session(
|
||||
private var params: JobParameters
|
||||
) : DownloadEngine.Session {
|
||||
) : DownloadSession {
|
||||
|
||||
override val context get() = this@JobService
|
||||
val engine = DownloadEngine(this)
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core
|
||||
import android.os.Bundle
|
||||
import com.topjohnwu.magisk.core.base.BaseProvider
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import com.topjohnwu.magisk.core.su.TestHandler
|
||||
|
||||
class Provider : BaseProvider() {
|
||||
|
||||
@@ -13,7 +12,7 @@ class Provider : BaseProvider() {
|
||||
SuCallbackHandler.run(context!!, method, extras)
|
||||
Bundle.EMPTY
|
||||
}
|
||||
else -> TestHandler.run(method)
|
||||
else -> Bundle.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.DownloadSession
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
|
||||
class Service : BaseService(), DownloadEngine.Session {
|
||||
class Service : BaseService(), DownloadSession {
|
||||
|
||||
private var mEngine: DownloadEngine? = null
|
||||
override val context get() = this
|
||||
|
||||
@@ -7,9 +7,13 @@ import kotlinx.coroutines.withContext
|
||||
|
||||
open class MagiskDB {
|
||||
|
||||
suspend fun <R> exec(
|
||||
class Literal(
|
||||
val str: String
|
||||
)
|
||||
|
||||
suspend inline fun <R> exec(
|
||||
query: String,
|
||||
mapper: suspend (Map<String, String>) -> R
|
||||
crossinline mapper: (Map<String, String>) -> R
|
||||
): List<R> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val out = Shell.cmd("magisk --sqlite '$query'").await().out
|
||||
@@ -18,13 +22,15 @@ open class MagiskDB {
|
||||
.map { it.split("=", limit = 2) }
|
||||
.filter { it.size == 2 }
|
||||
.associate { it[0] to it[1] }
|
||||
.let { mapper(it) }
|
||||
.let(mapper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun exec(query: String) {
|
||||
exec(query) {}
|
||||
suspend fun exec(query: String) {
|
||||
withContext(Dispatchers.IO) {
|
||||
Shell.cmd("magisk --sqlite '$query'").await()
|
||||
}
|
||||
}
|
||||
|
||||
fun Map<String, Any>.toQuery(): String {
|
||||
@@ -33,6 +39,7 @@ open class MagiskDB {
|
||||
when (it) {
|
||||
is Boolean -> if (it) "1" else "0"
|
||||
is Number -> it.toString()
|
||||
is Literal -> it.str
|
||||
else -> "\"$it\""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,24 +3,24 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val SELECT_QUERY = "SELECT (until - strftime(\"%s\", \"now\")) AS remain, *"
|
||||
|
||||
class PolicyDao : MagiskDB() {
|
||||
|
||||
suspend fun deleteOutdated() {
|
||||
val nowSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
|
||||
val query = "DELETE FROM ${Table.POLICY} WHERE " +
|
||||
"(until > 0 AND until < $nowSeconds) OR until < 0"
|
||||
"(until > 0 AND until < strftime(\"%s\", \"now\")) OR until < 0"
|
||||
exec(query)
|
||||
}
|
||||
|
||||
suspend fun delete(uid: Int) {
|
||||
val query = "DELETE FROM ${Table.POLICY} WHERE uid == $uid"
|
||||
val query = "DELETE FROM ${Table.POLICY} WHERE uid=$uid"
|
||||
exec(query)
|
||||
}
|
||||
|
||||
suspend fun fetch(uid: Int): SuPolicy? {
|
||||
val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT = 1"
|
||||
val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid=$uid LIMIT 1"
|
||||
return exec(query, ::toPolicy).firstOrNull()
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class PolicyDao : MagiskDB() {
|
||||
}
|
||||
|
||||
suspend fun fetchAll(): List<SuPolicy> {
|
||||
val query = "SELECT * FROM ${Table.POLICY} WHERE uid/100000 == ${Const.USER_ID}"
|
||||
val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid/100000=${Const.USER_ID}"
|
||||
return exec(query, ::toPolicy).filterNotNull()
|
||||
}
|
||||
|
||||
@@ -43,8 +43,15 @@ class PolicyDao : MagiskDB() {
|
||||
val uid = map["uid"]?.toInt() ?: return null
|
||||
val policy = SuPolicy(uid)
|
||||
|
||||
map["until"]?.toLong()?.let { until ->
|
||||
if (until <= 0) {
|
||||
policy.remain = until
|
||||
} else {
|
||||
map["remain"]?.toLong()?.let { policy.remain = it }
|
||||
}
|
||||
}
|
||||
|
||||
map["policy"]?.toInt()?.let { policy.policy = it }
|
||||
map["until"]?.toLong()?.let { policy.until = it }
|
||||
map["logging"]?.toInt()?.let { policy.logging = it != 0 }
|
||||
map["notification"]?.toInt()?.let { policy.notification = it != 0 }
|
||||
return policy
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
||||
class SettingsDao : MagiskDB() {
|
||||
|
||||
suspend fun delete(key: String) {
|
||||
val query = "DELETE FROM ${Table.SETTINGS} WHERE key == \"$key\""
|
||||
val query = "DELETE FROM ${Table.SETTINGS} WHERE key=\"$key\""
|
||||
exec(query)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class SettingsDao : MagiskDB() {
|
||||
}
|
||||
|
||||
suspend fun fetch(key: String, default: Int = -1): Int {
|
||||
val query = "SELECT value FROM ${Table.SETTINGS} WHERE key == \"$key\" LIMIT 1"
|
||||
val query = "SELECT value FROM ${Table.SETTINGS} WHERE key=\"$key\" LIMIT 1"
|
||||
return exec(query) { it["value"]?.toInt() }.firstOrNull() ?: default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
||||
class StringDao : MagiskDB() {
|
||||
|
||||
suspend fun delete(key: String) {
|
||||
val query = "DELETE FROM ${Table.STRINGS} WHERE key == \"$key\""
|
||||
val query = "DELETE FROM ${Table.STRINGS} WHERE key=\"$key\""
|
||||
exec(query)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class StringDao : MagiskDB() {
|
||||
}
|
||||
|
||||
suspend fun fetch(key: String, default: String = ""): String {
|
||||
val query = "SELECT value FROM ${Table.STRINGS} WHERE key == \"$key\" LIMIT 1"
|
||||
val query = "SELECT value FROM ${Table.STRINGS} WHERE key=\"$key\" LIMIT 1"
|
||||
return exec(query) { it["value"] }.firstOrNull() ?: default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.app.PendingIntent
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobScheduler
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -16,7 +15,6 @@ import androidx.collection.isNotEmpty
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.JobService
|
||||
@@ -25,18 +23,8 @@ import com.topjohnwu.magisk.core.base.IActivityExtension
|
||||
import com.topjohnwu.magisk.core.cmp
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.forEach
|
||||
import com.topjohnwu.magisk.core.ktx.set
|
||||
import com.topjohnwu.magisk.core.ktx.withStreams
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -44,13 +32,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.ResponseBody
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* This class drives the execution of file downloads and notification management.
|
||||
@@ -69,16 +51,7 @@ import java.util.zip.ZipOutputStream
|
||||
* For API 23 - 33, we use a foreground service as a session.
|
||||
* For API 34 and higher, we use user-initiated job services as a session.
|
||||
*/
|
||||
class DownloadEngine(
|
||||
private val session: Session
|
||||
) {
|
||||
|
||||
interface Session {
|
||||
val context: Context
|
||||
|
||||
fun attachNotification(id: Int, builder: Notification.Builder)
|
||||
fun onDownloadComplete()
|
||||
}
|
||||
class DownloadEngine(session: DownloadSession) : DownloadSession by session, DownloadNotifier {
|
||||
|
||||
companion object {
|
||||
const val ACTION = "com.topjohnwu.magisk.DOWNLOAD"
|
||||
@@ -99,33 +72,35 @@ class DownloadEngine(
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIntent(context: Context, subject: Subject) =
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
context.intent<com.topjohnwu.magisk.core.Receiver>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
} else {
|
||||
context.intent<com.topjohnwu.magisk.core.Service>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
}
|
||||
private fun createBroadcastIntent(context: Context, subject: Subject) =
|
||||
context.intent<com.topjohnwu.magisk.core.Receiver>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
|
||||
private fun createServiceIntent(context: Context, subject: Subject) =
|
||||
context.intent<com.topjohnwu.magisk.core.Service>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
|
||||
val flag = PendingIntent.FLAG_IMMUTABLE or
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or
|
||||
PendingIntent.FLAG_ONE_SHOT
|
||||
val intent = createIntent(context, subject)
|
||||
return if (Build.VERSION.SDK_INT >= 34) {
|
||||
// On API 34+, download tasks are handled with a user-initiated job.
|
||||
// However, there is no way to schedule a new job directly with a pending intent.
|
||||
// As a workaround, we send the subject to a broadcast receiver and have it
|
||||
// schedule the job for us.
|
||||
val intent = createBroadcastIntent(context, subject)
|
||||
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)
|
||||
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
|
||||
val intent = createServiceIntent(context, subject)
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +115,7 @@ class DownloadEngine(
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun start(context: Context, subject: Subject) {
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
val scheduler = context.getSystemService<JobScheduler>()!!
|
||||
@@ -152,24 +128,29 @@ class DownloadEngine(
|
||||
.setTransientExtras(extras)
|
||||
.build()
|
||||
scheduler.schedule(info)
|
||||
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.startForegroundService(createIntent(context, subject))
|
||||
} else {
|
||||
context.startService(createIntent(context, subject))
|
||||
val intent = createServiceIntent(context, subject)
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val notifications = SparseArrayCompat<Notification.Builder>()
|
||||
private var attachedId = -1
|
||||
private val job = Job()
|
||||
private val processor = DownloadProcessor(this)
|
||||
private val network get() = ServiceLocator.networkService
|
||||
|
||||
fun download(subject: Subject) {
|
||||
notifyUpdate(subject.notifyId)
|
||||
CoroutineScope(job + Dispatchers.IO).launch {
|
||||
try {
|
||||
val stream = network.fetchFile(subject.url).toProgressStream(subject)
|
||||
when (subject) {
|
||||
is Subject.App -> handleApp(stream, subject)
|
||||
is Subject.Module -> handleModule(stream, subject.file)
|
||||
else -> stream.copyAndClose(subject.file.outputStream())
|
||||
}
|
||||
processor.handle(stream, subject)
|
||||
val activity = AppContext.foregroundActivity
|
||||
if (activity != null && subject.autoLaunch) {
|
||||
notifyRemove(subject.notifyId)
|
||||
@@ -187,16 +168,13 @@ class DownloadEngine(
|
||||
@Synchronized
|
||||
fun reattach() {
|
||||
val builder = notifications[attachedId] ?: return
|
||||
session.attachNotification(attachedId, builder)
|
||||
attachNotification(attachedId, builder)
|
||||
}
|
||||
|
||||
private val notifications = SparseArrayCompat<Notification.Builder>()
|
||||
private var attachedId = -1
|
||||
|
||||
private val job = Job()
|
||||
|
||||
private val context get() = session.context
|
||||
private val network get() = ServiceLocator.networkService
|
||||
private fun attach(id: Int, notification: Notification.Builder) {
|
||||
attachedId = id
|
||||
attachNotification(id, notification)
|
||||
}
|
||||
|
||||
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
|
||||
val notification = notifyRemove(id)?.also(editor) ?: return -1
|
||||
@@ -223,19 +201,14 @@ class DownloadEngine(
|
||||
subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) }
|
||||
}
|
||||
|
||||
private fun attachNotification(id: Int, notification: Notification.Builder) {
|
||||
attachedId = id
|
||||
session.attachNotification(id, notification)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
|
||||
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {
|
||||
val notification = (notifications[id] ?: Notifications.startProgress("").also {
|
||||
notifications[id] = it
|
||||
}).apply(editor)
|
||||
|
||||
if (attachedId < 0)
|
||||
attachNotification(id, notification)
|
||||
attach(id, notification)
|
||||
else
|
||||
Notifications.mgr.notify(id, notification.build())
|
||||
}
|
||||
@@ -255,11 +228,11 @@ class DownloadEngine(
|
||||
// There are still remaining notifications, pick one and attach to the session
|
||||
val anotherId = notifications.keyAt(0)
|
||||
val notification = notifications.valueAt(0)
|
||||
attachNotification(anotherId, notification)
|
||||
attach(anotherId, notification)
|
||||
} else {
|
||||
// No more notifications left, terminate the session
|
||||
attachedId = -1
|
||||
session.onDownloadComplete()
|
||||
onDownloadComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,90 +241,6 @@ class DownloadEngine(
|
||||
return n
|
||||
}
|
||||
|
||||
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
val external = subject.file.outputStream()
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val updateApk = StubApk.update(context)
|
||||
try {
|
||||
// Download full APK to stub update path
|
||||
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
|
||||
|
||||
// Also upgrade stub
|
||||
notifyUpdate(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(context.getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
// Extract stub
|
||||
val zf = ZipFile(updateApk)
|
||||
val apk = context.cachedFile("stub.apk")
|
||||
apk.delete()
|
||||
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
||||
zf.close()
|
||||
|
||||
// Patch and install
|
||||
subject.intent = AppMigration.upgradeStub(context, apk)
|
||||
?: throw IOException("HideAPK patch error")
|
||||
apk.delete()
|
||||
} catch (e: Exception) {
|
||||
// If any error occurred, do not let stub load the new APK
|
||||
updateApk.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val session = APKInstall.startSession(context)
|
||||
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleModule(src: InputStream, file: Uri) {
|
||||
val input = ZipInputStream(src)
|
||||
val output = ZipOutputStream(file.outputStream())
|
||||
|
||||
withStreams(input, output) { zin, zout ->
|
||||
zout.putNextEntry(ZipEntry("META-INF/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
|
||||
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
|
||||
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray())
|
||||
|
||||
zin.forEach { entry ->
|
||||
val path = entry.name
|
||||
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
|
||||
zout.putNextEntry(ZipEntry(path))
|
||||
if (!entry.isDirectory) {
|
||||
zin.copyAll(zout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TeeOutputStream(
|
||||
private val o1: OutputStream,
|
||||
private val o2: OutputStream
|
||||
) : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
o1.write(b)
|
||||
o2.write(b)
|
||||
}
|
||||
override fun write(b: ByteArray?, off: Int, len: Int) {
|
||||
o1.write(b, off, len)
|
||||
o2.write(b, off, len)
|
||||
}
|
||||
override fun close() {
|
||||
o1.close()
|
||||
o2.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
|
||||
val max = contentLength()
|
||||
val total = max.toFloat() / 1048576
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.net.Uri
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.withInOut
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class DownloadProcessor(notifier: DownloadNotifier) : DownloadNotifier by notifier {
|
||||
|
||||
suspend fun handle(stream: InputStream, subject: Subject) {
|
||||
when (subject) {
|
||||
is Subject.App -> handleApp(stream, subject)
|
||||
is Subject.Module -> handleModule(stream, subject.file)
|
||||
else -> stream.copyAndClose(subject.file.outputStream())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
val external = subject.file.outputStream()
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val updateApk = StubApk.update(context)
|
||||
try {
|
||||
// Download full APK to stub update path
|
||||
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
|
||||
|
||||
// Also upgrade stub
|
||||
notifyUpdate(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(context.getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
// Extract stub
|
||||
val apk = context.cachedFile("stub.apk")
|
||||
ZipFile.Builder().setFile(updateApk).get().use { zf ->
|
||||
apk.delete()
|
||||
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
||||
}
|
||||
|
||||
// Patch and install
|
||||
subject.intent = AppMigration.upgradeStub(context, apk)
|
||||
?: throw IOException("HideAPK patch error")
|
||||
apk.delete()
|
||||
} catch (e: Exception) {
|
||||
// If any error occurred, do not let stub load the new APK
|
||||
updateApk.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val session = APKInstall.startSession(context)
|
||||
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleModule(src: InputStream, file: Uri) {
|
||||
val tmp = context.cachedFile("module.zip")
|
||||
try {
|
||||
// First download the entire zip into cache so we can process it
|
||||
src.writeTo(tmp)
|
||||
|
||||
val input = ZipFile.Builder().setFile(tmp).get()
|
||||
val output = ZipArchiveOutputStream(file.outputStream())
|
||||
withInOut(input, output) { zin, zout ->
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/"))
|
||||
zout.closeArchiveEntry()
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/"))
|
||||
zout.closeArchiveEntry()
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/"))
|
||||
zout.closeArchiveEntry()
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/"))
|
||||
zout.closeArchiveEntry()
|
||||
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/update-binary"))
|
||||
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
|
||||
zout.closeArchiveEntry()
|
||||
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray())
|
||||
zout.closeArchiveEntry()
|
||||
|
||||
// Then simply copy all entries to output
|
||||
zin.copyRawEntries(zout) { entry -> !entry.name.startsWith("META-INF") }
|
||||
}
|
||||
} finally {
|
||||
tmp.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private class TeeOutputStream(
|
||||
private val o1: OutputStream,
|
||||
private val o2: OutputStream
|
||||
) : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
o1.write(b)
|
||||
o2.write(b)
|
||||
}
|
||||
override fun write(b: ByteArray?, off: Int, len: Int) {
|
||||
o1.write(b, off, len)
|
||||
o2.write(b, off, len)
|
||||
}
|
||||
override fun close() {
|
||||
o1.close()
|
||||
o2.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
|
||||
interface DownloadSession {
|
||||
val context: Context
|
||||
fun attachNotification(id: Int, builder: Notification.Builder)
|
||||
fun onDownloadComplete()
|
||||
}
|
||||
|
||||
interface DownloadNotifier {
|
||||
val context: Context
|
||||
fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {})
|
||||
}
|
||||
@@ -2,7 +2,11 @@ package com.topjohnwu.magisk.core.ktx
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
@@ -23,7 +27,6 @@ import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import java.io.File
|
||||
import kotlin.String
|
||||
|
||||
fun Context.getBitmap(id: Int): Bitmap {
|
||||
var drawable = getDrawable(id)!!
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@@ -17,24 +18,14 @@ import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
inline fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
|
||||
var entry: ZipEntry? = nextEntry
|
||||
while (entry != null) {
|
||||
callback(entry)
|
||||
entry = nextEntry
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <In : InputStream, Out : OutputStream> withStreams(
|
||||
inStream: In,
|
||||
outStream: Out,
|
||||
inline fun <In : Closeable, Out : Closeable> withInOut(
|
||||
input: In,
|
||||
output: Out,
|
||||
withBoth: (In, Out) -> Unit
|
||||
) {
|
||||
inStream.use { reader ->
|
||||
outStream.use { writer ->
|
||||
input.use { reader ->
|
||||
output.use { writer ->
|
||||
withBoth(reader, writer)
|
||||
}
|
||||
}
|
||||
@@ -64,7 +55,7 @@ suspend inline fun InputStream.copyAndClose(
|
||||
out: OutputStream,
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = withStreams(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
|
||||
) = withInOut(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
|
||||
|
||||
@Throws(IOException::class)
|
||||
suspend inline fun InputStream.writeTo(
|
||||
|
||||
@@ -9,7 +9,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
data class LocalModule(
|
||||
private val path: String,
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
package com.topjohnwu.magisk.core.model.su
|
||||
|
||||
class SuPolicy(val uid: Int) {
|
||||
import com.topjohnwu.magisk.core.data.magiskdb.MagiskDB
|
||||
|
||||
class SuPolicy(
|
||||
val uid: Int,
|
||||
var policy: Int = INTERACTIVE,
|
||||
var remain: Long = -1L,
|
||||
var logging: Boolean = true,
|
||||
var notification: Boolean = true,
|
||||
) {
|
||||
companion object {
|
||||
const val INTERACTIVE = 0
|
||||
const val DENY = 1
|
||||
const val ALLOW = 2
|
||||
}
|
||||
|
||||
var policy: Int = INTERACTIVE
|
||||
var until: Long = -1L
|
||||
var logging: Boolean = true
|
||||
var notification: Boolean = true
|
||||
|
||||
fun toMap(): MutableMap<String, Any> = mutableMapOf(
|
||||
"uid" to uid,
|
||||
"policy" to policy,
|
||||
"until" to until,
|
||||
"logging" to logging,
|
||||
"notification" to notification
|
||||
)
|
||||
fun toMap(): MutableMap<String, Any> {
|
||||
val until = if (remain <= 0) {
|
||||
remain
|
||||
} else {
|
||||
MagiskDB.Literal("(strftime(\"%s\", \"now\") + $remain)")
|
||||
}
|
||||
return mutableMapOf(
|
||||
"uid" to uid,
|
||||
"policy" to policy,
|
||||
"until" to until,
|
||||
"logging" to logging,
|
||||
"notification" to notification
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class SuRequestHandler(
|
||||
return false
|
||||
}
|
||||
output = File(fifo)
|
||||
policy = SuPolicy(uid)
|
||||
policy = policyDB.fetch(uid) ?: SuPolicy(uid)
|
||||
try {
|
||||
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
|
||||
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
||||
@@ -81,15 +81,13 @@ class SuRequestHandler(
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun respond(action: Int, time: Int) {
|
||||
val until = if (time > 0)
|
||||
TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) +
|
||||
TimeUnit.MINUTES.toSeconds(time.toLong())
|
||||
else
|
||||
time.toLong()
|
||||
|
||||
suspend fun respond(action: Int, time: Long) {
|
||||
policy.policy = action
|
||||
policy.until = until
|
||||
if (time >= 0) {
|
||||
policy.remain = TimeUnit.MINUTES.toSeconds(time)
|
||||
} else {
|
||||
policy.remain = time
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -100,7 +98,7 @@ class SuRequestHandler(
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
}
|
||||
if (until >= 0) {
|
||||
if (time >= 0) {
|
||||
policyDB.update(policy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.su
|
||||
|
||||
import android.os.Bundle
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
|
||||
object TestHandler {
|
||||
|
||||
object LogList : CallbackList<String>(Runnable::run) {
|
||||
override fun onAddElement(e: String) {
|
||||
Timber.i(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun run(method: String): Bundle {
|
||||
var reason: String? = null
|
||||
|
||||
fun prerequisite(): Boolean {
|
||||
// Make sure the Magisk app can get root
|
||||
val shell = Shell.getShell()
|
||||
if (!shell.isRoot) {
|
||||
reason = "shell not root"
|
||||
return false
|
||||
}
|
||||
|
||||
// Make sure the root service is running
|
||||
RootUtils.Connection.await()
|
||||
return true
|
||||
}
|
||||
|
||||
fun setup(): Boolean {
|
||||
return runBlocking {
|
||||
MagiskInstaller.Emulator(LogList, LogList).exec()
|
||||
}
|
||||
}
|
||||
|
||||
fun test(): Boolean {
|
||||
// Make sure Zygisk works correctly
|
||||
if (!Info.isZygiskEnabled) {
|
||||
reason = "zygisk not enabled"
|
||||
return false
|
||||
}
|
||||
|
||||
// Clear existing grant for ADB shell
|
||||
runBlocking {
|
||||
ServiceLocator.policyDB.delete(2000)
|
||||
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||
Config.prefs.edit().commit()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val result = prerequisite() && runCatching {
|
||||
when (method) {
|
||||
"setup" -> setup()
|
||||
"test" -> test()
|
||||
else -> {
|
||||
reason = "unknown method"
|
||||
false
|
||||
}
|
||||
}
|
||||
}.getOrElse {
|
||||
reason = it.stackTraceToString()
|
||||
false
|
||||
}
|
||||
|
||||
return Bundle().apply {
|
||||
putBoolean("result", result)
|
||||
if (reason != null) putString("reason", reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.app.Activity
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
@@ -13,7 +14,6 @@ import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.ktx.await
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.signing.JarMap
|
||||
@@ -23,11 +23,9 @@ import com.topjohnwu.magisk.core.utils.Keygen
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.security.SecureRandom
|
||||
@@ -38,6 +36,7 @@ object AppMigration {
|
||||
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
|
||||
private const val ALPHADOTS = "$ALPHA....."
|
||||
private const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
||||
private const val TEST_PKG_NAME = "$APP_PACKAGE_NAME.test"
|
||||
|
||||
// Some arbitrary limit
|
||||
const val MAX_LABEL_LENGTH = 32
|
||||
@@ -133,21 +132,15 @@ object AppMigration {
|
||||
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
||||
val xml = AXML(jar.getRawData(je))
|
||||
val generator = classNameGenerator()
|
||||
|
||||
if (!xml.patchStrings {
|
||||
for (i in it.indices) {
|
||||
val s = it[i]
|
||||
if (s.contains(APP_PACKAGE_NAME)) {
|
||||
it[i] = s.replace(APP_PACKAGE_NAME, pkg)
|
||||
} else if (s.contains(PLACEHOLDER)) {
|
||||
it[i] = generator.next()
|
||||
} else if (s == origLabel) {
|
||||
it[i] = label.toString()
|
||||
}
|
||||
val p = xml.patchStrings {
|
||||
when {
|
||||
it.contains(APP_PACKAGE_NAME) -> it.replace(APP_PACKAGE_NAME, pkg)
|
||||
it.contains(PLACEHOLDER) -> generator.next()
|
||||
it == origLabel -> label.toString()
|
||||
else -> it
|
||||
}
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
if (!p) return false
|
||||
|
||||
// Write apk changes
|
||||
jar.getOutputStream(je).use { it.write(xml.bytes) }
|
||||
@@ -161,52 +154,87 @@ object AppMigration {
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchApp(activity: Activity, pkg: String) {
|
||||
val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ?: return
|
||||
private fun patchTest(apk: File, out: File, pkg: String): Boolean {
|
||||
try {
|
||||
JarMap.open(apk, true).use { jar ->
|
||||
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
||||
val xml = AXML(jar.getRawData(je))
|
||||
val p = xml.patchStrings {
|
||||
when (it) {
|
||||
APP_PACKAGE_NAME -> pkg
|
||||
TEST_PKG_NAME -> "$pkg.test"
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
if (!p) return false
|
||||
|
||||
// Write apk changes
|
||||
jar.getOutputStream(je).use { it.write(xml.bytes) }
|
||||
val keys = Keygen()
|
||||
out.outputStream().use { SignApk.sign(keys.cert, keys.key, jar, it) }
|
||||
return true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchApp(context: Context, pkg: String) {
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(pkg) ?: return
|
||||
intent.putExtra(Const.Key.PREV_CONFIG, Config.toBundle())
|
||||
val options = ActivityOptions.makeBasic()
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
options.setShareIdentityEnabled(true)
|
||||
}
|
||||
activity.startActivity(intent, options.toBundle())
|
||||
activity.finish()
|
||||
context.startActivity(intent, options.toBundle())
|
||||
if (context is Activity) {
|
||||
context.finish()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
|
||||
val stub = File(activity.cacheDir, "stub.apk")
|
||||
suspend fun patchAndHide(context: Context, label: String, pkg: String? = null): Boolean {
|
||||
val stub = File(context.cacheDir, "stub.apk")
|
||||
try {
|
||||
activity.assets.open("stub.apk").writeTo(stub)
|
||||
context.assets.open("stub.apk").writeTo(stub)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
|
||||
// Generate a new random package name and signature
|
||||
val repack = File(activity.cacheDir, "patched.apk")
|
||||
val pkg = genPackageName()
|
||||
// Generate a new random signature and package name if needed
|
||||
val pkg = pkg ?: genPackageName()
|
||||
Config.keyStoreRaw = ""
|
||||
|
||||
if (!patch(activity, stub, FileOutputStream(repack), pkg, label))
|
||||
return false
|
||||
// Check and patch the test APK
|
||||
try {
|
||||
val info = context.packageManager.getApplicationInfo(TEST_PKG_NAME, 0)
|
||||
val testApk = File(info.sourceDir)
|
||||
val testRepack = File(context.cacheDir, "test.apk")
|
||||
if (!patchTest(testApk, testRepack, pkg))
|
||||
return false
|
||||
val cmd = "adb_pm_install $testRepack $pkg.test"
|
||||
if (!Shell.cmd(cmd).exec().isSuccess)
|
||||
return false
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
}
|
||||
|
||||
val repack = File(context.cacheDir, "patched.apk")
|
||||
repack.outputStream().use {
|
||||
if (!patch(context, stub, it, pkg, label))
|
||||
return false
|
||||
}
|
||||
|
||||
// Install and auto launch app
|
||||
val session = APKInstall.startSession(activity, pkg, onFailure) {
|
||||
val cmd = "adb_pm_install $repack $pkg"
|
||||
if (Shell.cmd(cmd).exec().isSuccess) {
|
||||
Config.suManager = pkg
|
||||
Shell.cmd("touch $AppApkPath").exec()
|
||||
launchApp(activity, pkg)
|
||||
}
|
||||
|
||||
val cmd = "adb_pm_install $repack $pkg"
|
||||
if (Shell.cmd(cmd).exec().isSuccess) return true
|
||||
|
||||
try {
|
||||
repack.inputStream().copyAndClose(session.openStream(activity))
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
launchApp(context, pkg)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
session.waitIntent()?.let { activity.startActivity(it) } ?: return false
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -217,14 +245,25 @@ object AppMigration {
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
val onFailure = Runnable {
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
patchAndHide(activity, label)
|
||||
}
|
||||
if (!success) {
|
||||
dialog.dismiss()
|
||||
activity.toast(R.string.failure, Toast.LENGTH_LONG)
|
||||
}
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
patchAndHide(activity, label, onFailure)
|
||||
}
|
||||
|
||||
suspend fun restoreApp(context: Context): Boolean {
|
||||
val apk = StubApk.current(context)
|
||||
val cmd = "adb_pm_install $apk $APP_PACKAGE_NAME"
|
||||
if (Shell.cmd(cmd).await().isSuccess) {
|
||||
Config.suManager = ""
|
||||
Shell.cmd("touch $AppApkPath").exec()
|
||||
launchApp(context, APP_PACKAGE_NAME)
|
||||
return true
|
||||
}
|
||||
if (!success) onFailure.run()
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -235,30 +274,10 @@ object AppMigration {
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
val onFailure = Runnable {
|
||||
dialog.dismiss()
|
||||
if (!restoreApp(activity)) {
|
||||
activity.toast(R.string.failure, Toast.LENGTH_LONG)
|
||||
}
|
||||
val apk = StubApk.current(activity)
|
||||
val session = APKInstall.startSession(activity, APP_PACKAGE_NAME, onFailure) {
|
||||
Config.suManager = ""
|
||||
Shell.cmd("touch $AppApkPath").exec()
|
||||
launchApp(activity, APP_PACKAGE_NAME)
|
||||
dialog.dismiss()
|
||||
}
|
||||
val cmd = "adb_pm_install $apk $APP_PACKAGE_NAME"
|
||||
if (Shell.cmd(cmd).await().isSuccess) return
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
apk.inputStream().copyAndClose(session.openStream(activity))
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return@withContext false
|
||||
}
|
||||
session.waitIntent()?.let { activity.startActivity(it) } ?: return@withContext false
|
||||
return@withContext true
|
||||
}
|
||||
if (!success) onFailure.run()
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
suspend fun upgradeStub(context: Context, apk: File): Intent? {
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.unzip
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -47,20 +46,14 @@ open class FlashZip(
|
||||
}
|
||||
}
|
||||
|
||||
val isValid = try {
|
||||
zipFile.unzip(installDir, "META-INF/com/google/android", true)
|
||||
val script = File(installDir, "updater-script")
|
||||
script.readText().contains("#MAGISK")
|
||||
try {
|
||||
val binary = File(installDir, "update-binary")
|
||||
AppContext.assets.open("module_installer.sh").use { it.writeTo(binary) }
|
||||
} catch (e: IOException) {
|
||||
console.add("! Unzip error")
|
||||
throw e
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
console.add("! This zip is not a Magisk module!")
|
||||
return false
|
||||
}
|
||||
|
||||
console.add("- Installing ${mUri.displayName}")
|
||||
|
||||
return Shell.cmd("sh $installDir/update-binary dummy 1 \'$zipFile\'")
|
||||
|
||||
@@ -597,7 +597,9 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
if (result)
|
||||
return true
|
||||
|
||||
Shell.cmd("rm -rf $installDir").submit()
|
||||
// Not every operation initializes installDir
|
||||
if (::installDir.isInitialized)
|
||||
Shell.cmd("rm -rf $installDir").submit()
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toFile
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.unzip
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
|
||||
open class RunAction(
|
||||
private val module: String,
|
||||
private val console: MutableList<String>,
|
||||
private val logs: MutableList<String>
|
||||
) {
|
||||
@Throws(IOException::class)
|
||||
private suspend fun run(): Boolean {
|
||||
return Shell.cmd("run_action \'$module\'").to(console, logs).exec().isSuccess
|
||||
}
|
||||
|
||||
open suspend fun exec() = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (!run()) {
|
||||
console.add("! Run action failed")
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class AXML(b: ByteArray) {
|
||||
* Followed by an array of uint32_t with size = number of strings
|
||||
* Each entry points to an offset into the string data
|
||||
*/
|
||||
fun patchStrings(patchFn: (Array<String>) -> Unit): Boolean {
|
||||
fun patchStrings(mapFn: (String) -> String): Boolean {
|
||||
val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN)
|
||||
|
||||
fun findStringPool(): Int {
|
||||
@@ -65,7 +65,9 @@ class AXML(b: ByteArray) {
|
||||
}
|
||||
|
||||
val strArr = strList.toTypedArray()
|
||||
patchFn(strArr)
|
||||
for (i in strArr.indices) {
|
||||
strArr[i] = mapFn(strArr[i])
|
||||
}
|
||||
|
||||
// Write everything before string data, will patch values later
|
||||
val baos = RawByteStream()
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.topjohnwu.magisk.core.utils;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||
import org.apache.commons.compress.archivers.zip.ZipUtil;
|
||||
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
public class Desugar {
|
||||
public static FileTime getLastModifiedTime(ZipEntry entry) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return entry.getLastModifiedTime();
|
||||
} else {
|
||||
return FileTime.fromMillis(entry.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
public static FileTime getLastAccessTime(ZipEntry entry) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return entry.getLastAccessTime();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static FileTime getCreationTime(ZipEntry entry) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return entry.getCreationTime();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Within {@link ZipArchiveOutputStream#copyFromZipInputStream}, we redirect the method call
|
||||
* {@link ZipUtil#checkRequestedFeatures} to this method. This is safe because the only usage
|
||||
* of copyFromZipInputStream is in {@link ZipArchiveOutputStream#addRawArchiveEntry},
|
||||
* which does not need to actually understand the content of the zip entry. By removing
|
||||
* this feature check, we can modify zip files using unsupported compression methods.
|
||||
*/
|
||||
public static void checkRequestedFeatures(final ZipArchiveEntry ze) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,10 @@ import android.net.NetworkRequest
|
||||
import android.os.PowerManager
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.ktx.registerRuntimeReceiver
|
||||
|
||||
class NetworkObserver(context: Context): DefaultLifecycleObserver {
|
||||
class NetworkObserver(context: Context) {
|
||||
private val manager = context.getSystemService<ConnectivityManager>()!!
|
||||
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
@@ -55,16 +52,13 @@ class NetworkObserver(context: Context): DefaultLifecycleObserver {
|
||||
manager.registerNetworkCallback(request, networkCallback)
|
||||
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
|
||||
context.applicationContext.registerRuntimeReceiver(receiver, filter)
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
postCurrentState()
|
||||
}
|
||||
|
||||
private fun postCurrentState() {
|
||||
postValue(manager.getNetworkCapabilities(manager.activeNetwork)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ?: false)
|
||||
fun postCurrentState() {
|
||||
postValue(
|
||||
manager.getNetworkCapabilities(manager.activeNetwork)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == true
|
||||
)
|
||||
}
|
||||
|
||||
private fun postValue(b: Boolean) {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LifecycleDispatcher;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
// Use Java to bypass Kotlin internal visibility modifier
|
||||
public class ProcessLifecycle {
|
||||
public static void init(@NonNull Context context) {
|
||||
LifecycleDispatcher.init(context);
|
||||
ProcessLifecycleOwner.init$lifecycle_process_release(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import androidx.annotation.Keep
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.Until
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AdditionalTest : BaseTest {
|
||||
|
||||
companion object {
|
||||
private const val SHELL_PKG = "com.android.shell"
|
||||
private const val LSPOSED_CATEGORY = "org.lsposed.manager.LAUNCH_MANAGER"
|
||||
private const val LSPOSED_PKG = "org.lsposed.manager"
|
||||
}
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
device.pressHome()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchLsposedManager() {
|
||||
assumeTrue(Environment.lsposed())
|
||||
|
||||
uiAutomation.executeShellCommand(
|
||||
"am start -c $LSPOSED_CATEGORY $SHELL_PKG/.BugreportWarningActivity"
|
||||
).let { pfd -> AutoCloseInputStream(pfd).use { it.readBytes() } }
|
||||
|
||||
val pattern = Pattern.compile("$LSPOSED_PKG:id/.*")
|
||||
assertNotNull(
|
||||
"LSPosed manager launch failed",
|
||||
device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10))
|
||||
)
|
||||
}
|
||||
}
|
||||
26
app/core/src/main/java/com/topjohnwu/magisk/test/BaseTest.kt
Normal file
26
app/core/src/main/java/com/topjohnwu/magisk/test/BaseTest.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.app.Instrumentation
|
||||
import android.app.UiAutomation
|
||||
import android.content.Context
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
interface BaseTest {
|
||||
val instrumentation: Instrumentation
|
||||
get() = InstrumentationRegistry.getInstrumentation()
|
||||
val context: Context get() = instrumentation.targetContext
|
||||
val uiAutomation: UiAutomation get() = instrumentation.uiAutomation
|
||||
val device: UiDevice get() = UiDevice.getInstance(instrumentation)
|
||||
|
||||
companion object {
|
||||
fun prerequisite() {
|
||||
assertTrue("Should have root access", Shell.getShell().isRoot)
|
||||
// Make sure the root service is running
|
||||
RootUtils.Connection.await()
|
||||
}
|
||||
}
|
||||
}
|
||||
102
app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt
Normal file
102
app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.app.Notification
|
||||
import android.os.Build
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.net.toUri
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadNotifier
|
||||
import com.topjohnwu.magisk.core.download.DownloadProcessor
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.tasks.FlashZip
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import timber.log.Timber
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Environment : BaseTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() = BaseTest.prerequisite()
|
||||
|
||||
fun lsposed(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= 27 && Build.VERSION.SDK_INT <= 34
|
||||
}
|
||||
|
||||
private const val LSPOSED_URL =
|
||||
"https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip"
|
||||
}
|
||||
|
||||
object TimberLog : CallbackList<String>(Runnable::run) {
|
||||
override fun onAddElement(e: String) {
|
||||
Timber.i(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupMagisk() {
|
||||
runBlocking {
|
||||
assertTrue(
|
||||
"Magisk setup failed",
|
||||
MagiskInstaller.Emulator(TimberLog, TimberLog).exec()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupLsposed() {
|
||||
assumeTrue(lsposed())
|
||||
|
||||
val notify = object : DownloadNotifier {
|
||||
override val context = this@Environment.context
|
||||
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {}
|
||||
}
|
||||
val processor = DownloadProcessor(notify)
|
||||
val zip = context.cachedFile("lsposed.zip")
|
||||
runBlocking {
|
||||
ServiceLocator.networkService.fetchFile(LSPOSED_URL).byteStream().use {
|
||||
processor.handleModule(it, zip.toUri())
|
||||
}
|
||||
assertTrue(
|
||||
"LSPosed installation failed",
|
||||
FlashZip(zip.toUri(), TimberLog, TimberLog).exec()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupAppHide() {
|
||||
runBlocking {
|
||||
assertTrue(
|
||||
"App hiding failed",
|
||||
AppMigration.patchAndHide(
|
||||
context = context,
|
||||
label = "Settings",
|
||||
pkg = "repackaged.$APP_PACKAGE_NAME"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupAppRestore() {
|
||||
runBlocking {
|
||||
assertTrue(
|
||||
"App restoration failed",
|
||||
AppMigration.restoreApp(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import androidx.annotation.Keep
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MagiskAppTest : BaseTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() = BaseTest.prerequisite()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZygisk() {
|
||||
assertTrue("Zygisk should be enabled", Info.isZygiskEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSuRequest() {
|
||||
// Bypass the need to actually show a dialog
|
||||
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||
Config.prefs.edit().commit()
|
||||
|
||||
// Inject an undetermined + mute logging policy for ADB shell
|
||||
val policy = SuPolicy(
|
||||
uid = 2000,
|
||||
logging = false,
|
||||
notification = false,
|
||||
remain = 0L
|
||||
)
|
||||
runBlocking {
|
||||
ServiceLocator.policyDB.update(policy)
|
||||
}
|
||||
|
||||
val filter = IntentFilter(Intent.ACTION_VIEW)
|
||||
filter.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
val monitor = instrumentation.addMonitor(filter, null, false)
|
||||
|
||||
// Try to call su from ADB shell
|
||||
val cmd = if (Build.VERSION.SDK_INT < 24) {
|
||||
// API 23 runs executeShellCommand as root
|
||||
"/system/xbin/su 2000 su -c id"
|
||||
} else {
|
||||
"su -c id"
|
||||
}
|
||||
val pfd = uiAutomation.executeShellCommand(cmd)
|
||||
|
||||
// Make sure SuRequestActivity is launched
|
||||
val suRequest = monitor.waitForActivityWithTimeout(TimeUnit.SECONDS.toMillis(10))
|
||||
assertNotNull("SuRequestActivity is not launched", suRequest)
|
||||
|
||||
// Check that the request went through
|
||||
AutoCloseInputStream(pfd).reader().use {
|
||||
assertTrue(
|
||||
"Cannot grant root permission from shell",
|
||||
it.readText().contains("uid=0")
|
||||
)
|
||||
}
|
||||
|
||||
// Check that the database is updated
|
||||
runBlocking {
|
||||
val policy = ServiceLocator.policyDB.fetch(2000)
|
||||
?: throw AssertionError("PolicyDB is invalid")
|
||||
assertEquals("Policy for shell is incorrect", SuPolicy.ALLOW, policy.policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<string name="modules">Módulos</string>
|
||||
<string name="superuser">Superusuariu</string>
|
||||
<string name="logs">Rexistru</string>
|
||||
<string name="settings">Axustes</string>
|
||||
<string name="settings">Configuración</string>
|
||||
<string name="install">Instalar</string>
|
||||
<string name="section_home">Aniciu</string>
|
||||
<string name="section_theme">Estilos</string>
|
||||
@@ -15,7 +15,7 @@
|
||||
<string name="loading">Cargando…</string>
|
||||
<string name="update">Anovar</string>
|
||||
<string name="not_available">N/D</string>
|
||||
<string name="hide">Anubrir</string>
|
||||
<string name="hide">Esconder</string>
|
||||
<string name="home_package">Paquete</string>
|
||||
<string name="home_app_title">Aplicación</string>
|
||||
<string name="home_notice_content">Baxa Magisk NAMÁS dende la páxina oficial de GitHub. ¡Los ficheros de fontes desconocíes puen ser maliciosos!</string>
|
||||
@@ -39,10 +39,10 @@
|
||||
<string name="manager_download_install">Primi equí pa baxalu ya instalalu</string>
|
||||
<string name="direct_install">Instalación direuta (aconséyase)</string>
|
||||
<string name="install_inactive_slot">Instalar na ralura inactiva (darréu del OTA)</string>
|
||||
<string name="install_inactive_slot_msg">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva darréu de reaniciar!\nUsa esta opción namás dempués d\'acabar l\'anovamientu per OTA.\n¿Quies siguir?</string>
|
||||
<string name="install_inactive_slot_msg">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva dempués de reaniciar!\nUsa esta opción namás dempués d\'acabar l\'anovamientu per OTA.\n¿Quies siguir?</string>
|
||||
<string name="setup_title">Configuración adicional</string>
|
||||
<string name="select_patch_file">Seleicionar y parchiar un ficheru</string>
|
||||
<string name="patch_file_msg">Seleiciona una imaxe en bruto (*.img) o un archivu d\'ODIN (*.tar)</string>
|
||||
<string name="patch_file_msg">Seleiciona una imaxe en bruto (*.img), un archivu d\'ODIN (*.tar) o un ficheru payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Reaniciando en 5 segundos…</string>
|
||||
<string name="flash_screen_title">Instalación</string>
|
||||
<!--Superuser-->
|
||||
@@ -81,6 +81,9 @@
|
||||
<string name="logs_cleared">El rexistru borróse correutamente</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">UID de destín: %1$d</string>
|
||||
<string name="target_pid">Mount ns target PID: %s</string>
|
||||
<string name="selinux_context">Contestu de SELinux: %s</string>
|
||||
<string name="supp_group">Grupu suplementariu: %s</string>
|
||||
<!--SafetyNet-->
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Aplicaciones del sistema</string>
|
||||
@@ -94,15 +97,19 @@
|
||||
<string name="reboot_bootloader">Reaniciar al cargador d\'arrinque</string>
|
||||
<string name="reboot_download">Reaniciar al mou de descarga</string>
|
||||
<string name="reboot_edl">Reaniciar al mou EDL</string>
|
||||
<string name="reboot_safe_mode">Mou seguru</string>
|
||||
<string name="module_version_author">%1$s por %2$s</string>
|
||||
<string name="module_state_remove">Quitar</string>
|
||||
<string name="module_action">Aición</string>
|
||||
<string name="module_state_restore">Restaurar</string>
|
||||
<string name="module_action_install_external">Instalar dende l\'almacenamientu</string>
|
||||
<string name="update_available">Hai un anovamientu disponible</string>
|
||||
<string name="suspend_text_riru">Suspendióse\'l módulu porque s\'activó «%1$s»</string>
|
||||
<string name="suspend_text_zygisk">Suspendióse\'l módulu porque nun s\'activó «%1$s»</string>
|
||||
<string name="zygisk_module_unloaded">El módulu de Zygisk nun cargó pola mor d\'haber incompatibilidaes</string>
|
||||
<string name="zygisk_module_unloaded">El módulu de Zygisk nun cargó por haber incompatibilidaes</string>
|
||||
<string name="module_empty">Nun hai nengún módulu instaláu</string>
|
||||
<string name="confirm_install">¿Quies instalar el módulu «%1$s»?</string>
|
||||
<string name="confirm_install_title">Confirmación de la instalación</string>
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Mou del estilu</string>
|
||||
<string name="settings_dark_mode_message">¡Seleiciona\'l mou que meyor s\'adaute al to estilu!</string>
|
||||
@@ -111,7 +118,7 @@
|
||||
<string name="settings_dark_mode_dark">Escuridá</string>
|
||||
<string name="settings_download_path_title">Camín de les descargues</string>
|
||||
<string name="settings_download_path_message">Los ficheros van guardase en «%1$s»</string>
|
||||
<string name="settings_hide_app_title">Anubrir Magisk</string>
|
||||
<string name="settings_hide_app_title">Esconder Magisk</string>
|
||||
<string name="settings_hide_app_summary">Instala una aplicación intermedia con una ID y una etiqueta al debalu</string>
|
||||
<string name="settings_restore_app_title">Restaurar el mou visible</string>
|
||||
<string name="settings_restore_app_summary">Fai que l\'aplicación orixinal vuelva ser visible</string>
|
||||
@@ -149,14 +156,19 @@
|
||||
<string name="auto_response">Rempuesta automática</string>
|
||||
<string name="request_timeout">Tiempu d\'espera de les solicitúes</string>
|
||||
<string name="superuser_notification">Avisu de superusuariu</string>
|
||||
<string name="settings_su_reauth_title">Volver autenticar darréu d\'anovar</string>
|
||||
<string name="settings_su_reauth_title">Volver autenticar dempués d\'anovar</string>
|
||||
<string name="settings_su_reauth_summary">Vuelve pidir los permisos de superusuariu dempués d\'anovar les aplicaciones</string>
|
||||
<string name="settings_su_tapjack_title">Proteición escontra\'l tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">El diálogu de concesión de permisos de superusuariu nun respuende a la entrada mentanto lu torgue otra ventana o superposición</string>
|
||||
<string name="settings_su_auth_title">Autenticación d\'usuariu</string>
|
||||
<string name="settings_su_auth_summary">Pide l\'autenticación demientres les solicitúes de superusuariu</string>
|
||||
<string name="settings_su_auth_insecure">Nun se configuró nengún métodu d\'autenticación nel preséu</string>
|
||||
<string name="settings_customization">Personalización</string>
|
||||
<string name="setting_add_shortcut_summary">Amiesta un atayu a la pantalla d\'aniciu en casu de que\'l nome y l\'iconu seyan difíciles de reconocer darréu d\'anubrir l\'aplicación</string>
|
||||
<string name="setting_add_shortcut_summary">Amiesta un atayu a la pantalla d\'aniciu en casu de que\'l nome y l\'iconu seyan difíciles de reconocer dempués d\'esconder l\'aplicación</string>
|
||||
<string name="settings_doh_title">DNS per HTTPS</string>
|
||||
<string name="settings_doh_description">Una igua alternativa pal envelenamientu de DNS en dalgunos países</string>
|
||||
<string name="settings_random_name_title">Nome de la salida aleatoriu</string>
|
||||
<string name="settings_random_name_description">Fai que\'l nome de ficheru de la salida de les imáxenes parchiaes y los ficheros tar seya aleatoriu pa impidir la deteición</string>
|
||||
<string name="multiuser_mode">Mou de multiusuariu</string>
|
||||
<string name="settings_owner_only">Namás el propietariu del preséu</string>
|
||||
<string name="settings_owner_manage">El propietariu xestionáu del preséu</string>
|
||||
@@ -186,11 +198,14 @@
|
||||
<string name="repo_install_title">Instalación de: %1$s %2$s (%3$d)</string>
|
||||
<string name="download">Baxar</string>
|
||||
<string name="reboot">Reaniciar</string>
|
||||
<string name="close">Zarrar</string>
|
||||
<string name="release_notes">Notes de la versión</string>
|
||||
<string name="flashing">Flaxando…</string>
|
||||
<string name="running">Executando…</string>
|
||||
<string name="done">¡Fecho!</string>
|
||||
<string name="done_action">Completóse l\'aición de: %1$s</string>
|
||||
<string name="failure">¡Falló!</string>
|
||||
<string name="hide_app_title">Anubriendo l\'aplicación Magisk…</string>
|
||||
<string name="hide_app_title">Escondiendo l\'aplicación Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nun s\'atopó nenguna aplicación p\'abrir l\'enllaz</string>
|
||||
<string name="complete_uninstall">Desinstalar dafechu</string>
|
||||
<string name="restore_img">Restaurar les imáxenes</string>
|
||||
@@ -200,6 +215,7 @@
|
||||
<string name="setup_fail">La configuración falló</string>
|
||||
<string name="env_fix_title">Configuración adicional</string>
|
||||
<string name="env_fix_msg">El preséu precisa una configuración adicional pa que Magisk funcione afayadizamente. ¿Quies siguir y reaniciar?</string>
|
||||
<string name="env_full_fix_msg">El preséu precisa volver flaxar Magisk pa que funcione afayadizamente. Volvi instalar Magisk dientro de l\'aplicación porque\'l mou de recuperación nun pue consiguir la información correuta del preséu.</string>
|
||||
<string name="setup_msg">Executando la configuración del entornu…</string>
|
||||
<string name="unsupport_magisk_title">Versión non compatible</string>
|
||||
<string name="unsupport_magisk_msg">Esta versión de l\'aplicación nun ye compatible coles versiones de Magisk anteriores a la %1$s.\n\nL\'aplicación va comportase como si Magisk nun tuviere instaláu, anueva Magisk namás que puedas.</string>
|
||||
@@ -207,12 +223,13 @@
|
||||
<string name="unsupport_system_app_msg">Esta aplicación nun se pue executar nel espaciu del sistema. Volvi instalala mas nel espaciu del usuariu.</string>
|
||||
<string name="unsupport_other_su_msg">Detectóse un binariu «su» que nun ye de Magisk. Quita cualesquier solución de root y/o volvi instalar Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk ta instaláu nel almacenamientu esternu. Movi l\'aplicación al almacenamientu internu, por favor.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Magisk nun pue siguir funcionando nel mou anubríu darréu que se perdió\'l root. Restaura\'l mou visible de l\'aplicación.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Magisk nun pue siguir funcionando nel mou escondíu darréu que se perdió\'l root. Restaura\'l mou visible de l\'aplicación.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Concede\'l permisu d\'almacenamientu p\'activar esta funcionalidá</string>
|
||||
<string name="post_notifications_denied">Concede\'l permisu de los avisos p\'activar esta función</string>
|
||||
<string name="install_unknown_denied">Permite la instalación d\'aplicaciones desconocíes p\'activar esta funcionalidá</string>
|
||||
<string name="add_shortcut_title">Amestar un atayu a la pantalla d\'aniciu</string>
|
||||
<string name="add_shortcut_msg">Darréu d\'anubrir esta aplicación, el so nome ya iconu van ser difíciles de reconocer. ¿Quies amestar un atayu a la pantalla d\'aniciu?</string>
|
||||
<string name="add_shortcut_msg">Dempués d\'esconder esta aplicación, el so nome ya iconu van ser difíciles de reconocer. ¿Quies amestar un atayu a la pantalla d\'aniciu?</string>
|
||||
<string name="app_not_found">Nun s\'atopó nenguna aplicación pa remanar esta aición</string>
|
||||
<string name="reboot_apply_change">Reanicia p\'aplicar los cambeos</string>
|
||||
<string name="restore_app_confirmation">Esta aición va restaurar l\'aplicación orixinal y desanicia la intermedia. ¿De xuru que quies facelo?</string>
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
<string name="section_theme">עיצוב</string>
|
||||
<string name="denylist">רשימת דחייה</string>
|
||||
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">אין חיבור זמין</string>
|
||||
<string name="app_changelog">רשימת שינויים</string>
|
||||
<string name="app_changelog">יומן שינויים</string>
|
||||
<string name="loading">טוען…</string>
|
||||
<string name="update">עדכון</string>
|
||||
<string name="not_available">ל/ז</string>
|
||||
@@ -45,16 +44,16 @@
|
||||
<string name="install_inactive_slot_msg">ההתקן שלך ייאלץ אתחול לחריץ הלא פעיל הנוכחי שלך לאחר הפעלה מחדש!\nיש להשתמש באפשרות זו רק לאחר ביצוע OTA בלבד.\nלהמשיך?</string>
|
||||
<string name="setup_title">התקנה נוספת</string>
|
||||
<string name="select_patch_file">בחירה והתקנת קובץ</string>
|
||||
<string name="patch_file_msg">בחירת תמונה גולמית (*.img) או ODIN קובץ tar (*.tar)</string>
|
||||
<string name="patch_file_msg">בחירת תמונה גולמית (*.img) או ODIN tarfile (*.tar) או payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">מאתחל בעוד 5 שניות…</string>
|
||||
<string name="flash_screen_title">התקנה</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">בקשות משתמש על</string>
|
||||
<string name="touch_filtered_warning">מכיוון שיישום מסתיר בקשה של משתמש על, Magisk לא יכול לאמת את תגובתך</string>
|
||||
<string name="deny">דחה</string>
|
||||
<string name="deny">דחייה</string>
|
||||
<string name="prompt">מיידי</string>
|
||||
<string name="grant">הענק</string>
|
||||
<string name="grant">הענקה</string>
|
||||
<string name="su_warning">מעניק גישה מלאה להתקן שלך.\nיש לדחות באי וודאות!</string>
|
||||
<string name="forever">לצמיתות</string>
|
||||
<string name="once">פעם אחת</string>
|
||||
@@ -62,24 +61,24 @@
|
||||
<string name="twentymin">20 דקות</string>
|
||||
<string name="thirtymin">חצי שעה</string>
|
||||
<string name="sixtymin">שעה</string>
|
||||
<string name="su_allow_toast">%1$s הוענקו הרשאות משתמש עבור</string>
|
||||
<string name="su_deny_toast">%1$s נשללו הרשאות משתמש עבור</string>
|
||||
<string name="su_allow_toast">%1$s קיבל הרשאות משתמש על</string>
|
||||
<string name="su_deny_toast">%1$s נשללו הרשאות משתמש על</string>
|
||||
<string name="su_snack_grant">הרשאות משתמש על עבור %1$s הוענקו</string>
|
||||
<string name="su_snack_deny">הרשאות משתמש על עבור %1$s נשללו</string>
|
||||
<string name="su_snack_notif_on">התראות עבור %1$s פועלות</string>
|
||||
<string name="su_snack_notif_off">התראות עבור %1$s כבויות</string>
|
||||
<string name="su_snack_notif_on">התראות של %1$s מופעלות</string>
|
||||
<string name="su_snack_notif_off">התראות של %1$s מושבתות</string>
|
||||
<string name="su_snack_log_on">יומני רישום עבור %1$s פועלות</string>
|
||||
<string name="su_snack_log_off">יומני רישום עבור %1$s כבויות</string>
|
||||
<string name="su_snack_log_off">יומני רישום עבור %1$s מושבתות</string>
|
||||
<string name="su_revoke_title">להסיר?</string>
|
||||
<string name="su_revoke_msg">נא לאשר שלילת הרשאות עבור %1$s?</string>
|
||||
<string name="toast">הרמת כוסית</string>
|
||||
<string name="none">ללא</string>
|
||||
<string name="superuser_toggle_notification">התראות</string>
|
||||
<string name="superuser_toggle_revoke">הסרה</string>
|
||||
<string name="superuser_policy_none">לא נתבקשו הרשאות משתמש על על ידי שום יישום</string>
|
||||
<string name="superuser_policy_none">טרם נתבקשו הרשאות משתמש על על ידי יישומים</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">הינך ללא יומן רישום, יש לנסות להשתמש ביישומים מותאמים יותר למשתמש העל שלך</string>
|
||||
<string name="log_data_none">הינך ללא יומן, יש לנסות להשתמש יותר ביישומי השורש שלך</string>
|
||||
<string name="log_data_magisk_none">יומני רישום Magisk ריקים, זה מוזר</string>
|
||||
<string name="menuSaveLog">שמירת יומן רישום</string>
|
||||
<string name="menuClearLog">ניקוי יומן רישום כעת</string>
|
||||
@@ -92,7 +91,7 @@
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!-- MagiskHide -->
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">הצגת יישומי מערכת</string>
|
||||
<string name="show_os_app">הצגת יישומי מערכת הפעלה</string>
|
||||
<string name="hide_filter_hint">סינון לפי שם</string>
|
||||
@@ -100,14 +99,16 @@
|
||||
|
||||
<!--Module-->
|
||||
<string name="no_info_provided">(לא סופק מידע)</string>
|
||||
<string name="reboot_userspace">אתחול מהיר</string>
|
||||
<string name="reboot_userspace">אתחול רך</string>
|
||||
<string name="reboot_recovery">אתחול למצב שחזור</string>
|
||||
<string name="reboot_bootloader">אתחול מצב מנהל האתחול</string>
|
||||
<string name="reboot_download">אתחול מצב הורדה</string>
|
||||
<string name="reboot_bootloader">אתחול לטוען האתחול</string>
|
||||
<string name="reboot_download">אתחול למצב הורדה</string>
|
||||
<string name="reboot_edl">אתחול למצב EDL</string>
|
||||
<string name="reboot_safe_mode">מצב בטוח</string>
|
||||
<string name="module_version_author">%1$s מאת %2$s</string>
|
||||
<string name="module_state_remove">הסרה</string>
|
||||
<string name="module_state_restore">שיחזור</string>
|
||||
<string name="module_action">פעולה</string>
|
||||
<string name="module_state_restore">שחזור</string>
|
||||
<string name="module_action_install_external">התקנה מהאחסון</string>
|
||||
<string name="update_available">עדכונים זמינים</string>
|
||||
<string name="suspend_text_riru">מודול מושעה כי %1$s מופעל</string>
|
||||
@@ -117,7 +118,7 @@
|
||||
<string name="confirm_install">להתקין מודול %1$s?</string>
|
||||
<string name="confirm_install_title">אישור התקנה</string>
|
||||
|
||||
<!--Settings -->
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">מצב עיצוב</string>
|
||||
<string name="settings_dark_mode_message">נא לבחור מצב המתאים ביותר לסגנון שלך!</string>
|
||||
<string name="settings_dark_mode_light">תמיד בהיר</string>
|
||||
@@ -128,11 +129,11 @@
|
||||
<string name="settings_hide_app_title">הסתרת היישום Magisk</string>
|
||||
<string name="settings_hide_app_summary">התקנת יישום מתווך עם מזהה חבילה אקראי ותווית שם מותאמת אישית</string>
|
||||
<string name="settings_restore_app_title">שיחזור היישום Magisk</string>
|
||||
<string name="settings_restore_app_summary">יש לבטל את הסתרת היישום ולשחזור אותו ל-APK המקורי</string>
|
||||
<string name="settings_restore_app_summary">ביטול הסתרת היישום ושחזור אל ה-APK המקורי</string>
|
||||
<string name="language">שפה</string>
|
||||
<string name="system_default">(ברירת מחדל מערכת)</string>
|
||||
<string name="settings_check_update_title">בדיקת עדכונים</string>
|
||||
<string name="settings_check_update_summary">בדוק מעת לעת ברקע אם יש עדכונים</string>
|
||||
<string name="settings_check_update_summary">בדיקה מעת לעת ברקע אם יש עדכונים</string>
|
||||
<string name="settings_update_channel_title">ערוץ עדכון</string>
|
||||
<string name="settings_update_stable">יציב</string>
|
||||
<string name="settings_update_beta">בטא</string>
|
||||
@@ -161,12 +162,12 @@
|
||||
<string name="settings_su_request_60">60 שניות</string>
|
||||
<string name="superuser_access">גישת משתמש על</string>
|
||||
<string name="auto_response">תגובה אוטומטית</string>
|
||||
<string name="request_timeout">בקש פסק זמן</string>
|
||||
<string name="request_timeout">בקשת פסק זמן</string>
|
||||
<string name="superuser_notification">התראות משתמש על</string>
|
||||
<string name="settings_su_reauth_title">אימות מחדש לאחר שדרוג</string>
|
||||
<string name="settings_su_reauth_summary">אימות מחדש הרשאות של משתמש על לאחר שדרוג יישום</string>
|
||||
<string name="settings_su_tapjack_title">הפעלת הגנת Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או כיסוי אחר</string>
|
||||
<string name="settings_su_tapjack_title">הגנת Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או שכבת על אחרת</string>
|
||||
<string name="settings_su_auth_title">אימות משתמש</string>
|
||||
<string name="settings_su_auth_summary">בקשת אימות משתמש במהלך בקשות משתמש על</string>
|
||||
<string name="settings_su_auth_insecure">לא מוגדרת שיטת אימות בהתקן</string>
|
||||
@@ -174,6 +175,8 @@
|
||||
<string name="setting_add_shortcut_summary">הוספת קיצור דרך יפה במסך הבית למקרה שקשה לזהות את השם ואת הסמל לאחר הסתרת היישום</string>
|
||||
<string name="settings_doh_title">DNS על HTTPS</string>
|
||||
<string name="settings_doh_description">עקיפת DNS מורעל במדינות מסוימות</string>
|
||||
<string name="settings_random_name_title">שם פלט אקראי</string>
|
||||
<string name="settings_random_name_description">שם אקראי לקובץ הפלט של תמונות מתוקנות וקבצי tar כדי למנוע זיהוי</string>
|
||||
<string name="multiuser_mode">מצב מרובה משתמשים</string>
|
||||
<string name="settings_owner_only">בעל ההתקן בלבד</string>
|
||||
<string name="settings_owner_manage">אחראי ניהול ההתקן</string>
|
||||
@@ -205,10 +208,13 @@
|
||||
<string name="repo_install_title">מתקין %1$s %2$s(%3$d)</string>
|
||||
<string name="download">הורדה</string>
|
||||
<string name="reboot">הפעלה מחדש</string>
|
||||
<string name="close">סגירה</string>
|
||||
<string name="release_notes">הערות שחרור</string>
|
||||
<string name="flashing">צורב…</string>
|
||||
<string name="running">רץ…</string>
|
||||
<string name="done">בוצע!</string>
|
||||
<string name="failure">נכשל</string>
|
||||
<string name="done_action">בוצעה ריצת פעולה של %1$s</string>
|
||||
<string name="failure">נכשל!</string>
|
||||
<string name="hide_app_title">מסתיר את יישום Magisk…</string>
|
||||
<string name="open_link_failed_toast">לא נמצאו יישומים לפתיחת קישור זה</string>
|
||||
<string name="complete_uninstall">הסרה מלאה</string>
|
||||
@@ -237,4 +243,5 @@
|
||||
<string name="app_not_found">לא נמצא יישום לטיפול בפעולה זו</string>
|
||||
<string name="reboot_apply_change">ייש להפעיל מחדש כדי להחיל שינויים</string>
|
||||
<string name="restore_app_confirmation">פעולה זו תשחזר את היישום המוסתר חזרה ליישום המקורי. האם בוודאות ברצונך לעשות את זה?</string>
|
||||
|
||||
</resources>
|
||||
|
||||
252
app/core/src/main/res/values-ku/strings.xml
Normal file
252
app/core/src/main/res/values-ku/strings.xml
Normal file
@@ -0,0 +1,252 @@
|
||||
<resources>
|
||||
|
||||
<!--Sections-->
|
||||
<string name="modules">زیادکراوەکان</string>
|
||||
<string name="superuser">سوپەر یوسەر</string>
|
||||
<string name="logs">تۆمارەکان</string>
|
||||
<string name="settings">ڕێکخستنەکان</string>
|
||||
<string name="install">دامەزراندن</string>
|
||||
<string name="section_home">ماڵەوە</string>
|
||||
<string name="section_theme">ڕووکارەکان</string>
|
||||
<string name="denylist">پێڕستی ڕێگەپێنەدراوەکان</string>
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">هێڵ بەردەست نییە</string>
|
||||
<string name="app_changelog">گۆڕانکارییەکان</string>
|
||||
<string name="loading">کردنەوە…</string>
|
||||
<string name="update">بەرزکردنەوە</string>
|
||||
<string name="not_available">نییە</string>
|
||||
<string name="hide">شاردنەوە</string>
|
||||
<string name="home_package">پاکێج</string>
|
||||
<string name="home_app_title">ئەپ</string>
|
||||
|
||||
<string name="home_notice_content">تەنها لە گیتهەبی فەرمی ماجیسک دابگرە، لە شوێنی تر لەوانەیە زیانبەخش بێت</string>
|
||||
<string name="home_support_title">پشتگیریمان بکە</string>
|
||||
<string name="home_follow_title">شوێنمان بکەوە</string>
|
||||
<string name="home_item_source">سەرچاوە</string>
|
||||
<string name="home_support_content">ماجیسک بە خۆڕاییە و هەر واش ئەمێنێتەوە، بەهەرحاڵ ئەتوانیت پشتگیرییەکمان بکەی بۆ گرنگی پێدان</string>
|
||||
<string name="home_installed_version">داگیراوە</string>
|
||||
<string name="home_latest_version">دوایین وەشان</string>
|
||||
<string name="invalid_update_channel">کەناڵێکی نوێکردنەوەی هەڵە</string>
|
||||
<string name="uninstall_magisk_title">سڕینەوەی ماجیسک</string>
|
||||
<string name="uninstall_magisk_msg">هەموو زیادکراوەکان دەسڕێنەوە، ڕۆت دەسڕێتەوە، و هەر ڕەمزێنراوێک بەهۆی ماجیسک کرابێ لادەچێت!</string>
|
||||
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">ڕەمزاندنی بەزۆر بهێڵەوە</string>
|
||||
<string name="keep_dm_verity">بهێڵەوە AVB 2.0/dm-verity</string>
|
||||
<string name="recovery_mode">دۆخی ڕیکەڤەڕی</string>
|
||||
<string name="install_options_title">هەڵبژاردنەکان</string>
|
||||
<string name="install_method_title">ڕێگای</string>
|
||||
<string name="install_next">دواتر</string>
|
||||
<string name="install_start">با بیکەین</string>
|
||||
<string name="manager_download_install">بۆ داگرتن و ڕێکخستن کرتە بکە</string>
|
||||
<string name="direct_install">داگرتنی ڕاستەوخۆ(پێشنیارکراوە)</string>
|
||||
<string name="install_inactive_slot">دایبگرە بۆ خانەی ناچالاک(پاش OTA)</string>
|
||||
<string name="install_inactive_slot_msg">ئێستا ئامێرەکەت دەچێتە خانە ناچالاکەکە، ئەمە بەکاربهێنە تەنها دوای ئەپدەیت کردن لە ڕێگەی OTA، بەردەوام دەبیت؟</string>
|
||||
<string name="setup_title">ڕێکخستنی زیاتر</string>
|
||||
<string name="select_patch_file">فایلێک هەڵبژێرە و پینەی بکە</string>
|
||||
<string name="patch_file_msg">تکایە فایلێکی Tar یان img یان payload.bin هەڵبژێرە</string>
|
||||
<string name="reboot_delay_toast">ڕێستارت کردنەوە لە ماوەی ٥ چرکە…</string>
|
||||
<string name="flash_screen_title">ڕێکخستن</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">داواکاری سوپەریوسەر</string>
|
||||
<string name="touch_filtered_warning">ئەپێک لە سەر شاشەکەیە، ناتوانین دڵنیا بینەوە</string>
|
||||
<string name="deny">ڕەتکردنەوە</string>
|
||||
<string name="prompt">داواکاری</string>
|
||||
<string name="grant">ڕێگەپێدان</string>
|
||||
<string name="su_warning">ڕێگەپێدان بۆ تەواوی ئامێرەکەت، گەر دڵنیا نیت ڕەتی بکەوە</string>
|
||||
<string name="forever">بۆ هەمیشە</string>
|
||||
<string name="once">بۆ یەکجار</string>
|
||||
<string name="tenmin">بۆ ١٠ خولەک</string>
|
||||
<string name="twentymin">بۆ ٢٠ خولەک</string>
|
||||
<string name="thirtymin">بۆ ٣٠ خولەک</string>
|
||||
<string name="sixtymin">بۆ ٦٠ خولەک</string>
|
||||
<string name="su_allow_toast">%1$s ڕێگەپێدانی سوپەریوسەری بۆ زیادکرا</string>
|
||||
<string name="su_deny_toast">%1$s ڕێگەپێدانی سوپەریوسەر ڕەتکرایەوە</string>
|
||||
<string name="su_snack_grant">ڕێگەپێدانی سوپەریوسەری %1$s بۆ درا</string>
|
||||
<string name="su_snack_deny">ڕێگەپێدانی سوپەریوسەر %1$s ڕەتکرایەوە</string>
|
||||
<string name="su_snack_notif_on">ئاگەدارکردنەوەکانی %1$s کارا کراوە</string>
|
||||
<string name="su_snack_notif_off">ئاگەدارکردنەوەکانی %1$s کوژاوەتەوە</string>
|
||||
<string name="su_snack_log_on">تۆمارەکانی %1$s کراوەتەوە</string>
|
||||
<string name="su_snack_log_off">تۆمارەکانی %1$s کوژاوەتەوە</string>
|
||||
<string name="su_revoke_title">لابردن؟</string>
|
||||
<string name="su_revoke_msg">دڵنیابەوە بۆ لابردنی سوپەریوسەر بۆ %1$s </string>
|
||||
<string name="toast">هێنانەسەر</string>
|
||||
<string name="none">هیچ</string>
|
||||
|
||||
<string name="superuser_toggle_notification">ئاگادارییەکان</string>
|
||||
<string name="superuser_toggle_revoke">لابردن</string>
|
||||
<string name="superuser_policy_none">هیچ ئەپێک تا ئێستا داوای سوپەریوسەری نەکردووە</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">هیچ تۆمارێک نییە، ئەو ئەپانەی ڕۆتیان پێویستە زوزو بەکاریبێنە</string>
|
||||
<string name="log_data_magisk_none">تۆمارەکانی ماجیسک بەتاڵن، باشە بۆ؟</string>
|
||||
<string name="menuSaveLog">تۆمارەکان هەڵبگرە</string>
|
||||
<string name="menuClearLog">تۆمارەکان بسڕەوە</string>
|
||||
<string name="logs_cleared">بەسەرکەوتویی تۆمارەکان سڕانەوە</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">ئامانج UID: %1$d</string>
|
||||
<string name="target_pid">Mount ns target PID: %s</string>
|
||||
<string name="selinux_context">SELinux context: %s</string>
|
||||
<string name="supp_group">Supplementary group: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">پیشاندانی ئەپەکانی سیستەم</string>
|
||||
<string name="show_os_app">پیشاندانی ئەپەکان</string>
|
||||
<string name="hide_filter_hint">پاڵاوتنی بەپێی ناو</string>
|
||||
<string name="hide_search">گەڕان</string>
|
||||
|
||||
<!--Module-->
|
||||
<string name="no_info_provided">(هیچ زانیارییەک نییە)</string>
|
||||
<string name="reboot_userspace">ڕێستارت کردنەوە</string>
|
||||
<string name="reboot_recovery">چوونە ناو ڕیکەڤەری</string>
|
||||
<string name="reboot_bootloader">چوونە ناو بووتلۆدەر</string>
|
||||
<string name="reboot_download">چوونە ناو داونلۆد</string>
|
||||
<string name="reboot_edl">چوونە ناو EDL</string>
|
||||
<string name="reboot_safe_mode">دۆخی پارێزراو</string>
|
||||
<string name="module_version_author">%1$s by %2$s</string>
|
||||
<string name="module_state_remove">سڕینەوە</string>
|
||||
<string name="module_action">کارا</string>
|
||||
<string name="module_state_restore">گەڕاندنەوە</string>
|
||||
<string name="module_action_install_external">لە بیرگەکەتەوە ڕێکی بخە</string>
|
||||
<string name="update_available">وەشانی نوێ بەردەستە</string>
|
||||
<string name="suspend_text_riru">زیادکراوەکە کار ناکات چونکە %1$s کراوەتەوە</string>
|
||||
<string name="suspend_text_zygisk">زیادکراوەکە کارناکات چونکە %1$s نەکراوەتەوە</string>
|
||||
<string name="zygisk_module_unloaded">زیادکراوی Zygisk بەهۆی نەگونجان کارناکات</string>
|
||||
<string name="module_empty">هیچ زیادکراوێک دانەبەزیوە</string>
|
||||
<string name="confirm_install">دابەزاندنی زیادکراو %1$s?</string>
|
||||
<string name="confirm_install_title">دڵنیابوونەوە لە دابەزاندن</string>
|
||||
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">جۆری ڕووکار</string>
|
||||
<string name="settings_dark_mode_message">حەزت لە کامەی بوو ئەوە هەڵبژێرە</string>
|
||||
<string name="settings_dark_mode_light">هەمیشە دۆخی ڕوناک</string>
|
||||
<string name="settings_dark_mode_system">با بەگوێرەی سیستەمەکە بێت!</string>
|
||||
<string name="settings_dark_mode_dark">هەمیشە دۆخی تاریک</string>
|
||||
<string name="settings_download_path_title">شوێنی داگرتنەکە</string>
|
||||
<string name="settings_download_path_message">فایلەکان هەڵدەگیرێن لە %1$s</string>
|
||||
<string name="settings_hide_app_title">شاردنەوەی ئەپی ماجیسک</string>
|
||||
<string name="settings_hide_app_summary">داگرتنی ماجیسک بە ناوی جیاوە</string>
|
||||
<string name="settings_restore_app_title">ئەپە ڕەسەنەکە بهێنەوە</string>
|
||||
<string name="settings_restore_app_summary">ئەپەکە دەربخەوە و ڕەسەنڵ</string>
|
||||
<string name="language">زمان</string>
|
||||
<string name="system_default">(وەک هی ئامێرەکە)</string>
|
||||
<string name="settings_check_update_title">گەڕان بەدوای نوێکاری</string>
|
||||
<string name="settings_check_update_summary">گەڕان بەدوای نوێکاری خۆکارانە</string>
|
||||
<string name="settings_update_channel_title">کەناڵی نوێکاری</string>
|
||||
<string name="settings_update_stable">جێگیر</string>
|
||||
<string name="settings_update_beta">پێشوەختە(بێتا)</string>
|
||||
<string name="settings_update_custom">تایبەت</string>
|
||||
<string name="settings_update_custom_msg">بەستەرێکی تایبەت دابنێ</string>
|
||||
<string name="settings_zygisk_summary">کارپێکردنی بەشێکی ماجیسک لە zygote daemon</string>
|
||||
<string name="settings_denylist_title">پێڕستی نەرێنی کراوەکان</string>
|
||||
<string name="settings_denylist_summary">هەر ئەپێک لە پێڕستی نەرێنییەکان کاریگەریەکانی ماجیسکی لەسەر نییە</string>
|
||||
<string name="settings_denylist_config_title">دەستکاریکردنی پێڕستی نەرێنیکراوەکان</string>
|
||||
<string name="settings_denylist_config_summary">ئەو ئەپە هەڵبژێرە کە دەتەوێت نەرێنیی بکەیت</string>
|
||||
<string name="settings_hosts_title">هۆستی ناسیستەمی</string>
|
||||
<string name="settings_hosts_summary"> هۆستی ناسیستەمی بۆ لابردنی ڕیکلامەکان</string>
|
||||
<string name="settings_hosts_toast"> هۆستی ناسیستەمی زیادکرا</string>
|
||||
<string name="settings_app_name_hint">ناوی نوێ</string>
|
||||
<string name="settings_app_name_helper">ئەپەکە بەم ناوەوە دروست دەکرێتەوە</string>
|
||||
<string name="settings_app_name_error">هەڵەیە</string>
|
||||
<string name="settings_su_app_adb">ئەپەکان و ADB</string>
|
||||
<string name="settings_su_app">تەنها ئەپەکان</string>
|
||||
<string name="settings_su_adb">ADB تەنها</string>
|
||||
<string name="settings_su_disable">ناچالاک کراوە</string>
|
||||
<string name="settings_su_request_10">10 چرکە</string>
|
||||
<string name="settings_su_request_15">15 چرکە</string>
|
||||
<string name="settings_su_request_20">20 چرکە</string>
|
||||
<string name="settings_su_request_30">30 چرکە</string>
|
||||
<string name="settings_su_request_45">45 چرکە</string>
|
||||
<string name="settings_su_request_60">60 چرکە</string>
|
||||
<string name="superuser_access">دەسەڵاتی سوپەریوسەر</string>
|
||||
<string name="auto_response">وەڵامدانەوەی خۆکارانە</string>
|
||||
<string name="request_timeout">ماوەی وەڵامدانەوە</string>
|
||||
<string name="superuser_notification">ئاگەدارییەکانی سوپەریوسەر</string>
|
||||
<string name="settings_su_reauth_title">پرسیاربکەوە دوای هەر نوێکردنەوەیەک</string>
|
||||
<string name="settings_su_reauth_summary">دوای نوێکردنەوەی ئەپەکان دووبارە پرسیار بکەوە بۆ دەسەڵاتی سوپەریوسەر</string>
|
||||
<string name="settings_su_tapjack_title">پارێزگاری کردن لە ئەگەری دەستلێدانی تر</string>
|
||||
<string name="settings_su_tapjack_summary"> کاتێک ئەپێکی تر بەسەر شاشەکەوەیە، سوپەر یوسەر وەڵام ناداتەوە لە کردنی هەر بژاردەیەک لەبەر پارێزراوی</string>
|
||||
<string name="settings_su_auth_title">دڵنیاکردنەوەی کەسی</string>
|
||||
<string name="settings_su_auth_summary">داواکاری بکە بۆ دڵنیاکردنەوەی کەسی لەکاتی داواکاری سوپەریوسەر</string>
|
||||
<string name="settings_su_auth_insecure">هیچ دڵنیاکردنەوەیەک نییە</string>
|
||||
<string name="settings_customization">دەستکاریکردن</string>
|
||||
<string name="setting_add_shortcut_summary">یەک ئایکۆنی جوان زیادبکە بۆ سەر شاشەکە ئەگەر قورس بوو ئەوەی خۆی بدۆزیتەوە</string>
|
||||
<string name="settings_doh_title">DNS بەسەر HTTPS</string>
|
||||
<string name="settings_doh_description">Workaround DNS خراپە لە هەندێک شوێن</string>
|
||||
<string name="settings_random_name_title">ناوێک لەخۆیەوە</string>
|
||||
<string name="settings_random_name_description">دانانی ناوێک لەخۆوە تاوەکوو ئاشکرا نەبێت</string>
|
||||
|
||||
<string name="multiuser_mode">دۆخی فرەبەکارهێنەر</string>
|
||||
<string name="settings_owner_only">تەنها خاوەنی ئامێر</string>
|
||||
<string name="settings_owner_manage">خاوەنی ئامێر</string>
|
||||
<string name="settings_user_independent">بەکارهێنەری سەربەخۆ</string>
|
||||
<string name="owner_only_summary">تەنها خاوەنەکە دۆخی ڕۆتی هەیە</string>
|
||||
<string name="owner_manage_summary">تەنها خاوەنەکە دەسەڵاتی بەکارهێنانی ڕۆتی هەیە</string>
|
||||
<string name="user_independent_summary">هەر بەکارهێنەرێک یاسای جیاوازی هەیە</string>
|
||||
|
||||
<string name="mount_namespace_mode">چونە دۆخی بۆشایی ناو</string>
|
||||
<string name="settings_ns_global">بۆشاییناوی گشتی</string>
|
||||
<string name="settings_ns_requester">بۆشایی ناوی خۆیی</string>
|
||||
<string name="settings_ns_isolate">بۆشایی ناوی جیا</string>
|
||||
<string name="global_summary">هەمو ڕۆتەکان ناوی گشتی بەکار ئەهێنن</string>
|
||||
<string name="requester_summary">هەمو ڕۆتەکان ناوی خۆیی بەکار ئەهێنن</string>
|
||||
<string name="isolate_summary">هەمو ڕۆتەکان ناوی جیا بەکار ئەهێنن</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">نوێکردنەوەکانی ماجیسک</string>
|
||||
<string name="progress_channel">ئاگادارییە کاراکان</string>
|
||||
<string name="updated_channel">نوێکردنەوە سەرکەوتووبوو</string>
|
||||
<string name="download_complete">داگرتن سەرکەوتووبوو</string>
|
||||
<string name="download_file_error">هەڵەیەک رووی دا لەکاتی داگرتنی فایلەکە</string>
|
||||
<string name="magisk_update_title">وەشانی نوێی ماجیسک ئامادەیە!</string>
|
||||
<string name="updated_title">ماجیسک نوێکراوە!</string>
|
||||
<string name="updated_text">کرتە بکە بۆ کردنەوەی ئەپ</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">بەڵێ</string>
|
||||
<string name="no">نەخێر</string>
|
||||
<string name="repo_install_title">داگرتن %1$s %2$s(%3$d)</string>
|
||||
<string name="download">داگرتن</string>
|
||||
<string name="reboot">ڕێستارت</string>
|
||||
<string name="close">داخستن</string>
|
||||
<string name="release_notes">نێبینییەکان</string>
|
||||
<string name="flashing">فلاش کردن</string>
|
||||
<string name="running">کار کردن...</string>
|
||||
<string name="done">تەواو!</string>
|
||||
<string name="done_action">کارکردنی %1$s تەواو بوو</string>
|
||||
<string name="failure">Failed!</string>
|
||||
<string name="hide_app_title">شاردنەوەی ئەپی ماجیسک…</string>
|
||||
<string name="open_link_failed_toast">هیچ ئەپێک تییە تا لینکەکەی پێ بکرێتەوە</string>
|
||||
<string name="complete_uninstall">سڕینەوەی تەواوی</string>
|
||||
<string name="restore_img">هێنانەوەی img</string>
|
||||
<string name="restore_img_msg">هێنانەوە…</string>
|
||||
<string name="restore_done">هاتەوە!</string>
|
||||
<string name="restore_fail">هیچ فایلێکی هەڵگیراوت نیە!</string>
|
||||
<string name="setup_fail">ڕێکخستن شکستی هێنا</string>
|
||||
<string name="env_fix_title">پێویستی بە ڕێکخستنی زیاترە</string>
|
||||
<string name="env_fix_msg">مۆبایلەکەت پێویستی بە ڕێکخستنی زیاترە، ئایا ئەتەوێت بەردەوام بیت و ڕێستارتی بکەیتەوە؟</string>
|
||||
<string name="env_full_fix_msg"> پێویستە دوبارە ماجیسک دابگریتەوە بۆ ئەوەی بەباشی کاربکات تکایە ماجیسک دابگرەوە لەناو ئەپەکە خۆی چونکە لە ڕیکەڤەرییەوە ناتوانرێ زانیاری تەواو لەسەر ئامێرەکە دەستبخرێت </string>
|
||||
<string name="setup_msg">دەستپێکردن....</string>
|
||||
<string name="unsupport_magisk_title">وەشانی ماجیسک پاڵپشتینەکراوە</string>
|
||||
<string name="unsupport_magisk_msg">وەشانی ماجیسکەکەت زۆر کۆنە وەک ئەوە وایە هەر نەبێت، تکایە نوێی بکەوە بە زوترین کات</string>
|
||||
<string name="unsupport_general_title">باری نائاسایی</string>
|
||||
<string name="unsupport_system_app_msg">ئەم ئەپە وەکو ئەپی سیستەم کارناکات، تکایە بیگۆڕەوە بۆ ئەپی ئاسایی</string>
|
||||
<string name="unsupport_other_su_msg"> \"su\" binary یەکی بێگانە دۆزرایەوە، تکایە جگە لە ماجیسک ئەپی تر بەکارمەهێنە بۆ ڕۆت کردن </string>
|
||||
<string name="unsupport_external_storage_msg">ماجیسک لە بیرگەی دەرەکی داگیراوە، تکایە بیبەوە بۆ ناوەکی</string>
|
||||
<string name="unsupport_nonroot_stub_msg">ئەپە شاراوەکە کار ناکات چونکە ڕۆتەکە نەماوە، تکایە ئەپە ڕەسەنەکە بگەڕێنەوە</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">ڕەزامەندی بیرگە بدە تاوەکوو ئەمە کار بکات</string>
|
||||
<string name="post_notifications_denied">ڕەزامەندی ئاگاداری بکە تاوەکوو ئەمە کار بکات</string>
|
||||
<string name="install_unknown_denied">ڕەزامەندی "install unknown apps" تاوەکوو کار بکات</string>
|
||||
<string name="add_shortcut_title">زیادی بکە بۆ سەر شاشە</string>
|
||||
<string name="add_shortcut_msg">دوای شاردنەوەی ئەم ئەپە، ئەتەوێت یەک ئایکۆنی جوان زیادبکەیت بۆ سەر شاشەکە ئەگەر قورس بوو ئەوەی خۆی بدۆزیتەوە؟</string>
|
||||
<string name="app_not_found">هیچ ئەپێک نەدۆزرایەوە تاوەکوو ئەم کارەی پێ بکرێت</string>
|
||||
<string name="reboot_apply_change">ڕێستارت بکە تاوەکوو کاریگەریەکان کار بکەن</string>
|
||||
<string name="restore_app_confirmation">ئەمە ئەپە ڕەسەنەکە ئەهێنێتەوە، دڵنیایت لە کردنی؟</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
<string name="logs_cleared">Логи успешно очищены</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Целевой UID: %1$d</string>
|
||||
<string name="target_pid">Целевой PID пространства имён: %s</string>
|
||||
<string name="selinux_context">Контекст SELinux: %s</string>
|
||||
<string name="supp_group">Дополнительная группа: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
@@ -164,11 +167,16 @@
|
||||
<string name="settings_su_reauth_title">Повторная аутентификация</string>
|
||||
<string name="settings_su_reauth_summary">Повторный запрос прав суперпользователя после обновления приложений</string>
|
||||
<string name="settings_su_tapjack_title">Защита от перехвата нажатий</string>
|
||||
<string name="settings_su_auth_title">Аутентификация пользователя</string>
|
||||
<string name="settings_su_auth_summary">Требовать аутентификацию пользователя при запросах Superuser</string>
|
||||
<string name="settings_su_auth_insecure">На устройстве не настроен метод аутентификации</string>
|
||||
<string name="settings_su_tapjack_summary">Окно запроса прав суперпользователя будет неактивно пока активированы наложения экрана</string>
|
||||
<string name="settings_customization">Персонализация</string>
|
||||
<string name="setting_add_shortcut_summary">Добавить ярлык на рабочий стол для удобного восприятия приложения после его скрытия</string>
|
||||
<string name="settings_doh_title">DNS поверх HTTPS</string>
|
||||
<string name="settings_doh_description">Активировать DoH (используйте при проблемах с подключением к сети)</string>
|
||||
<string name="settings_random_name_title">Случайное имя образа</string>
|
||||
<string name="settings_random_name_description">Генерировать случайные имена для патченных образов и tar-файлов для предотвращения обнаружения</string>
|
||||
|
||||
<string name="multiuser_mode">Многопользовательский режим</string>
|
||||
<string name="settings_owner_only">Только администратор</string>
|
||||
|
||||
@@ -130,8 +130,8 @@
|
||||
<string name="settings_update_custom">Власний</string>
|
||||
<string name="settings_update_custom_msg">Вставте власний URL</string>
|
||||
<string name="settings_zygisk_summary">Запускати частини Magisk в сервісі zygote</string>
|
||||
<string name="settings_denylist_title">Enforce DenyList</string>
|
||||
<string name="settings_denylist_summary">Processes on the denylist will have all Magisk modifications reverted</string>
|
||||
<string name="settings_denylist_title">Увімкнути DenyList</string>
|
||||
<string name="settings_denylist_summary">Всі зміни, внесені Magisk, будуть приховані від процесів, позначених у DenyList</string>
|
||||
<string name="settings_denylist_config_title">Налаштувати DenyList</string>
|
||||
<string name="settings_denylist_config_summary">Вибрати процеси, які будуть додані до denylist</string>
|
||||
<string name="settings_hosts_title">Позасистемні хости</string>
|
||||
@@ -156,7 +156,7 @@
|
||||
<string name="superuser_notification">Сповіщення суперкористувача</string>
|
||||
<string name="settings_su_reauth_title">Повторна автентифікація</string>
|
||||
<string name="settings_su_reauth_summary">Перевидача прав суперкористувача після оновлення застосунку</string>
|
||||
<string name="settings_su_tapjack_title">Увімкнути захист від Tapjack</string>
|
||||
<string name="settings_su_tapjack_title">Увімкнути захист від підміни натискань</string>
|
||||
<string name="settings_su_tapjack_summary">Діалогове вікно суперкористувача не буде отримувати ввід від користувача, коли вікно перекрито іншим застосунком чи вікном</string>
|
||||
<string name="settings_customization">Оформлення</string>
|
||||
<string name="setting_add_shortcut_summary">Додати ярлик на домашній екран для зручного сприйняття застосунку після його приховування</string>
|
||||
|
||||
@@ -12,7 +12,7 @@ import dalvik.system.BaseDexClassLoader;
|
||||
public class DynamicClassLoader extends BaseDexClassLoader {
|
||||
|
||||
public DynamicClassLoader(File apk) {
|
||||
this(apk, getSystemClassLoader());
|
||||
this(apk, DynamicClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
public DynamicClassLoader(File apk, ClassLoader parent) {
|
||||
|
||||
@@ -15,7 +15,7 @@ android {
|
||||
val canary = !Config.version.contains(".")
|
||||
|
||||
val url = if (canary) null
|
||||
else "https://cdn.jsdelivr.net/gh/topjohnwu/magisk-files@${Config.version}/app-release.apk"
|
||||
else "https://github.com/topjohnwu/Magisk/releases/download/v${Config.version}/Magisk-v${Config.version}.apk"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
@@ -27,9 +27,9 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
proguardFiles("proguard-rules.pro")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = false
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
setupStub()
|
||||
setupStubApk()
|
||||
|
||||
dependencies {
|
||||
implementation(project(":app:shared"))
|
||||
|
||||
7
app/stub/src/main/res/values-ku/strings.xml
Normal file
7
app/stub/src/main/res/values-ku/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="upgrade_msg">ماجیسکەکەت بەرزبکەوە بۆ وەشانی تەواوەتی، دەتەوێت دایبگریت و ڕێکیبخەیت؟</string>
|
||||
<string name="no_internet_msg">تکایە پەیوەست ببە بە ئینتەرنێتەوە، پێویستە ماجیسکەکەت ڕێک بخەیت.</string>
|
||||
<string name="dling">داگرتن</string>
|
||||
<string name="relaunch_app">تکایە دووبارە ئەپەکە بکەوە</string>
|
||||
</resources>
|
||||
1
app/test/.gitignore
vendored
Normal file
1
app/test/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
30
app/test/build.gradle.kts
Normal file
30
app/test/build.gradle.kts
Normal file
@@ -0,0 +1,30 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk.test"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk.test"
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
proguardFile("proguard-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupAppCommon()
|
||||
|
||||
dependencies {
|
||||
implementation(libs.test.runner)
|
||||
implementation(libs.test.rules)
|
||||
implementation(libs.test.junit)
|
||||
implementation(libs.test.uiautomator)
|
||||
}
|
||||
13
app/test/proguard-rules.pro
vendored
Normal file
13
app/test/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Keep all test dependencies
|
||||
-keep class org.junit.** { *; }
|
||||
-keep class androidx.test.** { *; }
|
||||
|
||||
# Make sure the classloader constructor is kept
|
||||
-keepclassmembers class com.topjohnwu.magisk.test.TestClassLoader { <init>(); }
|
||||
|
||||
# Repackage dependencies
|
||||
-repackageclasses 'deps'
|
||||
-allowaccessmodification
|
||||
|
||||
# Keep attributes for stacktrace
|
||||
-keepattributes *
|
||||
23
app/test/src/main/AndroidManifest.xml
Normal file
23
app/test/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<queries tools:node="removeAll" />
|
||||
|
||||
<application tools:node="replace">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:name="com.topjohnwu.magisk.test.AppTestRunner"
|
||||
android:targetPackage="com.topjohnwu.magisk" />
|
||||
|
||||
<instrumentation
|
||||
android:name="com.topjohnwu.magisk.test.TestRunner"
|
||||
android:targetPackage="com.topjohnwu.magisk.test" />
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import androidx.annotation.Keep
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AppMigrationTest {
|
||||
|
||||
companion object {
|
||||
private const val APP_PKG = "com.topjohnwu.magisk"
|
||||
private const val STUB_PKG = "repackaged.$APP_PKG"
|
||||
private const val RECEIVER_TIMEOUT = 20L
|
||||
}
|
||||
|
||||
private val instrumentation get() = InstrumentationRegistry.getInstrumentation()
|
||||
private val context get() = instrumentation.context
|
||||
private val uiAutomation get() = instrumentation.uiAutomation
|
||||
private val registeredReceivers = mutableListOf<BroadcastReceiver>()
|
||||
|
||||
class PackageRemoveMonitor(
|
||||
context: Context,
|
||||
private val packageName: String
|
||||
) : BroadcastReceiver() {
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
init {
|
||||
val filter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED)
|
||||
filter.addDataScheme("package")
|
||||
context.registerReceiver(this, filter)
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != Intent.ACTION_PACKAGE_REMOVED)
|
||||
return
|
||||
val data = intent.data ?: return
|
||||
val pkg = data.schemeSpecificPart
|
||||
if (pkg == packageName) latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
registeredReceivers.forEach(context::unregisterReceiver)
|
||||
}
|
||||
|
||||
private fun testAppMigration(pkg: String, method: String) {
|
||||
val receiver = PackageRemoveMonitor(context, pkg)
|
||||
registeredReceivers.add(receiver)
|
||||
|
||||
// Trigger the test to run migration
|
||||
val pfd = uiAutomation.executeShellCommand(
|
||||
"am instrument -w --user 0 -e class .Environment#$method " +
|
||||
"$pkg.test/${AppTestRunner::class.java.name}"
|
||||
)
|
||||
val output = AutoCloseInputStream(pfd).reader().use { it.readText() }
|
||||
assertTrue("$method failed, inst out: $output", output.contains("OK ("))
|
||||
|
||||
// Wait for migration to complete
|
||||
assertTrue(
|
||||
"$pkg uninstallation failed",
|
||||
receiver.latch.await(RECEIVER_TIMEOUT, TimeUnit.SECONDS)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAppHide() {
|
||||
testAppMigration(APP_PKG, "setupAppHide")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAppRestore() {
|
||||
testAppMigration(STUB_PKG, "setupAppRestore")
|
||||
}
|
||||
}
|
||||
35
app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt
Normal file
35
app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.runner.AndroidJUnitRunner
|
||||
|
||||
open class TestRunner : AndroidJUnitRunner() {
|
||||
override fun onCreate(arguments: Bundle) {
|
||||
// Support short-hand ".ClassName"
|
||||
arguments.getString("class")?.let {
|
||||
val classArg = it.split(",").joinToString(separator = ",") { clz ->
|
||||
if (clz.startsWith(".")) {
|
||||
"com.topjohnwu.magisk.test$clz"
|
||||
} else {
|
||||
clz
|
||||
}
|
||||
}
|
||||
arguments.putString("class", classArg)
|
||||
}
|
||||
super.onCreate(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
class AppTestRunner : TestRunner() {
|
||||
override fun onCreate(arguments: Bundle) {
|
||||
// Force using the target context's classloader to run tests
|
||||
arguments.putString("classLoader", TestClassLoader::class.java.name)
|
||||
super.onCreate(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
private val targetClassLoader inline get() =
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.classLoader
|
||||
|
||||
class TestClassLoader : ClassLoader(targetClassLoader)
|
||||
41
build.py
41
build.py
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import copy
|
||||
import glob
|
||||
import lzma
|
||||
import multiprocessing
|
||||
@@ -218,8 +219,6 @@ def run_ndk_build(cmds: list):
|
||||
|
||||
|
||||
def build_cpp_src(targets: set):
|
||||
dump_flag_header()
|
||||
|
||||
cmds = []
|
||||
clean = False
|
||||
|
||||
@@ -335,7 +334,11 @@ def dump_flag_header():
|
||||
flag_txt += f"#define MAGISK_DEBUG {0 if args.release else 1}\n"
|
||||
|
||||
native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
write_if_diff(Path(native_gen_path, "flags.h"), flag_txt)
|
||||
write_if_diff(native_gen_path / "flags.h", flag_txt)
|
||||
|
||||
rust_flag_txt = f'pub const MAGISK_VERSION: &str = "{config["version"]}";\n'
|
||||
rust_flag_txt += f'pub const MAGISK_VER_CODE: i32 = {config["versionCode"]};\n'
|
||||
write_if_diff(native_gen_path / "flags.rs", rust_flag_txt)
|
||||
|
||||
|
||||
def build_native():
|
||||
@@ -362,6 +365,7 @@ def build_native():
|
||||
if ccache := shutil.which("ccache"):
|
||||
os.environ["NDK_CCACHE"] = ccache
|
||||
|
||||
dump_flag_header()
|
||||
build_rust_src(targets)
|
||||
build_cpp_src(targets)
|
||||
|
||||
@@ -426,19 +430,20 @@ def build_apk(module: str):
|
||||
source = Path(*paths, "build", "outputs", "apk", build_type, apk)
|
||||
target = config["outdir"] / apk
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
return target
|
||||
|
||||
|
||||
def build_app():
|
||||
header("* Building the Magisk app")
|
||||
build_apk(":app:apk")
|
||||
apk = build_apk(":app:apk")
|
||||
|
||||
build_type = "release" if args.release else "debug"
|
||||
|
||||
# Rename apk-variant.apk to app-variant.apk
|
||||
source = config["outdir"] / f"apk-{build_type}.apk"
|
||||
target = config["outdir"] / f"app-{build_type}.apk"
|
||||
source = apk
|
||||
target = apk.parent / apk.name.replace("apk-", "app-")
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
|
||||
# Stub building is directly integrated into the main app
|
||||
# build process. Copy the stub APK into output directory.
|
||||
@@ -449,7 +454,23 @@ def build_app():
|
||||
|
||||
def build_stub():
|
||||
header("* Building the stub app")
|
||||
build_apk(":app:stub")
|
||||
apk = build_apk(":app:stub")
|
||||
header(f"Output: {apk}")
|
||||
|
||||
|
||||
def build_test():
|
||||
global args
|
||||
args_bak = copy.copy(args)
|
||||
# Test APK has to be built as release to prevent classname clash
|
||||
args.release = True
|
||||
try:
|
||||
header("* Building the test app")
|
||||
source = build_apk(":app:test")
|
||||
target = source.parent / "test.apk"
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
finally:
|
||||
args = args_bak
|
||||
|
||||
|
||||
################
|
||||
@@ -491,6 +512,7 @@ def cleanup():
|
||||
def build_all():
|
||||
build_native()
|
||||
build_app()
|
||||
build_test()
|
||||
|
||||
|
||||
############
|
||||
@@ -719,6 +741,8 @@ def parse_args():
|
||||
|
||||
stub_parser = subparsers.add_parser("stub", help="build the stub app")
|
||||
|
||||
test_parser = subparsers.add_parser("test", help="build the test app")
|
||||
|
||||
clean_parser = subparsers.add_parser("clean", help="cleanup")
|
||||
clean_parser.add_argument(
|
||||
"targets", nargs="*", help="native, cpp, rust, java, or empty to clean all"
|
||||
@@ -757,6 +781,7 @@ def parse_args():
|
||||
rustup_parser.set_defaults(func=setup_rustup)
|
||||
app_parser.set_defaults(func=build_app)
|
||||
stub_parser.set_defaults(func=build_stub)
|
||||
test_parser.set_defaults(func=build_test)
|
||||
emu_parser.set_defaults(func=setup_avd)
|
||||
avd_patch_parser.set_defaults(func=patch_avd_file)
|
||||
clean_parser.set_defaults(func=cleanup)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
@@ -18,9 +18,9 @@ gradlePlugin {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
languageVersion = "2.0"
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
languageVersion = KotlinVersion.KOTLIN_2_0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
122
buildSrc/src/main/java/DesugarClassVisitorFactory.kt
Normal file
122
buildSrc/src/main/java/DesugarClassVisitorFactory.kt
Normal file
@@ -0,0 +1,122 @@
|
||||
import com.android.build.api.instrumentation.AsmClassVisitorFactory
|
||||
import com.android.build.api.instrumentation.ClassContext
|
||||
import com.android.build.api.instrumentation.ClassData
|
||||
import com.android.build.api.instrumentation.InstrumentationParameters
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Opcodes.ASM9
|
||||
|
||||
private const val DESUGAR_CLASS_NAME = "com.topjohnwu.magisk.core.utils.Desugar"
|
||||
private const val ZIP_ENTRY_CLASS_NAME = "java.util.zip.ZipEntry"
|
||||
private const val ZIP_OUT_STREAM_CLASS_NAME = "org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream"
|
||||
private const val ZIP_UTIL_CLASS_NAME = "org/apache/commons/compress/archivers/zip/ZipUtil"
|
||||
private const val ZIP_ENTRY_GET_TIME_DESC = "()Ljava/nio/file/attribute/FileTime;"
|
||||
private const val DESUGAR_GET_TIME_DESC =
|
||||
"(Ljava/util/zip/ZipEntry;)Ljava/nio/file/attribute/FileTime;"
|
||||
|
||||
private fun ClassData.isTypeOf(name: String) = className == name || superClasses.contains(name)
|
||||
|
||||
abstract class DesugarClassVisitorFactory : AsmClassVisitorFactory<InstrumentationParameters.None> {
|
||||
override fun createClassVisitor(
|
||||
classContext: ClassContext,
|
||||
nextClassVisitor: ClassVisitor
|
||||
): ClassVisitor {
|
||||
return if (classContext.currentClassData.className == ZIP_OUT_STREAM_CLASS_NAME) {
|
||||
ZipEntryPatcher(classContext, ZipOutputStreamPatcher(nextClassVisitor))
|
||||
} else {
|
||||
ZipEntryPatcher(classContext, nextClassVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInstrumentable(classData: ClassData) = classData.className != DESUGAR_CLASS_NAME
|
||||
|
||||
// Patch ALL references to ZipEntry#getXXXTime
|
||||
class ZipEntryPatcher(
|
||||
private val classContext: ClassContext,
|
||||
cv: ClassVisitor
|
||||
) : ClassVisitor(ASM9, cv) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String?,
|
||||
descriptor: String?,
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?
|
||||
) = MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
|
||||
|
||||
inner class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
|
||||
override fun visitMethodInsn(
|
||||
opcode: Int,
|
||||
owner: String,
|
||||
name: String,
|
||||
descriptor: String,
|
||||
isInterface: Boolean
|
||||
) {
|
||||
if (!process(owner, name, descriptor)) {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
|
||||
}
|
||||
}
|
||||
|
||||
private fun process(owner: String, name: String, descriptor: String): Boolean {
|
||||
val classData = classContext.loadClassData(owner.replace("/", ".")) ?: return false
|
||||
if (!classData.isTypeOf(ZIP_ENTRY_CLASS_NAME))
|
||||
return false
|
||||
if (descriptor != ZIP_ENTRY_GET_TIME_DESC)
|
||||
return false
|
||||
return when (name) {
|
||||
"getLastModifiedTime", "getLastAccessTime", "getCreationTime" -> {
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
DESUGAR_CLASS_NAME.replace('.', '/'),
|
||||
name,
|
||||
DESUGAR_GET_TIME_DESC,
|
||||
false
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Patch ZipArchiveOutputStream#copyFromZipInputStream
|
||||
class ZipOutputStreamPatcher(cv: ClassVisitor) : ClassVisitor(ASM9, cv) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String,
|
||||
descriptor: String,
|
||||
signature: String?,
|
||||
exceptions: Array<out String?>?
|
||||
): MethodVisitor? {
|
||||
return if (name == "copyFromZipInputStream") {
|
||||
MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
|
||||
} else {
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
}
|
||||
}
|
||||
|
||||
class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
|
||||
override fun visitMethodInsn(
|
||||
opcode: Int,
|
||||
owner: String,
|
||||
name: String,
|
||||
descriptor: String?,
|
||||
isInterface: Boolean
|
||||
) {
|
||||
if (owner == ZIP_UTIL_CLASS_NAME && name == "checkRequestedFeatures") {
|
||||
// Redirect
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
DESUGAR_CLASS_NAME.replace('.', '/'),
|
||||
name,
|
||||
descriptor,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import com.android.build.api.artifact.ArtifactTransformationRequest
|
||||
import com.android.build.api.artifact.SingleArtifact
|
||||
import com.android.build.api.dsl.ApkSigningConfig
|
||||
import com.android.build.api.instrumentation.FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
|
||||
import com.android.build.api.instrumentation.InstrumentationScope
|
||||
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
@@ -70,9 +72,9 @@ private val Project.androidComponents
|
||||
fun Project.setupCommon() {
|
||||
androidBase {
|
||||
compileSdkVersion(35)
|
||||
buildToolsVersion = "34.0.0"
|
||||
buildToolsVersion = "35.0.1"
|
||||
ndkPath = "$sdkDirectory/ndk/magisk"
|
||||
ndkVersion = "27.0.12077973"
|
||||
ndkVersion = "28.0.12674087"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
@@ -297,6 +299,9 @@ fun Project.setupAppCommon() {
|
||||
|
||||
defaultConfig {
|
||||
targetSdk = 35
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt")
|
||||
)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -348,7 +353,34 @@ fun Project.setupAppCommon() {
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.setupStub() {
|
||||
fun Project.setupMainApk() {
|
||||
setupAppCommon()
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
versionName = Config.version
|
||||
versionCode = Config.versionCode
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
|
||||
debugSymbolLevel = "FULL"
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
variant.instrumentation.apply {
|
||||
setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)
|
||||
transformClassesWith(
|
||||
DesugarClassVisitorFactory::class.java, InstrumentationScope.ALL) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.setupStubApk() {
|
||||
setupAppCommon()
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Magisk Changelog
|
||||
|
||||
### v28.1
|
||||
|
||||
- [App] Fix stub APK download link
|
||||
- [App] Fix support for Android lower than 8.0
|
||||
- [General] Fix support for MTK Samsung devices
|
||||
- [MagiskInit] Fix a regression for 2SI devices
|
||||
- [MagiskPolicy] Fix a regression causing `overlay.d` replaced files to be not accessible
|
||||
|
||||
### v28.0
|
||||
|
||||
- [General] Support 16k page size
|
||||
|
||||
@@ -124,6 +124,8 @@ If you place a file named `.replace` in any of the folders, instead of merging i
|
||||
|
||||
If you want to replace files in `/vendor`, `/product`, or `/system_ext`, please place them under `system/vendor`, `system/product`, and `system/system_ext` respectively. Magisk will transparently handle whether these partitions are in a separate partition or not.
|
||||
|
||||
If you want to remove a specific file or folder, please place a dummy character device with major number 0 and minor number 0 in the same path. For example, if you want to remove `/system/app/GoogleCamera`, you can `mknod GoogleCamera c 0 0` in `$MODDIR/system/app`.
|
||||
|
||||
#### Zygisk
|
||||
|
||||
Zygisk is a feature of Magisk that allows advanced module developers to run code directly in every Android applications' processes before they are specialized and running. For more details about the Zygisk API and building a Zygisk module, please checkout the [Zygisk Module Sample](https://github.com/topjohnwu/zygisk-module-sample) project.
|
||||
@@ -138,7 +140,7 @@ If your module requires some additional sepolicy patches, please add those rules
|
||||
|
||||
## Magisk Module Installer
|
||||
|
||||
A Magisk module installer is a Magisk module packaged in a zip file that can be flashed in the Magisk app or custom recoveries such as TWRP. The simplest Magisk module installer is just a Magisk module packed as a zip file, in addition to the following files:
|
||||
A Magisk module installer is a Magisk module packaged in a zip file that can be flashed in the Magisk app or custom recoveries such as TWRP. The simplest Magisk module installer is just a Magisk module packed as a zip file, in addition to the following files only if the module supports flashing in recovery:
|
||||
|
||||
- `update-binary`: Download the latest [module_installer.sh](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh) and rename/copy that script as `update-binary`
|
||||
- `updater-script`: This file should only contain the string `#MAGISK`
|
||||
@@ -148,7 +150,7 @@ The module installer script will setup the environment, extract the module files
|
||||
```
|
||||
module.zip
|
||||
│
|
||||
├── META-INF
|
||||
├── META-INF <--- Only needed for flashing in recovery
|
||||
│ └── com
|
||||
│ └── google
|
||||
│ └── android
|
||||
@@ -212,7 +214,7 @@ set_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission>
|
||||
|
||||
For convenience, you can also declare a list of folders you want to replace in the variable name `REPLACE`. The module installer script will create the `.replace` file into the folders listed in `REPLACE`. For example:
|
||||
|
||||
```
|
||||
```sh
|
||||
REPLACE="
|
||||
/system/app/YouTube
|
||||
/system/app/Bloatware
|
||||
@@ -221,6 +223,17 @@ REPLACE="
|
||||
|
||||
The list above will result in the following files being created: `$MODPATH/system/app/YouTube/.replace` and `$MODPATH/system/app/Bloatware/.replace`.
|
||||
|
||||
For convenience, you can also declare a list of files/folders you want to remove in the variable name `REMOVE`. The module installer script will create the corresponding dummy devices. For example:
|
||||
|
||||
```sh
|
||||
REMOVE="
|
||||
/system/app/YouTube
|
||||
/system/fonts/Roboto.ttf
|
||||
"
|
||||
```
|
||||
|
||||
The list above will result in the following dummy devices being created: `$MODPATH/system/app/YouTube` and `$MODPATH/system/fonts/Roboto.ttf`.
|
||||
|
||||
#### Notes
|
||||
|
||||
- When your module is downloaded with the Magisk app, `update-binary` will be **forcefully** replaced with the latest [`module_installer.sh`](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh). **DO NOT** try to add any custom logic in `update-binary`.
|
||||
|
||||
33
docs/releases/28100.md
Normal file
33
docs/releases/28100.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## 2024.12.6 Magisk v28.1
|
||||
|
||||
- [App] Fix stub APK download link
|
||||
- [App] Fix support for Android lower than 8.0
|
||||
- [General] Fix support for MTK Samsung devices
|
||||
- [MagiskInit] Fix a regression for 2SI devices
|
||||
- [MagiskPolicy] Fix a regression causing `overlay.d` replaced files to be not accessible
|
||||
|
||||
## Magisk v28.0 Changes
|
||||
|
||||
- [General] Support 16k page size
|
||||
- [General] Add basic support for RISC-V (not built in releases)
|
||||
- [General] Use a minimal libc to build static executables (`magiskinit` and `magiskboot`) for smaller sizes
|
||||
- [Core] Remove unnecessary mirror for magic mount
|
||||
- [Core] Update boot image detection logic to support more devices
|
||||
- [MagiskInit] Rewrite 2SI logic for injecting `magiskinit` as `init`
|
||||
- [MagiskInit] Update preinit partition detection
|
||||
- [Zygisk] Update internal JNI hooking implementation
|
||||
- [MagiskPolicy] Preserve sepolicy config flag after patching
|
||||
- [MagiskPolicy] Optimize patching rules to reduce the amount of new rules being injected
|
||||
- [DenyList] Support enforcing denylist when Zygisk is disabled
|
||||
- [Resetprop] Improve implementation to workaround several property modification detections
|
||||
- [Resetprop] Update to properly work with property overlays
|
||||
- [App] Major internal code refactoring
|
||||
- [App] Support patching Samsung firmware with images larger than 8GiB
|
||||
- [App] Use user-initiated job instead of foreground services on Android 14
|
||||
- [App] Support Android 13+ built-in per-app language preferences
|
||||
- [App] Add `action.sh` support to allow modules to define an action triggered from UI
|
||||
- [MagiskBoot] Support spliting kernel images without decompression
|
||||
- [MagiskBoot] Properly support vendor boot images
|
||||
- [MagiskBoot] Disable Samsung PROCA from kernel image
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,5 +1,6 @@
|
||||
# Release Notes
|
||||
|
||||
- [v28.1](28100.md)
|
||||
- [v28.0](28000.md)
|
||||
- [v27.0](27000.md)
|
||||
- [v26.4](26400.md)
|
||||
|
||||
@@ -30,5 +30,5 @@ android.nonFinalResIds=false
|
||||
|
||||
# Magisk
|
||||
magisk.stubVersion=40
|
||||
magisk.versionCode=28001
|
||||
magisk.ondkVersion=r27.4
|
||||
magisk.versionCode=28102
|
||||
magisk.ondkVersion=r28.2
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[versions]
|
||||
kotlin = "2.0.20"
|
||||
android = "8.7.0"
|
||||
ksp = "2.0.20-1.0.25"
|
||||
kotlin = "2.1.0"
|
||||
android = "8.8.0"
|
||||
ksp = "2.1.0-1.0.29"
|
||||
rikka = "1.3.0"
|
||||
navigation = "2.8.2"
|
||||
navigation = "2.8.4"
|
||||
libsu = "6.0.0"
|
||||
moshi = "1.15.1"
|
||||
okhttp = "4.12.0"
|
||||
@@ -11,7 +11,7 @@ retrofit = "2.11.0"
|
||||
room = "2.6.1"
|
||||
|
||||
[libraries]
|
||||
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.78.1" }
|
||||
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.80" }
|
||||
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.27.1" }
|
||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||
retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
|
||||
@@ -23,28 +23,31 @@ okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.
|
||||
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
|
||||
moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
|
||||
timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" }
|
||||
jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "6.10.0.202406032230-r" }
|
||||
jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "7.1.0.202411261347-r" }
|
||||
|
||||
# AndroidX
|
||||
activity = { module = "androidx.activity:activity", version = "1.9.2" }
|
||||
activity = { module = "androidx.activity:activity", version = "1.10.0" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" }
|
||||
core-ktx = { module = "androidx.core:core-ktx", version = "1.13.1" }
|
||||
core-ktx = { module = "androidx.core:core-ktx", version = "1.15.0" }
|
||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
|
||||
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.4" }
|
||||
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.4" }
|
||||
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.0" }
|
||||
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.5" }
|
||||
navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
|
||||
navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
|
||||
profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version = "1.4.1" }
|
||||
recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.3.2" }
|
||||
recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0" }
|
||||
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
||||
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
||||
swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" }
|
||||
transition = { module = "androidx.transition:transition", version = "1.5.1" }
|
||||
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.4.4" }
|
||||
lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version = "2.8.6" }
|
||||
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.4.5" }
|
||||
material = { module = "com.google.android.material:material", version = "1.12.0" }
|
||||
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.2" }
|
||||
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.3" }
|
||||
test-runner = { module = "androidx.test:runner", version = "1.6.2" }
|
||||
test-rules = { module = "androidx.test:rules", version = "1.6.1" }
|
||||
test-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
|
||||
test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
|
||||
|
||||
# topjohnwu
|
||||
indeterminate-checkbox = { module = "com.github.topjohnwu:indeterminate-checkbox", version = "1.0.7" }
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
3
gradlew
vendored
3
gradlew
vendored
@@ -86,8 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
@@ -4,35 +4,43 @@ LOCAL_PATH := $(call my-dir)
|
||||
# Rust compilation outputs
|
||||
###########################
|
||||
|
||||
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := magisk-rs
|
||||
LOCAL_EXPORT_C_INCLUDES := src/core/include
|
||||
LOCAL_SRC_FILES := $(LIBRARY_PATH)
|
||||
LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
|
||||
LOCAL_SRC_FILES := $(LOCAL_LIB)
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
else
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
endif
|
||||
|
||||
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagiskboot-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := boot-rs
|
||||
LOCAL_SRC_FILES := $(LIBRARY_PATH)
|
||||
LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskboot-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
|
||||
LOCAL_SRC_FILES := $(LOCAL_LIB)
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
else
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
endif
|
||||
|
||||
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagiskinit-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := init-rs
|
||||
LOCAL_SRC_FILES := $(LIBRARY_PATH)
|
||||
LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskinit-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
|
||||
LOCAL_SRC_FILES := $(LOCAL_LIB)
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
else
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
endif
|
||||
|
||||
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagiskpolicy-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := policy-rs
|
||||
LOCAL_SRC_FILES := $(LIBRARY_PATH)
|
||||
LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskpolicy-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
|
||||
LOCAL_SRC_FILES := $(LOCAL_LIB)
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
else
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
endif
|
||||
|
||||
@@ -20,10 +20,9 @@ LOCAL_SRC_FILES := \
|
||||
core/daemon.cpp \
|
||||
core/bootstages.cpp \
|
||||
core/socket.cpp \
|
||||
core/db.cpp \
|
||||
core/package.cpp \
|
||||
core/scripting.cpp \
|
||||
core/selinux.cpp \
|
||||
core/sqlite.cpp \
|
||||
core/module.cpp \
|
||||
core/thread.cpp \
|
||||
core/core-rs.cpp \
|
||||
@@ -169,6 +168,7 @@ LOCAL_SRC_FILES := \
|
||||
sepolicy/policy-rs.cpp
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include src/Android-rs.mk
|
||||
include src/base/Android.mk
|
||||
include src/external/Android.mk
|
||||
CWD := $(LOCAL_PATH)
|
||||
include $(CWD)/Android-rs.mk
|
||||
include $(CWD)/base/Android.mk
|
||||
include $(CWD)/external/Android.mk
|
||||
|
||||
271
native/src/Cargo.lock
generated
271
native/src/Cargo.lock
generated
@@ -1,6 +1,12 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "argh"
|
||||
@@ -32,9 +38,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "base"
|
||||
@@ -65,28 +71,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.11.0-rc.0"
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17092d478f4fadfb35a7e082f62e49f0907fdf048801d9d706277e34f9df8a78"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.11.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fd016a0ddc7cb13661bf5576073ce07330a693f8608a1320b4e20561cc12cdc"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.16.3"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83"
|
||||
checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b"
|
||||
checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -101,9 +122,12 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.7"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc"
|
||||
checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -111,6 +135,32 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
@@ -123,24 +173,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.0-rc.0"
|
||||
version = "0.10.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9adcf94f05e094fca3005698822ec791cb4433ced416afda1c5ca3b8dfc05a2f"
|
||||
checksum = "68ff6be19477a1bd5441f382916a89bc2a0b2c35db6d41e0f6e8538bf6d6463f"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.32"
|
||||
version = "0.2.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
|
||||
checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
|
||||
dependencies = [
|
||||
"const_format_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format_proc_macros"
|
||||
version = "0.2.32"
|
||||
version = "0.2.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
|
||||
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -149,18 +199,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.6.0-rc.2"
|
||||
version = "0.6.0-rc.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e43027691f1c055da3da4f7d96af09fcec420d435d5616e51f29afd0811c56a7"
|
||||
checksum = "d748d1f5b807ee6d0df5a548d0130417295c3aaed1dcbbb3d6a2e7106e11fcca"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
"num-traits",
|
||||
@@ -171,9 +221,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.0-rc.0"
|
||||
version = "0.2.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c070b79a496dccd931229780ad5bbedd535ceff6c3565605a8e440e18e1aa2b"
|
||||
checksum = "b0b8ce8218c97789f16356e7896b3714f26c2ee1079b79c0b7ae7064bb9089fa"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"hybrid-array",
|
||||
@@ -182,16 +232,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.124"
|
||||
version = "1.0.133"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-cmd",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-gen"
|
||||
version = "0.7.124"
|
||||
version = "0.7.133"
|
||||
dependencies = [
|
||||
"codespan-reporting",
|
||||
"proc-macro2",
|
||||
@@ -200,23 +252,35 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.124"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.124"
|
||||
name = "cxxbridge-cmd"
|
||||
version = "1.0.133"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codespan-reporting",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.133"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.133"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.8.0-rc.0"
|
||||
version = "0.8.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05d9c07d3bd80cf0935ce478d07edf7e7a5b158446757f988f3e62082227b700"
|
||||
checksum = "82db698b33305f0134faf590b9d1259dc171b5481ac41d5c8146c3b3ee7d4319"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"der_derive",
|
||||
@@ -227,9 +291,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "der_derive"
|
||||
version = "0.8.0-rc.0"
|
||||
version = "0.8.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c46c0d3c8dba679a95cc7caf42fe6220338e28c9d7c09e0a458e6f6156c06e7"
|
||||
checksum = "211bea8bb45f5f61bc857104606913ef8ac8b5ec698143aa2aa96a7ffdc94991"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -250,9 +314,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.17.0-pre.7"
|
||||
version = "0.17.0-pre.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad051af2b2d2f356d716138c76775929be913deb5b4ea217cd2613535936bef"
|
||||
checksum = "7e62f2041a28c40b8884b79fbd19bc7457d76c6397767831e9ff4029fc0473a9"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest",
|
||||
@@ -264,9 +328,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.14.0-pre.6"
|
||||
version = "0.14.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ed8e96bb573517f42470775f8ef1b9cd7595de52ba7a8e19c48325a92c8fe4f"
|
||||
checksum = "cc43715037532dc2d061e5c97e81b684c28993d52a4fa4eb7d2ce2826d78f2f2"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
@@ -305,6 +369,12 @@ version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
@@ -347,9 +417,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.2.0-rc.9"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d306b679262030ad8813a82d4915fc04efff97776e4db7f8eb5137039d56400"
|
||||
checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"zeroize",
|
||||
@@ -366,15 +436,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
version = "0.2.168"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -387,6 +457,7 @@ name = "magisk"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"base",
|
||||
"bit-set",
|
||||
"bytemuck",
|
||||
"cxx",
|
||||
"cxx-gen",
|
||||
@@ -394,6 +465,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"pb-rs",
|
||||
"quick-protobuf",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -523,9 +595,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.14.0-pre.1"
|
||||
version = "0.14.0-pre.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c32c18a74d9dda1314d2f945fb3e274848822f63f264a9e4d3f783e29b3bc1f"
|
||||
checksum = "71f3fd64a9cad9c26ed7f734b152196d5e56376b9957c832bcca0de48a708080"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
@@ -535,9 +607,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "p384"
|
||||
version = "0.14.0-pre.1"
|
||||
version = "0.14.0-pre.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99acc40dbfad9cc3dc102828f5678c8ca14f0cbf3a1f56f74c2875b5a84427af"
|
||||
checksum = "1e19554fe6ee269c860a0f231cbba714e5cbef26a927c75d8e30ac9040a4b32e"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
@@ -547,9 +619,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "p521"
|
||||
version = "0.14.0-pre.1"
|
||||
version = "0.14.0-pre.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ec5d919bea930a34a522bb1c95a89f559925deab255db2c2ffa174fc48df664"
|
||||
checksum = "957df9b5e6a7542f6430ec5187a4cb66d8498946c38b23fd14562bce6e32e6de"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"ecdsa",
|
||||
@@ -571,18 +643,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "1.0.0-rc.1"
|
||||
version = "1.0.0-rc.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c1cde4770761bf6bd336f947b9ac1fe700b0a4ec5867cf66cf08597fe89e8c"
|
||||
checksum = "c2dfbfa5c6f0906884269722c5478e72fd4d6c0e24fe600332c6d62359567ce1"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.8.0-rc.0"
|
||||
version = "0.8.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d2f4c73d459a85331915baebd5082dce5ee8ef16fd9a1ca75559ac91e66a9ee"
|
||||
checksum = "226eb25e2c46c166ce498ac0f606ac623142d640064879ff445938accddff1e2"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
@@ -591,9 +663,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.11.0-rc.0"
|
||||
version = "0.11.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66180445f1dce533620a7743467ef85fe1c5e80cdaf7c7053609d7a2fbcdae20"
|
||||
checksum = "eacd2c7141f32aef1cfd1ad0defb5287a3d94592d7ab57c1ae20e3f9f1f0db1f"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
@@ -616,18 +688,18 @@ checksum = "d3f2ce0fa9cccdaf216230d151ce51a15298aef50ad76081a830128ecbc6428a"
|
||||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.14.0-pre.1"
|
||||
version = "0.14.0-pre.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bed0c431186675ad845922b903d28c7faa2b634a6d130fb7b50bb289f5a4d52"
|
||||
checksum = "b794117b388378d55629f78f61e64e182baa200bf59c1a8205e0c46508ce5873"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -642,9 +714,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -690,9 +762,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.10.0-pre.2"
|
||||
version = "0.10.0-pre.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57e864e43f5d003321ab452feea6450f9611d7be6726489b4ec051da34774c62"
|
||||
checksum = "07058e83b684989ab0559f9e22322f4e3f7e49147834ed0bae40486b9e70473c"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
@@ -710,10 +782,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.8.0-rc.0"
|
||||
name = "rustversion"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32c98827dc6ed0ea1707286a3d14b4ad4e25e2643169cbf111568a46ff5b09f5"
|
||||
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.8.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1988446eff153796413a73669dfaa4caa3f5ce8b25fac89e3821a39c611772e"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
@@ -725,18 +803,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.204"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.204"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -765,6 +843,12 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.3.0-pre.4"
|
||||
@@ -795,14 +879,20 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.8.0-rc.0"
|
||||
version = "0.8.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee3fb1c675852398475928637b3ebbdd7e1d0cc24d27b3bbc81788b4eb51e310"
|
||||
checksum = "37ac66481418fd7afdc584adcf3be9aa572cf6c2858814494dc2a01755f050bc"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -811,9 +901,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.72"
|
||||
version = "2.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -831,18 +921,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -878,21 +968,21 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
@@ -984,8 +1074,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "x509-cert"
|
||||
version = "0.3.0-pre"
|
||||
source = "git+https://github.com/RustCrypto/formats.git?rev=9c0e851c6db9c2c8a2601840d46375afde2663fb#9c0e851c6db9c2c8a2601840d46375afde2663fb"
|
||||
version = "0.3.0-pre.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2db382aa43c1fb5c419a960f72c3847ab0f383f635fc2e25f0bd6c5fb94371d1"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"der",
|
||||
|
||||
@@ -10,21 +10,22 @@ libc = "0.2"
|
||||
cfg-if = "1.0"
|
||||
num-traits = "0.2"
|
||||
num-derive = "0.4"
|
||||
thiserror = "1.0"
|
||||
thiserror = "2.0"
|
||||
byteorder = "1"
|
||||
size = "0.4"
|
||||
sha1 = "0.11.0-pre.4"
|
||||
sha2 = "=0.11.0-pre.4"
|
||||
digest = "0.11.0-pre.9"
|
||||
p256 = "0.14.0-pre.1"
|
||||
p384 = "0.14.0-pre.1"
|
||||
p521 = "0.14.0-pre.1"
|
||||
rsa = "0.10.0-pre.2"
|
||||
#x509-cert = "0.3"
|
||||
der = "0.8.0-rc.0"
|
||||
p256 = "0.14.0-pre.2"
|
||||
p384 = "0.14.0-pre.2"
|
||||
p521 = "0.14.0-pre.2"
|
||||
rsa = "0.10.0-pre.3"
|
||||
x509-cert = "0.3.0-pre.0"
|
||||
der = "0.8.0-rc.1"
|
||||
bytemuck = "1.16"
|
||||
fdt = "0.1"
|
||||
const_format = "0.2"
|
||||
bit-set = "0.8"
|
||||
|
||||
[workspace.dependencies.argh]
|
||||
git = "https://github.com/google/argh.git"
|
||||
@@ -40,10 +41,6 @@ default-features = false
|
||||
git = "https://github.com/tafia/quick-protobuf.git"
|
||||
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
|
||||
|
||||
[workspace.dependencies.x509-cert]
|
||||
git = "https://github.com/RustCrypto/formats.git"
|
||||
rev = "9c0e851c6db9c2c8a2601840d46375afde2663fb"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = "z"
|
||||
lto = "thin"
|
||||
|
||||
@@ -340,6 +340,11 @@ impl Utf8CStr {
|
||||
Self::from_cstr(unsafe { CStr::from_ptr(ptr) })
|
||||
}
|
||||
|
||||
pub unsafe fn from_ptr_unchecked<'a>(ptr: *const c_char) -> &'a Utf8CStr {
|
||||
let cstr = CStr::from_ptr(ptr);
|
||||
Self::from_bytes_unchecked(cstr.to_bytes_with_nul())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_bytes_with_nul(&self) -> &[u8] {
|
||||
&self.0
|
||||
@@ -405,8 +410,8 @@ macro_rules! const_assert_eq {
|
||||
}
|
||||
|
||||
// Assert ABI layout
|
||||
const_assert_eq!(mem::size_of::<&Utf8CStr>(), mem::size_of::<[usize; 2]>());
|
||||
const_assert_eq!(mem::align_of::<&Utf8CStr>(), mem::align_of::<[usize; 2]>());
|
||||
const_assert_eq!(size_of::<&Utf8CStr>(), size_of::<[usize; 2]>());
|
||||
const_assert_eq!(align_of::<&Utf8CStr>(), align_of::<[usize; 2]>());
|
||||
|
||||
// File system path extensions types
|
||||
|
||||
@@ -471,7 +476,7 @@ impl<'a> FsPathBuf<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for FsPathBuf<'a> {
|
||||
impl Deref for FsPathBuf<'_> {
|
||||
type Target = FsPath;
|
||||
|
||||
fn deref(&self) -> &FsPath {
|
||||
@@ -479,7 +484,7 @@ impl<'a> Deref for FsPathBuf<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for FsPathBuf<'a> {
|
||||
impl DerefMut for FsPathBuf<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
FsPath::from_mut(&mut self.0)
|
||||
}
|
||||
|
||||
@@ -144,6 +144,8 @@ string resolve_preinit_dir(const char *base_dir) {
|
||||
dir += "/unencrypted/magisk";
|
||||
} else if (access((dir + "/adb").data(), F_OK) == 0) {
|
||||
dir += "/adb/modules";
|
||||
} else if (access((dir + "/watchdog").data(), F_OK) == 0) {
|
||||
dir += "/watchdog/magisk";
|
||||
} else {
|
||||
dir += "/magisk";
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
use mem::MaybeUninit;
|
||||
use std::cmp::min;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
|
||||
use std::ops::Deref;
|
||||
use std::os::fd::{AsFd, BorrowedFd, IntoRawFd};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::path::Path;
|
||||
use std::{io, mem, ptr, slice};
|
||||
|
||||
use bytemuck::{bytes_of_mut, Pod};
|
||||
use libc::{
|
||||
c_uint, dirent, makedev, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY,
|
||||
O_RDWR, O_TRUNC, O_WRONLY,
|
||||
};
|
||||
use num_traits::AsPrimitive;
|
||||
|
||||
use crate::cxx_extern::readlinkat_for_cxx;
|
||||
use crate::{
|
||||
cstr, errno, error, FsPath, FsPathBuf, LibcReturn, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr,
|
||||
Utf8CStrWrite,
|
||||
};
|
||||
use bytemuck::{bytes_of_mut, Pod};
|
||||
use libc::{
|
||||
c_uint, dirent, makedev, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY,
|
||||
O_RDWR, O_TRUNC, O_WRONLY,
|
||||
};
|
||||
use mem::MaybeUninit;
|
||||
use num_traits::AsPrimitive;
|
||||
use std::cmp::min;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::ops::Deref;
|
||||
use std::os::fd::{AsFd, BorrowedFd, IntoRawFd};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::{io, mem, ptr, slice};
|
||||
|
||||
pub fn __open_fd_impl(path: &Utf8CStr, flags: i32, mode: mode_t) -> io::Result<OwnedFd> {
|
||||
unsafe {
|
||||
@@ -333,7 +333,7 @@ impl Directory {
|
||||
unsafe {
|
||||
let entry = &*e;
|
||||
let d_name = CStr::from_ptr(entry.d_name.as_ptr());
|
||||
return if d_name == cstr!(".") || d_name == cstr!("..") {
|
||||
if d_name == cstr!(".") || d_name == cstr!("..") {
|
||||
self.read()
|
||||
} else {
|
||||
let e = DirEntry {
|
||||
@@ -342,7 +342,7 @@ impl Directory {
|
||||
d_name_len: d_name.to_bytes_with_nul().len(),
|
||||
};
|
||||
Ok(Some(e))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,3 +1030,32 @@ pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub enum SharedFd {
|
||||
#[default]
|
||||
None,
|
||||
Shared(Arc<OwnedFd>),
|
||||
}
|
||||
|
||||
impl From<OwnedFd> for SharedFd {
|
||||
fn from(fd: OwnedFd) -> Self {
|
||||
SharedFd::Shared(Arc::new(fd))
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedFd {
|
||||
pub const fn new() -> Self {
|
||||
SharedFd::None
|
||||
}
|
||||
|
||||
// This is unsafe because we cannot create multiple mutable references to the same fd.
|
||||
// This can only be safely used if and only if the underlying fd points to a pipe,
|
||||
// and the read/write operations performed on the file involves bytes less than PIPE_BUF.
|
||||
pub unsafe fn as_file(&self) -> Option<ManuallyDrop<File>> {
|
||||
match self {
|
||||
SharedFd::None => None,
|
||||
SharedFd::Shared(arc) => Some(ManuallyDrop::new(File::from_raw_fd(arc.as_raw_fd()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ pub mod ffi {
|
||||
}
|
||||
|
||||
#[namespace = "rust"]
|
||||
#[allow(unused_unsafe)]
|
||||
extern "Rust" {
|
||||
unsafe fn extract_boot_from_payload(
|
||||
partition: *const c_char,
|
||||
|
||||
@@ -128,7 +128,7 @@ impl Verifier {
|
||||
|
||||
fn verify(mut self, signature: &[u8]) -> LoggedResult<()> {
|
||||
let hash = self.digest.finalize_reset();
|
||||
return match &self.key {
|
||||
match &self.key {
|
||||
VerifyingKey::SHA256withRSA(key) => {
|
||||
let sig = RsaSignature::try_from(signature)?;
|
||||
key.verify_prehash(hash.as_ref(), &sig).log()
|
||||
@@ -145,7 +145,7 @@ impl Verifier {
|
||||
let sig = P521Signature::from_slice(signature)?;
|
||||
key.verify_prehash(hash.as_ref(), &sig).log()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,8 +241,8 @@ impl BootSignature {
|
||||
}
|
||||
let mut verifier = Verifier::from_public_key(
|
||||
self.certificate
|
||||
.tbs_certificate
|
||||
.subject_public_key_info
|
||||
.tbs_certificate()
|
||||
.subject_public_key_info()
|
||||
.owned_to_ref(),
|
||||
)?;
|
||||
verifier.update(payload);
|
||||
@@ -329,7 +329,7 @@ pub fn sign_boot_image(
|
||||
let sig = signer.sign()?;
|
||||
|
||||
// Create BootSignature DER
|
||||
let alg_id = cert.signature_algorithm.clone();
|
||||
let alg_id = cert.signature_algorithm().clone();
|
||||
let sig = BootSignature {
|
||||
format_version: 1,
|
||||
certificate: cert,
|
||||
|
||||
@@ -7,6 +7,10 @@ edition = "2021"
|
||||
crate-type = ["staticlib"]
|
||||
path = "lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["check-signature"]
|
||||
check-signature = []
|
||||
|
||||
[build-dependencies]
|
||||
cxx-gen = { workspace = true }
|
||||
pb-rs = { workspace = true }
|
||||
@@ -18,3 +22,5 @@ num-traits = { workspace = true }
|
||||
num-derive = { workspace = true }
|
||||
quick-protobuf = { workspace = true }
|
||||
bytemuck = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
bit-set = { workspace = true }
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <string>
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <db.hpp>
|
||||
#include <base.hpp>
|
||||
#include <core.hpp>
|
||||
#include <selinux.hpp>
|
||||
@@ -133,69 +132,59 @@ static bool check_key_combo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool check_safe_mode() {
|
||||
int bootloop_cnt;
|
||||
db_settings dbs;
|
||||
get_db_settings(dbs, BOOTLOOP_COUNT);
|
||||
bootloop_cnt = dbs[BOOTLOOP_COUNT];
|
||||
// Increment the bootloop counter
|
||||
set_db_settings(BOOTLOOP_COUNT, bootloop_cnt + 1);
|
||||
return bootloop_cnt >= 2 || get_prop("persist.sys.safemode", true) == "1" ||
|
||||
get_prop("ro.sys.safemode") == "1" || check_key_combo();
|
||||
}
|
||||
|
||||
/***********************
|
||||
* Boot Stage Handlers *
|
||||
***********************/
|
||||
|
||||
bool MagiskD::post_fs_data() const {
|
||||
as_rust().setup_logfile();
|
||||
bool MagiskD::post_fs_data() const noexcept {
|
||||
setup_logfile();
|
||||
|
||||
LOGI("** post-fs-data mode running\n");
|
||||
|
||||
preserve_stub_apk();
|
||||
prune_su_access();
|
||||
|
||||
bool safe_mode = false;
|
||||
|
||||
if (access(SECURE_DIR, F_OK) != 0) {
|
||||
if (SDK_INT < 24) {
|
||||
xmkdir(SECURE_DIR, 0700);
|
||||
} else {
|
||||
LOGE(SECURE_DIR " is not present, abort\n");
|
||||
safe_mode = true;
|
||||
return safe_mode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
prune_su_access();
|
||||
|
||||
if (!magisk_env()) {
|
||||
LOGE("* Magisk environment incomplete, abort\n");
|
||||
safe_mode = true;
|
||||
return safe_mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (check_safe_mode()) {
|
||||
// Check safe mode
|
||||
int bootloop_cnt = get_db_setting(DbEntryKey::BootloopCount);
|
||||
// Increment the boot counter
|
||||
set_db_setting(DbEntryKey::BootloopCount, bootloop_cnt + 1);
|
||||
bool safe_mode = bootloop_cnt >= 2 || get_prop("persist.sys.safemode", true) == "1" ||
|
||||
get_prop("ro.sys.safemode") == "1" || check_key_combo();
|
||||
|
||||
if (safe_mode) {
|
||||
LOGI("* Safe mode triggered\n");
|
||||
safe_mode = true;
|
||||
// Disable all modules and zygisk so next boot will be clean
|
||||
disable_modules();
|
||||
set_db_settings(ZYGISK_CONFIG, false);
|
||||
return safe_mode;
|
||||
set_db_setting(DbEntryKey::ZygiskConfig, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
exec_common_scripts("post-fs-data");
|
||||
db_settings dbs;
|
||||
get_db_settings(dbs, ZYGISK_CONFIG);
|
||||
zygisk_enabled = dbs[ZYGISK_CONFIG];
|
||||
zygisk_enabled = get_db_setting(DbEntryKey::ZygiskConfig);
|
||||
initialize_denylist();
|
||||
setup_mounts();
|
||||
handle_modules();
|
||||
load_modules();
|
||||
return safe_mode;
|
||||
return false;
|
||||
}
|
||||
|
||||
void MagiskD::late_start() const {
|
||||
as_rust().setup_logfile();
|
||||
void MagiskD::late_start() const noexcept {
|
||||
setup_logfile();
|
||||
|
||||
LOGI("** late_start service mode running\n");
|
||||
|
||||
@@ -203,13 +192,13 @@ void MagiskD::late_start() const {
|
||||
exec_module_scripts("service");
|
||||
}
|
||||
|
||||
void MagiskD::boot_complete() const {
|
||||
as_rust().setup_logfile();
|
||||
void MagiskD::boot_complete() const noexcept {
|
||||
setup_logfile();
|
||||
|
||||
LOGI("** boot-complete triggered\n");
|
||||
|
||||
// Reset the bootloop counter once we have boot-complete
|
||||
set_db_settings(BOOTLOOP_COUNT, 0);
|
||||
set_db_setting(DbEntryKey::BootloopCount, 0);
|
||||
|
||||
// At this point it's safe to create the folder
|
||||
if (access(SECURE_DIR, F_OK) != 0)
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::mem::size_of_val;
|
||||
use std::os::fd::{FromRawFd, RawFd};
|
||||
|
||||
use base::*;
|
||||
|
||||
const EOCD_MAGIC: u32 = 0x06054B50;
|
||||
const APK_SIGNING_BLOCK_MAGIC: [u8; 16] = *b"APK Sig Block 42";
|
||||
const SIGNATURE_SCHEME_V2_MAGIC: u32 = 0x7109871A;
|
||||
|
||||
macro_rules! bad_apk {
|
||||
($msg:literal) => {
|
||||
io::Error::new(io::ErrorKind::InvalidData, concat!("cert: ", $msg))
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* A v2/v3 signed APK has the format as following
|
||||
*
|
||||
* +---------------+
|
||||
* | zip content |
|
||||
* +---------------+
|
||||
* | signing block |
|
||||
* +---------------+
|
||||
* | central dir |
|
||||
* +---------------+
|
||||
* | EOCD |
|
||||
* +---------------+
|
||||
*
|
||||
* Scan from end of file to find EOCD, and figure our way back to the
|
||||
* offset of the signing block. Next, directly extract the certificate
|
||||
* from the v2 signature block.
|
||||
*
|
||||
* All structures above are mostly just for documentation purpose.
|
||||
*
|
||||
* This method extracts the first certificate of the first signer
|
||||
* within the APK v2 signature block.
|
||||
*/
|
||||
pub fn read_certificate(fd: RawFd, version: i32) -> Vec<u8> {
|
||||
fn inner(apk: &mut File, version: i32) -> io::Result<Vec<u8>> {
|
||||
let mut u32_val = 0u32;
|
||||
let mut u64_val = 0u64;
|
||||
|
||||
// Find EOCD
|
||||
for i in 0u16.. {
|
||||
let mut comment_sz = 0u16;
|
||||
apk.seek(SeekFrom::End(-(size_of_val(&comment_sz) as i64) - i as i64))?;
|
||||
apk.read_pod(&mut comment_sz)?;
|
||||
|
||||
if comment_sz == i {
|
||||
apk.seek(SeekFrom::Current(-22))?;
|
||||
let mut magic = 0u32;
|
||||
apk.read_pod(&mut magic)?;
|
||||
if magic == EOCD_MAGIC {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if i == 0xffff {
|
||||
return Err(bad_apk!("invalid APK format"));
|
||||
}
|
||||
}
|
||||
|
||||
// We are now at EOCD + sizeof(magic)
|
||||
// Seek and read central_dir_off to find the start of the central directory
|
||||
let mut central_dir_off = 0u32;
|
||||
apk.seek(SeekFrom::Current(12))?;
|
||||
apk.read_pod(&mut central_dir_off)?;
|
||||
|
||||
// Code for parse APK comment to get version code
|
||||
if version >= 0 {
|
||||
let mut comment_sz = 0u16;
|
||||
apk.read_pod(&mut comment_sz)?;
|
||||
let mut comment = vec![0u8; comment_sz as usize];
|
||||
apk.read_exact(&mut comment)?;
|
||||
let mut comment = Cursor::new(&comment);
|
||||
let mut apk_ver = 0;
|
||||
comment.foreach_props(|k, v| {
|
||||
if k == "versionCode" {
|
||||
apk_ver = v.parse::<i32>().unwrap_or(0);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
if version > apk_ver {
|
||||
return Err(bad_apk!("APK version too low"));
|
||||
}
|
||||
}
|
||||
|
||||
// Next, find the start of the APK signing block
|
||||
apk.seek(SeekFrom::Start((central_dir_off - 24) as u64))?;
|
||||
apk.read_pod(&mut u64_val)?; // u64_value = block_sz_
|
||||
let mut magic = [0u8; 16];
|
||||
apk.read_exact(&mut magic)?;
|
||||
if magic != APK_SIGNING_BLOCK_MAGIC {
|
||||
return Err(bad_apk!("invalid signing block magic"));
|
||||
}
|
||||
let mut signing_blk_sz = 0u64;
|
||||
apk.seek(SeekFrom::Current(
|
||||
-(u64_val as i64) - (size_of_val(&signing_blk_sz) as i64),
|
||||
))?;
|
||||
apk.read_pod(&mut signing_blk_sz)?;
|
||||
if signing_blk_sz != u64_val {
|
||||
return Err(bad_apk!("invalid signing block size"));
|
||||
}
|
||||
|
||||
// Finally, we are now at the beginning of the id-value pair sequence
|
||||
loop {
|
||||
apk.read_pod(&mut u64_val)?; // id-value pair length
|
||||
if u64_val == signing_blk_sz {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut id = 0u32;
|
||||
apk.read_pod(&mut id)?;
|
||||
if id == SIGNATURE_SCHEME_V2_MAGIC {
|
||||
// Skip [signer sequence length] + [1st signer length] + [signed data length]
|
||||
apk.seek(SeekFrom::Current((size_of_val(&u32_val) * 3) as i64))?;
|
||||
|
||||
apk.read_pod(&mut u32_val)?; // digest sequence length
|
||||
apk.seek(SeekFrom::Current(u32_val as i64))?; // skip all digests
|
||||
|
||||
apk.seek(SeekFrom::Current(size_of_val(&u32_val) as i64))?; // cert sequence length
|
||||
apk.read_pod(&mut u32_val)?; // 1st cert length
|
||||
|
||||
let mut cert = vec![0; u32_val as usize];
|
||||
apk.read_exact(cert.as_mut())?;
|
||||
return Ok(cert);
|
||||
} else {
|
||||
// Skip this id-value pair
|
||||
apk.seek(SeekFrom::Current(
|
||||
u64_val as i64 - (size_of_val(&id) as i64),
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
Err(bad_apk!("cannot find certificate"))
|
||||
}
|
||||
if fd == -1 {
|
||||
return vec![];
|
||||
}
|
||||
let mut file = unsafe { File::from_raw_fd(fd) };
|
||||
let r = inner(&mut file, version).log().unwrap_or(vec![]);
|
||||
std::mem::forget(file);
|
||||
r
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <base.hpp>
|
||||
#include <core.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <db.hpp>
|
||||
#include <flags.h>
|
||||
|
||||
using namespace std;
|
||||
@@ -128,20 +127,8 @@ static void poll_ctrl_handler(pollfd *pfd) {
|
||||
}
|
||||
}
|
||||
|
||||
const MagiskD &MagiskD::get() {
|
||||
return *reinterpret_cast<const MagiskD*>(&rust::get_magiskd());
|
||||
}
|
||||
|
||||
const rust::MagiskD *MagiskD::operator->() const {
|
||||
return reinterpret_cast<const rust::MagiskD*>(this);
|
||||
}
|
||||
|
||||
const rust::MagiskD &MagiskD::as_rust() const {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
void MagiskD::reboot() const {
|
||||
if (as_rust().is_recovery())
|
||||
void MagiskD::reboot() const noexcept {
|
||||
if (is_recovery())
|
||||
exec_command_sync("/system/bin/reboot", "recovery");
|
||||
else
|
||||
exec_command_sync("/system/bin/reboot");
|
||||
@@ -157,13 +144,13 @@ static void handle_request_async(int client, int code, const sock_cred &cred) {
|
||||
break;
|
||||
case +RequestCode::ZYGOTE_RESTART:
|
||||
LOGI("** zygote restarted\n");
|
||||
prune_su_access();
|
||||
MagiskD().prune_su_access();
|
||||
scan_deny_apps();
|
||||
reset_zygisk(false);
|
||||
close(client);
|
||||
break;
|
||||
case +RequestCode::SQLITE_CMD:
|
||||
exec_sql(client);
|
||||
MagiskD().db_exec(client);
|
||||
break;
|
||||
case +RequestCode::REMOVE_MODULES: {
|
||||
int do_reboot = read_int(client);
|
||||
@@ -171,7 +158,7 @@ static void handle_request_async(int client, int code, const sock_cred &cred) {
|
||||
write_int(client, 0);
|
||||
close(client);
|
||||
if (do_reboot) {
|
||||
MagiskD::get().reboot();
|
||||
MagiskD().reboot();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -196,7 +183,7 @@ static void handle_request_sync(int client, int code) {
|
||||
write_int(client, MAGISK_VER_CODE);
|
||||
break;
|
||||
case +RequestCode::START_DAEMON:
|
||||
MagiskD::get()->setup_logfile();
|
||||
setup_logfile();
|
||||
break;
|
||||
case +RequestCode::STOP_DAEMON: {
|
||||
// Unmount all overlays
|
||||
@@ -298,7 +285,7 @@ static void handle_request(pollfd *pfd) {
|
||||
exec_task([=, fd = client.release()] { handle_request_async(fd, code, cred); });
|
||||
} else {
|
||||
exec_task([=, fd = client.release()] {
|
||||
MagiskD::get()->boot_stage_handler(fd, code);
|
||||
MagiskD().boot_stage_handler(fd, code);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -341,8 +328,7 @@ static void daemon_entry() {
|
||||
setcon(MAGISK_PROC_CON);
|
||||
|
||||
rust::daemon_entry();
|
||||
|
||||
LOGI(NAME_WITH_VER(Magisk) " daemon started\n");
|
||||
SDK_INT = MagiskD().sdk_int();
|
||||
|
||||
// Escape from cgroup
|
||||
int pid = getpid();
|
||||
@@ -356,22 +342,10 @@ static void daemon_entry() {
|
||||
// Get self stat
|
||||
xstat("/proc/self/exe", &self_st);
|
||||
|
||||
// Get API level
|
||||
parse_prop_file("/system/build.prop", [](auto key, auto val) -> bool {
|
||||
if (key == "ro.build.version.sdk") {
|
||||
SDK_INT = parse_int(val);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (SDK_INT < 0) {
|
||||
// In case some devices do not store this info in build.prop, fallback to getprop
|
||||
auto sdk = get_prop("ro.build.version.sdk");
|
||||
if (!sdk.empty()) {
|
||||
SDK_INT = parse_int(sdk);
|
||||
}
|
||||
// Samsung workaround #7887
|
||||
if (access("/system_ext/app/mediatek-res/mediatek-res.apk", F_OK) == 0) {
|
||||
set_prop("ro.vendor.mtk_model", "0");
|
||||
}
|
||||
LOGI("* Device API level: %d\n", SDK_INT);
|
||||
|
||||
restore_tmpcon();
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::{io, mem};
|
||||
|
||||
use crate::consts::{MAGISK_FULL_VER, MAIN_CONFIG};
|
||||
use crate::db::Sqlite3;
|
||||
use crate::ffi::{get_magisk_tmp, RequestCode};
|
||||
use crate::get_prop;
|
||||
use crate::logging::{magisk_logging, start_log_daemon};
|
||||
use crate::package::ManagerInfo;
|
||||
use base::libc::{O_CLOEXEC, O_RDONLY};
|
||||
use base::{
|
||||
cstr, libc, open_fd, BufReadExt, Directory, FsPathBuf, ResultExt, Utf8CStr, Utf8CStrBuf,
|
||||
Utf8CStrBufArr, Utf8CStrBufRef, WalkResult,
|
||||
cstr, info, libc, open_fd, BufReadExt, Directory, FsPath, FsPathBuf, LoggedResult, ReadExt,
|
||||
Utf8CStr, Utf8CStrBufArr,
|
||||
};
|
||||
|
||||
use crate::consts::MAIN_CONFIG;
|
||||
use crate::ffi::{get_magisk_tmp, CxxMagiskD, RequestCode};
|
||||
use crate::get_prop;
|
||||
use crate::logging::magisk_logging;
|
||||
use bit_set::BitSet;
|
||||
use bytemuck::bytes_of;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{BufReader, Read, Write};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
// Global magiskd singleton
|
||||
pub static MAGISKD: OnceLock<MagiskD> = OnceLock::new();
|
||||
@@ -39,23 +41,82 @@ impl BootStateFlags {
|
||||
}
|
||||
}
|
||||
|
||||
pub const AID_ROOT: i32 = 0;
|
||||
pub const AID_SHELL: i32 = 2000;
|
||||
pub const AID_APP_START: i32 = 10000;
|
||||
pub const AID_APP_END: i32 = 19999;
|
||||
pub const AID_USER_OFFSET: i32 = 100000;
|
||||
|
||||
pub const fn to_app_id(uid: i32) -> i32 {
|
||||
uid % AID_USER_OFFSET
|
||||
}
|
||||
|
||||
pub const fn to_user_id(uid: i32) -> i32 {
|
||||
uid / AID_USER_OFFSET
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MagiskD {
|
||||
pub logd: Mutex<Option<File>>,
|
||||
pub sql_connection: Mutex<Option<Sqlite3>>,
|
||||
pub manager_info: Mutex<ManagerInfo>,
|
||||
boot_stage_lock: Mutex<BootStateFlags>,
|
||||
is_emulator: bool,
|
||||
sdk_int: i32,
|
||||
pub is_emulator: bool,
|
||||
is_recovery: bool,
|
||||
}
|
||||
|
||||
impl MagiskD {
|
||||
pub fn is_emulator(&self) -> bool {
|
||||
self.is_emulator
|
||||
}
|
||||
|
||||
pub fn is_recovery(&self) -> bool {
|
||||
self.is_recovery
|
||||
}
|
||||
|
||||
pub fn sdk_int(&self) -> i32 {
|
||||
self.sdk_int
|
||||
}
|
||||
|
||||
pub fn app_data_dir(&self) -> &'static Utf8CStr {
|
||||
if self.sdk_int >= 24 {
|
||||
cstr!("/data/user_de")
|
||||
} else {
|
||||
cstr!("/data/user")
|
||||
}
|
||||
}
|
||||
|
||||
// app_id = app_no + AID_APP_START
|
||||
// app_no range: [0, 9999]
|
||||
pub fn get_app_no_list(&self) -> BitSet {
|
||||
let mut list = BitSet::new();
|
||||
let _: LoggedResult<()> = try {
|
||||
let mut app_data_dir = Directory::open(self.app_data_dir())?;
|
||||
// For each user
|
||||
loop {
|
||||
let entry = match app_data_dir.read()? {
|
||||
None => break,
|
||||
Some(e) => e,
|
||||
};
|
||||
let mut user_dir = match entry.open_as_dir() {
|
||||
Err(_) => continue,
|
||||
Ok(dir) => dir,
|
||||
};
|
||||
// For each package
|
||||
loop {
|
||||
match user_dir.read()? {
|
||||
None => break,
|
||||
Some(e) => {
|
||||
let attr = e.get_attr()?;
|
||||
let app_id = to_app_id(attr.st.st_uid as i32);
|
||||
if (AID_APP_START..=AID_APP_END).contains(&app_id) {
|
||||
let app_no = app_id - AID_APP_START;
|
||||
list.insert(app_no as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
list
|
||||
}
|
||||
|
||||
pub fn boot_stage_handler(&self, client: i32, code: i32) {
|
||||
// Make sure boot stage execution is always serialized
|
||||
let mut state = self.boot_stage_lock.lock().unwrap();
|
||||
@@ -64,7 +125,7 @@ impl MagiskD {
|
||||
match code {
|
||||
RequestCode::POST_FS_DATA => {
|
||||
if check_data() && !state.contains(BootState::PostFsDataDone) {
|
||||
if self.as_cxx().post_fs_data() {
|
||||
if self.post_fs_data() {
|
||||
state.set(BootState::SafeMode);
|
||||
}
|
||||
state.set(BootState::PostFsDataDone);
|
||||
@@ -75,7 +136,7 @@ impl MagiskD {
|
||||
unsafe { libc::close(client) };
|
||||
if state.contains(BootState::PostFsDataDone) && !state.contains(BootState::SafeMode)
|
||||
{
|
||||
self.as_cxx().late_start();
|
||||
self.late_start();
|
||||
state.set(BootState::LateStartDone);
|
||||
}
|
||||
}
|
||||
@@ -83,7 +144,7 @@ impl MagiskD {
|
||||
unsafe { libc::close(client) };
|
||||
if state.contains(BootState::PostFsDataDone) {
|
||||
state.set(BootState::BootComplete);
|
||||
self.as_cxx().boot_complete()
|
||||
self.boot_complete()
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -91,14 +152,13 @@ impl MagiskD {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn as_cxx(&self) -> &CxxMagiskD {
|
||||
unsafe { mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn daemon_entry() {
|
||||
start_log_daemon();
|
||||
magisk_logging();
|
||||
info!("Magisk {} daemon started", MAGISK_FULL_VER);
|
||||
|
||||
let is_emulator = get_prop(cstr!("ro.kernel.qemu"), false) == "1"
|
||||
|| get_prop(cstr!("ro.boot.qemu"), false) == "1"
|
||||
|| get_prop(cstr!("ro.product.device"), false).contains("vsoc");
|
||||
@@ -120,14 +180,32 @@ pub fn daemon_entry() {
|
||||
});
|
||||
}
|
||||
|
||||
let mut sdk_int = -1;
|
||||
if let Ok(file) = FsPath::from(cstr!("/system/build.prop")).open(O_RDONLY | O_CLOEXEC) {
|
||||
let mut file = BufReader::new(file);
|
||||
file.foreach_props(|key, val| {
|
||||
if key == "ro.build.version.sdk" {
|
||||
sdk_int = val.parse::<i32>().unwrap_or(-1);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
if sdk_int < 0 {
|
||||
// In case some devices do not store this info in build.prop, fallback to getprop
|
||||
sdk_int = get_prop(cstr!("ro.build.version.sdk"), false)
|
||||
.parse::<i32>()
|
||||
.unwrap_or(-1);
|
||||
}
|
||||
info!("* Device API level: {}", sdk_int);
|
||||
|
||||
let magiskd = MagiskD {
|
||||
sdk_int,
|
||||
is_emulator,
|
||||
is_recovery,
|
||||
..Default::default()
|
||||
};
|
||||
magiskd.start_log_daemon();
|
||||
MAGISKD.set(magiskd).ok();
|
||||
magisk_logging();
|
||||
}
|
||||
|
||||
fn check_data() -> bool {
|
||||
@@ -165,30 +243,38 @@ pub fn get_magiskd() -> &'static MagiskD {
|
||||
unsafe { MAGISKD.get().unwrap_unchecked() }
|
||||
}
|
||||
|
||||
pub fn find_apk_path(pkg: &Utf8CStr, data: &mut [u8]) -> usize {
|
||||
use WalkResult::*;
|
||||
fn inner(pkg: &Utf8CStr, buf: &mut dyn Utf8CStrBuf) -> io::Result<usize> {
|
||||
Directory::open(cstr!("/data/app"))?.pre_order_walk(|e| {
|
||||
if !e.is_dir() {
|
||||
return Ok(Skip);
|
||||
}
|
||||
let d_name = e.d_name().to_bytes();
|
||||
if d_name.starts_with(pkg.as_bytes()) && d_name[pkg.len()] == b'-' {
|
||||
// Found the APK path, we can abort now
|
||||
e.path(buf)?;
|
||||
return Ok(Abort);
|
||||
}
|
||||
if d_name.starts_with(b"~~") {
|
||||
return Ok(Continue);
|
||||
}
|
||||
Ok(Skip)
|
||||
})?;
|
||||
if !buf.is_empty() {
|
||||
buf.push_str("/base.apk");
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
inner(pkg, &mut Utf8CStrBufRef::from(data))
|
||||
.log()
|
||||
.unwrap_or(0)
|
||||
pub trait IpcRead {
|
||||
fn ipc_read_int(&mut self) -> io::Result<i32>;
|
||||
fn ipc_read_string(&mut self) -> io::Result<String>;
|
||||
}
|
||||
|
||||
impl<T: Read> IpcRead for T {
|
||||
fn ipc_read_int(&mut self) -> io::Result<i32> {
|
||||
let mut val: i32 = 0;
|
||||
self.read_pod(&mut val)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn ipc_read_string(&mut self) -> io::Result<String> {
|
||||
let len = self.ipc_read_int()?;
|
||||
let mut val = "".to_string();
|
||||
self.take(len as u64).read_to_string(&mut val)?;
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IpcWrite {
|
||||
fn ipc_write_int(&mut self, val: i32) -> io::Result<()>;
|
||||
fn ipc_write_string(&mut self, val: &str) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Write> IpcWrite for T {
|
||||
fn ipc_write_int(&mut self, val: i32) -> io::Result<()> {
|
||||
self.write_all(bytes_of(&val))
|
||||
}
|
||||
|
||||
fn ipc_write_string(&mut self, val: &str) -> io::Result<()> {
|
||||
self.ipc_write_int(val.len() as i32)?;
|
||||
self.write_all(val.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <base.hpp>
|
||||
#include <db.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
#define DB_VERSION 12
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct sqlite3;
|
||||
|
||||
static sqlite3 *mDB = nullptr;
|
||||
|
||||
#define DBLOGV(...)
|
||||
//#define DBLOGV(...) LOGD("magiskdb: " __VA_ARGS__)
|
||||
|
||||
// SQLite APIs
|
||||
|
||||
#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */
|
||||
#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */
|
||||
#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */
|
||||
|
||||
using sqlite3_callback = int (*)(void*, int, char**, char**);
|
||||
static int (*sqlite3_open_v2)(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs);
|
||||
static const char *(*sqlite3_errmsg)(sqlite3 *db);
|
||||
static int (*sqlite3_close)(sqlite3 *db);
|
||||
static void (*sqlite3_free)(void *v);
|
||||
static int (*sqlite3_exec)(sqlite3 *db, const char *sql, sqlite3_callback fn, void *v, char **errmsg);
|
||||
|
||||
// Internal Android linker APIs
|
||||
|
||||
static void (*android_get_LD_LIBRARY_PATH)(char *buffer, size_t buffer_size);
|
||||
static void (*android_update_LD_LIBRARY_PATH)(const char *ld_library_path);
|
||||
|
||||
#define DLERR(ptr) if (!(ptr)) { \
|
||||
LOGE("db: %s\n", dlerror()); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define DLOAD(handle, arg) {\
|
||||
auto f = dlsym(handle, #arg); \
|
||||
DLERR(f) \
|
||||
*(void **) &(arg) = f; \
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
constexpr char apex_path[] = "/apex/com.android.runtime/lib64:/apex/com.android.art/lib64:/apex/com.android.i18n/lib64:";
|
||||
#else
|
||||
constexpr char apex_path[] = "/apex/com.android.runtime/lib:/apex/com.android.art/lib:/apex/com.android.i18n/lib:";
|
||||
#endif
|
||||
|
||||
static int dl_init = 0;
|
||||
|
||||
static bool dload_sqlite() {
|
||||
if (dl_init)
|
||||
return dl_init > 0;
|
||||
dl_init = -1;
|
||||
|
||||
auto sqlite = dlopen("libsqlite.so", RTLD_LAZY);
|
||||
if (!sqlite) {
|
||||
// Should only happen on Android 10+
|
||||
auto dl = dlopen("libdl_android.so", RTLD_LAZY);
|
||||
DLERR(dl);
|
||||
|
||||
DLOAD(dl, android_get_LD_LIBRARY_PATH);
|
||||
DLOAD(dl, android_update_LD_LIBRARY_PATH);
|
||||
|
||||
// Inject APEX into LD_LIBRARY_PATH
|
||||
char ld_path[4096];
|
||||
memcpy(ld_path, apex_path, sizeof(apex_path));
|
||||
constexpr int len = sizeof(apex_path) - 1;
|
||||
android_get_LD_LIBRARY_PATH(ld_path + len, sizeof(ld_path) - len);
|
||||
android_update_LD_LIBRARY_PATH(ld_path);
|
||||
sqlite = dlopen("libsqlite.so", RTLD_LAZY);
|
||||
|
||||
// Revert LD_LIBRARY_PATH just in case
|
||||
android_update_LD_LIBRARY_PATH(ld_path + len);
|
||||
}
|
||||
DLERR(sqlite);
|
||||
|
||||
DLOAD(sqlite, sqlite3_open_v2);
|
||||
DLOAD(sqlite, sqlite3_errmsg);
|
||||
DLOAD(sqlite, sqlite3_close);
|
||||
DLOAD(sqlite, sqlite3_exec);
|
||||
DLOAD(sqlite, sqlite3_free);
|
||||
|
||||
dl_init = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
int db_strings::get_idx(string_view key) const {
|
||||
int idx = 0;
|
||||
for (const char *k : DB_STRING_KEYS) {
|
||||
if (key == k)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
db_settings::db_settings() {
|
||||
// Default settings
|
||||
data[ROOT_ACCESS] = ROOT_ACCESS_APPS_AND_ADB;
|
||||
data[SU_MULTIUSER_MODE] = MULTIUSER_MODE_OWNER_ONLY;
|
||||
data[SU_MNT_NS] = NAMESPACE_MODE_REQUESTER;
|
||||
data[DENYLIST_CONFIG] = false;
|
||||
data[ZYGISK_CONFIG] = MagiskD::get()->is_emulator();
|
||||
data[BOOTLOOP_COUNT] = 0;
|
||||
}
|
||||
|
||||
int db_settings::get_idx(string_view key) const {
|
||||
int idx = 0;
|
||||
for (const char *k : DB_SETTING_KEYS) {
|
||||
if (key == k)
|
||||
break;
|
||||
++idx;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
static int ver_cb(void *ver, int, char **data, char **) {
|
||||
*((int *) ver) = parse_int(data[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool db_result::check_err() {
|
||||
if (!err.empty()) {
|
||||
LOGE("sqlite3_exec: %s\n", err.data());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static db_result sql_exec(sqlite3 *db, const char *sql, sqlite3_callback fn, void *v) {
|
||||
char *err = nullptr;
|
||||
sqlite3_exec(db, sql, fn, v, &err);
|
||||
if (err) {
|
||||
db_result r = err;
|
||||
sqlite3_free(err);
|
||||
return r;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
#define sql_exe_ret(...) if (auto r = sql_exec(__VA_ARGS__); !r) return r
|
||||
#define fn_run_ret(fn) if (auto r = fn(); !r) return r
|
||||
|
||||
static db_result open_and_init_db(sqlite3 *&db) {
|
||||
if (!dload_sqlite())
|
||||
return "Cannot load libsqlite.so";
|
||||
|
||||
int ret = sqlite3_open_v2(MAGISKDB, &db,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr);
|
||||
if (ret)
|
||||
return sqlite3_errmsg(db);
|
||||
int ver = 0;
|
||||
bool upgrade = false;
|
||||
sql_exe_ret(db, "PRAGMA user_version", ver_cb, &ver);
|
||||
if (ver > DB_VERSION) {
|
||||
// Don't support downgrading database
|
||||
sqlite3_close(db);
|
||||
return "Downgrading database is not supported";
|
||||
}
|
||||
|
||||
auto create_policy = [&] {
|
||||
return sql_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS policies "
|
||||
"(uid INT, policy INT, until INT, logging INT, "
|
||||
"notification INT, PRIMARY KEY(uid))",
|
||||
nullptr, nullptr);
|
||||
};
|
||||
auto create_settings = [&] {
|
||||
return sql_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS settings "
|
||||
"(key TEXT, value INT, PRIMARY KEY(key))",
|
||||
nullptr, nullptr);
|
||||
};
|
||||
auto create_strings = [&] {
|
||||
return sql_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS strings "
|
||||
"(key TEXT, value TEXT, PRIMARY KEY(key))",
|
||||
nullptr, nullptr);
|
||||
};
|
||||
auto create_denylist = [&] {
|
||||
return sql_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS denylist "
|
||||
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process))",
|
||||
nullptr, nullptr);
|
||||
};
|
||||
|
||||
// Database changelog:
|
||||
//
|
||||
// 0 - 6: DB stored in app private data. There are no longer any code in the project to
|
||||
// migrate these data, so no need to take any of these versions into consideration.
|
||||
// 7 : create table `hidelist` (process TEXT, PRIMARY KEY(process))
|
||||
// 8 : add new column (package_name TEXT) to table `hidelist`
|
||||
// 9 : rebuild table `hidelist` to change primary key (PRIMARY KEY(package_name, process))
|
||||
// 10: remove table `logs`
|
||||
// 11: remove table `hidelist` and create table `denylist` (same data structure)
|
||||
// 12: rebuild table `policies` to drop column `package_name`
|
||||
|
||||
if (/* 0, 1, 2, 3, 4, 5, 6 */ ver <= 6) {
|
||||
fn_run_ret(create_policy);
|
||||
fn_run_ret(create_settings);
|
||||
fn_run_ret(create_strings);
|
||||
fn_run_ret(create_denylist);
|
||||
|
||||
// Directly jump to latest
|
||||
ver = DB_VERSION;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 7) {
|
||||
sql_exe_ret(db,
|
||||
"BEGIN TRANSACTION;"
|
||||
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
|
||||
"CREATE TABLE IF NOT EXISTS hidelist "
|
||||
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
|
||||
"INSERT INTO hidelist SELECT process as package_name, process FROM hidelist_tmp;"
|
||||
"DROP TABLE hidelist_tmp;"
|
||||
"COMMIT;",
|
||||
nullptr, nullptr);
|
||||
// Directly jump to version 9
|
||||
ver = 9;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 8) {
|
||||
sql_exe_ret(db,
|
||||
"BEGIN TRANSACTION;"
|
||||
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
|
||||
"CREATE TABLE IF NOT EXISTS hidelist "
|
||||
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
|
||||
"INSERT INTO hidelist SELECT * FROM hidelist_tmp;"
|
||||
"DROP TABLE hidelist_tmp;"
|
||||
"COMMIT;",
|
||||
nullptr, nullptr);
|
||||
ver = 9;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 9) {
|
||||
sql_exe_ret(db, "DROP TABLE IF EXISTS logs", nullptr, nullptr);
|
||||
ver = 10;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 10) {
|
||||
sql_exe_ret(db,
|
||||
"DROP TABLE IF EXISTS hidelist;"
|
||||
"DELETE FROM settings WHERE key='magiskhide';",
|
||||
nullptr, nullptr);
|
||||
fn_run_ret(create_denylist);
|
||||
ver = 11;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 11) {
|
||||
sql_exe_ret(db,
|
||||
"BEGIN TRANSACTION;"
|
||||
"ALTER TABLE policies RENAME TO policies_tmp;"
|
||||
"CREATE TABLE IF NOT EXISTS policies "
|
||||
"(uid INT, policy INT, until INT, logging INT, "
|
||||
"notification INT, PRIMARY KEY(uid));"
|
||||
"INSERT INTO policies "
|
||||
"SELECT uid, policy, until, logging, notification FROM policies_tmp;"
|
||||
"DROP TABLE policies_tmp;"
|
||||
"COMMIT;",
|
||||
nullptr, nullptr);
|
||||
ver = 12;
|
||||
upgrade = true;
|
||||
}
|
||||
|
||||
if (upgrade) {
|
||||
// Set version
|
||||
char query[32];
|
||||
sprintf(query, "PRAGMA user_version=%d", ver);
|
||||
sql_exe_ret(db, query, nullptr, nullptr);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
db_result db_exec(const char *sql) {
|
||||
if (mDB == nullptr) {
|
||||
auto res = open_and_init_db(mDB);
|
||||
if (res.check_err()) {
|
||||
// Open fails, remove and reconstruct
|
||||
unlink(MAGISKDB);
|
||||
res = open_and_init_db(mDB);
|
||||
if (!res) return res;
|
||||
}
|
||||
}
|
||||
if (mDB) {
|
||||
sql_exe_ret(mDB, sql, nullptr, nullptr);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static int sqlite_db_row_callback(void *cb, int col_num, char **data, char **col_name) {
|
||||
auto &func = *static_cast<const db_row_cb*>(cb);
|
||||
db_row row;
|
||||
for (int i = 0; i < col_num; ++i)
|
||||
row[col_name[i]] = data[i];
|
||||
return func(row) ? 0 : 1;
|
||||
}
|
||||
|
||||
db_result db_exec(const char *sql, const db_row_cb &fn) {
|
||||
if (mDB == nullptr) {
|
||||
auto res = open_and_init_db(mDB);
|
||||
if (res.check_err()) {
|
||||
// Open fails, remove and reconstruct
|
||||
unlink(MAGISKDB);
|
||||
res = open_and_init_db(mDB);
|
||||
if (!res) return res;
|
||||
}
|
||||
}
|
||||
if (mDB) {
|
||||
sql_exe_ret(mDB, sql, sqlite_db_row_callback, (void *) &fn);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int get_db_settings(db_settings &cfg, int key) {
|
||||
db_result res;
|
||||
auto settings_cb = [&](db_row &row) -> bool {
|
||||
cfg[row["key"]] = parse_int(row["value"]);
|
||||
DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data());
|
||||
return true;
|
||||
};
|
||||
if (key >= 0) {
|
||||
char query[128];
|
||||
ssprintf(query, sizeof(query), "SELECT * FROM settings WHERE key='%s'", DB_SETTING_KEYS[key]);
|
||||
res = db_exec(query, settings_cb);
|
||||
} else {
|
||||
res = db_exec("SELECT * FROM settings", settings_cb);
|
||||
}
|
||||
return res.check_err() ? 1 : 0;
|
||||
}
|
||||
|
||||
int set_db_settings(int key, int value) {
|
||||
char sql[128];
|
||||
ssprintf(sql, sizeof(sql), "INSERT OR REPLACE INTO settings VALUES ('%s', %d)",
|
||||
DB_SETTING_KEYS[key], value);
|
||||
return db_exec(sql).check_err() ? 1 : 0;
|
||||
}
|
||||
|
||||
int get_db_strings(db_strings &str, int key) {
|
||||
db_result res;
|
||||
auto string_cb = [&](db_row &row) -> bool {
|
||||
str[row["key"]] = row["value"];
|
||||
DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data());
|
||||
return true;
|
||||
};
|
||||
if (key >= 0) {
|
||||
char query[128];
|
||||
ssprintf(query, sizeof(query), "SELECT * FROM strings WHERE key='%s'", DB_STRING_KEYS[key]);
|
||||
res = db_exec(query, string_cb);
|
||||
} else {
|
||||
res = db_exec("SELECT * FROM strings", string_cb);
|
||||
}
|
||||
return res.check_err() ? 1 : 0;
|
||||
}
|
||||
|
||||
void rm_db_strings(int key) {
|
||||
char query[128];
|
||||
ssprintf(query, sizeof(query), "DELETE FROM strings WHERE key == '%s'", DB_STRING_KEYS[key]);
|
||||
db_exec(query).check_err();
|
||||
}
|
||||
|
||||
void exec_sql(owned_fd client) {
|
||||
string sql = read_string(client);
|
||||
auto res = db_exec(sql.data(), [fd = (int) client](db_row &row) -> bool {
|
||||
string out;
|
||||
bool first = true;
|
||||
for (auto it : row) {
|
||||
if (first) first = false;
|
||||
else out += '|';
|
||||
out += it.first;
|
||||
out += '=';
|
||||
out += it.second;
|
||||
}
|
||||
write_string(fd, out);
|
||||
return true;
|
||||
});
|
||||
write_int(client, 0);
|
||||
res.check_err();
|
||||
}
|
||||
345
native/src/core/db.rs
Normal file
345
native/src/core/db.rs
Normal file
@@ -0,0 +1,345 @@
|
||||
#![allow(improper_ctypes, improper_ctypes_definitions)]
|
||||
use crate::daemon::{IpcRead, IpcWrite, MagiskD, MAGISKD};
|
||||
use crate::ffi::{
|
||||
open_and_init_db, sqlite3, sqlite3_errstr, DbEntryKey, DbSettings, DbStatement, DbValues,
|
||||
MntNsMode, MultiuserMode, RootAccess,
|
||||
};
|
||||
use base::{LoggedResult, ResultExt, Utf8CStr};
|
||||
use std::ffi::c_void;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
use std::ptr::NonNull;
|
||||
use thiserror::Error;
|
||||
use DbArg::{Integer, Text};
|
||||
|
||||
fn sqlite_err_str(code: i32) -> &'static Utf8CStr {
|
||||
// SAFETY: sqlite3 always returns UTF-8 strings
|
||||
unsafe { Utf8CStr::from_ptr_unchecked(sqlite3_errstr(code)) }
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Error, Debug)]
|
||||
#[error("sqlite3: {}", sqlite_err_str(self.0))]
|
||||
pub struct SqliteError(i32);
|
||||
|
||||
pub type SqliteResult<T> = Result<T, SqliteError>;
|
||||
|
||||
pub trait SqliteReturn {
|
||||
fn sql_result(self) -> SqliteResult<()>;
|
||||
}
|
||||
|
||||
impl SqliteReturn for i32 {
|
||||
fn sql_result(self) -> SqliteResult<()> {
|
||||
if self != 0 {
|
||||
Err(SqliteError(self))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SqlTable {
|
||||
fn on_row(&mut self, columns: &[String], values: &DbValues);
|
||||
}
|
||||
|
||||
impl<T> SqlTable for T
|
||||
where
|
||||
T: FnMut(&[String], &DbValues),
|
||||
{
|
||||
fn on_row(&mut self, columns: &[String], values: &DbValues) {
|
||||
self.call_mut((columns, values))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RootAccess {
|
||||
fn default() -> Self {
|
||||
RootAccess::AppsAndAdb
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MultiuserMode {
|
||||
fn default() -> Self {
|
||||
MultiuserMode::OwnerOnly
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MntNsMode {
|
||||
fn default() -> Self {
|
||||
MntNsMode::Requester
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_default_db_settings() -> DbSettings {
|
||||
DbSettings::default()
|
||||
}
|
||||
|
||||
impl DbEntryKey {
|
||||
fn to_str(self) -> &'static str {
|
||||
match self {
|
||||
DbEntryKey::RootAccess => "root_access",
|
||||
DbEntryKey::SuMultiuserMode => "multiuser_mode",
|
||||
DbEntryKey::SuMntNs => "mnt_ns",
|
||||
DbEntryKey::DenylistConfig => "denylist",
|
||||
DbEntryKey::ZygiskConfig => "zygisk",
|
||||
DbEntryKey::BootloopCount => "bootloop",
|
||||
DbEntryKey::SuManager => "requester",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SqlTable for DbSettings {
|
||||
fn on_row(&mut self, columns: &[String], values: &DbValues) {
|
||||
let mut key = "";
|
||||
let mut value = 0;
|
||||
for (i, column) in columns.iter().enumerate() {
|
||||
if column == "key" {
|
||||
key = values.get_text(i as i32);
|
||||
} else if column == "value" {
|
||||
value = values.get_int(i as i32);
|
||||
}
|
||||
}
|
||||
match key {
|
||||
"root_access" => self.root_access = RootAccess { repr: value },
|
||||
"multiuser_mode" => self.multiuser_mode = MultiuserMode { repr: value },
|
||||
"mnt_ns" => self.mnt_ns = MntNsMode { repr: value },
|
||||
"denylist" => self.denylist = value != 0,
|
||||
"zygisk" => self.zygisk = value != 0,
|
||||
"bootloop" => self.boot_count = value,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Sqlite3(NonNull<sqlite3>);
|
||||
unsafe impl Send for Sqlite3 {}
|
||||
|
||||
type SqlBindCallback = Option<unsafe extern "C" fn(*mut c_void, i32, Pin<&mut DbStatement>) -> i32>;
|
||||
type SqlExecCallback = Option<unsafe extern "C" fn(*mut c_void, &[String], &DbValues)>;
|
||||
|
||||
extern "C" {
|
||||
fn sql_exec_impl(
|
||||
db: *mut sqlite3,
|
||||
sql: &str,
|
||||
bind_callback: SqlBindCallback,
|
||||
bind_cookie: *mut c_void,
|
||||
exec_callback: SqlExecCallback,
|
||||
exec_cookie: *mut c_void,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
pub enum DbArg<'a> {
|
||||
Text(&'a str),
|
||||
Integer(i64),
|
||||
}
|
||||
|
||||
struct DbArgs<'a> {
|
||||
args: &'a [DbArg<'a>],
|
||||
curr: usize,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn bind_arguments(v: *mut c_void, idx: i32, stmt: Pin<&mut DbStatement>) -> i32 {
|
||||
let args = &mut *(v as *mut DbArgs<'_>);
|
||||
if args.curr < args.args.len() {
|
||||
let arg = &args.args[args.curr];
|
||||
args.curr += 1;
|
||||
match *arg {
|
||||
Text(v) => stmt.bind_text(idx, v),
|
||||
Integer(v) => stmt.bind_int64(idx, v),
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn read_db_row<T: SqlTable>(
|
||||
v: *mut c_void,
|
||||
columns: &[String],
|
||||
values: &DbValues,
|
||||
) {
|
||||
let table = &mut *(v as *mut T);
|
||||
table.on_row(columns, values);
|
||||
}
|
||||
|
||||
impl MagiskD {
|
||||
fn with_db<F: FnOnce(*mut sqlite3) -> i32>(&self, f: F) -> i32 {
|
||||
let mut db = self.sql_connection.lock().unwrap();
|
||||
if db.is_none() {
|
||||
let raw_db = open_and_init_db();
|
||||
*db = NonNull::new(raw_db).map(Sqlite3);
|
||||
}
|
||||
if let Some(ref mut db) = *db {
|
||||
f(db.0.as_ptr())
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
fn db_exec_impl(
|
||||
&self,
|
||||
sql: &str,
|
||||
args: &[DbArg],
|
||||
exec_callback: SqlExecCallback,
|
||||
exec_cookie: *mut c_void,
|
||||
) -> i32 {
|
||||
let mut bind_callback: SqlBindCallback = None;
|
||||
let mut bind_cookie: *mut c_void = ptr::null_mut();
|
||||
let mut db_args = DbArgs { args, curr: 0 };
|
||||
if !args.is_empty() {
|
||||
bind_callback = Some(bind_arguments);
|
||||
bind_cookie = (&mut db_args) as *mut DbArgs as *mut c_void;
|
||||
}
|
||||
self.with_db(|db| unsafe {
|
||||
sql_exec_impl(
|
||||
db,
|
||||
sql,
|
||||
bind_callback,
|
||||
bind_cookie,
|
||||
exec_callback,
|
||||
exec_cookie,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn db_exec_with_rows<T: SqlTable>(&self, sql: &str, args: &[DbArg], out: &mut T) -> i32 {
|
||||
self.db_exec_impl(
|
||||
sql,
|
||||
args,
|
||||
Some(read_db_row::<T>),
|
||||
out as *mut T as *mut c_void,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn db_exec(&self, sql: &str, args: &[DbArg]) -> i32 {
|
||||
self.db_exec_impl(sql, args, None, ptr::null_mut())
|
||||
}
|
||||
|
||||
pub fn set_db_setting(&self, key: DbEntryKey, value: i32) -> SqliteResult<()> {
|
||||
self.db_exec(
|
||||
"INSERT OR REPLACE INTO settings (key,value) VALUES(?,?)",
|
||||
&[Text(key.to_str()), Integer(value as i64)],
|
||||
)
|
||||
.sql_result()
|
||||
}
|
||||
|
||||
pub fn get_db_setting(&self, key: DbEntryKey) -> i32 {
|
||||
// Get default values
|
||||
let mut val = match key {
|
||||
DbEntryKey::RootAccess => RootAccess::default().repr,
|
||||
DbEntryKey::SuMultiuserMode => MultiuserMode::default().repr,
|
||||
DbEntryKey::SuMntNs => MntNsMode::default().repr,
|
||||
DbEntryKey::DenylistConfig => 0,
|
||||
DbEntryKey::ZygiskConfig => self.is_emulator as i32,
|
||||
DbEntryKey::BootloopCount => 0,
|
||||
_ => -1,
|
||||
};
|
||||
let mut func = |_: &[String], values: &DbValues| {
|
||||
val = values.get_int(0);
|
||||
};
|
||||
self.db_exec_with_rows(
|
||||
"SELECT value FROM settings WHERE key=?",
|
||||
&[Text(key.to_str())],
|
||||
&mut func,
|
||||
)
|
||||
.sql_result()
|
||||
.log()
|
||||
.ok();
|
||||
val
|
||||
}
|
||||
|
||||
pub fn get_db_settings(&self) -> SqliteResult<DbSettings> {
|
||||
let mut cfg = DbSettings {
|
||||
zygisk: self.is_emulator,
|
||||
..Default::default()
|
||||
};
|
||||
self.db_exec_with_rows("SELECT * FROM settings", &[], &mut cfg)
|
||||
.sql_result()?;
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
pub fn get_db_string(&self, key: DbEntryKey) -> String {
|
||||
let mut val = "".to_string();
|
||||
let mut func = |_: &[String], values: &DbValues| {
|
||||
val.push_str(values.get_text(0));
|
||||
};
|
||||
self.db_exec_with_rows(
|
||||
"SELECT value FROM strings WHERE key=?",
|
||||
&[Text(key.to_str())],
|
||||
&mut func,
|
||||
)
|
||||
.sql_result()
|
||||
.log()
|
||||
.ok();
|
||||
val
|
||||
}
|
||||
|
||||
pub fn rm_db_string(&self, key: DbEntryKey) -> SqliteResult<()> {
|
||||
self.db_exec("DELETE FROM strings WHERE key=?", &[Text(key.to_str())])
|
||||
.sql_result()
|
||||
}
|
||||
|
||||
fn db_exec_for_client(&self, fd: OwnedFd) -> LoggedResult<()> {
|
||||
let mut file = File::from(fd);
|
||||
let mut reader = BufReader::new(&mut file);
|
||||
let sql = reader.ipc_read_string()?;
|
||||
let mut writer = BufWriter::new(&mut file);
|
||||
let mut output_fn = |columns: &[String], values: &DbValues| {
|
||||
let mut out = "".to_string();
|
||||
for (i, column) in columns.iter().enumerate() {
|
||||
if i != 0 {
|
||||
out.push('|');
|
||||
}
|
||||
out.push_str(column);
|
||||
out.push('=');
|
||||
out.push_str(values.get_text(i as i32));
|
||||
}
|
||||
writer.ipc_write_string(&out).log().ok();
|
||||
};
|
||||
self.db_exec_with_rows(&sql, &[], &mut output_fn);
|
||||
writer.ipc_write_string("").log()
|
||||
}
|
||||
}
|
||||
|
||||
impl MagiskD {
|
||||
pub fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool {
|
||||
cfg.zygisk = self.is_emulator;
|
||||
self.db_exec_with_rows("SELECT * FROM settings", &[], cfg)
|
||||
.sql_result()
|
||||
.log()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool {
|
||||
self.set_db_setting(key, value).log().is_ok()
|
||||
}
|
||||
|
||||
pub fn db_exec_for_cxx(&self, client_fd: RawFd) {
|
||||
// Take ownership
|
||||
let fd = unsafe { OwnedFd::from_raw_fd(client_fd) };
|
||||
self.db_exec_for_client(fd).ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[export_name = "sql_exec_rs"]
|
||||
unsafe extern "C" fn sql_exec_for_cxx(
|
||||
sql: &str,
|
||||
bind_callback: SqlBindCallback,
|
||||
bind_cookie: *mut c_void,
|
||||
exec_callback: SqlExecCallback,
|
||||
exec_cookie: *mut c_void,
|
||||
) -> i32 {
|
||||
MAGISKD.get().unwrap_unchecked().with_db(|db| {
|
||||
sql_exec_impl(
|
||||
db,
|
||||
sql,
|
||||
bind_callback,
|
||||
bind_cookie,
|
||||
exec_callback,
|
||||
exec_cookie,
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <base.hpp>
|
||||
#include <db.hpp>
|
||||
#include <sqlite.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
#include "deny.hpp"
|
||||
@@ -201,7 +201,7 @@ void scan_deny_apps() {
|
||||
LOGI("denylist rm: [%s]\n", it->first.data());
|
||||
ssprintf(sql, sizeof(sql), "DELETE FROM denylist WHERE package_name='%s'",
|
||||
it->first.data());
|
||||
db_exec(sql).check_err();
|
||||
db_exec(sql);
|
||||
it = pkg_to_procs.erase(it);
|
||||
} else {
|
||||
update_app_id(app_id, it->first, false);
|
||||
@@ -222,11 +222,20 @@ static bool ensure_data() {
|
||||
LOGI("denylist: initializing internal data structures\n");
|
||||
|
||||
default_new(pkg_to_procs_);
|
||||
auto res = db_exec("SELECT * FROM denylist", [](db_row &row) -> bool {
|
||||
add_hide_set(row["package_name"].data(), row["process"].data());
|
||||
return true;
|
||||
bool res = db_exec("SELECT * FROM denylist", {}, [](StringSlice columns, const DbValues &values) {
|
||||
const char *package_name;
|
||||
const char *process;
|
||||
for (int i = 0; i < columns.size(); ++i) {
|
||||
const auto &name = columns[i];
|
||||
if (name == "package_name") {
|
||||
package_name = values.get_text(i);
|
||||
} else if (name == "process") {
|
||||
process = values.get_text(i);
|
||||
}
|
||||
}
|
||||
add_hide_set(package_name, process);
|
||||
});
|
||||
if (res.check_err())
|
||||
if (!res)
|
||||
goto error;
|
||||
|
||||
default_new(app_id_to_pkgs_);
|
||||
@@ -263,7 +272,7 @@ static int add_list(const char *pkg, const char *proc) {
|
||||
char sql[4096];
|
||||
ssprintf(sql, sizeof(sql),
|
||||
"INSERT INTO denylist (package_name, process) VALUES('%s', '%s')", pkg, proc);
|
||||
return db_exec(sql).check_err() ? DenyResponse::ERROR : DenyResponse::OK;
|
||||
return db_exec(sql) ? DenyResponse::OK : DenyResponse::ERROR;
|
||||
}
|
||||
|
||||
int add_list(int client) {
|
||||
@@ -307,7 +316,7 @@ static int rm_list(const char *pkg, const char *proc) {
|
||||
else
|
||||
ssprintf(sql, sizeof(sql),
|
||||
"DELETE FROM denylist WHERE package_name='%s' AND process='%s'", pkg, proc);
|
||||
return db_exec(sql).check_err() ? DenyResponse::ERROR : DenyResponse::OK;
|
||||
return db_exec(sql) ? DenyResponse::OK : DenyResponse::ERROR;
|
||||
}
|
||||
|
||||
int rm_list(int client) {
|
||||
@@ -376,7 +385,7 @@ int enable_deny() {
|
||||
}
|
||||
}
|
||||
|
||||
set_db_settings(DENYLIST_CONFIG, true);
|
||||
MagiskD().set_db_setting(DbEntryKey::DenylistConfig, true);
|
||||
return DenyResponse::OK;
|
||||
}
|
||||
|
||||
@@ -384,15 +393,13 @@ int disable_deny() {
|
||||
if (denylist_enforced.exchange(false)) {
|
||||
LOGI("* Disable DenyList\n");
|
||||
}
|
||||
set_db_settings(DENYLIST_CONFIG, false);
|
||||
MagiskD().set_db_setting(DbEntryKey::DenylistConfig, false);
|
||||
return DenyResponse::OK;
|
||||
}
|
||||
|
||||
void initialize_denylist() {
|
||||
if (!denylist_enforced) {
|
||||
db_settings dbs;
|
||||
get_db_settings(dbs, DENYLIST_CONFIG);
|
||||
if (dbs[DENYLIST_CONFIG])
|
||||
if (MagiskD().get_db_setting(DbEntryKey::DenylistConfig))
|
||||
enable_deny();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
#define AID_ROOT 0
|
||||
#define AID_SHELL 2000
|
||||
#define AID_APP_START 10000
|
||||
#define AID_APP_END 19999
|
||||
#define AID_USER_OFFSET 100000
|
||||
|
||||
#define to_app_id(uid) (uid % AID_USER_OFFSET)
|
||||
@@ -55,17 +53,10 @@ void init_thread_pool();
|
||||
void exec_task(std::function<void()> &&task);
|
||||
|
||||
// Daemon handlers
|
||||
void boot_stage_handler(int client, int code);
|
||||
void denylist_handler(int client, const sock_cred *cred);
|
||||
void su_daemon_handler(int client, const sock_cred *cred);
|
||||
void zygisk_handler(int client, const sock_cred *cred);
|
||||
|
||||
// Package
|
||||
void preserve_stub_apk();
|
||||
std::vector<bool> get_app_no_list();
|
||||
int get_manager(int user, std::string *pkg = nullptr, bool install = false);
|
||||
void prune_su_access();
|
||||
|
||||
// Module stuffs
|
||||
void handle_modules();
|
||||
void load_modules();
|
||||
@@ -77,8 +68,6 @@ void exec_module_scripts(const char *stage);
|
||||
void exec_script(const char *script);
|
||||
void exec_common_scripts(const char *stage);
|
||||
void exec_module_scripts(const char *stage, const std::vector<std::string_view> &modules);
|
||||
void install_apk(const char *apk);
|
||||
void uninstall_pkg(const char *pkg);
|
||||
void clear_pkg(const char *pkg, int user_id);
|
||||
[[noreturn]] void install_module(const char *file);
|
||||
|
||||
|
||||
@@ -2,30 +2,9 @@
|
||||
|
||||
#include <base.hpp>
|
||||
|
||||
namespace rust {
|
||||
struct MagiskD;
|
||||
}
|
||||
|
||||
struct MagiskD {
|
||||
// Make sure only references can exist
|
||||
~MagiskD() = delete;
|
||||
|
||||
// Binding to Rust
|
||||
static const MagiskD &get();
|
||||
|
||||
// C++ implementation
|
||||
void reboot() const;
|
||||
bool post_fs_data() const;
|
||||
void late_start() const;
|
||||
void boot_complete() const;
|
||||
|
||||
const rust::MagiskD *operator->() const;
|
||||
|
||||
private:
|
||||
const rust::MagiskD &as_rust() const;
|
||||
};
|
||||
|
||||
const char *get_magisk_tmp();
|
||||
void install_apk(rust::Utf8CStr apk);
|
||||
void uninstall_pkg(rust::Utf8CStr pkg);
|
||||
|
||||
// Rust bindings
|
||||
static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <functional>
|
||||
|
||||
template <class T, size_t N>
|
||||
class db_dict {
|
||||
public:
|
||||
T& operator [](std::string_view key) {
|
||||
return data[get_idx(key)];
|
||||
}
|
||||
|
||||
const T& operator [](std::string_view key) const {
|
||||
return data[get_idx(key)];
|
||||
}
|
||||
|
||||
T& operator [](int key) {
|
||||
return data[key];
|
||||
}
|
||||
|
||||
const T& operator [](int key) const {
|
||||
return data[key];
|
||||
}
|
||||
|
||||
protected:
|
||||
T data[N + 1];
|
||||
virtual int get_idx(std::string_view key) const = 0;
|
||||
};
|
||||
|
||||
/***************
|
||||
* DB Settings *
|
||||
***************/
|
||||
|
||||
constexpr const char *DB_SETTING_KEYS[] = {
|
||||
"root_access",
|
||||
"multiuser_mode",
|
||||
"mnt_ns",
|
||||
"denylist",
|
||||
"zygisk",
|
||||
"bootloop",
|
||||
};
|
||||
|
||||
// Settings key indices
|
||||
enum {
|
||||
ROOT_ACCESS = 0,
|
||||
SU_MULTIUSER_MODE,
|
||||
SU_MNT_NS,
|
||||
DENYLIST_CONFIG,
|
||||
ZYGISK_CONFIG,
|
||||
BOOTLOOP_COUNT,
|
||||
};
|
||||
|
||||
// Values for root_access
|
||||
enum {
|
||||
ROOT_ACCESS_DISABLED = 0,
|
||||
ROOT_ACCESS_APPS_ONLY,
|
||||
ROOT_ACCESS_ADB_ONLY,
|
||||
ROOT_ACCESS_APPS_AND_ADB
|
||||
};
|
||||
|
||||
// Values for multiuser_mode
|
||||
enum {
|
||||
MULTIUSER_MODE_OWNER_ONLY = 0,
|
||||
MULTIUSER_MODE_OWNER_MANAGED,
|
||||
MULTIUSER_MODE_USER
|
||||
};
|
||||
|
||||
// Values for mnt_ns
|
||||
enum {
|
||||
NAMESPACE_MODE_GLOBAL = 0,
|
||||
NAMESPACE_MODE_REQUESTER,
|
||||
NAMESPACE_MODE_ISOLATE
|
||||
};
|
||||
|
||||
class db_settings : public db_dict<int, std::size(DB_SETTING_KEYS)> {
|
||||
public:
|
||||
db_settings();
|
||||
protected:
|
||||
int get_idx(std::string_view key) const override;
|
||||
};
|
||||
|
||||
/**************
|
||||
* DB Strings *
|
||||
**************/
|
||||
|
||||
constexpr const char *DB_STRING_KEYS[] = { "requester" };
|
||||
|
||||
// Strings keys indices
|
||||
enum {
|
||||
SU_MANAGER = 0
|
||||
};
|
||||
|
||||
class db_strings : public db_dict<std::string, std::size(DB_STRING_KEYS)> {
|
||||
protected:
|
||||
int get_idx(std::string_view key) const override;
|
||||
};
|
||||
|
||||
/*************
|
||||
* SU Access *
|
||||
*************/
|
||||
|
||||
typedef enum {
|
||||
QUERY = 0,
|
||||
DENY = 1,
|
||||
ALLOW = 2,
|
||||
} policy_t;
|
||||
|
||||
struct su_access {
|
||||
policy_t policy;
|
||||
int log;
|
||||
int notify;
|
||||
};
|
||||
|
||||
#define DEFAULT_SU_ACCESS { QUERY, 1, 1 }
|
||||
#define SILENT_SU_ACCESS { ALLOW, 0, 0 }
|
||||
#define NO_SU_ACCESS { DENY, 0, 0 }
|
||||
|
||||
/********************
|
||||
* Public Functions *
|
||||
********************/
|
||||
|
||||
using db_row = std::map<std::string_view, std::string_view>;
|
||||
using db_row_cb = std::function<bool(db_row&)>;
|
||||
struct owned_fd;
|
||||
|
||||
struct db_result {
|
||||
db_result() = default;
|
||||
db_result(const char *s) : err(s) {}
|
||||
bool check_err();
|
||||
operator bool() { return err.empty(); }
|
||||
private:
|
||||
std::string err;
|
||||
};
|
||||
|
||||
int get_db_settings(db_settings &cfg, int key = -1);
|
||||
int set_db_settings(int key, int value);
|
||||
int get_db_strings(db_strings &str, int key = -1);
|
||||
void rm_db_strings(int key);
|
||||
void exec_sql(owned_fd client);
|
||||
db_result db_exec(const char *sql);
|
||||
db_result db_exec(const char *sql, const db_row_cb &fn);
|
||||
76
native/src/core/include/sqlite.hpp
Normal file
76
native/src/core/include/sqlite.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <cxx.h>
|
||||
|
||||
#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */
|
||||
#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */
|
||||
#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */
|
||||
|
||||
#define SQLITE_OK 0 /* Successful result */
|
||||
#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
|
||||
#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
|
||||
|
||||
struct sqlite3;
|
||||
struct sqlite3_stmt;
|
||||
|
||||
extern const char *(*sqlite3_errstr)(int);
|
||||
|
||||
// Transparent wrappers of sqlite3_stmt
|
||||
struct DbValues {
|
||||
const char *get_text(int index) const;
|
||||
rust::Str get_str(int index) const { return get_text(index); }
|
||||
int get_int(int index) const;
|
||||
~DbValues() = delete;
|
||||
};
|
||||
struct DbStatement {
|
||||
int bind_text(int index, rust::Str val);
|
||||
int bind_int64(int index, int64_t val);
|
||||
~DbStatement() = delete;
|
||||
};
|
||||
|
||||
using StringSlice = rust::Slice<rust::String>;
|
||||
using sql_bind_callback = int(*)(void*, int, DbStatement&);
|
||||
using sql_exec_callback = void(*)(void*, StringSlice, const DbValues&);
|
||||
|
||||
sqlite3 *open_and_init_db();
|
||||
|
||||
/************
|
||||
* C++ APIs *
|
||||
************/
|
||||
|
||||
using db_exec_callback = std::function<void(StringSlice, const DbValues&)>;
|
||||
|
||||
struct DbArg {
|
||||
enum {
|
||||
INT,
|
||||
TEXT,
|
||||
} type;
|
||||
union {
|
||||
int64_t int_val;
|
||||
rust::Str str_val;
|
||||
};
|
||||
DbArg(int64_t v) : type(INT), int_val(v) {}
|
||||
DbArg(const char *v) : type(TEXT), str_val(v) {}
|
||||
};
|
||||
|
||||
struct DbArgs {
|
||||
DbArgs() : curr(0) {}
|
||||
DbArgs(std::initializer_list<DbArg> list) : args(list), curr(0) {}
|
||||
int operator()(int index, DbStatement &stmt);
|
||||
bool empty() const { return args.empty(); }
|
||||
private:
|
||||
std::vector<DbArg> args;
|
||||
size_t curr;
|
||||
};
|
||||
|
||||
bool db_exec(const char *sql, DbArgs args = {}, db_exec_callback exec_fn = {});
|
||||
|
||||
template<typename T>
|
||||
concept DbData = requires(T t, StringSlice s, DbValues &v) { t(s, v); };
|
||||
|
||||
template<DbData T>
|
||||
bool db_exec(const char *sql, DbArgs args, T &data) {
|
||||
return db_exec(sql, std::move(args), (db_exec_callback) std::ref(data));
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
#![feature(format_args_nl)]
|
||||
#![feature(try_blocks)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(fn_traits)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
use base::Utf8CStr;
|
||||
use cert::read_certificate;
|
||||
use daemon::{daemon_entry, find_apk_path, get_magiskd, MagiskD};
|
||||
use logging::{
|
||||
android_logging, magisk_logging, zygisk_close_logd, zygisk_get_logd, zygisk_logging,
|
||||
};
|
||||
use mount::{find_preinit_device, revert_unmount, setup_mounts, clean_mounts};
|
||||
use daemon::{daemon_entry, get_magiskd, MagiskD};
|
||||
use db::get_default_db_settings;
|
||||
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
|
||||
use mount::{clean_mounts, find_preinit_device, revert_unmount, setup_mounts};
|
||||
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
|
||||
use su::get_default_root_settings;
|
||||
|
||||
mod cert;
|
||||
#[path = "../include/consts.rs"]
|
||||
mod consts;
|
||||
mod daemon;
|
||||
mod db;
|
||||
mod logging;
|
||||
mod mount;
|
||||
mod package;
|
||||
mod resetprop;
|
||||
mod su;
|
||||
|
||||
#[cxx::bridge]
|
||||
pub mod ffi {
|
||||
@@ -72,25 +74,93 @@ pub mod ffi {
|
||||
fn get_magisk_tmp() -> Utf8CStrRef<'static>;
|
||||
#[cxx_name = "resolve_preinit_dir_rs"]
|
||||
fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String;
|
||||
fn install_apk(apk: Utf8CStrRef);
|
||||
fn uninstall_pkg(apk: Utf8CStrRef);
|
||||
|
||||
fn switch_mnt_ns(pid: i32) -> i32;
|
||||
}
|
||||
|
||||
#[cxx_name = "MagiskD"]
|
||||
type CxxMagiskD;
|
||||
fn post_fs_data(self: &CxxMagiskD) -> bool;
|
||||
fn late_start(self: &CxxMagiskD);
|
||||
fn boot_complete(self: &CxxMagiskD);
|
||||
enum DbEntryKey {
|
||||
RootAccess,
|
||||
SuMultiuserMode,
|
||||
SuMntNs,
|
||||
DenylistConfig,
|
||||
ZygiskConfig,
|
||||
BootloopCount,
|
||||
SuManager,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
enum RootAccess {
|
||||
Disabled,
|
||||
AppsOnly,
|
||||
AdbOnly,
|
||||
AppsAndAdb,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
enum MultiuserMode {
|
||||
OwnerOnly,
|
||||
OwnerManaged,
|
||||
User,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
enum MntNsMode {
|
||||
Global,
|
||||
Requester,
|
||||
Isolate,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DbSettings {
|
||||
root_access: RootAccess,
|
||||
multiuser_mode: MultiuserMode,
|
||||
mnt_ns: MntNsMode,
|
||||
boot_count: i32,
|
||||
denylist: bool,
|
||||
zygisk: bool,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
enum SuPolicy {
|
||||
Query,
|
||||
Deny,
|
||||
Allow,
|
||||
}
|
||||
|
||||
struct RootSettings {
|
||||
policy: SuPolicy,
|
||||
log: bool,
|
||||
notify: bool,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("include/sqlite.hpp");
|
||||
|
||||
fn sqlite3_errstr(code: i32) -> *const c_char;
|
||||
|
||||
type sqlite3;
|
||||
fn open_and_init_db() -> *mut sqlite3;
|
||||
|
||||
type DbValues;
|
||||
type DbStatement;
|
||||
|
||||
fn get_int(self: &DbValues, index: i32) -> i32;
|
||||
#[cxx_name = "get_str"]
|
||||
fn get_text(self: &DbValues, index: i32) -> &str;
|
||||
|
||||
fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32;
|
||||
fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32;
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
fn rust_test_entry();
|
||||
fn android_logging();
|
||||
fn magisk_logging();
|
||||
fn zygisk_logging();
|
||||
fn zygisk_close_logd();
|
||||
fn zygisk_get_logd() -> i32;
|
||||
fn find_apk_path(pkg: Utf8CStrRef, data: &mut [u8]) -> usize;
|
||||
fn read_certificate(fd: i32, version: i32) -> Vec<u8>;
|
||||
fn setup_logfile();
|
||||
fn setup_mounts();
|
||||
fn clean_mounts();
|
||||
fn find_preinit_device() -> String;
|
||||
@@ -99,18 +169,46 @@ pub mod ffi {
|
||||
unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>);
|
||||
unsafe fn persist_delete_prop(name: Utf8CStrRef) -> bool;
|
||||
unsafe fn persist_set_prop(name: Utf8CStrRef, value: Utf8CStrRef) -> bool;
|
||||
|
||||
#[namespace = "rust"]
|
||||
fn daemon_entry();
|
||||
}
|
||||
|
||||
#[namespace = "rust"]
|
||||
// FFI for MagiskD
|
||||
extern "Rust" {
|
||||
fn daemon_entry();
|
||||
|
||||
type MagiskD;
|
||||
fn is_recovery(&self) -> bool;
|
||||
fn sdk_int(&self) -> i32;
|
||||
fn boot_stage_handler(&self, client: i32, code: i32);
|
||||
fn preserve_stub_apk(&self);
|
||||
fn prune_su_access(&self);
|
||||
fn uid_granted_root(&self, mut uid: i32) -> bool;
|
||||
#[cxx_name = "get_manager"]
|
||||
unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32;
|
||||
|
||||
#[cxx_name = "get_db_settings"]
|
||||
fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool;
|
||||
fn get_db_setting(&self, key: DbEntryKey) -> i32;
|
||||
#[cxx_name = "set_db_setting"]
|
||||
fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool;
|
||||
#[cxx_name = "db_exec"]
|
||||
fn db_exec_for_cxx(&self, client_fd: i32);
|
||||
#[cxx_name = "get_root_settings"]
|
||||
fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool;
|
||||
|
||||
#[cxx_name = "DbSettings"]
|
||||
fn get_default_db_settings() -> DbSettings;
|
||||
#[cxx_name = "RootSettings"]
|
||||
fn get_default_root_settings() -> RootSettings;
|
||||
#[cxx_name = "MagiskD"]
|
||||
fn get_magiskd() -> &'static MagiskD;
|
||||
fn setup_logfile(self: &MagiskD);
|
||||
fn is_emulator(self: &MagiskD) -> bool;
|
||||
fn is_recovery(self: &MagiskD) -> bool;
|
||||
fn boot_stage_handler(self: &MagiskD, client: i32, code: i32);
|
||||
}
|
||||
unsafe extern "C++" {
|
||||
#[allow(dead_code)]
|
||||
fn reboot(self: &MagiskD);
|
||||
fn post_fs_data(self: &MagiskD) -> bool;
|
||||
fn late_start(self: &MagiskD);
|
||||
fn boot_complete(self: &MagiskD);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
use crate::consts::{LOGFILE, LOG_PIPE};
|
||||
use crate::ffi::get_magisk_tmp;
|
||||
use crate::logging::LogFile::{Actual, Buffer};
|
||||
use base::libc::{
|
||||
getpid, gettid, localtime_r, pthread_sigmask, sigaddset, sigset_t, sigtimedwait, time_t,
|
||||
timespec, tm, O_CLOEXEC, O_RDWR, O_WRONLY, PIPE_BUF, SIGPIPE, SIG_BLOCK, SIG_SETMASK,
|
||||
};
|
||||
use base::{
|
||||
const_format::concatcp, libc, raw_cstr, FsPathBuf, LogLevel, Logger, ReadExt, SharedFd,
|
||||
Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrWrite, LOGGER,
|
||||
};
|
||||
use bytemuck::{bytes_of, write_zeroes, Pod, Zeroable};
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use num_traits::FromPrimitive;
|
||||
use std::cmp::min;
|
||||
use std::ffi::{c_char, c_void};
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::fs::File;
|
||||
use std::io::{IoSlice, Read, Write};
|
||||
use std::os::fd::{FromRawFd, RawFd};
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
use std::sync::Mutex;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{fs, io};
|
||||
|
||||
use bytemuck::{bytes_of, bytes_of_mut, write_zeroes, Pod, Zeroable};
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use base::libc::{
|
||||
clock_gettime, getpid, gettid, localtime_r, pthread_sigmask, sigaddset, sigset_t, sigtimedwait,
|
||||
timespec, tm, CLOCK_REALTIME, O_CLOEXEC, O_RDWR, O_WRONLY, PIPE_BUF, SIGPIPE, SIG_BLOCK,
|
||||
SIG_SETMASK,
|
||||
};
|
||||
use base::{
|
||||
const_format::concatcp, exit_on_error, libc, raw_cstr, FsPathBuf, LogLevel, Logger, Utf8CStr,
|
||||
Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrWrite, LOGGER,
|
||||
};
|
||||
|
||||
use crate::consts::{LOGFILE, LOG_PIPE};
|
||||
use crate::daemon::{MagiskD, MAGISKD};
|
||||
use crate::ffi::get_magisk_tmp;
|
||||
use crate::logging::LogFile::{Actual, Buffer};
|
||||
|
||||
#[allow(dead_code, non_camel_case_types)]
|
||||
#[derive(FromPrimitive, ToPrimitive)]
|
||||
#[repr(i32)]
|
||||
@@ -59,16 +57,17 @@ fn level_to_prio(level: LogLevel) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn android_logging() {
|
||||
fn android_log_write(level: LogLevel, msg: &Utf8CStr) {
|
||||
write_android_log(level_to_prio(level), msg);
|
||||
fn android_log_write(level: LogLevel, msg: &Utf8CStr) {
|
||||
unsafe {
|
||||
__android_log_write(level_to_prio(level), raw_cstr!("Magisk"), msg.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn android_logging() {
|
||||
let logger = Logger {
|
||||
write: android_log_write,
|
||||
flags: 0,
|
||||
};
|
||||
exit_on_error(false);
|
||||
unsafe {
|
||||
LOGGER = logger;
|
||||
}
|
||||
@@ -76,7 +75,7 @@ pub fn android_logging() {
|
||||
|
||||
pub fn magisk_logging() {
|
||||
fn magisk_log_write(level: LogLevel, msg: &Utf8CStr) {
|
||||
write_android_log(level_to_prio(level), msg);
|
||||
android_log_write(level, msg);
|
||||
magisk_log_to_pipe(level_to_prio(level), msg);
|
||||
}
|
||||
|
||||
@@ -84,7 +83,6 @@ pub fn magisk_logging() {
|
||||
write: magisk_log_write,
|
||||
flags: 0,
|
||||
};
|
||||
exit_on_error(false);
|
||||
unsafe {
|
||||
LOGGER = logger;
|
||||
}
|
||||
@@ -92,7 +90,7 @@ pub fn magisk_logging() {
|
||||
|
||||
pub fn zygisk_logging() {
|
||||
fn zygisk_log_write(level: LogLevel, msg: &Utf8CStr) {
|
||||
write_android_log(level_to_prio(level), msg);
|
||||
android_log_write(level, msg);
|
||||
zygisk_log_to_pipe(level_to_prio(level), msg);
|
||||
}
|
||||
|
||||
@@ -100,7 +98,6 @@ pub fn zygisk_logging() {
|
||||
write: zygisk_log_write,
|
||||
flags: 0,
|
||||
};
|
||||
exit_on_error(false);
|
||||
unsafe {
|
||||
LOGGER = logger;
|
||||
}
|
||||
@@ -115,13 +112,7 @@ struct LogMeta {
|
||||
tid: i32,
|
||||
}
|
||||
|
||||
fn write_android_log(prio: i32, msg: &Utf8CStr) {
|
||||
unsafe {
|
||||
__android_log_write(prio, raw_cstr!("Magisk"), msg.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_MSG_LEN: usize = PIPE_BUF - std::mem::size_of::<LogMeta>();
|
||||
const MAX_MSG_LEN: usize = PIPE_BUF - size_of::<LogMeta>();
|
||||
|
||||
fn write_log_to_pipe(logd: &mut File, prio: i32, msg: &Utf8CStr) -> io::Result<usize> {
|
||||
// Truncate message if needed
|
||||
@@ -138,35 +129,33 @@ fn write_log_to_pipe(logd: &mut File, prio: i32, msg: &Utf8CStr) -> io::Result<u
|
||||
let io1 = IoSlice::new(bytes_of(&meta));
|
||||
let io2 = IoSlice::new(msg);
|
||||
let result = logd.write_vectored(&[io1, io2]);
|
||||
if let Err(e) = result.as_ref() {
|
||||
if let Err(ref e) = result {
|
||||
let mut buf = Utf8CStrBufArr::default();
|
||||
buf.write_fmt(format_args_nl!("Cannot write_log_to_pipe: {}", e))
|
||||
.ok();
|
||||
write_android_log(level_to_prio(LogLevel::Error), &buf);
|
||||
android_log_write(LogLevel::Error, &buf);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn magisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {
|
||||
let magiskd = match MAGISKD.get() {
|
||||
None => return,
|
||||
Some(s) => s,
|
||||
};
|
||||
static MAGISK_LOGD_FD: Mutex<SharedFd> = Mutex::new(SharedFd::new());
|
||||
|
||||
let mut guard = magiskd.logd.lock().unwrap();
|
||||
let logd = match guard.as_mut() {
|
||||
None => return,
|
||||
Some(s) => s,
|
||||
};
|
||||
|
||||
let result = write_log_to_pipe(logd, prio, msg);
|
||||
|
||||
// If any error occurs, shut down the logd pipe
|
||||
if result.is_err() {
|
||||
*guard = None;
|
||||
fn with_logd_fd<F: FnOnce(&mut File) -> io::Result<()>>(f: F) {
|
||||
let fd = MAGISK_LOGD_FD.lock().unwrap().clone();
|
||||
// SAFETY: writing less than PIPE_BUF bytes is guaranteed to be atomic on Linux
|
||||
if let Some(mut logd) = unsafe { fd.as_file() } {
|
||||
if f(&mut logd).is_err() {
|
||||
// If any error occurs, shut down the logd pipe
|
||||
*MAGISK_LOGD_FD.lock().unwrap() = SharedFd::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn magisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {
|
||||
with_logd_fd(|logd| write_log_to_pipe(logd, prio, msg).map(|_| ()));
|
||||
}
|
||||
|
||||
// SAFETY: zygisk client code runs single threaded, so no need to prevent data race
|
||||
static ZYGISK_LOGD: AtomicI32 = AtomicI32::new(-1);
|
||||
|
||||
pub fn zygisk_close_logd() {
|
||||
@@ -228,13 +217,8 @@ fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {
|
||||
pthread_sigmask(SIG_BLOCK, &mask, &mut orig_mask);
|
||||
}
|
||||
|
||||
let result = {
|
||||
let mut logd = unsafe { File::from_raw_fd(fd) };
|
||||
let result = write_log_to_pipe(&mut logd, prio, msg);
|
||||
// Make sure the file descriptor is not closed after out of scope
|
||||
std::mem::forget(logd);
|
||||
result
|
||||
};
|
||||
let mut logd = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
|
||||
let result = write_log_to_pipe(&mut logd, prio, msg);
|
||||
|
||||
// Consume SIGPIPE if exists, then restore mask
|
||||
unsafe {
|
||||
@@ -251,12 +235,12 @@ fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {
|
||||
|
||||
// The following is implementation for the logging daemon
|
||||
|
||||
enum LogFile<'a> {
|
||||
Buffer(&'a mut Vec<u8>),
|
||||
enum LogFile {
|
||||
Buffer(Vec<u8>),
|
||||
Actual(File),
|
||||
}
|
||||
|
||||
impl Write for LogFile<'_> {
|
||||
impl Write for LogFile {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match self {
|
||||
Buffer(e) => e.write(buf),
|
||||
@@ -282,8 +266,7 @@ impl Write for LogFile<'_> {
|
||||
extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
|
||||
fn writer_loop(pipefd: RawFd) -> io::Result<()> {
|
||||
let mut pipe = unsafe { File::from_raw_fd(pipefd) };
|
||||
let mut tmp = Vec::new();
|
||||
let mut logfile: LogFile = Buffer(&mut tmp);
|
||||
let mut logfile: LogFile = Buffer(Vec::new());
|
||||
|
||||
let mut meta = LogMeta::zeroed();
|
||||
let mut msg_buf = [0u8; MAX_MSG_LEN];
|
||||
@@ -292,14 +275,13 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
|
||||
loop {
|
||||
// Read request
|
||||
write_zeroes(&mut meta);
|
||||
pipe.read_exact(bytes_of_mut(&mut meta))?;
|
||||
pipe.read_pod(&mut meta)?;
|
||||
|
||||
if meta.prio < 0 {
|
||||
if matches!(logfile, Buffer(_)) {
|
||||
if let Buffer(ref mut buf) = logfile {
|
||||
fs::rename(LOGFILE, concatcp!(LOGFILE, ".bak")).ok();
|
||||
let mut out = File::create(LOGFILE)?;
|
||||
out.write_all(tmp.as_slice())?;
|
||||
tmp = Vec::new();
|
||||
out.write_all(buf.as_slice())?;
|
||||
logfile = Actual(out);
|
||||
}
|
||||
continue;
|
||||
@@ -327,15 +309,15 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||
|
||||
// Note: the obvious better implementation is to use the rust chrono crate, however
|
||||
// the crate cannot fetch the proper local timezone without pulling in a bunch of
|
||||
// timezone handling code. To reduce binary size, fallback to use localtime_r in libc.
|
||||
unsafe {
|
||||
let mut ts: timespec = std::mem::zeroed();
|
||||
let secs: time_t = now.as_secs() as time_t;
|
||||
let mut tm: tm = std::mem::zeroed();
|
||||
if clock_gettime(CLOCK_REALTIME, &mut ts) < 0
|
||||
|| localtime_r(&ts.tv_sec, &mut tm).is_null()
|
||||
{
|
||||
if localtime_r(&secs, &mut tm).is_null() {
|
||||
continue;
|
||||
}
|
||||
let len = strftime(
|
||||
@@ -345,15 +327,17 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
|
||||
&tm,
|
||||
);
|
||||
aux.set_len(len);
|
||||
let ms = ts.tv_nsec / 1000000;
|
||||
aux.write_fmt(format_args!(
|
||||
".{:03} {:5} {:5} {} : ",
|
||||
ms, meta.pid, meta.tid, prio
|
||||
now.subsec_millis(),
|
||||
meta.pid,
|
||||
meta.tid,
|
||||
prio
|
||||
))
|
||||
.ok();
|
||||
}
|
||||
|
||||
let io1 = IoSlice::new(aux.as_bytes_with_nul());
|
||||
let io1 = IoSlice::new(aux.as_bytes());
|
||||
let io2 = IoSlice::new(msg);
|
||||
// We don't need to care the written len because we are writing less than PIPE_BUF
|
||||
// It's guaranteed to always write the whole thing atomically
|
||||
@@ -363,47 +347,34 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
|
||||
|
||||
writer_loop(arg as RawFd).ok();
|
||||
// If any error occurs, shut down the logd pipe
|
||||
if let Some(magiskd) = MAGISKD.get() {
|
||||
magiskd.close_log_pipe();
|
||||
}
|
||||
*MAGISK_LOGD_FD.lock().unwrap() = SharedFd::default();
|
||||
null_mut()
|
||||
}
|
||||
|
||||
impl MagiskD {
|
||||
pub fn start_log_daemon(&self) {
|
||||
let mut buf = Utf8CStrBufArr::default();
|
||||
let path = FsPathBuf::new(&mut buf)
|
||||
.join(get_magisk_tmp())
|
||||
.join(LOG_PIPE);
|
||||
|
||||
unsafe {
|
||||
libc::mkfifo(path.as_ptr(), 0o666);
|
||||
libc::chown(path.as_ptr(), 0, 0);
|
||||
let read = libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC);
|
||||
let write = libc::open(path.as_ptr(), O_WRONLY | O_CLOEXEC);
|
||||
*self.logd.lock().unwrap() = Some(File::from_raw_fd(write));
|
||||
new_daemon_thread(logfile_writer, read as *mut c_void);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close_log_pipe(&self) {
|
||||
*self.logd.lock().unwrap() = None;
|
||||
}
|
||||
|
||||
pub fn setup_logfile(&self) {
|
||||
let mut guard = self.logd.lock().unwrap();
|
||||
let logd = match guard.as_mut() {
|
||||
None => return,
|
||||
Some(s) => s,
|
||||
};
|
||||
|
||||
pub fn setup_logfile() {
|
||||
with_logd_fd(|logd| {
|
||||
let meta = LogMeta {
|
||||
prio: -1,
|
||||
len: 0,
|
||||
pid: 0,
|
||||
tid: 0,
|
||||
};
|
||||
logd.write_all(bytes_of(&meta))
|
||||
});
|
||||
}
|
||||
|
||||
logd.write_all(bytes_of(&meta)).ok();
|
||||
pub fn start_log_daemon() {
|
||||
let mut buf = Utf8CStrBufArr::default();
|
||||
let path = FsPathBuf::new(&mut buf)
|
||||
.join(get_magisk_tmp())
|
||||
.join(LOG_PIPE);
|
||||
|
||||
unsafe {
|
||||
libc::mkfifo(path.as_ptr(), 0o666);
|
||||
libc::chown(path.as_ptr(), 0, 0);
|
||||
let read = libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC);
|
||||
let write = libc::open(path.as_ptr(), O_WRONLY | O_CLOEXEC);
|
||||
*MAGISK_LOGD_FD.lock().unwrap() = SharedFd::from(OwnedFd::from_raw_fd(write));
|
||||
new_daemon_thread(logfile_writer, read as *mut c_void);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,13 +51,14 @@ bool dir_node::prepare() {
|
||||
for (auto it = children.begin(); it != children.end();) {
|
||||
// We also need to upgrade to tmpfs node if any child:
|
||||
// - Target does not exist
|
||||
// - Source or target is a symlink (since we cannot bind mount symlink)
|
||||
// - Source or target is a symlink (since we cannot bind mount symlink) or whiteout
|
||||
bool cannot_mnt;
|
||||
if (struct stat st{}; lstat(it->second->node_path().data(), &st) != 0) {
|
||||
cannot_mnt = true;
|
||||
// if it's a whiteout, we don't care if the target doesn't exist
|
||||
cannot_mnt = !it->second->is_wht();
|
||||
} else {
|
||||
it->second->set_exist(true);
|
||||
cannot_mnt = it->second->is_lnk() || S_ISLNK(st.st_mode);
|
||||
cannot_mnt = it->second->is_lnk() || S_ISLNK(st.st_mode) || it->second->is_wht();
|
||||
}
|
||||
|
||||
if (cannot_mnt) {
|
||||
@@ -107,6 +108,14 @@ void dir_node::collect_module_files(const char *module, int dfd) {
|
||||
node->collect_module_files(module, dirfd(dir.get()));
|
||||
}
|
||||
} else {
|
||||
if (entry->d_type == DT_CHR) {
|
||||
struct stat st{};
|
||||
int ret = fstatat(dirfd(dir.get()), entry->d_name, &st, AT_SYMLINK_NOFOLLOW);
|
||||
if (ret == 0 && st.st_rdev == 0) {
|
||||
// if the file is a whiteout, mark it as such
|
||||
entry->d_type = DT_WHT;
|
||||
}
|
||||
}
|
||||
emplace<module_node>(entry->d_name, module, entry);
|
||||
}
|
||||
}
|
||||
@@ -136,6 +145,10 @@ void node_entry::create_and_mount(const char *reason, const string &src, bool ro
|
||||
}
|
||||
|
||||
void module_node::mount() {
|
||||
if (is_wht()) {
|
||||
VLOGD("delete", "null", node_path().data());
|
||||
return;
|
||||
}
|
||||
std::string path = module + (parent()->root()->prefix + node_path());
|
||||
string mnt_src = module_mnt + path;
|
||||
{
|
||||
@@ -177,26 +190,23 @@ void tmpfs_node::mount() {
|
||||
class magisk_node : public node_entry {
|
||||
public:
|
||||
explicit magisk_node(const char *name) : node_entry(name, DT_REG, this) {}
|
||||
explicit magisk_node(const char *name, const char *target)
|
||||
: node_entry(name, DT_LNK, this), target(target) {}
|
||||
|
||||
void mount() override {
|
||||
const string src = get_magisk_tmp() + "/"s + name();
|
||||
if (access(src.data(), F_OK))
|
||||
return;
|
||||
|
||||
const string dir_name = isa<tmpfs_node>(parent()) ? parent()->worker_path() : parent()->node_path();
|
||||
if (name() == "magisk") {
|
||||
for (int i = 0; applet_names[i]; ++i) {
|
||||
string dest = dir_name + "/" + applet_names[i];
|
||||
VLOGD("create", "./magisk", dest.data());
|
||||
xsymlink("./magisk", dest.data());
|
||||
}
|
||||
if (target) {
|
||||
string dest = isa<tmpfs_node>(parent()) ? worker_path() : node_path();
|
||||
VLOGD("create", target, dest.data());
|
||||
xsymlink(target, dest.data());
|
||||
} else {
|
||||
string dest = dir_name + "/supolicy";
|
||||
VLOGD("create", "./magiskpolicy", dest.data());
|
||||
xsymlink("./magiskpolicy", dest.data());
|
||||
string src = get_magisk_tmp() + "/"s + name();
|
||||
if (access(src.data(), F_OK) == 0)
|
||||
create_and_mount("magisk", src, true);
|
||||
}
|
||||
create_and_mount("magisk", src, true);
|
||||
}
|
||||
|
||||
private:
|
||||
const char *target = nullptr;
|
||||
};
|
||||
|
||||
class zygisk_node : public node_entry {
|
||||
@@ -231,10 +241,10 @@ static void inject_magisk_bins(root_node *system) {
|
||||
bin->insert(new magisk_node("magisk"));
|
||||
bin->insert(new magisk_node("magiskpolicy"));
|
||||
|
||||
// Also delete all applets to make sure no modules can override it
|
||||
// Also insert all applets to make sure no one can override it
|
||||
for (int i = 0; applet_names[i]; ++i)
|
||||
delete bin->extract(applet_names[i]);
|
||||
delete bin->extract("supolicy");
|
||||
bin->insert(new magisk_node(applet_names[i], "./magisk"));
|
||||
bin->insert(new magisk_node("supolicy", "./magiskpolicy"));
|
||||
}
|
||||
|
||||
static void inject_zygisk_libs(root_node *system) {
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
bool is_dir() const { return file_type() == DT_DIR; }
|
||||
bool is_lnk() const { return file_type() == DT_LNK; }
|
||||
bool is_reg() const { return file_type() == DT_REG; }
|
||||
bool is_wht() const { return file_type() == DT_WHT; }
|
||||
const string &name() const { return _name; }
|
||||
dir_node *parent() const { return _parent; }
|
||||
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
#include <base.hpp>
|
||||
#include <consts.hpp>
|
||||
#include <core.hpp>
|
||||
#include <db.hpp>
|
||||
#include <flags.h>
|
||||
|
||||
using namespace std;
|
||||
using rust::Vec;
|
||||
|
||||
#define ENFORCE_SIGNATURE (!MAGISK_DEBUG)
|
||||
|
||||
// These functions will be called on every single zygote process specialization and su request,
|
||||
// so performance is absolutely critical. Most operations should either have its result cached
|
||||
// or simply skipped unless necessary.
|
||||
|
||||
static pthread_mutex_t pkg_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
// pkg_lock protects all following variables
|
||||
static int stub_apk_fd = -1;
|
||||
static int repackaged_app_id = -1; // Only used by dyn
|
||||
static string repackaged_pkg;
|
||||
static Vec<uint8_t> repackaged_cert;
|
||||
static Vec<uint8_t> trusted_cert;
|
||||
static map<int, pair<string, time_t>> user_to_check;
|
||||
enum status {
|
||||
INSTALLED,
|
||||
NO_INSTALLED,
|
||||
CERT_MISMATCH,
|
||||
};
|
||||
|
||||
static bool operator==(const Vec<uint8_t> &a, const Vec<uint8_t> &b) {
|
||||
return a.size() == b.size() && memcmp(a.data(), b.data(), a.size()) == 0;
|
||||
}
|
||||
|
||||
// app_id = app_no + AID_APP_START
|
||||
// app_no range: [0, 9999]
|
||||
vector<bool> get_app_no_list() {
|
||||
vector<bool> list;
|
||||
auto data_dir = xopen_dir(APP_DATA_DIR);
|
||||
if (!data_dir)
|
||||
return list;
|
||||
dirent *entry;
|
||||
while ((entry = xreaddir(data_dir.get()))) {
|
||||
// For each user
|
||||
int dfd = xopenat(dirfd(data_dir.get()), entry->d_name, O_RDONLY);
|
||||
if (auto dir = xopen_dir(dfd)) {
|
||||
while ((entry = xreaddir(dir.get()))) {
|
||||
// For each package
|
||||
struct stat st{};
|
||||
xfstatat(dfd, entry->d_name, &st, 0);
|
||||
int app_id = to_app_id(st.st_uid);
|
||||
if (app_id >= AID_APP_START && app_id <= AID_APP_END) {
|
||||
int app_no = app_id - AID_APP_START;
|
||||
if (list.size() <= app_no) {
|
||||
list.resize(app_no + 1);
|
||||
}
|
||||
list[app_no] = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
close(dfd);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void preserve_stub_apk() {
|
||||
mutex_guard g(pkg_lock);
|
||||
string stub_path = get_magisk_tmp() + "/stub.apk"s;
|
||||
stub_apk_fd = xopen(stub_path.data(), O_RDONLY | O_CLOEXEC);
|
||||
unlink(stub_path.data());
|
||||
trusted_cert = read_certificate(stub_apk_fd, MAGISK_VER_CODE);
|
||||
lseek(stub_apk_fd, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
static void install_stub() {
|
||||
if (stub_apk_fd < 0)
|
||||
return;
|
||||
struct stat st{};
|
||||
fstat(stub_apk_fd, &st);
|
||||
char apk[] = "/data/stub.apk";
|
||||
int dfd = xopen(apk, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
|
||||
xsendfile(dfd, stub_apk_fd, nullptr, st.st_size);
|
||||
lseek(stub_apk_fd, 0, SEEK_SET);
|
||||
close(dfd);
|
||||
install_apk(apk);
|
||||
}
|
||||
|
||||
static status check_dyn(int user, string &pkg) {
|
||||
struct stat st{};
|
||||
char apk[PATH_MAX];
|
||||
ssprintf(apk, sizeof(apk),
|
||||
"%s/%d/%s/dyn/current.apk", APP_DATA_DIR, user, pkg.data());
|
||||
int dyn = open(apk, O_RDONLY | O_CLOEXEC);
|
||||
if (dyn < 0) {
|
||||
LOGW("pkg: no dyn APK, ignore\n");
|
||||
return NO_INSTALLED;
|
||||
}
|
||||
auto cert = read_certificate(dyn, MAGISK_VER_CODE);
|
||||
fstat(dyn, &st);
|
||||
close(dyn);
|
||||
|
||||
if (cert.empty() || cert != trusted_cert) {
|
||||
LOGE("pkg: dyn APK signature mismatch: %s\n", apk);
|
||||
#if ENFORCE_SIGNATURE
|
||||
clear_pkg(pkg.data(), user);
|
||||
return CERT_MISMATCH;
|
||||
#endif
|
||||
}
|
||||
|
||||
repackaged_app_id = to_app_id(st.st_uid);
|
||||
user_to_check[user] = make_pair(apk, st.st_ctim.tv_sec);
|
||||
return INSTALLED;
|
||||
}
|
||||
|
||||
static status check_stub(int user, string &pkg) {
|
||||
struct stat st{};
|
||||
byte_array<PATH_MAX> buf;
|
||||
find_apk_path(pkg, buf);
|
||||
string apk((const char *) buf.buf(), buf.sz());
|
||||
int fd = xopen(apk.data(), O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return NO_INSTALLED;
|
||||
auto cert = read_certificate(fd, -1);
|
||||
fstat(fd, &st);
|
||||
close(fd);
|
||||
|
||||
if (cert.empty() || (pkg == repackaged_pkg && cert != repackaged_cert)) {
|
||||
LOGE("pkg: repackaged APK signature invalid: %s\n", apk.data());
|
||||
uninstall_pkg(pkg.data());
|
||||
return CERT_MISMATCH;
|
||||
}
|
||||
|
||||
repackaged_pkg.swap(pkg);
|
||||
repackaged_cert.swap(cert);
|
||||
user_to_check[user] = make_pair(apk, st.st_ctim.tv_sec);
|
||||
|
||||
return INSTALLED;
|
||||
}
|
||||
|
||||
static status check_orig(int user) {
|
||||
struct stat st{};
|
||||
byte_array<PATH_MAX> buf;
|
||||
find_apk_path(JAVA_PACKAGE_NAME, buf);
|
||||
string apk((const char *) buf.buf(), buf.sz());
|
||||
int fd = xopen(apk.data(), O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return NO_INSTALLED;
|
||||
auto cert = read_certificate(fd, MAGISK_VER_CODE);
|
||||
fstat(fd, &st);
|
||||
close(fd);
|
||||
|
||||
if (cert.empty() || cert != trusted_cert) {
|
||||
LOGE("pkg: APK signature mismatch: %s\n", apk.data());
|
||||
#if ENFORCE_SIGNATURE
|
||||
uninstall_pkg(JAVA_PACKAGE_NAME);
|
||||
return CERT_MISMATCH;
|
||||
#endif
|
||||
}
|
||||
|
||||
user_to_check[user] = make_pair(apk, st.st_ctim.tv_sec);
|
||||
return INSTALLED;
|
||||
}
|
||||
|
||||
static int get_pkg_uid(int user, const char *pkg) {
|
||||
char path[PATH_MAX];
|
||||
struct stat st{};
|
||||
ssprintf(path, sizeof(path), "%s/%d/%s", APP_DATA_DIR, user, pkg);
|
||||
if (stat(path, &st) == 0) {
|
||||
return st.st_uid;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int get_manager(int user, string *pkg, bool install) {
|
||||
mutex_guard g(pkg_lock);
|
||||
|
||||
struct stat st{};
|
||||
const auto &[path, time] = user_to_check[user];
|
||||
if (stat(path.data(), &st) == 0 && st.st_ctim.tv_sec == time) {
|
||||
// no APK
|
||||
if (path == "/data/system/packages.xml") {
|
||||
if (install) install_stub();
|
||||
if (pkg) pkg->clear();
|
||||
return -1;
|
||||
}
|
||||
// dyn APK is still the same
|
||||
if (path.starts_with(APP_DATA_DIR)) {
|
||||
if (pkg) *pkg = repackaged_pkg;
|
||||
return user * AID_USER_OFFSET + repackaged_app_id;
|
||||
}
|
||||
// stub APK is still the same
|
||||
if (!repackaged_pkg.empty()) {
|
||||
if (check_dyn(user, repackaged_pkg) == INSTALLED) {
|
||||
if (pkg) *pkg = repackaged_pkg;
|
||||
return user * AID_USER_OFFSET + repackaged_app_id;
|
||||
} else {
|
||||
if (pkg) pkg->clear();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// orig APK is still the same
|
||||
int uid = get_pkg_uid(user, JAVA_PACKAGE_NAME);
|
||||
if (uid < 0) {
|
||||
if (pkg) pkg->clear();
|
||||
return -1;
|
||||
} else {
|
||||
if (pkg) *pkg = JAVA_PACKAGE_NAME;
|
||||
return uid;
|
||||
}
|
||||
}
|
||||
|
||||
db_strings str;
|
||||
get_db_strings(str, SU_MANAGER);
|
||||
|
||||
if (!str[SU_MANAGER].empty()) {
|
||||
switch (check_stub(user, str[SU_MANAGER])) {
|
||||
case INSTALLED:
|
||||
if (check_dyn(user, repackaged_pkg) == INSTALLED) {
|
||||
if (pkg) *pkg = repackaged_pkg;
|
||||
return user * AID_USER_OFFSET + repackaged_app_id;
|
||||
} else {
|
||||
if (pkg) pkg->clear();
|
||||
return -1;
|
||||
}
|
||||
case CERT_MISMATCH:
|
||||
install = true;
|
||||
case NO_INSTALLED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
repackaged_pkg.clear();
|
||||
repackaged_cert.clear();
|
||||
switch (check_orig(user)) {
|
||||
case INSTALLED: {
|
||||
int uid = get_pkg_uid(user, JAVA_PACKAGE_NAME);
|
||||
if (uid < 0) {
|
||||
if (pkg) pkg->clear();
|
||||
return -1;
|
||||
} else {
|
||||
if (pkg) *pkg = JAVA_PACKAGE_NAME;
|
||||
return uid;
|
||||
}
|
||||
}
|
||||
case CERT_MISMATCH:
|
||||
install = true;
|
||||
case NO_INSTALLED:
|
||||
break;
|
||||
}
|
||||
|
||||
auto xml = "/data/system/packages.xml";
|
||||
stat(xml, &st);
|
||||
user_to_check[user] = make_pair(xml, st.st_ctim.tv_sec);
|
||||
|
||||
if (install) install_stub();
|
||||
if (pkg) pkg->clear();
|
||||
return -1;
|
||||
}
|
||||
486
native/src/core/package.rs
Normal file
486
native/src/core/package.rs
Normal file
@@ -0,0 +1,486 @@
|
||||
use crate::consts::{APP_PACKAGE_NAME, MAGISK_VER_CODE};
|
||||
use crate::daemon::{to_app_id, MagiskD, AID_USER_OFFSET};
|
||||
use crate::ffi::{get_magisk_tmp, install_apk, uninstall_pkg, DbEntryKey};
|
||||
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY};
|
||||
use base::WalkResult::{Abort, Continue, Skip};
|
||||
use base::{
|
||||
cstr, error, fd_get_attr, open_fd, warn, BufReadExt, Directory, FsPath, FsPathBuf,
|
||||
LoggedResult, ReadExt, ResultExt, Utf8CStrBuf, Utf8CStrBufArr,
|
||||
};
|
||||
use cxx::CxxString;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
|
||||
const EOCD_MAGIC: u32 = 0x06054B50;
|
||||
const APK_SIGNING_BLOCK_MAGIC: [u8; 16] = *b"APK Sig Block 42";
|
||||
const SIGNATURE_SCHEME_V2_MAGIC: u32 = 0x7109871A;
|
||||
|
||||
macro_rules! bad_apk {
|
||||
($msg:literal) => {
|
||||
io::Error::new(io::ErrorKind::InvalidData, concat!("cert: ", $msg))
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* A v2/v3 signed APK has the format as following
|
||||
*
|
||||
* +---------------+
|
||||
* | zip content |
|
||||
* +---------------+
|
||||
* | signing block |
|
||||
* +---------------+
|
||||
* | central dir |
|
||||
* +---------------+
|
||||
* | EOCD |
|
||||
* +---------------+
|
||||
*
|
||||
* Scan from end of file to find EOCD, and figure our way back to the
|
||||
* offset of the signing block. Next, directly extract the certificate
|
||||
* from the v2 signature block.
|
||||
*
|
||||
* All structures above are mostly just for documentation purpose.
|
||||
*
|
||||
* This method extracts the first certificate of the first signer
|
||||
* within the APK v2 signature block.
|
||||
*/
|
||||
fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
|
||||
fn inner(apk: &mut File, version: i32) -> io::Result<Vec<u8>> {
|
||||
let mut u32_val = 0u32;
|
||||
let mut u64_val = 0u64;
|
||||
|
||||
// Find EOCD
|
||||
for i in 0u16.. {
|
||||
let mut comment_sz = 0u16;
|
||||
apk.seek(SeekFrom::End(-(size_of_val(&comment_sz) as i64) - i as i64))?;
|
||||
apk.read_pod(&mut comment_sz)?;
|
||||
|
||||
if comment_sz == i {
|
||||
apk.seek(SeekFrom::Current(-22))?;
|
||||
let mut magic = 0u32;
|
||||
apk.read_pod(&mut magic)?;
|
||||
if magic == EOCD_MAGIC {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if i == 0xffff {
|
||||
return Err(bad_apk!("invalid APK format"));
|
||||
}
|
||||
}
|
||||
|
||||
// We are now at EOCD + sizeof(magic)
|
||||
// Seek and read central_dir_off to find the start of the central directory
|
||||
let mut central_dir_off = 0u32;
|
||||
apk.seek(SeekFrom::Current(12))?;
|
||||
apk.read_pod(&mut central_dir_off)?;
|
||||
|
||||
// Code for parse APK comment to get version code
|
||||
if version >= 0 {
|
||||
let mut comment_sz = 0u16;
|
||||
apk.read_pod(&mut comment_sz)?;
|
||||
let mut comment = vec![0u8; comment_sz as usize];
|
||||
apk.read_exact(&mut comment)?;
|
||||
let mut comment = Cursor::new(&comment);
|
||||
let mut apk_ver = 0;
|
||||
comment.foreach_props(|k, v| {
|
||||
if k == "versionCode" {
|
||||
apk_ver = v.parse::<i32>().unwrap_or(0);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
if version > apk_ver {
|
||||
return Err(bad_apk!("APK version too low"));
|
||||
}
|
||||
}
|
||||
|
||||
// Next, find the start of the APK signing block
|
||||
apk.seek(SeekFrom::Start((central_dir_off - 24) as u64))?;
|
||||
apk.read_pod(&mut u64_val)?; // u64_value = block_sz_
|
||||
let mut magic = [0u8; 16];
|
||||
apk.read_exact(&mut magic)?;
|
||||
if magic != APK_SIGNING_BLOCK_MAGIC {
|
||||
return Err(bad_apk!("invalid signing block magic"));
|
||||
}
|
||||
let mut signing_blk_sz = 0u64;
|
||||
apk.seek(SeekFrom::Current(
|
||||
-(u64_val as i64) - (size_of_val(&signing_blk_sz) as i64),
|
||||
))?;
|
||||
apk.read_pod(&mut signing_blk_sz)?;
|
||||
if signing_blk_sz != u64_val {
|
||||
return Err(bad_apk!("invalid signing block size"));
|
||||
}
|
||||
|
||||
// Finally, we are now at the beginning of the id-value pair sequence
|
||||
loop {
|
||||
apk.read_pod(&mut u64_val)?; // id-value pair length
|
||||
if u64_val == signing_blk_sz {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut id = 0u32;
|
||||
apk.read_pod(&mut id)?;
|
||||
if id == SIGNATURE_SCHEME_V2_MAGIC {
|
||||
// Skip [signer sequence length] + [1st signer length] + [signed data length]
|
||||
apk.seek(SeekFrom::Current((size_of_val(&u32_val) * 3) as i64))?;
|
||||
|
||||
apk.read_pod(&mut u32_val)?; // digest sequence length
|
||||
apk.seek(SeekFrom::Current(u32_val as i64))?; // skip all digests
|
||||
|
||||
apk.seek(SeekFrom::Current(size_of_val(&u32_val) as i64))?; // cert sequence length
|
||||
apk.read_pod(&mut u32_val)?; // 1st cert length
|
||||
|
||||
let mut cert = vec![0; u32_val as usize];
|
||||
apk.read_exact(cert.as_mut())?;
|
||||
return Ok(cert);
|
||||
} else {
|
||||
// Skip this id-value pair
|
||||
apk.seek(SeekFrom::Current(
|
||||
u64_val as i64 - (size_of_val(&id) as i64),
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
Err(bad_apk!("cannot find certificate"))
|
||||
}
|
||||
inner(apk, version).log().unwrap_or(vec![])
|
||||
}
|
||||
|
||||
fn find_apk_path(pkg: &str, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
|
||||
Directory::open(cstr!("/data/app"))?.pre_order_walk(|e| {
|
||||
if !e.is_dir() {
|
||||
return Ok(Skip);
|
||||
}
|
||||
let name_bytes = e.d_name().to_bytes();
|
||||
if name_bytes.starts_with(pkg.as_bytes()) && name_bytes[pkg.len()] == b'-' {
|
||||
// Found the APK path, we can abort now
|
||||
e.path(buf)?;
|
||||
return Ok(Abort);
|
||||
}
|
||||
if name_bytes.starts_with(b"~~") {
|
||||
return Ok(Continue);
|
||||
}
|
||||
Ok(Skip)
|
||||
})?;
|
||||
if !buf.is_empty() {
|
||||
buf.push_str("/base.apk");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum Status {
|
||||
Installed,
|
||||
NotInstalled,
|
||||
CertMismatch,
|
||||
}
|
||||
|
||||
pub struct ManagerInfo {
|
||||
stub_apk_fd: Option<File>,
|
||||
trusted_cert: Vec<u8>,
|
||||
repackaged_app_id: i32,
|
||||
repackaged_pkg: String,
|
||||
repackaged_cert: Vec<u8>,
|
||||
tracked_files: BTreeMap<i32, TrackedFile>,
|
||||
}
|
||||
|
||||
impl Default for ManagerInfo {
|
||||
fn default() -> Self {
|
||||
ManagerInfo {
|
||||
stub_apk_fd: None,
|
||||
trusted_cert: Vec::new(),
|
||||
repackaged_app_id: -1,
|
||||
repackaged_pkg: String::new(),
|
||||
repackaged_cert: Vec::new(),
|
||||
tracked_files: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TrackedFile {
|
||||
path: PathBuf,
|
||||
timestamp: Duration,
|
||||
}
|
||||
|
||||
impl TrackedFile {
|
||||
fn new<T: AsRef<Path>>(path: T) -> TrackedFile {
|
||||
fn inner(path: &Path) -> TrackedFile {
|
||||
let meta = match path.metadata() {
|
||||
Ok(meta) => meta,
|
||||
Err(_) => return TrackedFile::default(),
|
||||
};
|
||||
let timestamp = Duration::new(meta.ctime() as u64, meta.ctime_nsec() as u32);
|
||||
TrackedFile {
|
||||
path: PathBuf::from(path),
|
||||
timestamp,
|
||||
}
|
||||
}
|
||||
inner(path.as_ref())
|
||||
}
|
||||
|
||||
fn is_same(&self) -> bool {
|
||||
if self.path.as_os_str().is_empty() {
|
||||
return false;
|
||||
}
|
||||
let meta = match self.path.metadata() {
|
||||
Ok(meta) => meta,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let timestamp = Duration::new(meta.ctime() as u64, meta.ctime_nsec() as u32);
|
||||
timestamp == self.timestamp
|
||||
}
|
||||
}
|
||||
|
||||
impl ManagerInfo {
|
||||
fn check_dyn(&mut self, daemon: &MagiskD, user: i32, pkg: &str) -> Status {
|
||||
let mut arr = Utf8CStrBufArr::default();
|
||||
let apk = FsPathBuf::new(&mut arr)
|
||||
.join(daemon.app_data_dir())
|
||||
.join_fmt(user)
|
||||
.join(pkg)
|
||||
.join("dyn")
|
||||
.join("current.apk");
|
||||
let uid: i32;
|
||||
let cert = match apk.open(O_RDONLY | O_CLOEXEC) {
|
||||
Ok(mut fd) => {
|
||||
uid = fd_get_attr(fd.as_raw_fd())
|
||||
.map(|attr| attr.st.st_uid as i32)
|
||||
.unwrap_or(-1);
|
||||
read_certificate(&mut fd, MAGISK_VER_CODE)
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("pkg: no dyn APK, ignore");
|
||||
return Status::NotInstalled;
|
||||
}
|
||||
};
|
||||
|
||||
if cert.is_empty() || cert != self.trusted_cert {
|
||||
error!("pkg: dyn APK signature mismatch: {}", apk);
|
||||
#[cfg(all(feature = "check-signature", not(debug_assertions)))]
|
||||
{
|
||||
return Status::CertMismatch;
|
||||
}
|
||||
}
|
||||
|
||||
self.repackaged_app_id = to_app_id(uid);
|
||||
self.tracked_files.insert(user, TrackedFile::new(apk));
|
||||
Status::Installed
|
||||
}
|
||||
|
||||
fn check_stub(&mut self, user: i32, pkg: &str) -> Status {
|
||||
let mut arr = Utf8CStrBufArr::default();
|
||||
if find_apk_path(pkg, &mut arr).is_err() {
|
||||
return Status::NotInstalled;
|
||||
}
|
||||
let apk = FsPath::from(&arr);
|
||||
|
||||
let cert = match apk.open(O_RDONLY | O_CLOEXEC) {
|
||||
Ok(mut fd) => read_certificate(&mut fd, -1),
|
||||
Err(_) => return Status::NotInstalled,
|
||||
};
|
||||
|
||||
if cert.is_empty() || (pkg == self.repackaged_pkg && cert != self.repackaged_cert) {
|
||||
error!("pkg: repackaged APK signature invalid: {}", apk);
|
||||
uninstall_pkg(apk);
|
||||
return Status::CertMismatch;
|
||||
}
|
||||
|
||||
self.repackaged_pkg.clear();
|
||||
self.repackaged_pkg.push_str(pkg);
|
||||
self.repackaged_cert = cert;
|
||||
self.tracked_files.insert(user, TrackedFile::new(apk));
|
||||
Status::Installed
|
||||
}
|
||||
|
||||
fn check_orig(&mut self, user: i32) -> Status {
|
||||
let mut arr = Utf8CStrBufArr::default();
|
||||
if find_apk_path(APP_PACKAGE_NAME, &mut arr).is_err() {
|
||||
return Status::NotInstalled;
|
||||
}
|
||||
let apk = FsPath::from(&arr);
|
||||
|
||||
let cert = match apk.open(O_RDONLY | O_CLOEXEC) {
|
||||
Ok(mut fd) => read_certificate(&mut fd, MAGISK_VER_CODE),
|
||||
Err(_) => return Status::NotInstalled,
|
||||
};
|
||||
|
||||
if cert.is_empty() || cert != self.trusted_cert {
|
||||
error!("pkg: APK signature mismatch: {}", apk);
|
||||
#[cfg(all(feature = "check-signature", not(debug_assertions)))]
|
||||
{
|
||||
uninstall_pkg(cstr!(APP_PACKAGE_NAME));
|
||||
return Status::CertMismatch;
|
||||
}
|
||||
}
|
||||
|
||||
self.tracked_files.insert(user, TrackedFile::new(apk));
|
||||
Status::Installed
|
||||
}
|
||||
|
||||
fn install_stub(&mut self) {
|
||||
if let Some(ref mut stub_fd) = self.stub_apk_fd {
|
||||
// Copy the stub APK
|
||||
let tmp_apk = cstr!("/data/stub.apk");
|
||||
let result: LoggedResult<()> = try {
|
||||
{
|
||||
let mut tmp_fd = File::from(open_fd!(
|
||||
tmp_apk,
|
||||
O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
|
||||
0o600
|
||||
)?);
|
||||
io::copy(stub_fd, &mut tmp_fd)?;
|
||||
}
|
||||
// Seek the fd back to start
|
||||
stub_fd.seek(SeekFrom::Start(0))?;
|
||||
};
|
||||
if result.is_ok() {
|
||||
install_apk(tmp_apk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_manager(&mut self, daemon: &MagiskD, user: i32, mut install: bool) -> (i32, &str) {
|
||||
let db_pkg = daemon.get_db_string(DbEntryKey::SuManager);
|
||||
|
||||
// If database changed, always re-check files
|
||||
if db_pkg != self.repackaged_pkg {
|
||||
self.tracked_files.remove(&user);
|
||||
}
|
||||
|
||||
if let Some(file) = self.tracked_files.get(&user)
|
||||
&& file.is_same()
|
||||
{
|
||||
// no APK
|
||||
if file.path == Path::new("/data/system/packages.xml") {
|
||||
if install {
|
||||
self.install_stub();
|
||||
}
|
||||
return (-1, "");
|
||||
}
|
||||
// dyn APK is still the same
|
||||
if file.path.starts_with(daemon.app_data_dir()) {
|
||||
return (
|
||||
user * AID_USER_OFFSET + self.repackaged_app_id,
|
||||
&self.repackaged_pkg,
|
||||
);
|
||||
}
|
||||
// stub APK is still the same
|
||||
if !self.repackaged_pkg.is_empty() {
|
||||
return if matches!(
|
||||
self.check_dyn(daemon, user, self.repackaged_pkg.clone().as_str()),
|
||||
Status::Installed
|
||||
) {
|
||||
(
|
||||
user * AID_USER_OFFSET + self.repackaged_app_id,
|
||||
&self.repackaged_pkg,
|
||||
)
|
||||
} else {
|
||||
(-1, "")
|
||||
};
|
||||
}
|
||||
// orig APK is still the same
|
||||
let uid = daemon.get_package_uid(user, APP_PACKAGE_NAME);
|
||||
return if uid < 0 {
|
||||
(-1, "")
|
||||
} else {
|
||||
(uid, APP_PACKAGE_NAME)
|
||||
};
|
||||
}
|
||||
|
||||
if !db_pkg.is_empty() {
|
||||
match self.check_stub(user, &db_pkg) {
|
||||
Status::Installed => {
|
||||
return if matches!(self.check_dyn(daemon, user, &db_pkg), Status::Installed) {
|
||||
(
|
||||
user * AID_USER_OFFSET + self.repackaged_app_id,
|
||||
&self.repackaged_pkg,
|
||||
)
|
||||
} else {
|
||||
(-1, "")
|
||||
}
|
||||
}
|
||||
Status::NotInstalled => {
|
||||
daemon.rm_db_string(DbEntryKey::SuManager).ok();
|
||||
}
|
||||
Status::CertMismatch => {
|
||||
install = true;
|
||||
daemon.rm_db_string(DbEntryKey::SuManager).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.repackaged_pkg.clear();
|
||||
self.repackaged_cert.clear();
|
||||
|
||||
match self.check_orig(user) {
|
||||
Status::Installed => {
|
||||
let uid = daemon.get_package_uid(user, APP_PACKAGE_NAME);
|
||||
return if uid < 0 {
|
||||
(-1, "")
|
||||
} else {
|
||||
(uid, APP_PACKAGE_NAME)
|
||||
};
|
||||
}
|
||||
Status::CertMismatch => install = true,
|
||||
Status::NotInstalled => {}
|
||||
}
|
||||
|
||||
// If we cannot find any manager, track packages.xml for new package installs
|
||||
self.tracked_files
|
||||
.insert(user, TrackedFile::new("/data/system/packages.xml"));
|
||||
|
||||
if install {
|
||||
self.install_stub();
|
||||
}
|
||||
(-1, "")
|
||||
}
|
||||
}
|
||||
|
||||
impl MagiskD {
|
||||
fn get_package_uid(&self, user: i32, pkg: &str) -> i32 {
|
||||
let mut arr = Utf8CStrBufArr::default();
|
||||
let path = FsPathBuf::new(&mut arr)
|
||||
.join(self.app_data_dir())
|
||||
.join_fmt(user)
|
||||
.join(pkg);
|
||||
path.get_attr()
|
||||
.map(|attr| attr.st.st_uid as i32)
|
||||
.unwrap_or(-1)
|
||||
}
|
||||
|
||||
pub fn preserve_stub_apk(&self) {
|
||||
let mut info = self.manager_info.lock().unwrap();
|
||||
|
||||
let mut arr = Utf8CStrBufArr::default();
|
||||
let apk = FsPathBuf::new(&mut arr)
|
||||
.join(get_magisk_tmp())
|
||||
.join("stub.apk");
|
||||
|
||||
if let Ok(mut fd) = apk.open(O_RDONLY | O_CLOEXEC) {
|
||||
info.trusted_cert = read_certificate(&mut fd, MAGISK_VER_CODE);
|
||||
// Seek the fd back to start
|
||||
fd.seek(SeekFrom::Start(0)).log().ok();
|
||||
info.stub_apk_fd = Some(fd);
|
||||
}
|
||||
|
||||
apk.remove().log().ok();
|
||||
}
|
||||
|
||||
pub unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32 {
|
||||
let mut info = self.manager_info.lock().unwrap();
|
||||
let (uid, pkg) = info.get_manager(self, user, install);
|
||||
if let Some(str) = ptr.as_mut() {
|
||||
let mut str = Pin::new_unchecked(str);
|
||||
str.as_mut().clear();
|
||||
str.push_str(pkg);
|
||||
}
|
||||
uid
|
||||
}
|
||||
}
|
||||
@@ -159,10 +159,10 @@ appops set %s REQUEST_INSTALL_PACKAGES allow
|
||||
rm -f $APK
|
||||
)EOF";
|
||||
|
||||
void install_apk(const char *apk) {
|
||||
setfilecon(apk, MAGISK_FILE_CON);
|
||||
void install_apk(rust::Utf8CStr apk) {
|
||||
setfilecon(apk.c_str(), MAGISK_FILE_CON);
|
||||
char cmds[sizeof(install_script) + 4096];
|
||||
ssprintf(cmds, sizeof(cmds), install_script, apk, JAVA_PACKAGE_NAME);
|
||||
ssprintf(cmds, sizeof(cmds), install_script, apk.c_str(), JAVA_PACKAGE_NAME);
|
||||
exec_command_async("/system/bin/sh", "-c", cmds);
|
||||
}
|
||||
|
||||
@@ -172,9 +172,9 @@ log -t Magisk "pm_uninstall: $PKG"
|
||||
log -t Magisk "pm_uninstall: $(pm uninstall $PKG 2>&1)"
|
||||
)EOF";
|
||||
|
||||
void uninstall_pkg(const char *pkg) {
|
||||
void uninstall_pkg(rust::Utf8CStr pkg) {
|
||||
char cmds[sizeof(uninstall_script) + 256];
|
||||
ssprintf(cmds, sizeof(cmds), uninstall_script, pkg);
|
||||
ssprintf(cmds, sizeof(cmds), uninstall_script, pkg.c_str());
|
||||
exec_command_async("/system/bin/sh", "-c", cmds);
|
||||
}
|
||||
|
||||
@@ -202,10 +202,9 @@ static void abort(FILE *fp, const char *fmt, ...) {
|
||||
}
|
||||
|
||||
constexpr char install_module_script[] = R"EOF(
|
||||
exec %s sh -c '
|
||||
. /data/adb/magisk/util_functions.sh
|
||||
install_module
|
||||
exit 0'
|
||||
exit 0
|
||||
)EOF";
|
||||
|
||||
void install_module(const char *file) {
|
||||
@@ -229,10 +228,7 @@ void install_module(const char *file) {
|
||||
xdup2(fd, STDERR_FILENO);
|
||||
close(fd);
|
||||
|
||||
char cmds[256];
|
||||
ssprintf(cmds, sizeof(cmds), install_module_script, bbpath());
|
||||
|
||||
const char *argv[] = { "/system/bin/sh", "-c", cmds, nullptr };
|
||||
const char *argv[] = { BBEXEC_CMD, "-c", install_module_script, nullptr };
|
||||
execve(argv[0], (char **) argv, environ);
|
||||
abort(stdout, "Failed to execute BusyBox shell");
|
||||
}
|
||||
|
||||
350
native/src/core/sqlite.cpp
Normal file
350
native/src/core/sqlite.cpp
Normal file
@@ -0,0 +1,350 @@
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <base.hpp>
|
||||
#include <sqlite.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define DB_VERSION 12
|
||||
#define DB_VERSION_STR "12"
|
||||
|
||||
// SQLite APIs
|
||||
|
||||
static int (*sqlite3_open_v2)(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs);
|
||||
static int (*sqlite3_close)(sqlite3 *db);
|
||||
const char *(*sqlite3_errstr)(int);
|
||||
static int (*sqlite3_prepare_v2)(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail);
|
||||
static int (*sqlite3_bind_parameter_count)(sqlite3_stmt*);
|
||||
static int (*sqlite3_bind_int64)(sqlite3_stmt*, int, int64_t);
|
||||
static int (*sqlite3_bind_text)(sqlite3_stmt*,int,const char*,int,void(*)(void*));
|
||||
static int (*sqlite3_column_count)(sqlite3_stmt *pStmt);
|
||||
static const char *(*sqlite3_column_name)(sqlite3_stmt*, int N);
|
||||
static const char *(*sqlite3_column_text)(sqlite3_stmt*, int iCol);
|
||||
static int (*sqlite3_column_int)(sqlite3_stmt*, int iCol);
|
||||
static int (*sqlite3_step)(sqlite3_stmt*);
|
||||
static int (*sqlite3_finalize)(sqlite3_stmt *pStmt);
|
||||
|
||||
// Internal Android linker APIs
|
||||
|
||||
static void (*android_get_LD_LIBRARY_PATH)(char *buffer, size_t buffer_size);
|
||||
static void (*android_update_LD_LIBRARY_PATH)(const char *ld_library_path);
|
||||
|
||||
#define DLERR(ptr) if (!(ptr)) { \
|
||||
LOGE("db: %s\n", dlerror()); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define DLOAD(handle, arg) {\
|
||||
auto f = dlsym(handle, #arg); \
|
||||
DLERR(f) \
|
||||
*(void **) &(arg) = f; \
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
constexpr char apex_path[] = "/apex/com.android.runtime/lib64:/apex/com.android.art/lib64:/apex/com.android.i18n/lib64:";
|
||||
#else
|
||||
constexpr char apex_path[] = "/apex/com.android.runtime/lib:/apex/com.android.art/lib:/apex/com.android.i18n/lib:";
|
||||
#endif
|
||||
|
||||
static bool load_sqlite() {
|
||||
static int dl_init = 0;
|
||||
if (dl_init)
|
||||
return dl_init > 0;
|
||||
dl_init = -1;
|
||||
|
||||
auto sqlite = dlopen("libsqlite.so", RTLD_LAZY);
|
||||
if (!sqlite) {
|
||||
// Should only happen on Android 10+
|
||||
auto dl = dlopen("libdl_android.so", RTLD_LAZY);
|
||||
DLERR(dl);
|
||||
|
||||
DLOAD(dl, android_get_LD_LIBRARY_PATH);
|
||||
DLOAD(dl, android_update_LD_LIBRARY_PATH);
|
||||
|
||||
// Inject APEX into LD_LIBRARY_PATH
|
||||
char ld_path[4096];
|
||||
memcpy(ld_path, apex_path, sizeof(apex_path));
|
||||
constexpr int len = sizeof(apex_path) - 1;
|
||||
android_get_LD_LIBRARY_PATH(ld_path + len, sizeof(ld_path) - len);
|
||||
android_update_LD_LIBRARY_PATH(ld_path);
|
||||
sqlite = dlopen("libsqlite.so", RTLD_LAZY);
|
||||
|
||||
// Revert LD_LIBRARY_PATH just in case
|
||||
android_update_LD_LIBRARY_PATH(ld_path + len);
|
||||
}
|
||||
DLERR(sqlite);
|
||||
|
||||
DLOAD(sqlite, sqlite3_open_v2);
|
||||
DLOAD(sqlite, sqlite3_close);
|
||||
DLOAD(sqlite, sqlite3_errstr);
|
||||
DLOAD(sqlite, sqlite3_prepare_v2);
|
||||
DLOAD(sqlite, sqlite3_bind_parameter_count);
|
||||
DLOAD(sqlite, sqlite3_bind_int64);
|
||||
DLOAD(sqlite, sqlite3_bind_text);
|
||||
DLOAD(sqlite, sqlite3_step);
|
||||
DLOAD(sqlite, sqlite3_column_count);
|
||||
DLOAD(sqlite, sqlite3_column_name);
|
||||
DLOAD(sqlite, sqlite3_column_text);
|
||||
DLOAD(sqlite, sqlite3_column_int);
|
||||
DLOAD(sqlite, sqlite3_finalize);
|
||||
|
||||
dl_init = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
using StringVec = rust::Vec<rust::String>;
|
||||
using sql_bind_callback_real = int(*)(void*, int, sqlite3_stmt*);
|
||||
using sql_exec_callback_real = void(*)(void*, StringSlice, sqlite3_stmt*);
|
||||
|
||||
#define sql_chk(fn, ...) if (int rc = fn(__VA_ARGS__); rc != SQLITE_OK) return rc
|
||||
|
||||
// Exports to Rust
|
||||
extern "C" int sql_exec_impl(
|
||||
sqlite3 *db, rust::Str zSql,
|
||||
sql_bind_callback bind_cb = nullptr, void *bind_cookie = nullptr,
|
||||
sql_exec_callback exec_cb = nullptr, void *exec_cookie = nullptr) {
|
||||
const char *sql = zSql.begin();
|
||||
unique_ptr<sqlite3_stmt, decltype(sqlite3_finalize)> stmt(nullptr, sqlite3_finalize);
|
||||
|
||||
while (sql != zSql.end()) {
|
||||
// Step 1: prepare statement
|
||||
{
|
||||
sqlite3_stmt *st = nullptr;
|
||||
sql_chk(sqlite3_prepare_v2, db, sql, zSql.end() - sql, &st, &sql);
|
||||
if (st == nullptr) continue;
|
||||
stmt.reset(st);
|
||||
}
|
||||
|
||||
// Step 2: bind arguments
|
||||
if (bind_cb) {
|
||||
if (int count = sqlite3_bind_parameter_count(stmt.get())) {
|
||||
auto real_cb = reinterpret_cast<sql_bind_callback_real>(bind_cb);
|
||||
for (int i = 1; i <= count; ++i) {
|
||||
sql_chk(real_cb, bind_cookie, i, stmt.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: execute
|
||||
bool first = true;
|
||||
StringVec columns;
|
||||
for (;;) {
|
||||
int rc = sqlite3_step(stmt.get());
|
||||
if (rc == SQLITE_DONE) break;
|
||||
if (rc != SQLITE_ROW) return rc;
|
||||
if (exec_cb == nullptr) continue;
|
||||
if (first) {
|
||||
int count = sqlite3_column_count(stmt.get());
|
||||
for (int i = 0; i < count; ++i) {
|
||||
columns.emplace_back(sqlite3_column_name(stmt.get(), i));
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
auto real_cb = reinterpret_cast<sql_exec_callback_real>(exec_cb);
|
||||
real_cb(exec_cookie, StringSlice(columns), stmt.get());
|
||||
}
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int DbValues::get_int(int index) const {
|
||||
return sqlite3_column_int((sqlite3_stmt*) this, index);
|
||||
}
|
||||
|
||||
const char *DbValues::get_text(int index) const {
|
||||
return sqlite3_column_text((sqlite3_stmt*) this, index);
|
||||
}
|
||||
|
||||
int DbStatement::bind_int64(int index, int64_t val) {
|
||||
return sqlite3_bind_int64(reinterpret_cast<sqlite3_stmt*>(this), index, val);
|
||||
}
|
||||
|
||||
int DbStatement::bind_text(int index, rust::Str val) {
|
||||
return sqlite3_bind_text(reinterpret_cast<sqlite3_stmt*>(this), index, val.data(), val.size(), nullptr);
|
||||
}
|
||||
|
||||
#define sql_chk_log(fn, ...) if (int rc = fn(__VA_ARGS__); rc != SQLITE_OK) { \
|
||||
LOGE("sqlite3(line:%d): %s\n", __LINE__, sqlite3_errstr(rc)); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
static bool open_and_init_db_impl(sqlite3 **dbOut) {
|
||||
if (!load_sqlite()) {
|
||||
LOGE("sqlite3: Cannot load libsqlite.so\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
unique_ptr<sqlite3, decltype(sqlite3_close)> db(nullptr, sqlite3_close);
|
||||
{
|
||||
sqlite3 *sql;
|
||||
// We open the connection with SQLITE_OPEN_NOMUTEX because we are guarding it ourselves
|
||||
sql_chk_log(sqlite3_open_v2, MAGISKDB, &sql,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, nullptr);
|
||||
db.reset(sql);
|
||||
}
|
||||
|
||||
int ver = 0;
|
||||
bool upgrade = false;
|
||||
auto ver_cb = [](void *ver, auto, const DbValues &values) {
|
||||
*static_cast<int *>(ver) = values.get_int(0);
|
||||
};
|
||||
sql_chk_log(sql_exec_impl, db.get(), "PRAGMA user_version", nullptr, nullptr, ver_cb, &ver);
|
||||
if (ver > DB_VERSION) {
|
||||
// Don't support downgrading database
|
||||
LOGE("sqlite3: Downgrading database is not supported\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto create_policy = [&] {
|
||||
return sql_exec_impl(db.get(),
|
||||
"CREATE TABLE IF NOT EXISTS policies "
|
||||
"(uid INT, policy INT, until INT, logging INT, "
|
||||
"notification INT, PRIMARY KEY(uid))");
|
||||
};
|
||||
auto create_settings = [&] {
|
||||
return sql_exec_impl(db.get(),
|
||||
"CREATE TABLE IF NOT EXISTS settings "
|
||||
"(key TEXT, value INT, PRIMARY KEY(key))");
|
||||
};
|
||||
auto create_strings = [&] {
|
||||
return sql_exec_impl(db.get(),
|
||||
"CREATE TABLE IF NOT EXISTS strings "
|
||||
"(key TEXT, value TEXT, PRIMARY KEY(key))");
|
||||
};
|
||||
auto create_denylist = [&] {
|
||||
return sql_exec_impl(db.get(),
|
||||
"CREATE TABLE IF NOT EXISTS denylist "
|
||||
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process))");
|
||||
};
|
||||
|
||||
// Database changelog:
|
||||
//
|
||||
// 0 - 6: DB stored in app private data. There are no longer any code in the project to
|
||||
// migrate these data, so no need to take any of these versions into consideration.
|
||||
// 7 : create table `hidelist` (process TEXT, PRIMARY KEY(process))
|
||||
// 8 : add new column (package_name TEXT) to table `hidelist`
|
||||
// 9 : rebuild table `hidelist` to change primary key (PRIMARY KEY(package_name, process))
|
||||
// 10: remove table `logs`
|
||||
// 11: remove table `hidelist` and create table `denylist` (same data structure)
|
||||
// 12: rebuild table `policies` to drop column `package_name`
|
||||
|
||||
if (/* 0, 1, 2, 3, 4, 5, 6 */ ver <= 6) {
|
||||
sql_chk_log(create_policy);
|
||||
sql_chk_log(create_settings);
|
||||
sql_chk_log(create_strings);
|
||||
sql_chk_log(create_denylist);
|
||||
|
||||
// Directly jump to latest
|
||||
ver = DB_VERSION;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 7) {
|
||||
sql_chk_log(sql_exec_impl, db.get(),
|
||||
"BEGIN TRANSACTION;"
|
||||
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
|
||||
"CREATE TABLE IF NOT EXISTS hidelist "
|
||||
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
|
||||
"INSERT INTO hidelist SELECT process as package_name, process FROM hidelist_tmp;"
|
||||
"DROP TABLE hidelist_tmp;"
|
||||
"COMMIT;");
|
||||
// Directly jump to version 9
|
||||
ver = 9;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 8) {
|
||||
sql_chk_log(sql_exec_impl, db.get(),
|
||||
"BEGIN TRANSACTION;"
|
||||
"ALTER TABLE hidelist RENAME TO hidelist_tmp;"
|
||||
"CREATE TABLE IF NOT EXISTS hidelist "
|
||||
"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));"
|
||||
"INSERT INTO hidelist SELECT * FROM hidelist_tmp;"
|
||||
"DROP TABLE hidelist_tmp;"
|
||||
"COMMIT;");
|
||||
ver = 9;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 9) {
|
||||
sql_chk_log(sql_exec_impl, db.get(), "DROP TABLE IF EXISTS logs", nullptr, nullptr);
|
||||
ver = 10;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 10) {
|
||||
sql_chk_log(sql_exec_impl, db.get(),
|
||||
"DROP TABLE IF EXISTS hidelist;"
|
||||
"DELETE FROM settings WHERE key='magiskhide';");
|
||||
sql_chk_log(create_denylist);
|
||||
ver = 11;
|
||||
upgrade = true;
|
||||
}
|
||||
if (ver == 11) {
|
||||
sql_chk_log(sql_exec_impl, db.get(),
|
||||
"BEGIN TRANSACTION;"
|
||||
"ALTER TABLE policies RENAME TO policies_tmp;"
|
||||
"CREATE TABLE IF NOT EXISTS policies "
|
||||
"(uid INT, policy INT, until INT, logging INT, "
|
||||
"notification INT, PRIMARY KEY(uid));"
|
||||
"INSERT INTO policies "
|
||||
"SELECT uid, policy, until, logging, notification FROM policies_tmp;"
|
||||
"DROP TABLE policies_tmp;"
|
||||
"COMMIT;");
|
||||
ver = 12;
|
||||
upgrade = true;
|
||||
}
|
||||
|
||||
if (upgrade) {
|
||||
// Set version
|
||||
sql_chk_log(sql_exec_impl, db.get(), "PRAGMA user_version=" DB_VERSION_STR);
|
||||
}
|
||||
|
||||
*dbOut = db.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
sqlite3 *open_and_init_db() {
|
||||
sqlite3 *db = nullptr;
|
||||
return open_and_init_db_impl(&db) ? db : nullptr;
|
||||
}
|
||||
|
||||
// Exported from Rust
|
||||
extern "C" int sql_exec_rs(
|
||||
rust::Str zSql,
|
||||
sql_bind_callback bind_cb, void *bind_cookie,
|
||||
sql_exec_callback exec_cb, void *exec_cookie);
|
||||
|
||||
bool db_exec(const char *sql, DbArgs args, db_exec_callback exec_fn) {
|
||||
using db_bind_callback = std::function<int(int, DbStatement&)>;
|
||||
|
||||
db_bind_callback bind_fn = {};
|
||||
sql_bind_callback bind_cb = nullptr;
|
||||
if (!args.empty()) {
|
||||
bind_fn = std::ref(args);
|
||||
bind_cb = [](void *v, int index, DbStatement &stmt) -> int {
|
||||
auto fn = static_cast<db_bind_callback*>(v);
|
||||
return fn->operator()(index, stmt);
|
||||
};
|
||||
}
|
||||
sql_exec_callback exec_cb = nullptr;
|
||||
if (exec_fn) {
|
||||
exec_cb = [](void *v, StringSlice columns, const DbValues &values) {
|
||||
auto fn = static_cast<db_exec_callback*>(v);
|
||||
fn->operator()(columns, values);
|
||||
};
|
||||
}
|
||||
sql_chk_log(sql_exec_rs, sql, bind_cb, &bind_fn, exec_cb, &exec_fn);
|
||||
return true;
|
||||
}
|
||||
|
||||
int DbArgs::operator()(int index, DbStatement &stmt) {
|
||||
if (curr < args.size()) {
|
||||
const auto &arg = args[curr++];
|
||||
switch (arg.type) {
|
||||
case DbArg::INT:
|
||||
return stmt.bind_int64(index, arg.int_val);
|
||||
case DbArg::TEXT:
|
||||
return stmt.bind_text(index, arg.str_val);
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@@ -175,7 +175,7 @@ void app_log(const su_context &ctx) {
|
||||
extras.emplace_back("from.uid", ctx.info->uid);
|
||||
extras.emplace_back("to.uid", static_cast<int>(ctx.req.uid));
|
||||
extras.emplace_back("pid", ctx.pid);
|
||||
extras.emplace_back("policy", ctx.info->access.policy);
|
||||
extras.emplace_back("policy", +ctx.info->access.policy);
|
||||
extras.emplace_back("target", ctx.req.target);
|
||||
extras.emplace_back("context", ctx.req.context.data());
|
||||
extras.emplace_back("gids", &ctx.req.gids);
|
||||
@@ -193,7 +193,7 @@ void app_notify(const su_context &ctx) {
|
||||
extras.reserve(3);
|
||||
extras.emplace_back("from.uid", ctx.info->uid);
|
||||
extras.emplace_back("pid", ctx.pid);
|
||||
extras.emplace_back("policy", ctx.info->access.policy);
|
||||
extras.emplace_back("policy", +ctx.info->access.policy);
|
||||
|
||||
exec_cmd("notify", extras, ctx.info);
|
||||
exit(0);
|
||||
|
||||
145
native/src/core/su/mod.rs
Normal file
145
native/src/core/su/mod.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use crate::daemon::{
|
||||
to_app_id, to_user_id, MagiskD, AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL,
|
||||
};
|
||||
use crate::db::DbArg::Integer;
|
||||
use crate::db::{SqlTable, SqliteResult, SqliteReturn};
|
||||
use crate::ffi::{DbValues, MultiuserMode, RootAccess, RootSettings, SuPolicy};
|
||||
use base::ResultExt;
|
||||
|
||||
impl Default for SuPolicy {
|
||||
fn default() -> Self {
|
||||
SuPolicy::Query
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RootSettings {
|
||||
fn default() -> Self {
|
||||
RootSettings {
|
||||
policy: Default::default(),
|
||||
log: true,
|
||||
notify: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SqlTable for RootSettings {
|
||||
fn on_row(&mut self, columns: &[String], values: &DbValues) {
|
||||
for (i, column) in columns.iter().enumerate() {
|
||||
let val = values.get_int(i as i32);
|
||||
if column == "policy" {
|
||||
self.policy.repr = val;
|
||||
} else if column == "logging" {
|
||||
self.log = val != 0;
|
||||
} else if column == "notify" {
|
||||
self.notify = val != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UidList(Vec<i32>);
|
||||
|
||||
impl SqlTable for UidList {
|
||||
fn on_row(&mut self, _: &[String], values: &DbValues) {
|
||||
self.0.push(values.get_int(0));
|
||||
}
|
||||
}
|
||||
|
||||
impl MagiskD {
|
||||
fn get_root_settings(&self, uid: i32, settings: &mut RootSettings) -> SqliteResult<()> {
|
||||
self.db_exec_with_rows(
|
||||
"SELECT policy, logging, notification FROM policies \
|
||||
WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))",
|
||||
&[Integer(uid as i64)],
|
||||
settings,
|
||||
)
|
||||
.sql_result()
|
||||
}
|
||||
|
||||
pub fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool {
|
||||
self.get_root_settings(uid, settings).log().is_ok()
|
||||
}
|
||||
|
||||
pub fn prune_su_access(&self) {
|
||||
let mut list = UidList(Vec::new());
|
||||
if self
|
||||
.db_exec_with_rows("SELECT uid FROM policies", &[], &mut list)
|
||||
.sql_result()
|
||||
.log()
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let app_list = self.get_app_no_list();
|
||||
let mut rm_uids = Vec::new();
|
||||
|
||||
for uid in list.0 {
|
||||
let app_id = to_app_id(uid);
|
||||
if (AID_APP_START..=AID_APP_END).contains(&app_id) {
|
||||
let app_no = app_id - AID_APP_START;
|
||||
if !app_list.contains(app_no as usize) {
|
||||
// The app_id is no longer installed
|
||||
rm_uids.push(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for uid in rm_uids {
|
||||
self.db_exec("DELETE FROM policies WHERE uid=?", &[Integer(uid as i64)]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uid_granted_root(&self, mut uid: i32) -> bool {
|
||||
if uid == AID_ROOT {
|
||||
return true;
|
||||
}
|
||||
|
||||
let cfg = match self.get_db_settings().log() {
|
||||
Ok(cfg) => cfg,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
// Check user root access settings
|
||||
match cfg.root_access {
|
||||
RootAccess::Disabled => return false,
|
||||
RootAccess::AppsOnly => {
|
||||
if uid == AID_SHELL {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
RootAccess::AdbOnly => {
|
||||
if uid != AID_SHELL {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Check multiuser settings
|
||||
match cfg.multiuser_mode {
|
||||
MultiuserMode::OwnerOnly => {
|
||||
if to_user_id(uid) != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MultiuserMode::OwnerManaged => uid = to_app_id(uid),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut granted = false;
|
||||
let mut output_fn =
|
||||
|_: &[String], values: &DbValues| granted = values.get_int(0) == SuPolicy::Allow.repr;
|
||||
self.db_exec_with_rows(
|
||||
"SELECT policy FROM policies WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))",
|
||||
&[Integer(uid as i64)],
|
||||
&mut output_fn,
|
||||
);
|
||||
|
||||
granted
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_default_root_settings() -> RootSettings {
|
||||
RootSettings::default()
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <memory>
|
||||
|
||||
#include <db.hpp>
|
||||
#include <sqlite.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
#define DEFAULT_SHELL "/system/bin/sh"
|
||||
@@ -14,6 +14,9 @@
|
||||
#define ATTY_OUT (1 << 1)
|
||||
#define ATTY_ERR (1 << 2)
|
||||
|
||||
#define SILENT_ALLOW { SuPolicy::Allow, false, false }
|
||||
#define SILENT_DENY { SuPolicy::Deny, false, false }
|
||||
|
||||
class su_info {
|
||||
public:
|
||||
// Unique key
|
||||
@@ -21,8 +24,8 @@ public:
|
||||
|
||||
// These should be guarded with internal lock
|
||||
int eval_uid; // The effective UID, taking multiuser settings into consideration
|
||||
db_settings cfg;
|
||||
su_access access;
|
||||
struct DbSettings cfg;
|
||||
struct RootSettings access;
|
||||
std::string mgr_pkg;
|
||||
int mgr_uid;
|
||||
void check_db();
|
||||
|
||||
@@ -18,8 +18,8 @@ static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static shared_ptr<su_info> cached;
|
||||
|
||||
su_info::su_info(int uid) :
|
||||
uid(uid), eval_uid(-1), access(DEFAULT_SU_ACCESS), mgr_uid(-1),
|
||||
timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {}
|
||||
uid(uid), eval_uid(-1), cfg(DbSettings()), access(RootSettings()),
|
||||
mgr_uid(-1), timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {}
|
||||
|
||||
su_info::~su_info() {
|
||||
pthread_mutex_destroy(&_lock);
|
||||
@@ -44,130 +44,40 @@ void su_info::refresh() {
|
||||
|
||||
void su_info::check_db() {
|
||||
eval_uid = uid;
|
||||
get_db_settings(cfg);
|
||||
auto &daemon = MagiskD();
|
||||
daemon.get_db_settings(cfg);
|
||||
|
||||
// Check multiuser settings
|
||||
switch (cfg[SU_MULTIUSER_MODE]) {
|
||||
case MULTIUSER_MODE_OWNER_ONLY:
|
||||
switch (cfg.multiuser_mode) {
|
||||
case MultiuserMode::OwnerOnly:
|
||||
if (to_user_id(uid) != 0) {
|
||||
eval_uid = -1;
|
||||
access = NO_SU_ACCESS;
|
||||
access = SILENT_DENY;
|
||||
}
|
||||
break;
|
||||
case MULTIUSER_MODE_OWNER_MANAGED:
|
||||
case MultiuserMode::OwnerManaged:
|
||||
eval_uid = to_app_id(uid);
|
||||
break;
|
||||
case MULTIUSER_MODE_USER:
|
||||
case MultiuserMode::User:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (eval_uid > 0) {
|
||||
char query[256];
|
||||
ssprintf(query, sizeof(query),
|
||||
"SELECT policy, logging, notification FROM policies "
|
||||
"WHERE uid=%d AND (until=0 OR until>%li)", eval_uid, time(nullptr));
|
||||
auto res = db_exec(query, [&](db_row &row) -> bool {
|
||||
access.policy = (policy_t) parse_int(row["policy"]);
|
||||
access.log = parse_int(row["logging"]);
|
||||
access.notify = parse_int(row["notification"]);
|
||||
LOGD("magiskdb: query policy=[%d] log=[%d] notify=[%d]\n",
|
||||
access.policy, access.log, access.notify);
|
||||
return true;
|
||||
});
|
||||
if (res.check_err())
|
||||
if (!daemon.get_root_settings(eval_uid, access))
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to check our manager
|
||||
if (access.log || access.notify) {
|
||||
mgr_uid = get_manager(to_user_id(eval_uid), &mgr_pkg, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool uid_granted_root(int uid) {
|
||||
if (uid == AID_ROOT)
|
||||
return true;
|
||||
|
||||
db_settings cfg;
|
||||
get_db_settings(cfg);
|
||||
|
||||
// Check user root access settings
|
||||
switch (cfg[ROOT_ACCESS]) {
|
||||
case ROOT_ACCESS_DISABLED:
|
||||
return false;
|
||||
case ROOT_ACCESS_APPS_ONLY:
|
||||
if (uid == AID_SHELL)
|
||||
return false;
|
||||
break;
|
||||
case ROOT_ACCESS_ADB_ONLY:
|
||||
if (uid != AID_SHELL)
|
||||
return false;
|
||||
break;
|
||||
case ROOT_ACCESS_APPS_AND_ADB:
|
||||
break;
|
||||
}
|
||||
|
||||
// Check multiuser settings
|
||||
switch (cfg[SU_MULTIUSER_MODE]) {
|
||||
case MULTIUSER_MODE_OWNER_ONLY:
|
||||
if (to_user_id(uid) != 0)
|
||||
return false;
|
||||
break;
|
||||
case MULTIUSER_MODE_OWNER_MANAGED:
|
||||
uid = to_app_id(uid);
|
||||
break;
|
||||
case MULTIUSER_MODE_USER:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
bool granted = false;
|
||||
|
||||
char query[256];
|
||||
ssprintf(query, sizeof(query),
|
||||
"SELECT policy FROM policies WHERE uid=%d AND (until=0 OR until>%li)",
|
||||
uid, time(nullptr));
|
||||
auto res = db_exec(query, [&](db_row &row) -> bool {
|
||||
granted = parse_int(row["policy"]) == ALLOW;
|
||||
return true;
|
||||
});
|
||||
if (res.check_err())
|
||||
return false;
|
||||
|
||||
return granted;
|
||||
}
|
||||
|
||||
void prune_su_access() {
|
||||
cached.reset();
|
||||
vector<bool> app_no_list = get_app_no_list();
|
||||
vector<int> rm_uids;
|
||||
auto res = db_exec("SELECT uid FROM policies", [&](db_row &row) -> bool {
|
||||
int uid = parse_int(row["uid"]);
|
||||
int app_id = to_app_id(uid);
|
||||
if (app_id >= AID_APP_START && app_id <= AID_APP_END) {
|
||||
int app_no = app_id - AID_APP_START;
|
||||
if (app_no >= app_no_list.size() || !app_no_list[app_no]) {
|
||||
// The app_id is no longer installed
|
||||
rm_uids.push_back(uid);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (res.check_err())
|
||||
return;
|
||||
|
||||
for (int uid : rm_uids) {
|
||||
char query[256];
|
||||
ssprintf(query, sizeof(query), "DELETE FROM policies WHERE uid == %d", uid);
|
||||
db_exec(query).check_err();
|
||||
if (access.policy == SuPolicy::Query || access.log || access.notify) {
|
||||
mgr_uid = daemon.get_manager(to_user_id(eval_uid), &mgr_pkg, true);
|
||||
}
|
||||
}
|
||||
|
||||
static shared_ptr<su_info> get_su_info(unsigned uid) {
|
||||
if (uid == AID_ROOT) {
|
||||
auto info = make_shared<su_info>(uid);
|
||||
info->access = SILENT_SU_ACCESS;
|
||||
info->access = SILENT_ALLOW;
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -182,45 +92,45 @@ static shared_ptr<su_info> get_su_info(unsigned uid) {
|
||||
|
||||
mutex_guard lock = info->lock();
|
||||
|
||||
if (info->access.policy == QUERY) {
|
||||
if (info->access.policy == SuPolicy::Query) {
|
||||
// Not cached, get data from database
|
||||
info->check_db();
|
||||
|
||||
// If it's the manager, allow it silently
|
||||
if (to_app_id(info->uid) == to_app_id(info->mgr_uid)) {
|
||||
info->access = SILENT_SU_ACCESS;
|
||||
info->access = SILENT_ALLOW;
|
||||
return info;
|
||||
}
|
||||
|
||||
// Check su access settings
|
||||
switch (info->cfg[ROOT_ACCESS]) {
|
||||
case ROOT_ACCESS_DISABLED:
|
||||
switch (info->cfg.root_access) {
|
||||
case RootAccess::Disabled:
|
||||
LOGW("Root access is disabled!\n");
|
||||
info->access = NO_SU_ACCESS;
|
||||
info->access = SILENT_DENY;
|
||||
break;
|
||||
case ROOT_ACCESS_ADB_ONLY:
|
||||
case RootAccess::AdbOnly:
|
||||
if (info->uid != AID_SHELL) {
|
||||
LOGW("Root access limited to ADB only!\n");
|
||||
info->access = NO_SU_ACCESS;
|
||||
info->access = SILENT_DENY;
|
||||
}
|
||||
break;
|
||||
case ROOT_ACCESS_APPS_ONLY:
|
||||
case RootAccess::AppsOnly:
|
||||
if (info->uid == AID_SHELL) {
|
||||
LOGW("Root access is disabled for ADB!\n");
|
||||
info->access = NO_SU_ACCESS;
|
||||
info->access = SILENT_DENY;
|
||||
}
|
||||
break;
|
||||
case ROOT_ACCESS_APPS_AND_ADB:
|
||||
case RootAccess::AppsAndAdb:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (info->access.policy != QUERY)
|
||||
if (info->access.policy != SuPolicy::Query)
|
||||
return info;
|
||||
|
||||
// If still not determined, check if manager exists
|
||||
if (info->mgr_uid < 0) {
|
||||
info->access = NO_SU_ACCESS;
|
||||
info->access = SILENT_DENY;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -266,19 +176,19 @@ void su_daemon_handler(int client, const sock_cred *cred) {
|
||||
|| !read_vector(client, ctx.req.gids)) {
|
||||
LOGW("su: remote process probably died, abort\n");
|
||||
ctx.info.reset();
|
||||
write_int(client, DENY);
|
||||
write_int(client, +SuPolicy::Deny);
|
||||
close(client);
|
||||
return;
|
||||
}
|
||||
|
||||
// If still not determined, ask manager
|
||||
if (ctx.info->access.policy == QUERY) {
|
||||
if (ctx.info->access.policy == SuPolicy::Query) {
|
||||
int fd = app_request(ctx);
|
||||
if (fd < 0) {
|
||||
ctx.info->access.policy = DENY;
|
||||
ctx.info->access.policy = SuPolicy::Deny;
|
||||
} else {
|
||||
int ret = read_int_be(fd);
|
||||
ctx.info->access.policy = ret < 0 ? DENY : static_cast<policy_t>(ret);
|
||||
ctx.info->access.policy = ret < 0 ? SuPolicy::Deny : static_cast<SuPolicy>(ret);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
@@ -289,10 +199,10 @@ void su_daemon_handler(int client, const sock_cred *cred) {
|
||||
app_notify(ctx);
|
||||
|
||||
// Fail fast
|
||||
if (ctx.info->access.policy == DENY) {
|
||||
if (ctx.info->access.policy == SuPolicy::Deny) {
|
||||
LOGW("su: request rejected (%u)\n", ctx.info->uid);
|
||||
ctx.info.reset();
|
||||
write_int(client, DENY);
|
||||
write_int(client, +SuPolicy::Deny);
|
||||
close(client);
|
||||
return;
|
||||
}
|
||||
@@ -396,19 +306,19 @@ void su_daemon_handler(int client, const sock_cred *cred) {
|
||||
if (ctx.req.target == -1)
|
||||
ctx.req.target = ctx.pid;
|
||||
else if (ctx.req.target == 0)
|
||||
ctx.info->cfg[SU_MNT_NS] = NAMESPACE_MODE_GLOBAL;
|
||||
else if (ctx.info->cfg[SU_MNT_NS] == NAMESPACE_MODE_GLOBAL)
|
||||
ctx.info->cfg[SU_MNT_NS] = NAMESPACE_MODE_REQUESTER;
|
||||
switch (ctx.info->cfg[SU_MNT_NS]) {
|
||||
case NAMESPACE_MODE_GLOBAL:
|
||||
ctx.info->cfg.mnt_ns = MntNsMode::Global;
|
||||
else if (ctx.info->cfg.mnt_ns == MntNsMode::Global)
|
||||
ctx.info->cfg.mnt_ns = MntNsMode::Requester;
|
||||
switch (ctx.info->cfg.mnt_ns) {
|
||||
case MntNsMode::Global:
|
||||
LOGD("su: use global namespace\n");
|
||||
break;
|
||||
case NAMESPACE_MODE_REQUESTER:
|
||||
case MntNsMode::Requester:
|
||||
LOGD("su: use namespace of pid=[%d]\n", ctx.req.target);
|
||||
if (switch_mnt_ns(ctx.req.target))
|
||||
LOGD("su: setns failed, fallback to global\n");
|
||||
break;
|
||||
case NAMESPACE_MODE_ISOLATE:
|
||||
case MntNsMode::Isolate:
|
||||
LOGD("su: use new isolated namespace\n");
|
||||
switch_mnt_ns(ctx.req.target);
|
||||
xunshare(CLONE_NEWNS);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user