diff options
| -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 add645dee718..b48fe419cca5 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. @@ -411,19 +423,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, @@ -453,30 +458,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. @@ -488,13 +519,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)); |