fix(soter): harden on-demand mount recovery
The supervisor mounted the forge on the happy path but could not re-attempt: mount() returned silently on inject/handshake failure, and a live-but-uninjected binding never died to trigger a rebind, stranding the forge for the life of that soterserver process (audit F1). Route every unmounted outcome through scheduleRetry(): inject failure, post-inject handshake-null, and register failure now schedule a re-bind instead of returning. Add onNullBinding (F2) and exponential backoff capped at 30s, reset on a clean mount (F3). register() now returns whether the transact succeeded so mount() retries on a false reply (F4); existing keystore callers ignore the new return. Audit remediation. compileDebugKotlin clean.
This commit is contained in:
@@ -305,20 +305,26 @@ abstract class BinderInterceptor : Binder() {
|
||||
target: IBinder,
|
||||
interceptor: BinderInterceptor,
|
||||
filteredCodes: IntArray = intArrayOf(),
|
||||
) {
|
||||
): Boolean {
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
try {
|
||||
return try {
|
||||
data.writeStrongBinder(target)
|
||||
data.writeStrongBinder(interceptor)
|
||||
data.writeInt(filteredCodes.size)
|
||||
for (code in filteredCodes) data.writeInt(code)
|
||||
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
|
||||
SystemLogger.info(
|
||||
"Registered interceptor for target: $target (${filteredCodes.size} filtered codes)"
|
||||
)
|
||||
val ok = backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
|
||||
if (ok) {
|
||||
SystemLogger.info(
|
||||
"Registered interceptor for target: $target (${filteredCodes.size} filtered codes)"
|
||||
)
|
||||
} else {
|
||||
SystemLogger.error("Register transact returned false for target: $target")
|
||||
}
|
||||
ok
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to register binder interceptor.", e)
|
||||
false
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
|
||||
+41
-12
@@ -45,9 +45,13 @@ object SoterProcessSupervisor {
|
||||
"exec ./inject `pidof $SOTER_PACKAGE` libTEESimulator.so entry"
|
||||
|
||||
private const val REBIND_DELAY_MS = 1000L
|
||||
private const val REBIND_MAX_MS = 30_000L
|
||||
|
||||
private val started = AtomicBoolean(false)
|
||||
|
||||
/** Re-bind backoff; doubles each failed (re)bind up to [REBIND_MAX_MS], resets on a clean mount. Handler-thread-confined. */
|
||||
private var rebindDelay = REBIND_DELAY_MS
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var handler: Handler
|
||||
|
||||
@@ -74,12 +78,17 @@ object SoterProcessSupervisor {
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
SystemLogger.debug("SOTER service disconnected (process died); rebinding")
|
||||
rebind()
|
||||
scheduleRetry()
|
||||
}
|
||||
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
SystemLogger.debug("SOTER binding died; rebinding")
|
||||
rebind()
|
||||
scheduleRetry()
|
||||
}
|
||||
|
||||
override fun onNullBinding(name: ComponentName?) {
|
||||
SystemLogger.debug("SOTER onBind returned null; rebinding")
|
||||
scheduleRetry()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,13 +106,24 @@ object SoterProcessSupervisor {
|
||||
SystemLogger.debug("SOTER bind requested (on-demand poke)")
|
||||
} else {
|
||||
SystemLogger.debug("SOTER bindService returned false; retrying")
|
||||
handler.postDelayed({ rebind() }, REBIND_DELAY_MS)
|
||||
scheduleRetry()
|
||||
}
|
||||
}
|
||||
|
||||
private fun rebind() {
|
||||
runCatching { context.unbindService(connection) }
|
||||
handler.postDelayed({ bind() }, REBIND_DELAY_MS)
|
||||
bind()
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-attempts the bind after the current backoff, then widens it (capped at [REBIND_MAX_MS]).
|
||||
* Every path that fails to leave the forge mounted routes here, so a live-but-uninjected
|
||||
* binding is re-attempted instead of stranding the forge. A clean [mount] resets the backoff.
|
||||
*/
|
||||
private fun scheduleRetry() {
|
||||
val delay = rebindDelay
|
||||
rebindDelay = (rebindDelay * 2).coerceAtMost(REBIND_MAX_MS)
|
||||
handler.postDelayed({ rebind() }, delay)
|
||||
}
|
||||
|
||||
/** Confirms injection via the `0xdeadbeef` handshake, injecting first if absent, then registers. */
|
||||
@@ -112,21 +132,30 @@ object SoterProcessSupervisor {
|
||||
if (backdoor == null) {
|
||||
SystemLogger.debug("SOTER backdoor absent; injecting libTEESimulator.so")
|
||||
if (!injectLibrary()) {
|
||||
SystemLogger.debug("SOTER injection failed; will retry on next (re)bind")
|
||||
SystemLogger.debug("SOTER injection failed; scheduling re-bind")
|
||||
scheduleRetry()
|
||||
return
|
||||
}
|
||||
backdoor = BinderInterceptor.getBackdoor(soterBinder)
|
||||
}
|
||||
if (backdoor == null) {
|
||||
SystemLogger.debug("SOTER backdoor handshake failed after injection")
|
||||
SystemLogger.debug("SOTER backdoor handshake failed after injection; scheduling re-bind")
|
||||
scheduleRetry()
|
||||
return
|
||||
}
|
||||
BinderInterceptor.register(
|
||||
backdoor,
|
||||
soterBinder,
|
||||
SoterServiceInterceptor,
|
||||
SoterServiceInterceptor.interceptedCodes,
|
||||
)
|
||||
val registered =
|
||||
BinderInterceptor.register(
|
||||
backdoor,
|
||||
soterBinder,
|
||||
SoterServiceInterceptor,
|
||||
SoterServiceInterceptor.interceptedCodes,
|
||||
)
|
||||
if (!registered) {
|
||||
SystemLogger.debug("SOTER register failed; scheduling re-bind")
|
||||
scheduleRetry()
|
||||
return
|
||||
}
|
||||
rebindDelay = REBIND_DELAY_MS
|
||||
SystemLogger.debug("SOTER forge mounted; handshake ok")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user