Skip to content

usb: serialize transfers against close to fix use-after-free - #89

Open
amaanq wants to merge 1 commit into
mimi89999:mainfrom
amaanq:usb-uaf-fix
Open

usb: serialize transfers against close to fix use-after-free#89
amaanq wants to merge 1 commit into
mimi89999:mainfrom
amaanq:usb-uaf-fix

Conversation

@amaanq

@amaanq amaanq commented Aug 5, 2026

Copy link
Copy Markdown

Closing a transport mid-transfer frees the native usb_device while the
transfer is still using it, and neither bulkTransfer nor requestWait checks
that the connection is still open once it has read the pointer. I hit this on my
phone with MTE enabled, where I ran into a SIGSEGV as soon as the freed struct
was read.

sendRaw now holds a lock across the whole exchange, and close() cancels the
in-flight transfer before waiting on that same lock, so everything is released by
the time close() returns.

I've been running this patch on my phone now and have not run into any more MTE issues. Please note that I did use an LLM to help me diagnose this and propose a fix, but I've reviewed the code myself & tested it locally with a reproducer that trigger the UAF reliably, and this patch no longer triggers it.

Full tombstone
Build fingerprint: 'google/komodo/komodo:17/CP2A.260705.006/2026080300:user/release-keys'
Kernel Release: '6.1.175-android14-11'
Revision: 'MP1.0'
ABI: 'arm64'
Timestamp: 2026-08-05 05:02:25.299244224-0400
Process uptime: 20s
Executable: /system/bin/app_process64
Cmdline: pl.lebihan.authnkey
pid: 3997, ppid: 817, tid: 4122, name: DefaultDispatch  >>> pl.lebihan.authnkey <<<
uid: 10269
tagged_addr_ctrl: 000000000007fff7 (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, PR_MTE_TCF_ASYNC, mask 0xfffe)
pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
esr: 0000000092000011 (Data Abort Exception 0x24)
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0b00caa22ecc4044 (read)
    x0  0b00caa22ecc0000  x1  0000000000000084  x2  0f00c8a0846c2640  x3  0000000000000040
    x4  0000000000001388  x5  0000000000000040  x6  0000000000001388  x7  0000000000000000
    x8  abe9a5c4ecd7ac00  x9  0000000000004044  x10 0000000000000002  x11 000000000000000f
    x12 0000000000000004  x13 000000000000000a  x14 0000000000000040  x15 0000000000000050
    x16 0000cb7452331fb0  x17 0000cb74460dfde0  x18 0000c84a58af4000  x19 0000c84a61b3c040
    x20 0800c90c35f92900  x21 0000000000000000  x22 0000000000001388  x23 0000c84a61aba450
    x24 0000000000000084  x25 0000000000000040  x26 0b00caa22ecc0000  x27 0900c8fec5ef9760
    x28 0000000000000000  x29 0000c84a61aba320
    lr  0064cb74522bdd88  sp  0000c84a61aba300  pc  0000cb74460dfe08  pst 0000000080001000
    esr 0000000092000011  vg  0000000000000002

