// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Implementation file for the sandbox2::Client class.

#include "sandboxed_api/sandbox2/client.h"

#include <fcntl.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
#include <syscall.h>
#include <unistd.h>

#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <utility>

#include "absl/base/internal/raw_logging.h"
#include "absl/base/attributes.h"
#include "absl/base/macros.h"
#include "absl/memory/memory.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/sanitizer.h"
#include "sandboxed_api/sandbox2/util/strerror.h"
#include "sandboxed_api/util/raw_logging.h"

namespace sandbox2 {

constexpr uint32_t Client::kClient2SandboxReady;
constexpr uint32_t Client::kSandbox2ClientDone;
constexpr const char* Client::kFDMapEnvVar;

Client::Client(Comms* comms) : comms_(comms) {
  char* fdmap_envvar = getenv(kFDMapEnvVar);
  if (!fdmap_envvar) {
    return;
  }
  std::map<absl::string_view, absl::string_view> vars =
      absl::StrSplit(fdmap_envvar, ',', absl::SkipEmpty());
  for (const auto& var : vars) {
    int fd;
    SAPI_RAW_CHECK(absl::SimpleAtoi(var.second, &fd), "failed to parse fd map");
    SAPI_RAW_CHECK(fd_map_.emplace(std::string{var.first}, fd).second,
                   "could not insert mapping into fd map  (duplicate)");
  }
  unsetenv(kFDMapEnvVar);
}

std::string Client::GetFdMapEnvVar() const {
  return absl::StrCat(kFDMapEnvVar, "=",
                      absl::StrJoin(fd_map_, ",", absl::PairFormatter(",")));
}

void Client::PrepareEnvironment() {
  SetUpIPC();
  SetUpCwd();
}

void Client::EnableSandbox() {
  ReceivePolicy();
  ApplyPolicyAndBecomeTracee();
}

void Client::SandboxMeHere() {
  PrepareEnvironment();
  EnableSandbox();
}

void Client::SetUpCwd() {
  {
    // Get the current working directory to check if we are in a mount
    // namespace.
    // Note: glibc 2.27 no longer returns a relative path in that case, but
    //       fails with ENOENT and returns a nullptr instead. The code still
    //       needs to run on lower version for the time being.
    char cwd_buf[PATH_MAX + 1] = {0};
    char* cwd = getcwd(cwd_buf, ABSL_ARRAYSIZE(cwd_buf));
    SAPI_RAW_PCHECK(cwd != nullptr || errno == ENOENT,
                    "no current working directory");

    // Outside of the mount namespace, the path is of the form
    // '(unreachable)/...'. Only check for the slash, since Linux might make up
    // other prefixes in the future.
    if (errno == ENOENT || cwd_buf[0] != '/') {
      SAPI_RAW_VLOG(1, "chdir into mount namespace, cwd was '%s'", cwd_buf);
      // If we are in a mount namespace but fail to chdir, then it can lead to a
      // sandbox escape -- we need to fail with FATAL if the chdir fails.
      SAPI_RAW_PCHECK(chdir("/") != -1, "corrective chdir");
    }
  }

  // Receive the user-supplied current working directory and change into it.
  std::string cwd;
  SAPI_RAW_CHECK(comms_->RecvString(&cwd), "receiving working directory");
  if (!cwd.empty()) {
    // On the other hand this chdir can fail without a sandbox escape. It will
    // probably not have the intended behavior though.
    if (chdir(cwd.c_str()) == -1) {
      SAPI_RAW_VLOG(
          1,
          "chdir(%s) failed, falling back to previous cwd or / (with "
          "namespaces). Use Executor::SetCwd() to set a working directory: %s",
          cwd, StrError(errno));
    }
  }
}

void Client::SetUpIPC() {
  uint32_t num_of_fd_pairs;
  SAPI_RAW_CHECK(comms_->RecvUint32(&num_of_fd_pairs),
                 "receiving number of fd pairs");
  SAPI_RAW_CHECK(fd_map_.empty(), "fd map not empty");

  SAPI_RAW_VLOG(1, "Will receive %d file descriptor pairs", num_of_fd_pairs);

  for (uint32_t i = 0; i < num_of_fd_pairs; ++i) {
    int32_t requested_fd;
    int32_t fd;
    std::string name;

    SAPI_RAW_CHECK(comms_->RecvInt32(&requested_fd), "receiving requested fd");
    SAPI_RAW_CHECK(comms_->RecvFD(&fd), "receiving current fd");
    SAPI_RAW_CHECK(comms_->RecvString(&name), "receiving name string");

    if (requested_fd != -1 && fd != requested_fd) {
      if (requested_fd > STDERR_FILENO && fcntl(requested_fd, F_GETFD) != -1) {
        // Dup2 will silently close the FD if one is already at requested_fd.
        // If someone is using the deferred sandbox entry, ie. SandboxMeHere,
        // the application might have something actually using that fd.
        // Therefore let's log a big warning if that FD is already in use.
        // Note: this check doesn't happen for STDIN,STDOUT,STDERR.
        SAPI_RAW_LOG(
            WARNING,
            "Cloning received fd %d over %d which is already open and will "
            "be silently closed. This may lead to unexpected behavior!",
            fd, requested_fd);
      }

      SAPI_RAW_VLOG(1, "Cloning received fd=%d onto fd=%d", fd, requested_fd);
      SAPI_RAW_PCHECK(dup2(fd, requested_fd) != -1, "");

      // Close the newly received FD if it differs from the new one.
      close(fd);
      fd = requested_fd;
    }

    if (!name.empty()) {
      SAPI_RAW_CHECK(fd_map_.emplace(name, fd).second, "duplicate fd mapping");
    }
  }
}

void Client::ReceivePolicy() {
  std::vector<uint8_t> bytes;
  SAPI_RAW_CHECK(comms_->RecvBytes(&bytes), "receive bytes");
  policy_len_ = bytes.size();

  policy_ = absl::make_unique<uint8_t[]>(policy_len_);
  memcpy(policy_.get(), bytes.data(), policy_len_);
}

void Client::ApplyPolicyAndBecomeTracee() {
  // When running under TSAN, we need to notify TSANs background thread that we
  // want it to exit and wait for it to be done. When not running under TSAN,
  // this function does nothing.
  sanitizer::WaitForTsan();

  // Creds can be received w/o synchronization, once the connection is
  // established.
  pid_t cred_pid;
  uid_t cred_uid ABSL_ATTRIBUTE_UNUSED;
  gid_t cred_gid ABSL_ATTRIBUTE_UNUSED;
  SAPI_RAW_CHECK(comms_->RecvCreds(&cred_pid, &cred_uid, &cred_gid),
                 "receiving credentials");

  SAPI_RAW_CHECK(prctl(PR_SET_DUMPABLE, 1) == 0,
                 "setting PR_SET_DUMPABLE flag");
  if (prctl(PR_SET_PTRACER, cred_pid) == -1) {
    SAPI_RAW_VLOG(1, "No YAMA on this system. Continuing");
  }

  SAPI_RAW_CHECK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0,
                 "setting PR_SET_NO_NEW_PRIVS flag");
  SAPI_RAW_CHECK(prctl(PR_SET_KEEPCAPS, 0) == 0,
                 "setting PR_SET_KEEPCAPS flag");

