// Copyright 2015 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 <fcntl.h>
#include <stdint.h>
#include <sys/stat.h>
#include <unistd.h>

#include "base/process/kill.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "sandbox/mac/sandbox_compiler.h"
#include "sandbox/mac/seatbelt.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"

namespace sandbox {

class SandboxMacCompilerTest : public base::MultiProcessTest {};

MULTIPROCESS_TEST_MAIN(BasicProfileProcess) {
  std::string profile =
      "(version 1)"
      "(deny default (with no-log))"
      "(allow file-read* file-write* (literal \"/\"))";

  SandboxCompiler compiler(profile);

  std::string error;
  CHECK(compiler.CompileAndApplyProfile(&error));

  return 0;
}

TEST_F(SandboxMacCompilerTest, BasicProfileTest) {
  base::Process process = SpawnChild("BasicProfileProcess");
  ASSERT_TRUE(process.IsValid());
  int exit_code = 42;
  EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                             &exit_code));
  EXPECT_EQ(exit_code, 0);
}

MULTIPROCESS_TEST_MAIN(BasicProfileWithParamProcess) {
  std::string profile =
      "(version 1)"
      "(deny default (with no-log))"
      "(allow file-read* file-write* (literal (param \"DIR\")))";

  SandboxCompiler compiler(profile);
  CHECK(compiler.InsertStringParam("DIR", "/"));

  std::string error;
  CHECK(compiler.CompileAndApplyProfile(&error));

  return 0;
}

TEST_F(SandboxMacCompilerTest, BasicProfileTestWithParam) {
  base::Process process = SpawnChild("BasicProfileWithParamProcess");
  ASSERT_TRUE(process.IsValid());
  int exit_code = 42;
  EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                             &exit_code));
  EXPECT_EQ(exit_code, 0);
}

MULTIPROCESS_TEST_MAIN(ProfileFunctionalProcess) {
  std::string profile =
      "(version 1)"
      "(deny default (with no-log))"
      "(allow file-read-data file-read-metadata (literal \"/dev/urandom\"))";

  SandboxCompiler compiler(profile);

  std::string error;
  CHECK(compiler.CompileAndApplyProfile(&error));

  // The profile compiled and applied successfully, now try and read 1 byte from
  // /dev/urandom.
  uint8_t byte;
  int fd = open("/dev/urandom", O_RDONLY);
  CHECK_NE(fd, -1);

  EXPECT_TRUE(read(fd, &byte, sizeof(byte)) == sizeof(byte));

  return 0;
}

TEST_F(SandboxMacCompilerTest, ProfileFunctionalityTest) {
  base::Process process = SpawnChild("ProfileFunctionalProcess");
  ASSERT_TRUE(process.IsValid());
  int exit_code = 42;
  EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                             &exit_code));
  EXPECT_EQ(exit_code, 0);
}

MULTIPROCESS_TEST_MAIN(ProfileFunctionalTestWithParamsProcess) {
  std::string profile =
      "(version 1)"
      "(deny default (with no-log))"
      "(if (string=? (param \"ALLOW_FILE\") \"TRUE\")"
      "    (allow file-read-data file-read-metadata (literal (param "
      "\"URANDOM\"))))";

  SandboxCompiler compiler(profile);

  CHECK(compiler.InsertBooleanParam("ALLOW_FILE", true));
  CHECK(compiler.InsertStringParam("URANDOM", "/dev/urandom"));

  std::string error;
  CHECK(compiler.CompileAndApplyProfile(&error));

  // The profile compiled and applied successfully, now try and read 1 byte from
  // /dev/urandom.
  uint8_t byte;
  int fd = open("/dev/urandom", O_RDONLY);
  CHECK_NE(fd, -1);

  EXPECT_TRUE(read(fd, &byte, sizeof(byte)) == sizeof(byte));

  // Make sure the sandbox isn't overly permissive.
  struct stat st;
  EXPECT_EQ(stat("/", &st), -1);

  return 0;
}

TEST_F(SandboxMacCompilerTest, ProfileFunctionalityTestWithParams) {
  base::Process process = SpawnChild("ProfileFunctionalTestWithParamsProcess");
  ASSERT_TRUE(process.IsValid());
  int exit_code = 42;
  EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                             &exit_code));
  EXPECT_EQ(exit_code, 0);
}

MULTIPROCESS_TEST_MAIN(ProfileFunctionalityTestErrorProcess) {
  std::string profile = "(+ 5 a)";

  SandboxCompiler compiler(profile);

  // Make sure that this invalid profile results in an error returned.
  std::string error;
  CHECK_EQ(error, "");
  CHECK(!compiler.CompileAndApplyProfile(&error));
  CHECK_NE(error, "");

  return 0;
}

TEST_F(SandboxMacCompilerTest, ProfileFunctionalityTestError) {
  base::Process process = SpawnChild("ProfileFunctionalityTestErrorProcess");
  ASSERT_TRUE(process.IsValid());
  int exit_code = 42;
  EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                             &exit_code));
  EXPECT_EQ(exit_code, 0);
}

MULTIPROCESS_TEST_MAIN(SandboxCheckTestProcess) {
  CHECK(!Seatbelt::IsSandboxed());
  std::string profile =
      "(version 1)"
      "(deny default (with no-log))";

  SandboxCompiler compiler(profile);
  std::string error;
  CHECK(compiler.CompileAndApplyProfile(&error));
  CHECK(Seatbelt::IsSandboxed());

  return 0;
}

TEST_F(SandboxMacCompilerTest, SandboxCheckTest) {
  base::Process process = SpawnChild("SandboxCheckTestProcess");
  ASSERT_TRUE(process.IsValid());
  int exit_code = 42;
  EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                             &exit_code));
  EXPECT_EQ(exit_code, 0);
}

}  // namespace sandbox