25 total frames
backtrace:
      #00 pc 0000000000005e08  /system/lib64/libusbhost.so (usb_device_bulk_transfer+40) (BuildId: ba81c89198d98e06d5541f1864ccdeba)
      #01 pc 0000000000267d84  /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_bulk_request(_JNIEnv*, _jobject*, int, _jbyteArray*, int, int, int) (.__uniq.225495418272668945951341274111615333304)+260) (BuildId: 919851cace96579c3adde4de7319ca84)
      #02 pc 000000000082c59c  /system/framework/arm64/boot-framework.oat (art_jni_trampoline+124) (BuildId: 42aec0129340bbbb4f1185c75701a99cd6d9de76)
      #03 pc 0000000000741c18  /apex/com.android.art/lib64/libart.so (nterp_helper+2952) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #04 pc 0000000000421452  /system/framework/framework.jar (offset 0x968000) (android.hardware.usb.UsbDeviceConnection.bulkTransfer+70)
      #05 pc 0000000000741b98  /apex/com.android.art/lib64/libart.so (nterp_helper+2824) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #06 pc 00000000004213f0  /system/framework/framework.jar (offset 0x968000) (android.hardware.usb.UsbDeviceConnection.bulkTransfer+12)
      #07 pc 000000000037b794  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (pl.lebihan.authnkey.UsbTransport.access$sendRaw+964)
      #08 pc 00000000003648b0  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (pl.lebihan.authnkey.NfcTransport$sendCtapCommand$2.invokeSuspend+240)
      #09 pc 0000000000364674  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (pl.lebihan.authnkey.NfcTransport$sendCtapCommand$2.invoke+260)
      #10 pc 00000000002e1f44  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (kotlin.text.CharsKt.startUndispatchedOrReturn+148)
      #11 pc 00000000002fec50  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (kotlinx.coroutines.JobKt.withContext+2480)
      #12 pc 000000000037c280  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (pl.lebihan.authnkey.UsbTransport.sendCtapCommand+176)
      #13 pc 0000000000327824  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (pl.lebihan.authnkey.CredentialProviderActivity$executeGetAssertion$response$1.invokeSuspend+628)
      #14 pc 00000000002d7ba8  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (kotlin.coroutines.jvm.internal.ContinuationImpl.resumeWith+104)
      #15 pc 00000000002f43fc  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (kotlinx.coroutines.DispatchedTask.run+828)
      #16 pc 0000000000178afc  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (androidx.core.provider.CallbackWrapper$1.run+156)
      #17 pc 000000000030be54  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (kotlinx.coroutines.scheduling.TaskImpl.run+52)
      #18 pc 000000000030863c  /data/app/~~ZOlR1DgyzvxdamHJqehwkQ==/pl.lebihan.authnkey-kWKOUxuTqDwC0w9TAz937g==/oat/arm64/base.odex (kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run+2572)
      #19 pc 00000000002a3194  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #20 pc 00000000002a1404  /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+148) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #21 pc 00000000004e4ba0  /apex/com.android.art/lib64/libart.so (art::Thread::CreateCallback(void*)+1392) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #22 pc 00000000004e461c  /apex/com.android.art/lib64/libart.so (art::Thread::CreateCallbackWithUffdGc(void*)+12) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #23 pc 0000000000097024  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*) (.__uniq.67847048707805468364044055584648682506)+180) (BuildId: 98507be200f152a345f4bdd57c06bfb7)
      #24 pc 0000000000086e34  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+68) (BuildId: 98507be200f152a345f4bdd57c06bfb7)

Memory tags around the fault address (0xb00caa22ecc4044), one tag per 16 bytes:
      0xcaa22ecc3800: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc3900: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc3a00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc3b00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc3c00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc3d00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc3e00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc3f00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
    =>0xcaa22ecc4000: 0  0  0  0 [0] 0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc4100: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc4200: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc4300: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc4400: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc4500: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc4600: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xcaa22ecc4700: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0

Thanks

@mimi89999

Copy link
Copy Markdown
Owner

Thanks for digging into this.

I don't like that the release ends up waiting on a transfer though, and moving that wait into a coroutine means it now happens after close() has returned.

@mimi89999

Copy link
Copy Markdown
Owner

@amaanq I believe that the correct fix for this issue would be something like

--- a/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt
+++ b/app/src/main/java/pl/lebihan/authnkey/UsbTransport.kt
@@ -5,6 +5,7 @@
 import kotlinx.coroutines.withContext
 import java.nio.ByteBuffer
 import java.nio.ByteOrder
