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

#include "sandboxed_api/sandbox2/namespace.h"

#include <unistd.h>

#include <cstdint>
#include <cstdlib>
#include <initializer_list>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/log/check.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "sandboxed_api/sandbox2/allow_all_syscalls.h"
#include "sandboxed_api/sandbox2/executor.h"
#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/result.h"
#include "sandboxed_api/sandbox2/sandbox2.h"
#include "sandboxed_api/testing.h"
#include "sandboxed_api/util/fileops.h"
#include "sandboxed_api/util/status_matchers.h"
#include "sandboxed_api/util/temp_file.h"

namespace sandbox2 {
namespace {

namespace file_util = ::sapi::file_util;
using ::sapi::CreateDefaultPermissiveTestPolicy;
using ::sapi::CreateNamedTempFile;
using ::sapi::GetTestSourcePath;
using ::sapi::GetTestTempPath;
using ::testing::AllOf;
using ::testing::AnyOfArray;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Matcher;
using ::testing::Ne;
using ::testing::SizeIs;
using ::testing::StartsWith;
using ::testing::StrEq;  // sapi::google3-only(broken matchers)

std::string GetTestcaseBinPath(absl::string_view bin_name) {
  return GetTestSourcePath(absl::StrCat("sandbox2/testcases/", bin_name));
}

std::vector<std::string> RunSandboxeeWithArgsAndPolicy(
    const std::string& bin_path, std::initializer_list<std::string> args,
    std::unique_ptr<Policy> policy = nullptr) {
  if (!policy) {
    policy = CreateDefaultPermissiveTestPolicy(bin_path).BuildOrDie();
  }
  Sandbox2 sandbox(std::make_unique<Executor>(bin_path, args),
                   std::move(policy));

  CHECK(sandbox.RunAsync());
  Comms* comms = sandbox.comms();
  uint64_t num;

  std::vector<std::string> entries;
  if (comms->RecvUint64(&num)) {
    entries.reserve(num);
    for (int i = 0; i < num; ++i) {
      std::string entry;
      CHECK(comms->RecvString(&entry));
      entries.push_back(std::move(entry));
    }
  }
  Result result = sandbox.AwaitResult();
  EXPECT_THAT(result.final_status(), Eq(Result::OK));
  EXPECT_THAT(result.reason_code(), Eq(0));
  return entries;
}

TEST(NamespaceTest, FileNamespaceWorks) {
  // Mount /binary_path RO and check that it exists and is readable.
  // /etc/passwd should not exist.

  const std::string path = GetTestcaseBinPath("namespace");
  SAPI_ASSERT_OK_AND_ASSIGN(auto policy, CreateDefaultPermissiveTestPolicy(path)
                                             .AddFileAt(path, "/binary_path")
                                             .TryBuild());
  std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
      path, {path, "0", "/binary_path", "/etc/passwd"}, std::move(policy));
  EXPECT_THAT(result, ElementsAre("/binary_path"));
}

TEST(NamespaceTest, ReadOnlyIsRespected) {
  // Mount temporary file as RO and check that it actually is RO.
  auto [name, fd] = CreateNamedTempFile(GetTestTempPath("temp_file")).value();
  file_util::fileops::FDCloser temp_closer(fd);

  const std::string path = GetTestcaseBinPath("namespace");
  {
    SAPI_ASSERT_OK_AND_ASSIGN(auto policy,
                              CreateDefaultPermissiveTestPolicy(path)
                                  .AddFileAt(name, "/temp_file")
                                  .TryBuild());
    // Check that it is readable
    std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
        path, {path, "0", "/temp_file"}, std::move(policy));
    EXPECT_THAT(result, ElementsAre("/temp_file"));
  }
  {
    SAPI_ASSERT_OK_AND_ASSIGN(auto policy,
                              CreateDefaultPermissiveTestPolicy(path)
                                  .AddFileAt(name, "/temp_file")
                                  .TryBuild());
    // Now check that it is not writeable
    std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
        path, {path, "1", "/temp_file"}, std::move(policy));
    EXPECT_THAT(result, IsEmpty());
  }
}

TEST(NamespaceTest, UserNamespaceWorks) {
  const std::string path = GetTestcaseBinPath("namespace");

  // Check that getpid() returns 2 (which is the case inside pid NS).
  {
    std::vector<std::string> result =
        RunSandboxeeWithArgsAndPolicy(path, {path, "2"});
    EXPECT_THAT(result, ElementsAre("2"));
  }

  // Validate that getpid() does not return 2 when outside of a pid NS.
  {
    std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
        path, {path, "2"},
        PolicyBuilder()
            .DisableNamespaces()
            .DefaultAction(AllowAllSyscalls())  // Do not restrict syscalls
            .BuildOrDie());
    EXPECT_THAT(result, ElementsAre(Ne("2")));
  }
}

TEST(NamespaceTest, UserNamespaceIDMapWritten) {
  // Check that the idmap is initialized before the sandbox application is
  // started.
  const std::string path = GetTestcaseBinPath("namespace");
  {
    std::vector<std::string> result =
        RunSandboxeeWithArgsAndPolicy(path, {path, "3", "1000", "1000"});
    EXPECT_THAT(result, ElementsAre("1000", "1000"));
  }

  // Check that the uid/gid is the same when not using namespaces.
  {
    std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
        path, {path, "3"},
        PolicyBuilder()
            .DisableNamespaces()
            .DefaultAction(AllowAllSyscalls())  // Do not restrict syscalls
            .BuildOrDie());
    EXPECT_THAT(result,
                ElementsAre(absl::StrCat(getuid()), absl::StrCat(getgid())));
  }
}

