// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/341324165): Fix and remove.
#pragma allow_unsafe_buffers
#endif

#include "content/browser/sandbox_ipc_linux.h"

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

#include "base/command_line.h"
#include "base/files/scoped_file.h"
#include "base/linux_util.h"
#include "base/logging.h"
#include "base/memory/platform_shared_memory_region.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/unix_domain_socket.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "content/public/common/content_switches.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, sizeof(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";
    }
  }
  if (fds.empty())
    return;

  base::Pickle pickle = base::Pickle::WithUnownedBuffer(
      base::span(buf, base::checked_cast<size_t>(len)));
  base::PickleIterator iter(pickle);

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

  // Give sandbox first shot at request, if it is not handled, then
  // false is returned and we continue on.
  if (sandbox::HandleInterceptedCall(kind, fd, iter, fds))
    return;

  if (kind ==
      sandbox::policy::SandboxLinux::METHOD_MAKE_SHARED_MEMORY_SEGMENT) {
    HandleMakeSharedMemorySegment(fd, iter, fds);
    return;
  }
  NOTREACHED();
}

void SandboxIPCHandler::HandleMakeSharedMemorySegment(
    int fd,
    base::PickleIterator iter,
    const std::vector<base::ScopedFD>& fds) {
  uint32_t size;
  if (!iter.ReadUInt32(&size))
    return;
  // TODO(crbug.com/41470149): executable shared memory should be removed when
  // NaCl is unshipped.
  bool executable;
  if (!iter.ReadBool(&executable))
    return;
  base::ScopedFD shm_fd;
  if (executable) {
    shm_fd =
        base::subtle::PlatformSharedMemoryRegion::ExecutableRegion::CreateFD(
            size);
  } else {
    base::subtle::PlatformSharedMemoryRegion region =
        base::subtle::PlatformSharedMemoryRegion::CreateUnsafe(size);
    shm_fd = std::move(region.PassPlatformHandle().fd);
  }
  base::Pickle reply;
  SendRendererReply(fds, reply, shm_fd.get());
  // shm_fd will close the handle which is no longer needed by this process.
}

void SandboxIPCHandler::SendRendererReply(
    const std::vector<base::ScopedFD>& fds,
    const base::Pickle& reply,
    int reply_fd) {
  struct msghdr msg;
  memset(&msg, 0, sizeof(msg));
  struct iovec iov = {const_cast<uint8_t*>(reply.data()), reply.size()};
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;

  char control_buffer[CMSG_SPACE(sizeof(reply_fd))];

  if (reply_fd != -1) {
    struct stat st;
    if (fstat(reply_fd, &st) == 0 && S_ISDIR(st.st_mode)) {
      LOG(FATAL) << "Tried to send a directory descriptor over sandbox IPC";
      // We must never send directory descriptors to a sandboxed process
      // because they can use openat with ".." elements in the path in order
      // to escape the sandbox and reach the real filesystem.
    }

    struct cmsghdr* cmsg;
    msg.msg_control = control_buffer;
    msg.msg_controllen = sizeof(control_buffer);
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(reply_fd));
    memcpy(CMSG_DATA(cmsg), &reply_fd, sizeof(reply_fd));
    msg.msg_controllen = cmsg->cmsg_len;
  }

  if (HANDLE_EINTR(sendmsg(fds[0].get(), &msg, MSG_DONTWAIT)) < 0)
    PLOG(ERROR) << "sendmsg";
}

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

}  // namespace content