diff options
author | Martijn Coenen <maco@google.com> | 2024-02-29 12:03:05 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-05-14 05:40:29 +0000 |
commit | 6fa8602a120947e6b1713345b4fb6251dc9bcbfd (patch) | |
tree | ec6282a2d8df300847517c589ede83192912a83c | |
parent | 23265610e3906b160736a49b10bd389485fdb80c (diff) |
Verify UID of incoming Zygote connections.
Only the system UID should be allowed to connect to the Zygote. While
for generic Zygotes this is also covered by SELinux policy, this is not
true for App Zygotes: the preload code running in an app zygote could
connect to another app zygote socket, if it had access to its (random)
socket address.
On the Java layer, simply check the UID when the connection is made. In
the native layer, this check was already present, but it actually didn't
work in the case where we receive a new incoming connection on the
socket, and receive a 'non-fork' command: in that case, we will simply
exit the native loop, and let the Java layer handle the command, without
any further UID checking.
Modified the native logic to drop new connections with a mismatching
UID, and to keep serving the existing connection (if it was still
there).
Bug: 319081336
Test: manual
(cherry picked from commit 2ffc7cb220e4220b7e108c4043a3f0f2a85b6508)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e4b3ba817073b66ee37da8f1aba93b345309b435)
Merged-In: I3f85a17107849e2cd3e82d6ef15c90b9e2f26532
Change-Id: I3f85a17107849e2cd3e82d6ef15c90b9e2f26532
-rw-r--r-- | core/java/com/android/internal/os/ZygoteConnection.java | 3 | ||||
-rw-r--r-- | core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp | 82 |
2 files changed, 56 insertions, 29 deletions
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 993e4e7b4b3d..765901a043a0 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -93,6 +93,9 @@ class ZygoteConnection { throw ex; } + if (peer.getUid() != Process.SYSTEM_UID) { + throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote."); + } isEof = false; } diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp index 2b5b8f7a108e..b586b9c27dd8 100644 --- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp +++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp @@ -353,6 +353,18 @@ jstring com_android_internal_os_ZygoteCommandBuffer_nativeNextArg(JNIEnv* env, j return result; } +static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) { + struct ucred credentials; + socklen_t cred_size = sizeof credentials; + if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1 + || cred_size != sizeof credentials) { + fail_fn(CREATE_ERROR("Failed to get socket credentials, %s", + strerror(errno))); + } + + return credentials.uid; +} + // Read all lines from the current command into the buffer, and then reset the buffer, so // we will start reading again at the beginning of the command, starting with the argument // count. And we don't need access to the fd to do so. @@ -412,19 +424,12 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( fail_fn_z("Failed to retrieve session socket timeout"); } - struct ucred credentials; - socklen_t cred_size = sizeof credentials; - if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1 - || cred_size != sizeof credentials) { - fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s", - strerror(errno))); + uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1); + if (peerUid != static_cast<uid_t>(expected_uid)) { + return JNI_FALSE; } - bool first_time = true; do { - if (credentials.uid != expected_uid) { - return JNI_FALSE; - } n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n); n_buffer->reset(); int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds, @@ -454,30 +459,56 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( // Clear buffer and get count from next command. n_buffer->clear(); for (;;) { + bool valid_session_socket = true; // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect. int poll_res = TEMP_FAILURE_RETRY(poll(fd_structs, 2, -1 /* infinite timeout */)); if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) { if (n_buffer->getCount(fail_fn_z) != 0) { break; - } // else disconnected; + } else { + // Session socket was disconnected + valid_session_socket = false; + close(session_socket); + } } else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) { fail_fn_z( CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res)); } - // We've now seen either a disconnect or connect request. - close(session_socket); - int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr)); + int new_fd = -1; + do { + // We've now seen either a disconnect or connect request. + new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr)); + if (new_fd == -1) { + fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno))); + } + uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1); + if (newPeerUid != static_cast<uid_t>(expected_uid)) { + ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid); + close(new_fd); + new_fd = -1; + } else { + // If we still have a valid session socket, close it now + if (valid_session_socket) { + close(session_socket); + } + valid_session_socket = true; + } + } while (!valid_session_socket); + + // At this point we either have a valid new connection (new_fd > 0), or + // an existing session socket we can poll on if (new_fd == -1) { - fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno))); + // The new connection wasn't valid, and we still have an old one; retry polling + continue; } if (new_fd != session_socket) { - // Move new_fd back to the old value, so that we don't have to change Java-level data - // structures to reflect a change. This implicitly closes the old one. - if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) { - fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s", - new_fd, session_socket, strerror(errno))); - } - close(new_fd); // On Linux, fd is closed even if EINTR is returned. + // Move new_fd back to the old value, so that we don't have to change Java-level data + // structures to reflect a change. This implicitly closes the old one. + if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) { + fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s", + new_fd, session_socket, strerror(errno))); + } + close(new_fd); // On Linux, fd is closed even if EINTR is returned. } // If we ever return, we effectively reuse the old Java ZygoteConnection. // None of its state needs to change. @@ -489,13 +520,6 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s", session_socket, strerror(errno))); } - if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) { - fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno))); - } - if (cred_size != sizeof credentials) { - fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d", - cred_size, static_cast<int>(sizeof credentials))); - } } first_time = false; } while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n)); |