feat(logging): per-UID NDJSON on external storage
Move debug diagnostics off /data/local/tmp/teesim to /data/media/0/TEESimulator (visible at /sdcard/TEESimulator), so users can pull them without a root explorer. The logging code runs in the keystore SELinux domain, so a debug-only media_rw_data_file grant plus a debug-only diag.sh fragment gate the plane: diag.sh's presence is the signal service.sh (setup) and action.sh (export) test. customize.sh extracts diag.sh on debug installs or sweeps the dir on release, since the release keystore domain cannot remove it itself. Replace the per-call .bin parcel dumps (a fresh undecodable file per generateKey) with one NDJSON record per event on the UID's own file, carrying decoded fields plus the raw parcel as base64 for the offline parsers. computeIfAbsent makes per-UID writer creation atomic.
This commit is contained in:
@@ -202,6 +202,7 @@ androidComponents {
|
|||||||
val sourceModuleDir = rootProject.projectDir.resolve("module")
|
val sourceModuleDir = rootProject.projectDir.resolve("module")
|
||||||
from(sourceModuleDir) {
|
from(sourceModuleDir) {
|
||||||
exclude("module.prop") // Exclude the template file.
|
exclude("module.prop") // Exclude the template file.
|
||||||
|
exclude("diag.sh") // Debug-only diagnostic plane; included for debug below.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy and filter the module.prop template separately.
|
// Copy and filter the module.prop template separately.
|
||||||
@@ -214,8 +215,21 @@ androidComponents {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDebug) {
|
||||||
|
from(sourceModuleDir) { include("diag.sh") }
|
||||||
|
}
|
||||||
|
|
||||||
// The destination for all the above 'from' operations.
|
// The destination for all the above 'from' operations.
|
||||||
into(tempModuleDir)
|
into(tempModuleDir)
|
||||||
|
|
||||||
|
if (isDebug) {
|
||||||
|
doLast {
|
||||||
|
// Debug-only: grant the keystore domain external-storage access; diag.sh
|
||||||
|
// (shipped only in debug) carries the shell side of the diagnostic plane.
|
||||||
|
tempModuleDir.get().asFile.resolve("sepolicy.rule")
|
||||||
|
.appendText("\nallow keystore media_rw_data_file { dir file } *\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task 2: Zip the prepared files from the temporary directory.
|
// Task 2: Zip the prepared files from the temporary directory.
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ import android.content.Context
|
|||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import java.io.File
|
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.matrix.TEESimulator.config.BootStateManager
|
import org.matrix.TEESimulator.config.BootStateManager
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
||||||
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils
|
|
||||||
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
||||||
import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor
|
import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
@@ -41,7 +39,6 @@ object App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
purgeDebugDiagnostics()
|
|
||||||
prepareEnvironment()
|
prepareEnvironment()
|
||||||
|
|
||||||
// Spoof boot-state props before any hook attaches, so keystore2's
|
// Spoof boot-state props before any hook attaches, so keystore2's
|
||||||
@@ -74,41 +71,6 @@ object App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Release builds never emit diagnostics. Sweep anything a prior debug install left behind so it
|
|
||||||
* cannot act as a detection artifact: the `.bin` dumps in the world-readable temp dir, and the
|
|
||||||
* per-UID diagnostic logs under the config dir.
|
|
||||||
*/
|
|
||||||
private fun purgeDebugDiagnostics() {
|
|
||||||
if (SystemLogger.isDebugBuild) return
|
|
||||||
// .bin dumps and per-UID logs now share the diagnostic dir.
|
|
||||||
purgeStale(File(InterceptorUtils.DIAGNOSTIC_DIR), InterceptorUtils.DIAGNOSTIC_DIR) { name ->
|
|
||||||
name.startsWith("teesim-") &&
|
|
||||||
(name.endsWith(".bin") || name.endsWith(".log") || name.endsWith(".log.1"))
|
|
||||||
}
|
|
||||||
// Older debug installs wrote dumps loose in /data/local/tmp and per-UID logs under the
|
|
||||||
// module config dir; sweep both legacy locations so upgrading to a release build leaves
|
|
||||||
// nothing behind.
|
|
||||||
purgeStale(File("/data/local/tmp"), "/data/local/tmp") { name ->
|
|
||||||
name.startsWith("teesim-") && name.endsWith(".bin")
|
|
||||||
}
|
|
||||||
purgeStale(File("${ConfigurationManager.CONFIG_PATH}/logs"), "legacy per-UID log dir") { name ->
|
|
||||||
name.startsWith("teesim-uid-") && (name.endsWith(".log") || name.endsWith(".log.1"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes matching files in [dir], logging a single once-per-boot audit line if any existed.
|
|
||||||
*/
|
|
||||||
private fun purgeStale(dir: File, label: String, matches: (String) -> Boolean) {
|
|
||||||
val stale = dir.listFiles { _, name -> matches(name) } ?: return
|
|
||||||
if (stale.isEmpty()) return
|
|
||||||
stale.forEach { runCatching { it.delete() } }
|
|
||||||
// warning() bypasses the rate limiter, so this once-per-boot audit line survives the noisy
|
|
||||||
// startup window.
|
|
||||||
SystemLogger.warning("Purged ${stale.size} stale debug diagnostic(s) from $label")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Initializes the necessary Android framework internals to satisfy KeyStore requirements. */
|
/** Initializes the necessary Android framework internals to satisfy KeyStore requirements. */
|
||||||
private fun prepareEnvironment() {
|
private fun prepareEnvironment() {
|
||||||
// 1. Prepare Main Looper
|
// 1. Prepare Main Looper
|
||||||
|
|||||||
+6
-12
@@ -19,13 +19,6 @@ data class KeyIdentifier(val uid: Int, val alias: String)
|
|||||||
/** A collection of utility functions to support binder interception. */
|
/** A collection of utility functions to support binder interception. */
|
||||||
object InterceptorUtils {
|
object InterceptorUtils {
|
||||||
|
|
||||||
/**
|
|
||||||
* Dedicated subfolder for the debug-only diagnostic `.bin` dumps. Keeping them out of the
|
|
||||||
* world-readable `/data/local/tmp` root means they no longer litter a directory shared with
|
|
||||||
* every other tool, and the release purge can sweep the whole folder in one shot.
|
|
||||||
*/
|
|
||||||
const val DIAGNOSTIC_DIR = "/data/local/tmp/teesim"
|
|
||||||
|
|
||||||
private const val EX_SERVICE_SPECIFIC = -8
|
private const val EX_SERVICE_SPECIFIC = -8
|
||||||
|
|
||||||
private const val FLAT_STRIDE_HEADER = 12
|
private const val FLAT_STRIDE_HEADER = 12
|
||||||
@@ -127,24 +120,25 @@ object InterceptorUtils {
|
|||||||
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Correlates a captured reply parcel to the app that triggered it, for [createTypedObjectReply]. */
|
||||||
|
data class ReplyDiagnostic(val uid: Int, val txId: Long?, val event: String)
|
||||||
|
|
||||||
/** Creates an `OverrideReply` parcel containing a Parcelable object. */
|
/** Creates an `OverrideReply` parcel containing a Parcelable object. */
|
||||||
fun <T : Parcelable?> createTypedObjectReply(
|
fun <T : Parcelable?> createTypedObjectReply(
|
||||||
obj: T,
|
obj: T,
|
||||||
flags: Int = 0,
|
flags: Int = 0,
|
||||||
diagnosticTag: String? = null,
|
diagnostic: ReplyDiagnostic? = null,
|
||||||
): BinderInterceptor.TransactionResult.OverrideReply {
|
): BinderInterceptor.TransactionResult.OverrideReply {
|
||||||
val parcel =
|
val parcel =
|
||||||
Parcel.obtain().apply {
|
Parcel.obtain().apply {
|
||||||
writeNoException()
|
writeNoException()
|
||||||
writeTypedObject(obj, flags)
|
writeTypedObject(obj, flags)
|
||||||
}
|
}
|
||||||
if (diagnosticTag != null && SystemLogger.isDebugBuild) {
|
if (diagnostic != null && SystemLogger.isUidLogged(diagnostic.uid)) {
|
||||||
val savedPos = parcel.dataPosition()
|
val savedPos = parcel.dataPosition()
|
||||||
val wire = parcel.marshall()
|
val wire = parcel.marshall()
|
||||||
parcel.setDataPosition(savedPos)
|
parcel.setDataPosition(savedPos)
|
||||||
val path = "$DIAGNOSTIC_DIR/teesim-$diagnosticTag.bin"
|
SystemLogger.uidLogRaw(diagnostic.uid, diagnostic.txId, diagnostic.event, "len=${wire.size}", wire)
|
||||||
runCatching { java.io.File(path).apply { parentFile?.mkdirs() }.writeBytes(wire) }
|
|
||||||
SystemLogger.debug("[$diagnosticTag] reply len=${wire.size} path=$path")
|
|
||||||
}
|
}
|
||||||
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-9
@@ -527,16 +527,11 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
callingPid: Int,
|
callingPid: Int,
|
||||||
data: Parcel,
|
data: Parcel,
|
||||||
): TransactionResult {
|
): TransactionResult {
|
||||||
if (SystemLogger.isDebugBuild) {
|
if (SystemLogger.isUidLogged(callingUid)) {
|
||||||
val savedPos = data.dataPosition()
|
val savedPos = data.dataPosition()
|
||||||
val req = data.marshall()
|
val req = data.marshall()
|
||||||
data.setDataPosition(savedPos)
|
data.setDataPosition(savedPos)
|
||||||
val path =
|
SystemLogger.uidLogRaw(callingUid, txId, "genkey-request", "len=${req.size}", req)
|
||||||
"${InterceptorUtils.DIAGNOSTIC_DIR}/teesim-gen-mode-req-uid${callingUid}-tx${txId}-${System.nanoTime()}.bin"
|
|
||||||
runCatching { java.io.File(path).apply { parentFile?.mkdirs() }.writeBytes(req) }
|
|
||||||
SystemLogger.debug(
|
|
||||||
"[gen-mode-req] uid=$callingUid txId=$txId len=${req.size} path=$path"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
val oversized = data.dataSize() > MAX_ALIAS_LENGTH
|
val oversized = data.dataSize() > MAX_ALIAS_LENGTH
|
||||||
|
|
||||||
@@ -881,7 +876,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(
|
return InterceptorUtils.createTypedObjectReply(
|
||||||
metadata,
|
metadata,
|
||||||
diagnosticTag = "gen-mode-sym-uid$callingUid-tx$txId",
|
diagnostic = InterceptorUtils.ReplyDiagnostic(callingUid, txId, "genkey-reply-sym"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -996,7 +991,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
|
|
||||||
return InterceptorUtils.createTypedObjectReply(
|
return InterceptorUtils.createTypedObjectReply(
|
||||||
response.metadata,
|
response.metadata,
|
||||||
diagnosticTag = "gen-mode-asym-uid$callingUid-tx$txId",
|
diagnostic = InterceptorUtils.ReplyDiagnostic(callingUid, txId, "genkey-reply-asym"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
package org.matrix.TEESimulator.logging
|
package org.matrix.TEESimulator.logging
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import java.io.BufferedWriter
|
import java.io.BufferedWriter
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileWriter
|
import java.io.FileWriter
|
||||||
import java.text.SimpleDateFormat
|
import java.time.Instant
|
||||||
import java.util.Date
|
import java.time.ZoneId
|
||||||
import java.util.Locale
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
import org.json.JSONObject
|
||||||
import org.matrix.TEESimulator.BuildConfig
|
import org.matrix.TEESimulator.BuildConfig
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
|
|
||||||
@@ -141,18 +143,17 @@ object SystemLogger {
|
|||||||
ConfigurationManager.getPackagesForUid(uid).firstOrNull() ?: "uid:$uid"
|
ConfigurationManager.getPackagesForUid(uid).firstOrNull() ?: "uid:$uid"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits one structured diagnostic record for a targeted [uid] as `[<pkg> tx=<txId>] <event>:
|
* Emits one structured diagnostic record for a targeted [uid]. The human form
|
||||||
* <detail>`. The record is mirrored to logcat and appended to that UID's own file. In-scope
|
* `[<pkg> tx=<txId>] <event>: <detail>` goes to logcat; the file sink receives one NDJSON object
|
||||||
* records bypass the global rate limiter — a targeted app's traffic is already volume-bounded,
|
* per line under that UID's own file. In-scope records bypass the global rate limiter: a
|
||||||
* and dropping a line mid-probe would corrupt the very trace we are trying to read. No-op for
|
* targeted app's traffic is already volume-bounded, and dropping a line mid-probe would corrupt
|
||||||
* untargeted UIDs and in release builds.
|
* the very trace we are trying to read. No-op for untargeted UIDs and in release builds.
|
||||||
*/
|
*/
|
||||||
fun uidLog(uid: Int, txId: Long?, event: String, detail: String) {
|
fun uidLog(uid: Int, txId: Long?, event: String, detail: String) {
|
||||||
if (!isUidLogged(uid)) return
|
if (!isUidLogged(uid)) return
|
||||||
val correlation = txId?.let { " tx=$it" } ?: ""
|
val correlation = txId?.let { " tx=$it" } ?: ""
|
||||||
val line = "[${label(uid)}$correlation] $event: $detail"
|
Log.d(TAG, "[${label(uid)}$correlation] $event: $detail")
|
||||||
Log.d(TAG, line)
|
runCatching { uidWriter(uid).append(jsonRecord(uid, txId, event, detail, null)) }
|
||||||
runCatching { uidWriter(uid).append(line) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Lazy [uidLog]: [detail] is only built for targeted UIDs in debug builds. */
|
/** Lazy [uidLog]: [detail] is only built for targeted UIDs in debug builds. */
|
||||||
@@ -161,35 +162,88 @@ object SystemLogger {
|
|||||||
uidLog(uid, txId, event, detail())
|
uidLog(uid, txId, event, detail())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Co-located with the .bin dumps in InterceptorUtils.DIAGNOSTIC_DIR so every debug artifact sits
|
/**
|
||||||
// in one adb-pullable dir; release builds purge it (see App.purgeDebugDiagnostics).
|
* [uidLog] plus the exact wire bytes that produced the event, base64 (NO_WRAP) in a `raw_b64`
|
||||||
private val uidLogDir = File("/data/local/tmp/teesim")
|
* field. This is the structured replacement for the per-call `.bin` parcel dumps: one NDJSON
|
||||||
|
* line on the per-UID file instead of a fresh undecodable file per transaction, with the raw
|
||||||
|
* parcel still recoverable for offline parsers.
|
||||||
|
*/
|
||||||
|
fun uidLogRaw(uid: Int, txId: Long?, event: String, detail: String, raw: ByteArray) {
|
||||||
|
if (!isUidLogged(uid)) return
|
||||||
|
val correlation = txId?.let { " tx=$it" } ?: ""
|
||||||
|
Log.d(TAG, "[${label(uid)}$correlation] $event: $detail (raw ${raw.size}B)")
|
||||||
|
runCatching {
|
||||||
|
val encoded = Base64.encodeToString(raw, Base64.NO_WRAP)
|
||||||
|
uidWriter(uid).append(jsonRecord(uid, txId, event, detail, encoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External-storage root for every debug diagnostic. `/data/media/0/TEESimulator` is the
|
||||||
|
* in-namespace backing path the keystore domain can reach; a normal file manager sees the same
|
||||||
|
* files at `/sdcard/TEESimulator`. Release builds never write here and purge it on boot
|
||||||
|
* (App.purgeDebugDiagnostics). The domain reaches it via a debug-only media_rw_data_file
|
||||||
|
* sepolicy grant, and service.sh pre-creates the directory.
|
||||||
|
*/
|
||||||
|
const val DIAGNOSTIC_DIR = "/data/media/0/TEESimulator"
|
||||||
|
|
||||||
|
private val uidLogDir = File(DIAGNOSTIC_DIR)
|
||||||
private const val UID_LOG_MAX_BYTES = 4L * 1024 * 1024
|
private const val UID_LOG_MAX_BYTES = 4L * 1024 * 1024
|
||||||
private val uidWriters = ConcurrentHashMap<Int, UidLogFile>()
|
private val uidWriters = ConcurrentHashMap<Int, UidLogFile>()
|
||||||
|
|
||||||
private fun uidWriter(uid: Int): UidLogFile = uidWriters.getOrPut(uid) { UidLogFile(uid, uidLogDir) }
|
private val recordClock =
|
||||||
|
DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault())
|
||||||
|
|
||||||
|
private fun jsonRecord(
|
||||||
|
uid: Int,
|
||||||
|
txId: Long?,
|
||||||
|
event: String,
|
||||||
|
detail: String,
|
||||||
|
rawB64: String?,
|
||||||
|
): String =
|
||||||
|
JSONObject()
|
||||||
|
.apply {
|
||||||
|
put("ts", recordClock.format(Instant.now()))
|
||||||
|
put("uid", uid)
|
||||||
|
put("pkg", label(uid))
|
||||||
|
txId?.let { put("tx", it) }
|
||||||
|
put("event", event)
|
||||||
|
put("detail", detail)
|
||||||
|
rawB64?.let { put("raw_b64", it) }
|
||||||
|
}
|
||||||
|
.toString()
|
||||||
|
|
||||||
|
private fun uidWriter(uid: Int): UidLogFile =
|
||||||
|
uidWriters.computeIfAbsent(uid) { key ->
|
||||||
|
UidLogFile(key, uidLogDir).also { file ->
|
||||||
|
val packages =
|
||||||
|
ConfigurationManager.getPackagesForUid(key).joinToString().ifEmpty { "<unresolved>" }
|
||||||
|
runCatching {
|
||||||
|
file.append(jsonRecord(key, null, "session", "packages=[$packages]", null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An append-only diagnostic file for a single UID at `<logDir>/teesim-uid-<uid>.log`, rotated
|
* Append-only NDJSON sink for a single UID at `<logDir>/teesim-uid-<uid>.ndjson`, rotated once
|
||||||
* once to `.log.1` at [UID_LOG_MAX_BYTES]. Writes are synchronised because the keystore binder
|
* to `.ndjson.1` at [UID_LOG_MAX_BYTES]; one JSON object per line. Writes are synchronised
|
||||||
* pool is multi-threaded, and every operation is wrapped so a logging fault can never propagate
|
* because the keystore binder pool is multi-threaded, and every operation is wrapped so a
|
||||||
* into the daemon. Created only on the debug-gated path, so release builds never touch this dir.
|
* logging fault can never propagate into the daemon. Created only on the debug-gated path.
|
||||||
*/
|
*/
|
||||||
private class UidLogFile(private val uid: Int, private val logDir: File) {
|
private class UidLogFile(uid: Int, private val logDir: File) {
|
||||||
private val primary = File(logDir, "teesim-uid-$uid.log")
|
private val primary = File(logDir, "teesim-uid-$uid.ndjson")
|
||||||
private val rotated = File(logDir, "teesim-uid-$uid.log.1")
|
private val rotated = File(logDir, "teesim-uid-$uid.ndjson.1")
|
||||||
private val clock = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
|
||||||
private var writer: BufferedWriter? = null
|
private var writer: BufferedWriter? = null
|
||||||
private var size = 0L
|
private var size = 0L
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun append(message: String) {
|
fun append(jsonLine: String) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val out = writer ?: open()
|
val out = writer ?: open()
|
||||||
val line = "${clock.format(Date())} $message\n"
|
out.write(jsonLine)
|
||||||
out.write(line)
|
out.write("\n")
|
||||||
out.flush()
|
out.flush()
|
||||||
size += line.length
|
size += jsonLine.length + 1
|
||||||
if (size >= UID_LOG_MAX_BYTES) rotate()
|
if (size >= UID_LOG_MAX_BYTES) rotate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,12 +253,6 @@ object SystemLogger {
|
|||||||
val out = BufferedWriter(FileWriter(primary, /* append = */ true))
|
val out = BufferedWriter(FileWriter(primary, /* append = */ true))
|
||||||
writer = out
|
writer = out
|
||||||
size = primary.length()
|
size = primary.length()
|
||||||
val packages =
|
|
||||||
ConfigurationManager.getPackagesForUid(uid).joinToString().ifEmpty { "<unresolved>" }
|
|
||||||
val header = "${clock.format(Date())} === session uid=$uid packages=[$packages] ===\n"
|
|
||||||
out.write(header)
|
|
||||||
out.flush()
|
|
||||||
size += header.length
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-11
@@ -4,17 +4,6 @@ CONFIG_DIR=/data/adb/tricky_store
|
|||||||
|
|
||||||
. "$MODDIR/action_i18n.sh"
|
. "$MODDIR/action_i18n.sh"
|
||||||
|
|
||||||
echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo " ⚠️ $(_msg confirm_header)"
|
|
||||||
echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo " "
|
|
||||||
echo " $(_msg confirm_warning_1)"
|
|
||||||
echo " $(_msg confirm_warning_2)"
|
|
||||||
echo " "
|
|
||||||
echo " 🔊 $(_msg confirm_vol_up)"
|
|
||||||
echo " 🔉 $(_msg confirm_vol_down)"
|
|
||||||
echo " "
|
|
||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
# Sample getevent in 1s bursts; a piped stream block-buffers and misses
|
# Sample getevent in 1s bursts; a piped stream block-buffers and misses
|
||||||
# a single key-press before the timeout.
|
# a single key-press before the timeout.
|
||||||
@@ -29,6 +18,31 @@ confirm() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Debug builds ship diag.sh, adding a one-tap log export before the destructive clear-keys action.
|
||||||
|
if [ -f "$MODDIR/diag.sh" ]; then
|
||||||
|
. "$MODDIR/diag.sh"
|
||||||
|
echo " "
|
||||||
|
echo " 📦 Export diagnostic logs to /sdcard/Download?"
|
||||||
|
echo " 🔊 Vol-Up = export logs"
|
||||||
|
echo " 🔉 Vol-Down = skip to clear keys"
|
||||||
|
echo " "
|
||||||
|
if confirm; then
|
||||||
|
diag_export
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " ⚠️ $(_msg confirm_header)"
|
||||||
|
echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " "
|
||||||
|
echo " $(_msg confirm_warning_1)"
|
||||||
|
echo " $(_msg confirm_warning_2)"
|
||||||
|
echo " "
|
||||||
|
echo " 🔊 $(_msg confirm_vol_up)"
|
||||||
|
echo " 🔉 $(_msg confirm_vol_down)"
|
||||||
|
echo " "
|
||||||
|
|
||||||
if ! confirm; then
|
if ! confirm; then
|
||||||
echo " "
|
echo " "
|
||||||
echo " ❌ $(_msg confirm_cancelled)"
|
echo " ❌ $(_msg confirm_cancelled)"
|
||||||
|
|||||||
@@ -76,6 +76,16 @@ mv "$MODPATH/libsupervisor.so" "$MODPATH/supervisor"
|
|||||||
chmod 755 "$MODPATH/inject"
|
chmod 755 "$MODPATH/inject"
|
||||||
chmod 755 "$MODPATH/supervisor"
|
chmod 755 "$MODPATH/supervisor"
|
||||||
|
|
||||||
|
# Debug builds carry diag.sh (the diagnostic plane); release builds do not. Extract it when
|
||||||
|
# present; otherwise sweep any external-storage diagnostics a prior debug install left behind,
|
||||||
|
# since the release keystore domain has no grant to remove them itself.
|
||||||
|
if unzip -qqjo "$ZIPFILE" "diag.sh" -d "$MODPATH" 2>/dev/null; then
|
||||||
|
chmod 644 "$MODPATH/diag.sh"
|
||||||
|
ui_print "- Debug diagnostic plane enabled"
|
||||||
|
else
|
||||||
|
rm -rf /data/media/0/TEESimulator /data/local/tmp/teesim
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Configuration Files ---
|
# --- Configuration Files ---
|
||||||
if [ ! -d "$CONFIG_DIR" ]; then
|
if [ ! -d "$CONFIG_DIR" ]; then
|
||||||
ui_print "- Creating configuration directory"
|
ui_print "- Creating configuration directory"
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#!/system/bin/sh
|
||||||
|
# Debug-only diagnostic plane. Shipped solely in debug ZIPs; its presence is the gate that
|
||||||
|
# service.sh (setup) and action.sh (export) test before touching external storage.
|
||||||
|
DIAG_DIR=/data/media/0/TEESimulator
|
||||||
|
|
||||||
|
diag_setup() {
|
||||||
|
mkdir -p "$DIAG_DIR"
|
||||||
|
chmod 0777 "$DIAG_DIR"
|
||||||
|
chcon u:object_r:media_rw_data_file:s0 "$DIAG_DIR" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
diag_export() {
|
||||||
|
_ts=$(date +%Y%m%d-%H%M%S)
|
||||||
|
_dest=/sdcard/Download/teesim-logs-$_ts
|
||||||
|
mkdir -p "$_dest"
|
||||||
|
cp -f "$DIAG_DIR"/teesim-uid-* "$_dest"/ 2>/dev/null
|
||||||
|
cp -f /data/adb/tricky_store/logs/certgen.log* "$_dest"/ 2>/dev/null
|
||||||
|
logcat -d -s TEESimulator > "$_dest/logcat.txt" 2>/dev/null
|
||||||
|
echo " ✅ Saved to $_dest"
|
||||||
|
}
|
||||||
@@ -4,6 +4,12 @@ cd $MODDIR
|
|||||||
# Fork-based supervisor for instant restart
|
# Fork-based supervisor for instant restart
|
||||||
./supervisor ./daemon "$MODDIR" &
|
./supervisor ./daemon "$MODDIR" &
|
||||||
|
|
||||||
|
# Debug builds ship diag.sh; its presence enables the external-storage diagnostic plane.
|
||||||
|
if [ -f "$MODDIR/diag.sh" ]; then
|
||||||
|
. "$MODDIR/diag.sh"
|
||||||
|
diag_setup
|
||||||
|
fi
|
||||||
|
|
||||||
# Clear logd size persist properties once boot completes
|
# Clear logd size persist properties once boot completes
|
||||||
(
|
(
|
||||||
until [ "$(getprop sys.boot_completed)" = "1" ]; do
|
until [ "$(getprop sys.boot_completed)" = "1" ]; do
|
||||||
|
|||||||
@@ -11,3 +11,6 @@ rm -rf "$CONFIG_DIR/persistent_keys"
|
|||||||
rm -f "$CONFIG_DIR/tee_status.txt"
|
rm -f "$CONFIG_DIR/tee_status.txt"
|
||||||
rm -f "$CONFIG_DIR/boot_hash.bin" "$CONFIG_DIR/boot_key.bin"
|
rm -f "$CONFIG_DIR/boot_hash.bin" "$CONFIG_DIR/boot_key.bin"
|
||||||
rm -f "$CONFIG_DIR/security_patch.txt" "$CONFIG_DIR/security_patch.txt.next" "$CONFIG_DIR/last_bulletin_fetch.json"
|
rm -f "$CONFIG_DIR/security_patch.txt" "$CONFIG_DIR/security_patch.txt.next" "$CONFIG_DIR/last_bulletin_fetch.json"
|
||||||
|
|
||||||
|
# Debug diagnostics live on external storage; remove them on uninstall.
|
||||||
|
rm -rf /data/media/0/TEESimulator
|
||||||
|
|||||||
+1
-1
@@ -158,7 +158,7 @@ deploy_zip() {
|
|||||||
|
|
||||||
if [[ "$CLEAR_LOGS" == true ]]; then
|
if [[ "$CLEAR_LOGS" == true ]]; then
|
||||||
bold "==> Clearing per-UID diagnostic logs"
|
bold "==> Clearing per-UID diagnostic logs"
|
||||||
adb shell "rm -f /data/local/tmp/teesim/teesim-uid-*" 2>/dev/null || true
|
adb shell "rm -rf /data/media/0/TEESimulator /data/local/tmp/teesim" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
bold "==> Deploying $name"
|
bold "==> Deploying $name"
|
||||||
|
|||||||
Reference in New Issue
Block a user