/*
 * Copyright (C) 2021 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <gtest/gtest.h>
#include <stdio.h>
#include <sys/file.h>
#include <string>

#if defined(__BIONIC__)

#include "android-base/file.h"
#include "android-base/silent_death_test.h"
#include "android-base/test_utils.h"
#include "gwp_asan/options.h"
#include "platform/bionic/malloc.h"
#include "sys/system_properties.h"
#include "utils.h"

using gwp_asan_integration_DeathTest = SilentDeathTest;

// basename is a mess, use gnu basename explicitly to avoid the need for string
// mutation.
extern "C" const char* __gnu_basename(const char* path);

// GWP-ASan tests can run much slower, especially when combined with HWASan.
// Triple the deadline to avoid flakes (b/238585984).
extern "C" bool GetInitialArgs(const char*** args, size_t* num_args) {
  static const char* initial_args[] = {"--deadline_threshold_ms=270000"};
  *args = initial_args;
  *num_args = 1;
  return true;
}

// This file implements "torture testing" under GWP-ASan, where we sample every
// single allocation. The upper limit for the number of GWP-ASan allocations in
// the torture mode is is generally 40,000, so that svelte devices don't
// explode, as this uses ~163MiB RAM (4KiB per live allocation).
TEST(gwp_asan_integration, malloc_tests_under_torture) {
  // Do not override HWASan with GWP ASan.
  SKIP_WITH_HWASAN;

  RunGwpAsanTest("malloc.*:-malloc.mallinfo*");
}

class SyspropRestorer {
 private:
  std::vector<std::pair<std::string, std::string>> props_to_restore_;
  // System properties are global for a device, so the tests that mutate the
  // GWP-ASan system properties must be run mutually exclusive. Because
  // bionic-unit-tests is run in an isolated gtest fashion (each test is run in
  // its own process), we have to use flocks to synchronise between tests.
  int flock_fd_;

 public:
  SyspropRestorer() {
    std::string path = testing::internal::GetArgvs()[0];
    flock_fd_ = open(path.c_str(), O_RDONLY);
    EXPECT_NE(flock_fd_, -1) << "failed to open self for a flock";
    EXPECT_NE(flock(flock_fd_, LOCK_EX), -1) << "failed to flock myself";

    const char* basename = __gnu_basename(path.c_str());
    std::vector<std::string> props = {
        std::string("libc.debug.gwp_asan.sample_rate.") + basename,
        std::string("libc.debug.gwp_asan.process_sampling.") + basename,
        std::string("libc.debug.gwp_asan.max_allocs.") + basename,
        "libc.debug.gwp_asan.sample_rate.system_default",
        "libc.debug.gwp_asan.sample_rate.app_default",
        "libc.debug.gwp_asan.process_sampling.system_default",
        "libc.debug.gwp_asan.process_sampling.app_default",
        "libc.debug.gwp_asan.max_allocs.system_default",
        "libc.debug.gwp_asan.max_allocs.app_default",
    };

    size_t base_props_size = props.size();
    for (size_t i = 0; i < base_props_size; ++i) {
      props.push_back("persist." + props[i]);
    }

    std::string reset_log;

    for (const std::string& prop : props) {
      std::string value = GetSysprop(prop);
      props_to_restore_.emplace_back(prop, value);
      if (!value.empty()) {
        __system_property_set(prop.c_str(), "");
      }
    }
  }

  ~SyspropRestorer() {
    for (const auto& kv : props_to_restore_) {
      if (kv.second != GetSysprop(kv.first)) {
        __system_property_set(kv.first.c_str(), kv.second.c_str());
      }
    }
    close(flock_fd_);
  }

  static std::string GetSysprop(const std::string& name) {
    std::string value;
    const prop_info* pi = __system_property_find(name.c_str());
    if (pi == nullptr) return value;
    __system_property_read_callback(
        pi,
        [](void* cookie, const char* /* name */, const char* value, uint32_t /* serial */) {
          std::string* v = static_cast<std::string*>(cookie);
          *v = value;
        },
        &value);
    return value;
  }
};

TEST_F(gwp_asan_integration_DeathTest, DISABLED_assert_gwp_asan_enabled) {
  std::string maps;
  EXPECT_TRUE(android::base::ReadFileToString("/proc/self/maps", &maps));
  EXPECT_TRUE(maps.find("GWP-ASan") != std::string::npos) << maps;

  volatile int* x = new int;
  delete x;
  EXPECT_DEATH({ *x = 7; }, "");
}

// A weaker version of the above tests, only checking that GWP-ASan is enabled
// for any pointer, not *our* pointer. This allows us to test the system_default
// sysprops without potentially OOM-ing other random processes:
// b/273904016#comment5
TEST(gwp_asan_integration, DISABLED_assert_gwp_asan_enabled_weaker) {
  std::string maps;
  EXPECT_TRUE(android::base::ReadFileToString("/proc/self/maps", &maps));
  EXPECT_TRUE(maps.find("GWP-ASan") != std::string::npos) << maps;
}

TEST(gwp_asan_integration, DISABLED_assert_gwp_asan_disabled) {
  std::string maps;
  EXPECT_TRUE(android::base::ReadFileToString("/proc/self/maps", &maps));
  EXPECT_TRUE(maps.find("GWP-ASan") == std::string::npos);
}

