Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a2966c02 authored by Martijn Coenen's avatar Martijn Coenen Committed by Automerger Merge Worker
Browse files

Merge "Verify UID of incoming Zygote connections." into sc-dev am: eca6d61e am: 4019f500

parents 3123a733 4019f500
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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;
    }

+53 −28
Original line number Diff line number Diff line
@@ -341,6 +341,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.
@@ -398,18 +410,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("ForkMany 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,
@@ -439,22 +445,48 @@ 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));
      }
      int new_fd = -1;
      do {
        // 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));
        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) {
        // 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.
@@ -474,13 +506,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));