Skip to content

Commit

Permalink
Merge branch 'master' into thghj
Browse files Browse the repository at this point in the history
  • Loading branch information
topjohnwu authored Dec 27, 2024
2 parents 75bae6f + d276835 commit 4b82792
Show file tree
Hide file tree
Showing 42 changed files with 799 additions and 310 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
jobs:
build:
name: Build Magisk artifacts
runs-on: macos-14
runs-on: macos-15
strategy:
fail-fast: false
steps:
Expand Down Expand Up @@ -51,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
Expand All @@ -69,7 +69,7 @@ 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
Expand Down Expand Up @@ -115,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ 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()
}
Expand Down
1 change: 1 addition & 0 deletions app/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@ dependencies {
// However, we don't want to bundle test dependencies.
// That's why we make it compileOnly.
compileOnly(libs.test.junit)
compileOnly(libs.test.uiautomator)
}
11 changes: 1 addition & 10 deletions app/core/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,4 @@
-flattenpackagehierarchy
-allowaccessmodification

-dontwarn org.junit.Assert
-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.**
50 changes: 0 additions & 50 deletions app/core/src/main/java/com/topjohnwu/magisk/core/TestImpl.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class PolicyDao : MagiskDB() {
}

suspend fun fetch(uid: Int): SuPolicy? {
val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT = 1"
val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT 1"
return exec(query, ::toPolicy).firstOrNull()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package com.topjohnwu.magisk.core.model.su

class SuPolicy(val uid: Int) {
class SuPolicy(
val uid: Int,
var policy: Int = INTERACTIVE,
var until: 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
153 changes: 86 additions & 67 deletions app/core/src/main/java/com/topjohnwu/magisk/core/tasks/AppMigration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) }
Expand All @@ -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) {
}

// Install and auto launch app
val session = APKInstall.startSession(activity, pkg, onFailure) {
Config.suManager = pkg
Shell.cmd("touch $AppApkPath").exec()
launchApp(activity, pkg)
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 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)
if (Shell.cmd(cmd).exec().isSuccess) {
Config.suManager = pkg
Shell.cmd("touch $AppApkPath").exec()
launchApp(context, pkg)
return true
} else {
return false
}
session.waitIntent()?.let { activity.startActivity(it) } ?: return false
return true
}

@Suppress("DEPRECATION")
Expand All @@ -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")
Expand All @@ -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? {
Expand Down
Loading

0 comments on commit 4b82792

Please sign in to comment.