CVE-2024-34720 AI-generated analysis
platform/frameworks/base, Zygote (core/java/com/android/internal/os/ZygoteConnection.java, core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp)
Adds a peer-UID check (must be Process.SYSTEM_UID) when a Zygote connection is created in Java, and reworks the native nativeForkRepeatedly loop so that newly accepted connections are UID-checked immediately and dropped on mismatch instead of being handed, unchecked, back to the Java command handler.
Root cause
The Java ZygoteConnection constructor read the peer credentials but did not verify the UID. For generic Zygotes SELinux policy also restricts access, but App Zygotes are not covered: per the commit message, preload code running in one app zygote could connect to another app zygote's (randomly named) socket. In the native layer, com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly did fetch SO_PEERCRED, but the comparison against expected_uid was only at the top of the do/while loop. When a new connection was accepted inside the inner for(;;) loop, credentials were re-read at the end of that loop but the UID was only compared if the next command was a simple fork command (loop condition isSimpleForkCommand); for a non-fork command the function returned to Java, which then processed the command with no UID check. The old code also unconditionally closed the existing session socket before accept(), so a bad new connection cost the existing one.
How the fix works
- ZygoteConnection.java:ZygoteConnection(LocalSocket, String): after obtaining peer credentials via mSocket.getPeerCredentials(), throws ZygoteSecurityException("Only system UID is allowed to connect to Zygote.") if peer.getUid() != Process.SYSTEM_UID.
- com_android_internal_os_ZygoteCommandBuffer.cpp: adds static helper getSocketPeerUid(int socket, fail_fn) that wraps getsockopt(SOL_SOCKET, SO_PEERCRED), validates cred_size, and returns credentials.uid; it replaces the two inline getsockopt blocks.
- com_android_internal_os_ZygoteCommandBuffer.cpp:nativeForkRepeatedly: the initial UID check is moved out of the do/while body to before the loop (peerUid = getSocketPeerUid(session_socket, fail_fn_1); return JNI_FALSE on mismatch), so it runs once for the initial connection.
- com_android_internal_os_ZygoteCommandBuffer.cpp:nativeForkRepeatedly inner for(;;): introduces valid_session_socket; the session socket is now closed only when the poll shows it disconnected (getCount == 0), not unconditionally before accept(). A new do/while accepts a connection, calls getSocketPeerUid(new_fd, ...) and compares to expected_uid: on mismatch it logs ALOGW("Dropping new connection with a mismatched uid"), closes new_fd and sets new_fd = -1, looping again only if there is no valid session socket; on match it closes the old session socket if still valid. If new_fd == -1 after the loop (rejected connection, existing session still valid) it does `continue` to poll again instead of returning to Java. The trailing SO_PEERCRED re-read at the end of the loop is removed because the check now happens at accept time.
Completeness
- Covers both places visible in the diff: the Java ZygoteConnection constructor and the native nativeForkRepeatedly path that accepts replacement connections inside the native loop.
- The native check compares against the expected_uid parameter passed from Java, whose origin is not shown in the material.
- Only the UID is checked; no GID or SELinux-context check is added (SELinux coverage for generic zygotes is stated in the commit message, not in the diff).
- In the native path, a rejected new connection when the existing session is already gone loops accepting again; a rejected new connection when the session is still valid returns to polling. Nothing in the material shows other accept paths in this file.
Behavior changes
- Connections from any UID other than Process.SYSTEM_UID now cause ZygoteSecurityException to be thrown from the ZygoteConnection constructor.
- nativeForkRepeatedly now returns JNI_FALSE before entering the loop if the initial session socket peer UID mismatches, and drops (closes, logs ALOGW) mismatched newly accepted connections while continuing to serve the existing connection if it is still open.
- The existing session socket is no longer closed before accept() unless it was detected as disconnected, so a rejected connection no longer tears down a valid session.
- Credential fetch errors now go through getSocketPeerUid with a different error message text (fail_fn_1 is used for both the initial and newly accepted sockets).
Per major
- Android 14: same fix: commit 7905ffaa89e7 in android-security-14.0.0_r10 (cherry-pick of 2ffc7cb220e4); at android-security-14.0.0_r29 the patched region is present (7/8 lines matched) and the Java UID check is visible in ZygoteConnection.java lines 96-98.
- Android 15: not affected per the bulletin; 7/8 lines of the patched region are present at android-security-15.0.0_r17.
- Android 16: not affected per the bulletin; 7/8 lines of the patched region are present at android-security-16.0.0_r8.
- Android 17: not affected per the bulletin; 7/8 lines of the patched region are present at android-security-17.0.0_r1.
Unknowns
- How the ZygoteSecurityException thrown from the constructor is handled by the caller (ZygoteServer accept path) and whether it affects the zygote's own lifetime is not shown.
- Which code paths and Zygote types construct ZygoteConnection is not shown.
- Where expected_uid passed to nativeForkRepeatedly originates and whether it is always SYSTEM_UID is not shown.
- The 1 of 8 non-matching lines in the patched region at the latest tags is not identified in the material.