TEST(gwp_asan_integration, sysprops_program_specific) {
  // Do not override HWASan with GWP ASan.
  SKIP_WITH_HWASAN;

  SyspropRestorer restorer;

  std::string path = testing::internal::GetArgvs()[0];
  const char* basename = __gnu_basename(path.c_str());
  __system_property_set((std::string("libc.debug.gwp_asan.sample_rate.") + basename).c_str(), "1");
  __system_property_set((std::string("libc.debug.gwp_asan.process_sampling.") + basename).c_str(),
                        "1");
  __system_property_set((std::string("libc.debug.gwp_asan.max_allocs.") + basename).c_str(),
                        "40000");

  RunSubtestNoEnv("gwp_asan_integration_DeathTest.DISABLED_assert_gwp_asan_enabled");
}

TEST(gwp_asan_integration, sysprops_persist_program_specific) {
  // Do not override HWASan with GWP ASan.
  SKIP_WITH_HWASAN;

  SyspropRestorer restorer;

  std::string path = testing::internal::GetArgvs()[0];
  const char* basename = __gnu_basename(path.c_str());
  __system_property_set(
      (std::string("persist.libc.debug.gwp_asan.sample_rate.") + basename).c_str(), "1");
  __system_property_set(
      (std::string("persist.libc.debug.gwp_asan.process_sampling.") + basename).c_str(), "1");
  __system_property_set((std::string("persist.libc.debug.gwp_asan.max_allocs.") + basename).c_str(),
                        "40000");

  RunSubtestNoEnv("gwp_asan_integration_DeathTest.DISABLED_assert_gwp_asan_enabled");
}

TEST(gwp_asan_integration, sysprops_non_persist_overrides_persist) {
  // Do not override HWASan with GWP ASan.
  SKIP_WITH_HWASAN;

  SyspropRestorer restorer;

  __system_property_set("libc.debug.gwp_asan.sample_rate.system_default", "1");
  __system_property_set("libc.debug.gwp_asan.process_sampling.system_default", "1");
  // Note, any processes launched elsewhere on the system right now will have
  // GWP-ASan enabled. Make sure that we only use a single slot, otherwise we
  // could end up causing said badly-timed processes to use up to 163MiB extra
  // penalty that 40,000 allocs would cause. See b/273904016#comment5 for more
  // context.
  __system_property_set("libc.debug.gwp_asan.max_allocs.system_default", "1");

  __system_property_set("persist.libc.debug.gwp_asan.sample_rate.system_default", "0");
  __system_property_set("persist.libc.debug.gwp_asan.process_sampling.system_default", "0");
  __system_property_set("persist.libc.debug.gwp_asan.max_allocs.system_default", "0");

  RunSubtestNoEnv("gwp_asan_integration.DISABLED_assert_gwp_asan_enabled_weaker");
}

TEST(gwp_asan_integration, sysprops_program_specific_overrides_default) {
  // Do not override HWASan with GWP ASan.
  SKIP_WITH_HWASAN;

  SyspropRestorer restorer;

  std::string path = testing::internal::GetArgvs()[0];
  const char* basename = __gnu_basename(path.c_str());
  __system_property_set(
      (std::string("persist.libc.debug.gwp_asan.sample_rate.") + basename).c_str(), "1");
  __system_property_set(
      (std::string("persist.libc.debug.gwp_asan.process_sampling.") + basename).c_str(), "1");
  __system_property_set((std::string("persist.libc.debug.gwp_asan.max_allocs.") + basename).c_str(),
                        "40000");

  __system_property_set("libc.debug.gwp_asan.sample_rate.system_default", "0");
  __system_property_set("libc.debug.gwp_asan.process_sampling.system_default", "0");
  __system_property_set("libc.debug.gwp_asan.max_allocs.system_default", "0");

  RunSubtestNoEnv("gwp_asan_integration_DeathTest.DISABLED_assert_gwp_asan_enabled");
}

TEST(gwp_asan_integration, sysprops_can_disable) {
  // Do not override HWASan with GWP ASan.
  SKIP_WITH_HWASAN;

  SyspropRestorer restorer;

  __system_property_set("libc.debug.gwp_asan.sample_rate.system_default", "0");
  __system_property_set("libc.debug.gwp_asan.process_sampling.system_default", "0");
  __system_property_set("libc.debug.gwp_asan.max_allocs.system_default", "0");

  RunSubtestNoEnv("gwp_asan_integration.DISABLED_assert_gwp_asan_disabled");
}

TEST(gwp_asan_integration, env_overrides_sysprop) {
  // Do not override HWASan with GWP ASan.
  SKIP_WITH_HWASAN;

  SyspropRestorer restorer;

  __system_property_set("libc.debug.gwp_asan.sample_rate.system_default", "0");
  __system_property_set("libc.debug.gwp_asan.process_sampling.system_default", "0");
  __system_property_set("libc.debug.gwp_asan.max_allocs.system_default", "0");

  RunGwpAsanTest("gwp_asan_integration_DeathTest.DISABLED_assert_gwp_asan_enabled");
}

#endif  // defined(__BIONIC__)
