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,
|
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()
|
||||||
|
|||||||
+41
-12
@@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user