Skip to content

Commit 798f57e

Browse files
fix: harden manager boot fallback
1 parent 40dd314 commit 798f57e

3 files changed

Lines changed: 123 additions & 8 deletions

File tree

apd/src/event.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,11 @@ pub fn on_boot_completed(superkey: Option<String>) -> Result<()> {
424424
}
425425

426426
pub fn on_manager_boot_completed(superkey: Option<String>) -> Result<()> {
427+
let superkey = superkey.or_else(|| {
428+
info!("Manager boot fallback invoked without explicit superkey, defaulting to trusted-manager key 'su'");
429+
Some("su".to_string())
430+
});
431+
427432
info!("on_manager_boot_completed triggered!");
428433

429434
if Path::new(defs::UTS_SPOOF_BOOT_PENDING).exists() {

app/src/main/java/me/bmax/apatch/receiver/BootCompletedReceiver.kt

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import android.content.Context
55
import android.content.Intent
66
import android.util.Log
77
import kotlin.concurrent.thread
8-
import me.bmax.apatch.util.execApd
8+
import me.bmax.apatch.util.ApdExecResult
9+
import me.bmax.apatch.util.execApdBootFallback
910

1011
class BootCompletedReceiver : BroadcastReceiver() {
1112
override fun onReceive(context: Context, intent: Intent) {
@@ -26,10 +27,27 @@ class BootCompletedReceiver : BroadcastReceiver() {
2627
TAG,
2728
"Boot fallback attempt ${index + 1}/${retryDelaysMs.size}: triggering manager-boot-completed"
2829
)
29-
if (execApd("manager-boot-completed", newShell = true)) {
30-
Log.i(TAG, "Boot fallback succeeded on attempt ${index + 1}")
30+
val result = try {
31+
execApdBootFallback("manager-boot-completed")
32+
} catch (t: Throwable) {
33+
Log.e(TAG, "Boot fallback attempt ${index + 1} crashed before completion", t)
34+
null
35+
}
36+
37+
if (result != null && result.success) {
38+
Log.i(
39+
TAG,
40+
"Boot fallback succeeded on attempt ${index + 1}: ${formatResult(result)}"
41+
)
3142
return@thread
3243
}
44+
45+
if (result != null) {
46+
Log.w(
47+
TAG,
48+
"Boot fallback attempt ${index + 1} failed: ${formatResult(result)}"
49+
)
50+
}
3351
}
3452

3553
Log.e(TAG, "Boot fallback failed after all retry attempts")
@@ -46,5 +64,18 @@ class BootCompletedReceiver : BroadcastReceiver() {
4664

4765
companion object {
4866
private const val TAG = "FPBootReceiver"
67+
68+
private fun formatResult(result: ApdExecResult): String {
69+
val parts = mutableListOf("command=${result.commandLabel}")
70+
result.exitCode?.let { parts += "exit=$it" }
71+
result.errorMessage?.takeIf { it.isNotBlank() }?.let { parts += "error=$it" }
72+
result.output
73+
.takeIf { it.isNotBlank() }
74+
?.let { output ->
75+
val compact = output.replace('\n', ' ').take(400)
76+
parts += "output=$compact"
77+
}
78+
return parts.joinToString(", ")
79+
}
4980
}
5081
}

app/src/main/java/me/bmax/apatch/util/APatchCli.kt

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import java.io.IOException
2828
import java.security.MessageDigest
2929
import java.security.cert.CertificateFactory
3030
import java.security.cert.X509Certificate
31+
import java.util.concurrent.TimeUnit
3132
import java.util.zip.ZipFile
3233
import kotlinx.coroutines.CoroutineScope
3334
import kotlinx.coroutines.Dispatchers
@@ -40,6 +41,14 @@ import kotlinx.coroutines.withTimeout
4041
private const val TAG = "APatchCli"
4142
private const val SHELL_TIMEOUT_MS = 10_000L
4243

44+
data class ApdExecResult(
45+
val success: Boolean,
46+
val commandLabel: String,
47+
val exitCode: Int? = null,
48+
val output: String = "",
49+
val errorMessage: String? = null,
50+
)
51+
4352
private fun getKPatchPath(): String {
4453
return apApp.applicationInfo.nativeLibraryDir + File.separator + "libkpatch.so"
4554
}
@@ -191,12 +200,82 @@ fun rootShellForResult(vararg cmds: String): Shell.Result {
191200
}
192201

193202
fun execApd(args: String, newShell: Boolean = false): Boolean {
194-
return if (newShell) {
195-
withNewRootShell {
196-
ShellUtils.fastCmdResult(this, "${APApplication.APD_PATH} $args")
203+
return try {
204+
if (newShell) {
205+
withNewRootShell {
206+
ShellUtils.fastCmdResult(this, "${APApplication.APD_PATH} $args")
207+
}
208+
} else {
209+
ShellUtils.fastCmdResult(getRootShell(), "${APApplication.APD_PATH} $args")
197210
}
198-
} else {
199-
ShellUtils.fastCmdResult(getRootShell(), "${APApplication.APD_PATH} $args")
211+
} catch (t: Throwable) {
212+
Log.e(TAG, "execApd failed: args='$args', newShell=$newShell", t)
213+
false
214+
}
215+
}
216+
217+
private fun configureRootProcessEnv(builder: ProcessBuilder) {
218+
val basePath = System.getenv("PATH").orEmpty()
219+
builder.environment().apply {
220+
this["PATH"] = "$basePath:/system_ext/bin:/vendor/bin:${APApplication.APATCH_FOLDER}bin"
221+
this["BUSYBOX"] = "${APApplication.APATCH_FOLDER}bin/busybox"
222+
}
223+
}
224+
225+
fun execApdBootFallback(vararg args: String, timeoutMs: Long = SHELL_TIMEOUT_MS): ApdExecResult {
226+
val effectiveSuperKey = APApplication.superKey.ifBlank { "su" }
227+
val command = mutableListOf(
228+
APApplication.SUPERCMD,
229+
"su",
230+
"-Z",
231+
APApplication.MAGISK_SCONTEXT,
232+
"exec",
233+
APApplication.APD_PATH,
234+
"-s",
235+
effectiveSuperKey,
236+
).apply {
237+
addAll(args)
238+
}
239+
val commandLabel =
240+
"${File(APApplication.SUPERCMD).name} su -Z ${APApplication.MAGISK_SCONTEXT} exec ${APApplication.APD_PATH} -s <superkey> ${args.joinToString(" ")}"
241+
242+
return try {
243+
val builder = ProcessBuilder(command).redirectErrorStream(true)
244+
configureRootProcessEnv(builder)
245+
246+
val process = builder.start()
247+
val finished = process.waitFor(timeoutMs, TimeUnit.MILLISECONDS)
248+
if (!finished) {
249+
process.destroy()
250+
if (!process.waitFor(500, TimeUnit.MILLISECONDS)) {
251+
process.destroyForcibly()
252+
}
253+
val output = runCatching {
254+
process.inputStream.bufferedReader().use { it.readText().trim() }
255+
}.getOrDefault("")
256+
ApdExecResult(
257+
success = false,
258+
commandLabel = commandLabel,
259+
output = output,
260+
errorMessage = "timed out after ${timeoutMs}ms",
261+
)
262+
} else {
263+
val exitCode = process.exitValue()
264+
val output = process.inputStream.bufferedReader().use { it.readText().trim() }
265+
ApdExecResult(
266+
success = exitCode == 0,
267+
commandLabel = commandLabel,
268+
exitCode = exitCode,
269+
output = output,
270+
errorMessage = if (exitCode == 0) null else "exit code $exitCode",
271+
)
272+
}
273+
} catch (t: Throwable) {
274+
ApdExecResult(
275+
success = false,
276+
commandLabel = commandLabel,
277+
errorMessage = t.message ?: t.javaClass.simpleName,
278+
)
200279
}
201280
}
202281

0 commit comments

Comments
 (0)