// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/sandbox_ipc_linux.h"

#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/stat.h>

#include "base/containers/span.h"
#include "base/files/scoped_file.h"
#include "base/linux_util.h"
#include "base/logging.h"
#include "base/pickle.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/unix_domain_socket.h"
#include "sandbox/linux/services/libc_interceptor.h"
#include "sandbox/policy/linux/sandbox_linux.h"

namespace content {

const size_t kMaxSandboxIPCMessagePayloadSize = 64;

// static
SandboxIPCHandler::SandboxIPCHandler(int lifeline_fd, int browser_socket)
    : lifeline_fd_(lifeline_fd), browser_socket_(browser_socket) {}

void SandboxIPCHandler::Run() {
  struct pollfd pfds[2];
  pfds[0].fd = lifeline_fd_;
  pfds[0].events = POLLIN;
  pfds[1].fd = browser_socket_;
  pfds[1].events = POLLIN;

  int failed_polls = 0;
  for (;;) {
    const int r =
        HANDLE_EINTR(poll(pfds, std::size(pfds), -1 /* no timeout */));
    // '0' is not a possible return value with no timeout.
    DCHECK_NE(0, r);
    if (r < 0) {
      PLOG(WARNING) << "poll";
      if (failed_polls++ == 3) {
        LOG(FATAL) << "poll(2) failing. SandboxIPCHandler aborting.";
      }
      continue;
    }

    failed_polls = 0;

    // The browser process will close the other end of this pipe on shutdown,
    // so we should exit.
    if (pfds[0].revents) {
      break;
    }

    // If poll(2) reports an error condition in this fd,
    // we assume the zygote is gone and we exit the loop.
    if (pfds[1].revents & (POLLERR | POLLHUP)) {
      break;
    }

    if (pfds[1].revents & POLLIN) {
      HandleRequestFromChild(browser_socket_);
    }
  }

  VLOG(1) << "SandboxIPCHandler stopping.";
}

void SandboxIPCHandler::HandleRequestFromChild(int fd) {
  std::vector<base::ScopedFD> fds;

  // A FontConfigIPC::METHOD_MATCH message could be kMaxFontFamilyLength
  // bytes long (this is the largest message type).
  // The size limit  used to be FontConfigIPC::kMaxFontFamilyLength which was
  // 2048, but we do not receive FontConfig IPC here anymore. The only payloads
  // here are sandbox::policy::SandboxLinux::METHOD_MAKE_SHARED_MEMORY_SEGMENT
  // and HandleLocalTime from libc_interceptor for which
  // kMaxSandboxIPCMessagePayloadSize set to 64 should be plenty.
  // 128 bytes padding are necessary so recvmsg() does not return MSG_TRUNC
  // error for a maximum length message.
  uint8_t buf[kMaxSandboxIPCMessagePayloadSize + 128];

  const ssize_t len = base::UnixDomainSocket::RecvMsg(fd, buf, &fds);
  if (len == -1) {
    // TODO: should send an error reply, or the sender might block forever.
    if (errno == EMSGSIZE) {
      NOTREACHED() << "Sandbox host message is larger than "
                      "kMaxSandboxIPCMessagePayloadSize";
    } else {
      // TODO(pbos): Consider implementing PNOTREACHED() instead of using PCHECK
      // here.
      PCHECK(false) << "Recvmsg failed";
    }
  }
  // Note that FDs may be exhausted in this process, in which case len == 0. No
  // op here. The sender should get EOF; see
  // UnixDomainSocket::SendRecvMsgWithFlags.
  if (fds.empty())
    return;

  base::PickleIterator iter = base::PickleIterator::WithData(
      base::span(buf).first(base::checked_cast<size_t>(len)));

  int kind;
  if (!iter.ReadInt(&kind))
    return;

  CHECK(sandbox::HandleInterceptedCall(kind, fd, iter, fds));
}

SandboxIPCHandler::~SandboxIPCHandler() {
  if (IGNORE_EINTR(close(lifeline_fd_)) < 0)
    PLOG(ERROR) << "close";
  if (IGNORE_EINTR(close(browser_socket_)) < 0)
    PLOG(ERROR) << "close";
}

}  // namespace content
