// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sandbox/linux/services/credentials.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <sys/capability.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <memory>
#include <vector>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "sandbox/linux/services/proc_util.h"
#include "sandbox/linux/services/syscall_wrappers.h"
#include "sandbox/linux/system_headers/capability.h"
#include "sandbox/linux/tests/unit_tests.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace sandbox {

namespace {

struct CapFreeDeleter {
  inline void operator()(cap_t cap) const {
    int ret = cap_free(cap);
    CHECK_EQ(0, ret);
  }
};

// Wrapper to manage libcap2's cap_t type.
typedef std::unique_ptr<typeof(*((cap_t)0)), CapFreeDeleter> ScopedCap;

bool WorkingDirectoryIsRoot() {
  char current_dir[PATH_MAX];
  char* cwd = getcwd(current_dir, sizeof(current_dir));
  PCHECK(cwd);
  if (strcmp("/", cwd)) return false;

  // The current directory is the root. Add a few paranoid checks.
  struct stat current;
  CHECK_EQ(0, stat(".", &current));
  struct stat parrent;
  CHECK_EQ(0, stat("..", &parrent));
  CHECK_EQ(current.st_dev, parrent.st_dev);
  CHECK_EQ(current.st_ino, parrent.st_ino);
  CHECK_EQ(current.st_mode, parrent.st_mode);
  CHECK_EQ(current.st_uid, parrent.st_uid);
  CHECK_EQ(current.st_gid, parrent.st_gid);
  return true;
}

SANDBOX_TEST(Credentials, DropAllCaps) {
  CHECK(Credentials::DropAllCapabilities());
  CHECK(!Credentials::HasAnyCapability());
}

SANDBOX_TEST(Credentials, MoveToNewUserNS) {
  CHECK(Credentials::DropAllCapabilities());
  bool moved_to_new_ns = Credentials::MoveToNewUserNS();
  fprintf(stdout,
          "Unprivileged CLONE_NEWUSER supported: %s\n",
          moved_to_new_ns ? "true." : "false.");
  fflush(stdout);
  if (!moved_to_new_ns) {
    fprintf(stdout, "This kernel does not support unprivileged namespaces. "
            "USERNS tests will succeed without running.\n");
    fflush(stdout);
    return;
  }
  CHECK(Credentials::HasAnyCapability());
  CHECK(Credentials::DropAllCapabilities());
  CHECK(!Credentials::HasAnyCapability());
}

SANDBOX_TEST(Credentials, CanCreateProcessInNewUserNS) {
  CHECK(Credentials::DropAllCapabilities());
  bool user_ns_supported = Credentials::CanCreateProcessInNewUserNS();
  bool moved_to_new_ns = Credentials::MoveToNewUserNS();
  CHECK_EQ(user_ns_supported, moved_to_new_ns);
}

SANDBOX_TEST(Credentials, UidIsPreserved) {
  CHECK(Credentials::DropAllCapabilities());
  uid_t old_ruid, old_euid, old_suid;
  gid_t old_rgid, old_egid, old_sgid;
  PCHECK(0 == getresuid(&old_ruid, &old_euid, &old_suid));
  PCHECK(0 == getresgid(&old_rgid, &old_egid, &old_sgid));
  // Probably missing kernel support.
  if (!Credentials::MoveToNewUserNS()) return;
  uid_t new_ruid, new_euid, new_suid;
  PCHECK(0 == getresuid(&new_ruid, &new_euid, &new_suid));
  CHECK(old_ruid == new_ruid);
  CHECK(old_euid == new_euid);
  CHECK(old_suid == new_suid);

  gid_t new_rgid, new_egid, new_sgid;
  PCHECK(0 == getresgid(&new_rgid, &new_egid, &new_sgid));
  CHECK(old_rgid == new_rgid);
  CHECK(old_egid == new_egid);
  CHECK(old_sgid == new_sgid);
}

bool NewUserNSCycle() {
  if (!Credentials::MoveToNewUserNS() ||
      !Credentials::HasAnyCapability() ||
      !Credentials::DropAllCapabilities() ||
      Credentials::HasAnyCapability()) {
    return false;
  }
  return true;
}

SANDBOX_TEST(Credentials, NestedUserNS) {
  CHECK(Credentials::DropAllCapabilities());
  // Probably missing kernel support.
  if (!Credentials::MoveToNewUserNS()) return;
  CHECK(Credentials::DropAllCapabilities());
  // As of 3.12, the kernel has a limit of 32. See create_user_ns().
  const int kNestLevel = 10;
  for (int i = 0; i < kNestLevel; ++i) {
    CHECK(NewUserNSCycle()) << "Creating new user NS failed at iteration "
                                  << i << ".";
  }
}

// Test the WorkingDirectoryIsRoot() helper.
SANDBOX_TEST(Credentials, CanDetectRoot) {
  PCHECK(0 == chdir("/proc/"));
  CHECK(!WorkingDirectoryIsRoot());
  PCHECK(0 == chdir("/"));
  CHECK(WorkingDirectoryIsRoot());
}

// Disabled on ASAN because of crbug.com/451603.
SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(DropFileSystemAccessIsSafe)) {
  CHECK(Credentials::HasFileSystemAccess());
  CHECK(Credentials::DropAllCapabilities());
  // Probably missing kernel support.
  if (!Credentials::MoveToNewUserNS()) return;
  CHECK(Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get()));
  CHECK(!Credentials::HasFileSystemAccess());
  CHECK(WorkingDirectoryIsRoot());
  CHECK(base::IsDirectoryEmpty(base::FilePath("/")));
  // We want the chroot to never have a subdirectory. A subdirectory
  // could allow a chroot escape.
  CHECK_NE(0, mkdir("/test", 0700));
}