+import java.util.concurrent.TimeoutException
 import kotlin.random.Random
 
 /**
@@ -26,7 +27,32 @@
     override val isConnected: Boolean
         get() = _isConnected
 
-    private val packetSize = outEndpoint.maxPacketSize.coerceAtLeast(64)
+    private val inPacketSize = inEndpoint.maxPacketSize.coerceAtLeast(64)
+    private val outPacketSize = outEndpoint.maxPacketSize.coerceAtLeast(64)
+
+    private val inRequest = UsbRequest().apply { initialize(connection, inEndpoint) }
+    private val outRequest = UsbRequest().apply { initialize(connection, outEndpoint) }
+
+    /**
+     * Transfers a single HID report, sending or receiving depending on the direction
+     * of this request's endpoint. [buffer] holds the report to send, or is filled
+     * with the one received.
+     *
+     * Returns the number of bytes transferred, or -1 if nothing arrived within
+     * [timeoutMs].
+     */
+    private fun UsbRequest.transfer(buffer: ByteBuffer, timeoutMs: Long): Int {
+        if (!queue(buffer)) return -1
+        val completed = try {
+            connection.requestWait(timeoutMs)
+        } catch (e: TimeoutException) {
+            // The request stays queued after a timeout, so cancel and reap it
+            cancel()
+            try { connection.requestWait(CANCEL_TIMEOUT_MS) } catch (_: Exception) {}
+            null
+        }
+        return if (completed === this) buffer.position() else -1
+    }
 
     /**
      * Initialize CTAPHID channel
@@ -69,7 +95,7 @@
 
     private fun sendRaw(cid: Int, cmd: Int, data: ByteArray): ByteArray {
         // Build and send initialization packet
-        val initPacket = ByteArray(packetSize)
+        val initPacket = ByteArray(outPacketSize)
         var offset = 0
 
         // Channel ID (4 bytes, big endian)
@@ -85,19 +111,19 @@
         initPacket[5] = (data.size shr 8).toByte()
         initPacket[6] = (data.size and 0xFF).toByte()
 
-        // Data (up to packetSize - 7 bytes in init packet)
-        val initDataLen = minOf(data.size, packetSize - 7)
+        // Data (up to outPacketSize - 7 bytes in init packet)
+        val initDataLen = minOf(data.size, outPacketSize - 7)
         System.arraycopy(data, 0, initPacket, 7, initDataLen)
         offset = initDataLen
 
         // Send init packet
-        val sent = connection.bulkTransfer(outEndpoint, initPacket, packetSize, TIMEOUT_MS)
+        val sent = outRequest.transfer(ByteBuffer.wrap(initPacket), TIMEOUT_MS)
         if (sent < 0) throw Exception("Failed to send init packet")
 
         // Send continuation packets if needed
         var seq = 0
         while (offset < data.size) {
-            val contPacket = ByteArray(packetSize)
+            val contPacket = ByteArray(outPacketSize)
 
             // Channel ID
             contPacket[0] = (cid shr 24).toByte()
@@ -110,11 +136,11 @@
             seq++
 
             // Data
-            val contDataLen = minOf(data.size - offset, packetSize - 5)
+            val contDataLen = minOf(data.size - offset, outPacketSize - 5)
             System.arraycopy(data, offset, contPacket, 5, contDataLen)
             offset += contDataLen
 
-            val contSent = connection.bulkTransfer(outEndpoint, contPacket, packetSize, TIMEOUT_MS)
+            val contSent = outRequest.transfer(ByteBuffer.wrap(contPacket), TIMEOUT_MS)
             if (contSent < 0) throw Exception("Failed to send continuation packet")
         }
 
@@ -139,8 +165,8 @@
                 throw Exception("Timeout waiting for response")
             }
 
-            val packet = ByteArray(packetSize)
-            val received = connection.bulkTransfer(inEndpoint, packet, packetSize, TIMEOUT_MS)
+            val packet = ByteArray(inPacketSize)
+            val received = inRequest.transfer(ByteBuffer.wrap(packet), TIMEOUT_MS)
 
             if (received < 0) {
                 // Timeout on this read, but keep trying if within max wait time
@@ -205,6 +231,8 @@
     override fun close() {
         _isConnected = false
         try {
+            inRequest.close()
+            outRequest.close()
             auxiliaryInterfaces.forEach {
                 try { connection.releaseInterface(it) } catch (_: Exception) {}
             }
@@ -221,7 +249,8 @@
         private const val CMD_CBOR = 0x10
         private const val CMD_KEEPALIVE = 0x3B
         private const val CMD_ERROR = 0x3F
-        private const val TIMEOUT_MS = 5000
+        private const val TIMEOUT_MS = 5000L
+        private const val CANCEL_TIMEOUT_MS = 100L
 
         /**
          * Find FIDO HID interface on a USB device

Please test it and feel free to adjust it.

@amaanq

amaanq commented Aug 5, 2026

Copy link
Copy Markdown
Author

Thanks for the quick reply. I've played with your patch a bit, and it doesn't actually fix the UAF, but rather it just shifts the crash site, that being a segv in usb_request_wait rather than usb_device_bulk_transfer. (I probably should've led with a backtrace in the original pr body...)

The reason your version still crashes is that native_request_wait has the same shape as native_bulk_request (link):

// frameworks/base/core/jni/android_hardware_UsbDeviceConnection.cpp
struct usb_device* device = get_device_from_object(env, thiz);
if (!device) {
    ALOGE("device is closed in native_request_wait");
    return NULL;
}
// ...
request = usb_request_wait(device, timeout);

And close() frees before it nulls the field:

struct usb_device* device = get_device_from_object(env, thiz);
if (device) {
        usb_device_close(device);
        env->SetLongField(thiz, field_context, 0);
}

Meaning, any thread that already got past the null check is holding a pointer that gets freed under it.

I do agree not deferring close is ideal, so I believe I've come up with a better solution based on your patch. It cancels the in-flight transfer so close can stay synchronous, whilst fixing the UAF.

Please take a look and let me know your thoughts

@mimi89999

Copy link
Copy Markdown
Owner

Have you tried just adding a cancel before the close() in the

@@ -205,6 +231,8 @@
     override fun close() {
         _isConnected = false
         try {
+            inRequest.close()
+            outRequest.close()
             auxiliaryInterfaces.forEach {
                 try { connection.releaseInterface(it) } catch (_: Exception) {}
             }

block?

@amaanq

amaanq commented Aug 5, 2026

Copy link
Copy Markdown
Author

Yeah I tried that, it still crashes at usb_request_wait+196.

Tombstone
Build fingerprint: 'google/komodo/komodo:17/CP2A.260705.006/2026080300:user/release-keys'
Kernel Release: '6.1.175-android14-11'
Revision: 'MP1.0'
ABI: 'arm64'
Timestamp: 2026-08-05 15:44:53.142454547-0400
Process uptime: 11s
Executable: /system/bin/app_process64
Cmdline: pl.lebihan.authnkey.uaftest
pid: 31988, ppid: 814, tid: 32052, name: DefaultDispatch  >>> pl.lebihan.authnkey.uaftest <<<
uid: 10137
tagged_addr_ctrl: 000000000007fff7 (PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, PR_MTE_TCF_ASYNC, mask 0xfffe)
pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
esr: 0000000092000011 (Data Abort Exception 0x24)
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0f00c65e3004b2f0 (read)
    x0  0000000000000000  x1  000000004008550d  x2  0000c61cb4f691c0  x3  0000000000000000
    x4  0000000000000000  x5  00000000003c6298  x6  747865746e6f4365  x7  0000c61d8f7f991e
    x8  0f00c65e3004b2c0  x9  27d6655292511600  x10 0000c61cb4f69170  x11 0000c61cb4f69140
    x12 ffffff80ffffffd0  x13 000000007fffffff  x14 00000000003c6298  x15 000000633cc9ba7c
    x16 0000c958b66682a0  x17 0000c958cbea8500  x18 0000c61cb4260000  x19 0b00c87288771000
    x20 000000004008550d  x21 0000c61cb4fd7040  x22 0000000000004044  x23 431bde82d7b634db
    x24 0000c61d91509c00  x25 0000c61cb4f69358  x26 0c00c8153665a270  x27 0700c6ce4c8a82a0
    x28 0000c61cb4f69250  x29 0000c61cb4f691d0
    lr  0052c958b6666080  sp  0000c61cb4f691c0  pc  0000c958b66660a4  pst 0000000000001000
    esr 0000000092000011  vg  0000000000000002

100 total frames
backtrace:
      #00 pc 00000000000060a4  /system/lib64/libusbhost.so (usb_request_wait+196) (BuildId: ba81c89198d98e06d5541f1864ccdeba)
      #01 pc 0000000000267e5c  /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long) (.__uniq.225495418272668945951341274111615333304)+124) (BuildId: 919851cace96579c3adde4de7319ca84)
      #02 pc 00000000002ba5a0  /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #03 pc 0000000000741b3c  /apex/com.android.art/lib64/libart.so (nterp_helper+2732) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #04 pc 0000000000421196  /system/framework/framework.jar (offset 0x968000) (android.hardware.usb.UsbDeviceConnection.requestWait+18)
      #05 pc 00000000002a3194  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #06 pc 0000000000894898  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+1688) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #07 pc 0000000000712a9c  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>(art::interpreter::SwitchImplContext*)+588) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #08 pc 0000000000319e68  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #09 pc 00000000000376e4  /data/app/~~xKi0Qn6PkJyvb9_FxXQBMA==/pl.lebihan.authnkey.uaftest-jMXCHtCmcl4MCHMCxnMeyw==/base.apk (offset 0xf15000) (pl.lebihan.authnkey.UsbTransport.transfer+0)
      #10 pc 0000000000894c24  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+2596) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #11 pc 0000000000712d80  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>(art::interpreter::SwitchImplContext*)+1328) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #12 pc 0000000000319e68  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #13 pc 00000000000372b0  /data/app/~~xKi0Qn6PkJyvb9_FxXQBMA==/pl.lebihan.authnkey.uaftest-jMXCHtCmcl4MCHMCxnMeyw==/base.apk (offset 0xf15000) (pl.lebihan.authnkey.UsbTransport.receiveResponse+0)
      #14 pc 0000000000311044  /apex/com.android.art/lib64/libart.so (art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame*, art::JValue*)+372) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #15 pc 00000000002ce00c  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<true>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+2028) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #16 pc 0000000000715dc0  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>(art::interpreter::SwitchImplContext*)+13680) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #17 pc 0000000000319e68  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #18 pc 000000000003754c  /data/app/~~xKi0Qn6PkJyvb9_FxXQBMA==/pl.lebihan.authnkey.uaftest-jMXCHtCmcl4MCHMCxnMeyw==/base.apk (offset 0xf15000) (pl.lebihan.authnkey.UsbTransport.sendRaw+0)
      #19 pc 0000000000894c24  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+2596) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #20 pc 0000000000712d80  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>(art::interpreter::SwitchImplContext*)+1328) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #21 pc 0000000000319e68  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #22 pc 0000000000037294  /data/app/~~xKi0Qn6PkJyvb9_FxXQBMA==/pl.lebihan.authnkey.uaftest-jMXCHtCmcl4MCHMCxnMeyw==/base.apk (offset 0xf15000) (pl.lebihan.authnkey.UsbTransport.access$sendRaw+0)
      #23 pc 0000000000894c24  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+2596) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #24 pc 0000000000712978  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>(art::interpreter::SwitchImplContext*)+296) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #25 pc 0000000000319e68  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #26 pc 00000000000371cc  /data/app/~~xKi0Qn6PkJyvb9_FxXQBMA==/pl.lebihan.authnkey.uaftest-jMXCHtCmcl4MCHMCxnMeyw==/base.apk (offset 0xf15000) (pl.lebihan.authnkey.UsbTransport$sendCtapCommand$2.invokeSuspend+0)
      #27 pc 0000000000894c24  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+2596) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #28 pc 0000000000712a9c  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>(art::interpreter::SwitchImplContext*)+588) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #29 pc 0000000000319e68  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 4fa5ef6a5e4548134bc26bfce4c994a5)
      #30 pc 00000000000371a0  /data/app/~~xKi0Qn6PkJyvb9_FxXQBMA==/pl.lebihan.authnkey.uaftest-jMXCHtCmcl4MCHMCxnMeyw==/base.apk (offset 0xf15000) (pl.lebihan.authnkey.UsbTransport$sendCtapCommand$2.invoke+0)
      ... frames #31-#99 elided, coroutine dispatch and thread startup ...

Memory tags around the fault address (0xf00c65e3004b2f0), one tag per 16 bytes:
      0xc65e3004aa00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004ab00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004ac00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004ad00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004ae00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004af00: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004b000: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004b100: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
    =>0xc65e3004b200: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0 [0]
      0xc65e3004b300: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004b400: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004b500: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004b600: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004b700: 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
      0xc65e3004b800: 0  0  0  0  0  0  0  0  e  e  e  e  0  0  0  0
      0xc65e3004b900: 0  0  0  0  2  2  2  2  9  9  9  9  a  a  a  a

@mimi89999

Copy link
Copy Markdown
Owner

Sorry. I meant cancel() and then requestWait() like in UsbRequest.transfer(). If the lock is the only viable solution, maybe let's acquire it in UsbRequest.transfer(). Also, I think that maybe @Volatile could be used instead of AtomicBoolean.

@amaanq

amaanq commented Aug 5, 2026

Copy link
Copy Markdown
Author

Sorry. I meant cancel() and then requestWait() like in UsbRequest.transfer().

I just tried this as well, and it still crashes unfortunately. @Volatile is fine with me, though we lose compareAndSet.

Closing a transport mid-transfer frees the native `usb_device` while the
transfer is still using it, and neither `bulkTransfer` nor `requestWait` checks
that the connection is still open once it has read the pointer. I hit this on my
phone with MTE enabled, where I ran into a SIGSEGV as soon as the freed struct
was read.

Each transfer now runs under a lock, and `close()` cancels the in-flight one
before waiting on that same lock, so everything is released by the time
`close()` returns.

Co-authored-by: mimi89999 <michel@lebihan.pl>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants