// 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
//
//     https://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 of the sandbox2::Executor class

#include "sandboxed_api/sandbox2/executor.h"

#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>

#include <algorithm>
#include <cerrno>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "sandboxed_api/config.h"
#include "sandboxed_api/sandbox2/fork_client.h"
#include "sandboxed_api/sandbox2/forkserver.pb.h"
#include "sandboxed_api/sandbox2/global_forkclient.h"
#include "sandboxed_api/sandbox2/ipc.h"
#include "sandboxed_api/sandbox2/util.h"
#include "sandboxed_api/util/fileops.h"
#include "sandboxed_api/util/raw_logging.h"

namespace sandbox2 {

namespace file_util = ::sapi::file_util;

namespace {
void DisableCompressStackDepot(ForkRequest& request) {
  auto disable_compress_stack_depot = [&request](absl::string_view sanitizer) {
    auto prefix = absl::StrCat(sanitizer, "_OPTIONS=");
    auto it = std::find_if(request.mutable_envs()->begin(),
                           request.mutable_envs()->end(),
                           [&prefix](const std::string& env) {
                             return absl::StartsWith(env, prefix);
                           });
    constexpr absl::string_view option = "compress_stack_depot=0";
    if (it != request.mutable_envs()->end()) {
      // If it's already there, the last value will be used.
      absl::StrAppend(&*it, ":", option);
      return;
    }
    request.add_envs(absl::StrCat(prefix, option));
  };
  if constexpr (sapi::sanitizers::IsASan()) {
    disable_compress_stack_depot("ASAN");
  }
  if constexpr (sapi::sanitizers::IsMSan()) {
    disable_compress_stack_depot("MSAN");
  }
  if constexpr (sapi::sanitizers::IsLSan()) {
    disable_compress_stack_depot("LSAN");
  }
  if constexpr (sapi::sanitizers::IsHwASan()) {
    disable_compress_stack_depot("HWSAN");
  }
  if constexpr (sapi::sanitizers::IsTSan()) {
    disable_compress_stack_depot("TSAN");
  }
}
}  // namespace

std::vector<std::string> Executor::CopyEnviron() {
  return util::CharPtrArray(environ).ToStringVector();
}

absl::StatusOr<SandboxeeProcess> Executor::StartSubProcess(int32_t clone_flags,
                                                           const Namespace* ns,
                                                           MonitorType type) {
  if (started_) {
    return absl::FailedPreconditionError(
        "This executor has already been started");
  }

  if (!path_.empty()) {
    exec_fd_ = file_util::fileops::FDCloser(open(path_.c_str(), O_PATH));
    if (exec_fd_.get() < 0) {
      if (errno == ENOENT) {
        return absl::ErrnoToStatus(errno, path_);
      }
      return absl::ErrnoToStatus(errno,
                                 absl::StrCat("Could not open file ", path_));
    }
  }

  if (libunwind_sbox_for_pid_ != 0) {
    VLOG(1) << "StartSubProcces, starting libunwind";
  } else if (exec_fd_.get() < 0) {
    VLOG(1) << "StartSubProcess, with [Fork-Server]";
  } else if (!path_.empty()) {
    VLOG(1) << "StartSubProcess, with file " << path_;
  } else {
    VLOG(1) << "StartSubProcess, with fd " << exec_fd_.get();
  }

  ForkRequest request;
  *request.mutable_args() = {argv_.begin(), argv_.end()};
  *request.mutable_envs() = {envp_.begin(), envp_.end()};

  // Add LD_ORIGIN_PATH to envs, as it'll make the amount of syscalls invoked by
  // ld.so smaller.
  if (!path_.empty()) {
    request.add_envs(absl::StrCat("LD_ORIGIN_PATH=",
                                  file_util::fileops::StripBasename(path_)));
  }

  // Disable optimization to avoid related syscalls.
  if constexpr (sapi::sanitizers::IsAny()) {
    DisableCompressStackDepot(request);
  }

  // If neither the path, nor exec_fd is specified, just assume that we need to
  // send a fork request.
  //
  // Otherwise, it's either sandboxing pre- or post-execve with the global
  // Fork-Server.
  if (exec_fd_.get() == -1) {
    request.set_mode(FORKSERVER_FORK);
  } else if (enable_sandboxing_pre_execve_) {
    request.set_mode(FORKSERVER_FORK_EXECVE_SANDBOX);
  } else {
    request.set_mode(FORKSERVER_FORK_EXECVE);
  }

  if (ns) {
    clone_flags |= ns->clone_flags();
    *request.mutable_mount_tree() = ns->mounts().GetMountTree();
    request.set_hostname(ns->hostname());
    request.set_allow_mount_propagation(ns->allow_mount_propagation());
  }

  request.set_clone_flags(clone_flags);
  request.set_monitor_type(type);

  SandboxeeProcess process;

  if (fork_client_) {
    process = fork_client_->SendRequest(request, exec_fd_.get(),
                                        client_comms_fd_.get());
  } else {
    process = GlobalForkClient::SendRequest(request, exec_fd_.get(),
                                            client_comms_fd_.get());
  }

  started_ = true;

  client_comms_fd_.Close();
  exec_fd_.Close();

  VLOG(1) << "StartSubProcess returned with: " << process.main_pid;
  return process;
}

std::unique_ptr<ForkClient> Executor::StartForkServer() {
  // This flag is set explicitly to 'true' during object instantiation, and
  // custom fork-servers should never be sandboxed.
  set_enable_sandbox_before_exec(false);
  absl::StatusOr<SandboxeeProcess> process = StartSubProcess(0);
  if (!process.ok()) {
    return nullptr;
  }
  return std::make_unique<ForkClient>(process->main_pid, ipc_.comms());
}

void Executor::SetUpServerSideCommsFd() {
  int sv[2];
  if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
    PLOG(FATAL) << "socketpair(AF_UNIX, SOCK_STREAM) failed";
  }

  client_comms_fd_ = file_util::fileops::FDCloser(sv[0]);
  ipc_.SetUpServerSideComms(sv[1]);
}

}  // namespace sandbox2