// Check that after dropping filesystem access and dropping privileges
// it is not possible to regain capabilities.
SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(CannotRegainPrivileges)) {
  base::ScopedFD proc_fd(ProcUtil::OpenProc());
  CHECK(Credentials::DropAllCapabilities(proc_fd.get()));
  // Probably missing kernel support.
  if (!Credentials::MoveToNewUserNS()) return;
  CHECK(Credentials::DropFileSystemAccess(proc_fd.get()));
  CHECK(Credentials::DropAllCapabilities(proc_fd.get()));

  // The kernel should now prevent us from regaining capabilities because we
  // are in a chroot.
  CHECK(!Credentials::CanCreateProcessInNewUserNS());
  CHECK(!Credentials::MoveToNewUserNS());
}

SANDBOX_TEST(Credentials, SetCapabilities) {
  // Probably missing kernel support.
  if (!Credentials::MoveToNewUserNS())
    return;

  base::ScopedFD proc_fd(ProcUtil::OpenProc());

  CHECK(Credentials::HasCapability(Credentials::Capability::SYS_ADMIN));
  CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT));

  std::vector<Credentials::Capability> caps;
  caps.push_back(Credentials::Capability::SYS_CHROOT);
  CHECK(Credentials::SetCapabilities(proc_fd.get(), caps));

  CHECK(!Credentials::HasCapability(Credentials::Capability::SYS_ADMIN));
  CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT));

  const std::vector<Credentials::Capability> no_caps;
  CHECK(Credentials::SetCapabilities(proc_fd.get(), no_caps));
  CHECK(!Credentials::HasAnyCapability());
}

SANDBOX_TEST(Credentials, SetCapabilitiesAndChroot) {
  // Probably missing kernel support.
  if (!Credentials::MoveToNewUserNS())
    return;

  base::ScopedFD proc_fd(ProcUtil::OpenProc());

  CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT));
  PCHECK(chroot("/") == 0);

  std::vector<Credentials::Capability> caps;
  caps.push_back(Credentials::Capability::SYS_CHROOT);
  CHECK(Credentials::SetCapabilities(proc_fd.get(), caps));
  PCHECK(chroot("/") == 0);

  CHECK(Credentials::DropAllCapabilities());
  PCHECK(chroot("/") == -1 && errno == EPERM);
}

SANDBOX_TEST(Credentials, SetCapabilitiesMatchesLibCap2) {
  // Probably missing kernel support.
  if (!Credentials::MoveToNewUserNS())
    return;

  base::ScopedFD proc_fd(ProcUtil::OpenProc());

  std::vector<Credentials::Capability> caps;
  caps.push_back(Credentials::Capability::SYS_CHROOT);
  CHECK(Credentials::SetCapabilities(proc_fd.get(), caps));

  ScopedCap actual_cap(cap_get_proc());
  PCHECK(actual_cap != nullptr);

  ScopedCap expected_cap(cap_init());
  PCHECK(expected_cap != nullptr);

  const cap_value_t allowed_cap = CAP_SYS_CHROOT;
  for (const cap_flag_t flag : {CAP_EFFECTIVE, CAP_PERMITTED}) {
    PCHECK(cap_set_flag(expected_cap.get(), flag, 1, &allowed_cap, CAP_SET) ==
           0);
  }

  CHECK_EQ(0, cap_compare(expected_cap.get(), actual_cap.get()));
}

volatile sig_atomic_t signal_handler_called;
void SignalHandler(int sig) {
  signal_handler_called = 1;
}

// glibc (and some other libcs) caches the PID and TID in TLS. This test
// verifies that these values are correct after DropFilesystemAccess.
// Disabled on ASAN because of crbug.com/451603.
SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(DropFileSystemAccessPreservesTLS)) {
  // Probably missing kernel support.
  if (!Credentials::MoveToNewUserNS()) return;
  CHECK(Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get()));

  // The libc getpid implementation may return a cached PID. Ensure that
  // it matches the PID returned from the getpid system call.
  CHECK_EQ(sys_getpid(), getpid());

  // raise uses the cached TID in glibc.
  struct sigaction action = {};
  action.sa_handler = &SignalHandler;
  PCHECK(sigaction(SIGUSR1, &action, nullptr) == 0);

  PCHECK(raise(SIGUSR1) == 0);
  CHECK_EQ(1, signal_handler_called);
}

}  // namespace.

}  // namespace sandbox.