TEST(NamespaceTest, RootReadOnly) {
  // Mount rw tmpfs at /tmp and check it is RW.
  // Check also that / is RO.
  const std::string path = GetTestcaseBinPath("namespace");
  SAPI_ASSERT_OK_AND_ASSIGN(
      auto policy, CreateDefaultPermissiveTestPolicy(path)
                       .AddTmpfs("/tmp", /*size=*/4ULL << 20 /* 4 MiB */)
                       .TryBuild());
  std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
      path, {path, "4", "/tmp/testfile", "/testfile"}, std::move(policy));
  EXPECT_THAT(result, ElementsAre("/tmp/testfile"));
}

TEST(NamespaceTest, RootWritable) {
  // Mount root rw and check it
  const std::string path = GetTestcaseBinPath("namespace");
  SAPI_ASSERT_OK_AND_ASSIGN(
      auto policy,
      CreateDefaultPermissiveTestPolicy(path).SetRootWritable().TryBuild());
  std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
      path, {path, "4", "/testfile"}, std::move(policy));
  EXPECT_THAT(result, ElementsAre("/testfile"));
}

TEST(NamespaceTest, HostnameNone) {
  const std::string path = GetTestcaseBinPath("namespace");
  std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
      path, {path, "7"},
      PolicyBuilder()
          .DisableNamespaces()
          .DefaultAction(AllowAllSyscalls())  // Do not restrict syscalls
          .BuildOrDie());
  EXPECT_THAT(result, ElementsAre(Ne("sandbox2")));
}

TEST(NamespaceTest, HostnameDefault) {
  const std::string path = GetTestcaseBinPath("namespace");
  std::vector<std::string> result =
      RunSandboxeeWithArgsAndPolicy(path, {path, "7"});
  EXPECT_THAT(result, ElementsAre("sandbox2"));
}

TEST(NamespaceTest, HostnameConfigured) {
  const std::string path = GetTestcaseBinPath("namespace");
  SAPI_ASSERT_OK_AND_ASSIGN(auto policy, CreateDefaultPermissiveTestPolicy(path)
                                             .SetHostname("configured")
                                             .TryBuild());
  std::vector<std::string> result =
      RunSandboxeeWithArgsAndPolicy(path, {path, "7"}, std::move(policy));
  EXPECT_THAT(result, ElementsAre("configured"));
}

TEST(NamespaceTest, TestInterfacesNoNetwork) {
  const std::string path = GetTestcaseBinPath("namespace");
  std::vector<std::string> result =
      RunSandboxeeWithArgsAndPolicy(path, {path, "5"});
  // Only loopback network interface 'lo'.
  EXPECT_THAT(result, ElementsAre("lo"));
}

TEST(NamespaceTest, TestInterfacesWithNetwork) {
  const std::string path = GetTestcaseBinPath("namespace");
  SAPI_ASSERT_OK_AND_ASSIGN(auto policy, CreateDefaultPermissiveTestPolicy(path)
                                             .AllowUnrestrictedNetworking()
                                             .TryBuild());

  std::vector<std::string> result =
      RunSandboxeeWithArgsAndPolicy(path, {path, "5"}, std::move(policy));
  // Loopback network interface 'lo' and more.
  EXPECT_THAT(result, Contains("lo"));
  EXPECT_THAT(result, SizeIs(Gt(1)));
}

TEST(NamespaceTest, TestFiles) {
  SKIP_ANDROID;
  const std::string path = GetTestcaseBinPath("namespace");
  std::vector<std::string> result =
      RunSandboxeeWithArgsAndPolicy(path, {path, "6", "/"});

  std::vector<Matcher<std::string>> lib_paths = {
      StartsWith("/lib/"),  // Often a symlink -> /usr/lib
      StartsWith("/usr/lib/"),
      StartsWith("/lib64/"),  // Often a symlink -> /usr/lib64
      StartsWith("/usr/lib64/")};
  auto correct_lib_path_matcher =
      AllOf(HasSubstr(".so"), AnyOfArray(lib_paths));
  std::vector<Matcher<std::string>> matchers = {
      correct_lib_path_matcher,
      // Conditionally mapped if Tomoyo is active
      StrEq(absl::StrCat("/dev/fd/", Comms::kSandbox2TargetExecFD)),
      // System ldconfig cache
      StrEq("/etc/ld.so.cache"),
      // GRTE ldconfig cache
      StrEq("/usr/grte/v4/etc/ld.so.cache"),
      StrEq("/usr/grte/v5/etc/ld.so.cache"),
      // procfs and sysfs
      StartsWith("/proc"), StartsWith("/sys")};
  // Coverage DIR
  char* coverage_dir = getenv("COVERAGE_DIR");
  if (coverage_dir != nullptr) {
    matchers.push_back(StartsWith(coverage_dir));
  }
  for (const auto& file : result) {
    EXPECT_THAT(file, AnyOfArray(matchers));
  }
}

}  // namespace
}  // namespace sandbox2
