CVE-2026-28604 AI-generated analysis
platform/packages/modules/adb, adbd daemon TLS authentication (daemon/auth.cpp, transport.cpp)
Reworks adbd's adbd_auth_tls_handshake so the detached TLS handshake thread no longer holds a raw atransport pointer. The thread now captures a weak-pointer callback id plus the transport's connection object, and re-enters the fdevent looper to check that the transport is still alive before touching transport state. The fix also splits FdConnection::DoTlsHandshake into host and daemon variants and adds a 3-second socket timeout to the daemon handshake.
Root cause
In daemon/auth.cpp, adbd_auth_tls_handshake(atransport* t) spawned a detached std::thread that captured the raw pointer t. The thread blocked in t->connection()->DoTlsHandshake(rsa_pkey, &auth_key), with no socket timeout on the daemon side. Afterwards it dereferenced t directly from the background thread (t->IsTcpDevice(), t->auth_key = ..., adbd_wifi_secure_connect(t), adbd_auth_verified(t), adbd_notify_framework_connected_key(t), t->Kick()). Nothing verified that the transport still existed, and the transport state changes happened off the fdevent looper thread. If the transport was destroyed while the handshake was pending, the thread could use freed memory. The existing transport_to_callback_arg/transport_from_callback_arg weak-pointer mechanism in the same file was not used for this path.
How the fix works
- Commit e96f6790c785 (Change-Id Ic419a1fb..., applied first, daemon/auth.cpp 05f51801 -> 0be3f995) documents the global 'transports' map (uint32_t id -> weak_ptr<atransport>) and the transport-to-callback flow. It adds fdevent_check_looper() to transport_to_callback_arg() and transport_from_callback_arg(). It also rewrites transport_from_callback_arg() to use transports->extract(id_u32), so the entry is always removed from the map on lookup. Before, find() was followed by erase(it) only on the success path. The function returns nullptr when the node is empty or the weak_ptr is dead.
- Commit c7995e05cc7b (Change-Id I8ed271ab..., daemon/auth.cpp 0be3f995 -> afcf6da1), daemon/auth.cpp:adbd_auth_tls_handshake: before spawning the thread, the function computes void* arg = transport_to_callback_arg(t) and auto connection = t->connection(). The detached thread captures only [arg, connection] and runs bool connected = connection->DoTlsHandshake(rsa_pkey, &auth_key). It then posts a lambda via fdevent_run_on_looper. On the looper, the lambda calls transport_from_callback_arg(arg). If that returns nullptr, the result is dropped, with a log if the handshake had succeeded. If !connected, it calls t->Kick(). Otherwise it runs the original success path: IsTcpDevice -> set t->auth_key and call adbd_wifi_secure_connect(t); else adbd_auth_verified(t) and adbd_notify_framework_connected_key(t), now annotated as likely dead code.
- Same commit, transport.cpp:FdConnection::DoTlsHandshake: the single function with internal #if ADB_HOST blocks is split into two whole-function variants. The ADB_HOST client variant keeps the same logic. The daemon variant (Role::Server, adbd_tls_verify_cert, adbd_tls_client_ca_list) now sets SO_RCVTIMEO and SO_SNDTIMEO to 3 seconds on fd_ via adb_setsockopt before tls_->DoHandshake(); setsockopt failures are only PLOG'd. It resets both timeouts to 0 afterwards. On failure, tls_ is reset and false is returned, as before.
- On Android 17 the same commit also inserts CHECK_NOT_LOOPER_THREAD() at the start of both DoTlsHandshake variants. This is not in the 14/15/16 diffs.
Completeness
- Covers the daemon-side adbd_auth_tls_handshake path: the raw pointer capture is removed, and all transport dereferences move to the fdevent looper behind a weak_ptr liveness check.
- The handshake socket timeout is added only to the non-ADB_HOST (daemon) variant of FdConnection::DoTlsHandshake. The host client variant has no timeout.
- If adb_setsockopt fails when installing the timeout, the failure is logged and the handshake proceeds without a timeout.
- Per index search (android-14.0.0_r75), the host-side client/auth.cpp:adb_auth_tls_handshake still has the original shape: a detached thread capturing raw atransport* t, then t->connection()->DoTlsHandshake and t->Kick(). These commits do not touch it.
Behavior changes
- The daemon TLS handshake socket now has 3-second SO_RCVTIMEO/SO_SNDTIMEO timeouts during tls_->DoHandshake(), cleared back to 0 afterwards. A failed handshake leads to t->Kick() on the looper.
- Handshake results that arrive after the transport was destroyed are discarded, with LOG(ERROR) 'TLS handshake succeeded for deleted transport, ignoring' when the handshake succeeded.
- Transport state updates and Kick() after the handshake now run on the fdevent looper thread instead of the handshake thread.
- transport_to_callback_arg/transport_from_callback_arg now assert that they run on the fdevent thread (fdevent_check_looper()). Map entries are always removed on lookup via extract().
- Android 17 only: DoTlsHandshake asserts CHECK_NOT_LOOPER_THREAD().
Per major
- Android 14: same fix: both commits (e96f6790c785 hardening, then c7995e05cc7b UAF fix) are in android-security-14.0.0_r29. The hardening uses fdevent_check_looper(), while the commit message names CHECK_LOOPER_THREAD(). The resolver reports 7/8 patched-region lines present.
- Android 15: same fix: 119250e41944 in android-security-15.0.0_r17. The auth.cpp hunk is identical to Android 14 (LOG(INFO) auth_key line); transport.cpp line offsets differ slightly. The resolver reports 7/8 lines present.
- Android 16: same fix: f487f473c82d in android-security-16.0.0_r8, with identical logic; the context line uses VLOG(AUTH) instead of LOG(INFO). The resolver reports 7/8 lines present.
- Android 17: adapted fix: 12700491ebb3 in android-security-17.0.0_r1. Same auth.cpp change (VLOG(AUTH) context), plus CHECK_NOT_LOOPER_THREAD() at the top of both DoTlsHandshake variants. The resolver reports 7/8 lines present.
Unknowns
- The exact trigger sequence, and whether the bulletin's RCE rating comes from this UAF alone, are not shown in the material.
- Whether the hardening commit (Ic419a1fb) was applied on Android 15/16/17, and in what form; only the Android 14 diff of that commit is provided.
- The return type of atransport::connection(), and therefore whether the captured connection keeps the connection object alive during the handshake.
- Which line of the patched region accounts for the 7/8 match on each tag.
- Whether TlsConnection::DoHandshake returns a failure when the socket timeout fires.