// Copyright 2014 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 <errno.h>
#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_util.h"
#include "base/sys_info.h"
#include "sandbox/linux/services/scoped_process.h"
#include "sandbox/linux/services/yama.h"
#include "sandbox/linux/tests/unit_tests.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace sandbox {

namespace {

bool HasLinux32Bug() {
#if defined(__i386__)
  // On 3.2 kernels, yama doesn't work for 32-bit binaries on 64-bit kernels.
  // This is fixed in 3.4.
  bool is_kernel_64bit =
      base::SysInfo::OperatingSystemArchitecture() == "x86_64";
  bool is_linux = base::SysInfo::OperatingSystemName() == "Linux";
  bool is_3_dot_2 = base::StartsWith(
      base::SysInfo::OperatingSystemVersion(), "3.2",
      base::CompareCase::INSENSITIVE_ASCII);
  if (is_kernel_64bit && is_linux && is_3_dot_2)
    return true;
#endif  // defined(__i386__)
  return false;
}

bool CanPtrace(pid_t pid) {
  int ret;
  ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
  if (ret == -1) {
    CHECK_EQ(EPERM, errno);
    return false;
  }
  // Wait for the process to be stopped so that it can be detached.
  siginfo_t process_info;
  int wait_ret = HANDLE_EINTR(waitid(P_PID, pid, &process_info, WSTOPPED));
  PCHECK(0 == wait_ret);
  PCHECK(0 == ptrace(PTRACE_DETACH, pid, NULL, NULL));
  return true;
}

// _exit(0) if pid can be ptraced by the current process.
// _exit(1) otherwise.
void ExitZeroIfCanPtrace(pid_t pid) {
  if (CanPtrace(pid)) {
    _exit(0);
  } else {
    _exit(1);
  }
}

bool CanSubProcessPtrace(pid_t pid) {
  ScopedProcess process(base::Bind(&ExitZeroIfCanPtrace, pid));
  bool signaled;
  int exit_code = process.WaitForExit(&signaled);
  CHECK(!signaled);
  return 0 == exit_code;
}

// The tests below assume that the system-level configuration will not change
// while they run.

TEST(Yama, GetStatus) {
  int status1 = Yama::GetStatus();

  // Check that the value is a possible bitmask.
  ASSERT_LE(0, status1);
  ASSERT_GE(Yama::STATUS_KNOWN | Yama::STATUS_PRESENT | Yama::STATUS_ENFORCING |
                Yama::STATUS_STRICT_ENFORCING,
            status1);

  // The status should not just be a random value.
  int status2 = Yama::GetStatus();
  EXPECT_EQ(status1, status2);

  // This test is not running sandboxed, there is no reason to not know the
  // status.
  EXPECT_NE(0, Yama::STATUS_KNOWN & status1);

  if (status1 & Yama::STATUS_STRICT_ENFORCING) {
    // If Yama is strictly enforcing, it is also enforcing.
    EXPECT_TRUE(status1 & Yama::STATUS_ENFORCING);
  }

  if (status1 & Yama::STATUS_ENFORCING) {
    // If Yama is enforcing, Yama is present.
    EXPECT_NE(0, status1 & Yama::STATUS_PRESENT);
  }

  // Verify that the helper functions work as intended.
  EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_ENFORCING),
            Yama::IsEnforcing());
  EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_PRESENT),
            Yama::IsPresent());

  fprintf(stdout,
          "Yama present: %s - enforcing: %s\n",
          Yama::IsPresent() ? "Y" : "N",
          Yama::IsEnforcing() ? "Y" : "N");
}

SANDBOX_TEST(Yama, RestrictPtraceSucceedsWhenYamaPresent) {
  // This call will succeed iff Yama is present.
  bool restricted = Yama::RestrictPtracersToAncestors();
  CHECK_EQ(restricted, Yama::IsPresent());
}

// Attempts to enable or disable Yama restrictions.
void SetYamaRestrictions(bool enable_restriction) {
  if (enable_restriction) {
    Yama::RestrictPtracersToAncestors();
  } else {
    Yama::DisableYamaRestrictions();
  }
}

TEST(Yama, RestrictPtraceWorks) {
  if (HasLinux32Bug())
    return;

  ScopedProcess process1(base::Bind(&SetYamaRestrictions, true));
  ASSERT_TRUE(process1.WaitForClosureToRun());

  if (Yama::IsEnforcing()) {
    // A sibling process cannot ptrace process1.
    ASSERT_FALSE(CanSubProcessPtrace(process1.GetPid()));
  }

  if (!(Yama::GetStatus() & Yama::STATUS_STRICT_ENFORCING)) {
    // However, parent can ptrace process1.
    ASSERT_TRUE(CanPtrace(process1.GetPid()));

    // A sibling can ptrace process2 which disables any Yama protection.
    ScopedProcess process2(base::Bind(&SetYamaRestrictions, false));
    ASSERT_TRUE(process2.WaitForClosureToRun());
    ASSERT_TRUE(CanSubProcessPtrace(process2.GetPid()));
  }
}

SANDBOX_TEST(Yama, RestrictPtraceIsDefault) {
  if (!Yama::IsPresent() || HasLinux32Bug())
    return;

  CHECK(Yama::DisableYamaRestrictions());
  ScopedProcess process1(base::Bind(&base::DoNothing));

  if (Yama::IsEnforcing()) {
    // Check that process1 is protected by Yama, even though it has
    // been created from a process that disabled Yama.
    CHECK(!CanSubProcessPtrace(process1.GetPid()));
  }
}

}  // namespace

}  // namespace sandbox
