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:
Enginex0
2026-06-26 16:57:12 +01:00
parent a4875dc200
commit 5ddd8137df
2 changed files with 53 additions and 18 deletions
@@ -305,20 +305,26 @@ abstract class BinderInterceptor : Binder() {
target: IBinder, target: IBinder,
interceptor: BinderInterceptor, interceptor: BinderInterceptor,
filteredCodes: IntArray = intArrayOf(), filteredCodes: IntArray = intArrayOf(),
) { ): Boolean {
val data = Parcel.obtain() val data = Parcel.obtain()
val reply = Parcel.obtain() val reply = Parcel.obtain()
try { return try {
data.writeStrongBinder(target) data.writeStrongBinder(target)
data.writeStrongBinder(interceptor) data.writeStrongBinder(interceptor)
data.writeInt(filteredCodes.size) data.writeInt(filteredCodes.size)
for (code in filteredCodes) data.writeInt(code) for (code in filteredCodes) data.writeInt(code)
backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0) val ok = backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
SystemLogger.info( if (ok) {
"Registered interceptor for target: $target (${filteredCodes.size} filtered codes)" 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) { } catch (e: Exception) {
SystemLogger.error("Failed to register binder interceptor.", e) SystemLogger.error("Failed to register binder interceptor.", e)
false
} finally { } finally {
data.recycle() data.recycle()
reply.recycle() reply.recycle()
@@ -45,9 +45,13 @@ object SoterProcessSupervisor {
"exec ./inject `pidof $SOTER_PACKAGE` libTEESimulator.so entry" "exec ./inject `pidof $SOTER_PACKAGE` libTEESimulator.so entry"
private const val REBIND_DELAY_MS = 1000L private const val REBIND_DELAY_MS = 1000L
private const val REBIND_MAX_MS = 30_000L
private val started = AtomicBoolean(false) 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 context: Context
private lateinit var handler: Handler private lateinit var handler: Handler
@@ -74,12 +78,17 @@ object SoterProcessSupervisor {
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
SystemLogger.debug("SOTER service disconnected (process died); rebinding") SystemLogger.debug("SOTER service disconnected (process died); rebinding")
rebind() scheduleRetry()
} }
override fun onBindingDied(name: ComponentName?) { override fun onBindingDied(name: ComponentName?) {
SystemLogger.debug("SOTER binding died; rebinding") 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)") SystemLogger.debug("SOTER bind requested (on-demand poke)")
} else { } else {
SystemLogger.debug("SOTER bindService returned false; retrying") SystemLogger.debug("SOTER bindService returned false; retrying")
handler.postDelayed({ rebind() }, REBIND_DELAY_MS) scheduleRetry()
} }
} }
private fun rebind() { private fun rebind() {
runCatching { context.unbindService(connection) } 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. */ /** Confirms injection via the `0xdeadbeef` handshake, injecting first if absent, then registers. */
@@ -112,21 +132,30 @@ object SoterProcessSupervisor {
if (backdoor == null) { if (backdoor == null) {
SystemLogger.debug("SOTER backdoor absent; injecting libTEESimulator.so") SystemLogger.debug("SOTER backdoor absent; injecting libTEESimulator.so")
if (!injectLibrary()) { if (!injectLibrary()) {
SystemLogger.debug("SOTER injection failed; will retry on next (re)bind") SystemLogger.debug("SOTER injection failed; scheduling re-bind")
scheduleRetry()
return return
} }
backdoor = BinderInterceptor.getBackdoor(soterBinder) backdoor = BinderInterceptor.getBackdoor(soterBinder)
} }
if (backdoor == null) { if (backdoor == null) {
SystemLogger.debug("SOTER backdoor handshake failed after injection") SystemLogger.debug("SOTER backdoor handshake failed after injection; scheduling re-bind")
scheduleRetry()
return return
} }
BinderInterceptor.register( val registered =
backdoor, BinderInterceptor.register(
soterBinder, backdoor,
SoterServiceInterceptor, soterBinder,
SoterServiceInterceptor.interceptedCodes, 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") SystemLogger.debug("SOTER forge mounted; handshake ok")
} }