  sock_fprog prog;
  prog.len = static_cast<uint16_t>(policy_len_ / sizeof(sock_filter));
  prog.filter = reinterpret_cast<sock_filter*>(policy_.get());

  SAPI_RAW_VLOG(
      1, "Applying policy in PID %d, sock_fprog.len: %hd entries (%d bytes)",
      syscall(__NR_gettid), prog.len, policy_len_);

  // Signal executor we are ready to have limits applied on us and be ptraced.
  // We want limits at the last moment to avoid triggering them too early and we
  // want ptrace at the last moment to avoid synchronization deadlocks.
  SAPI_RAW_CHECK(comms_->SendUint32(kClient2SandboxReady),
                 "receiving ready signal from executor");
  uint32_t ret;  // wait for confirmation
  SAPI_RAW_CHECK(comms_->RecvUint32(&ret),
                 "receving confirmation from executor");
  SAPI_RAW_CHECK(ret == kSandbox2ClientDone,
                 "invalid confirmation from executor");

  SAPI_RAW_CHECK(
      syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC,
              reinterpret_cast<uintptr_t>(&prog)) == 0,
      "setting SECCOMP_FILTER_FLAG_TSYNC flag");
}

int Client::GetMappedFD(const std::string& name) {
  auto it = fd_map_.find(name);
  SAPI_RAW_CHECK(it != fd_map_.end(),
                 "mapped fd not found (function called twice?)");
  int fd = it->second;
  fd_map_.erase(it);
  return fd;
}

bool Client::HasMappedFD(const std::string& name) {
  return fd_map_.find(name) != fd_map_.end();
}

void Client::SendLogsToSupervisor() {
  // This LogSink will register itself and send all logs to the executor until
  // the object is destroyed.
  logsink_ = absl::make_unique<LogSink>(GetMappedFD(LogSink::kLogFDName));
}

NetworkProxyClient* Client::GetNetworkProxyClient() {
  if (proxy_client_ == nullptr) {
    proxy_client_ = absl::make_unique<NetworkProxyClient>(
        GetMappedFD(NetworkProxyClient::kFDName));
  }
  return proxy_client_.get();
}

absl::Status Client::InstallNetworkProxyHandler() {
  if (fd_map_.find(NetworkProxyClient::kFDName) == fd_map_.end()) {
    return absl::FailedPreconditionError(
        "InstallNetworkProxyHandler() must be called at most once after the "
        "sandbox is installed. Also, the NetworkProxyServer needs to be "
        "enabled.");
  }
  return NetworkProxyHandler::InstallNetworkProxyHandler(
      GetNetworkProxyClient());
}

}  // namespace sandbox2