blob: 7174c960190891335c1f34e5ac7ba69267ce9398 [file] [log] [blame]
// Copyright 2018 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 "components/gwp_asan/client/gwp_asan.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include "base/allocator/buildflags.h"
#include "base/debug/crash_logging.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/field_trial_params.h"
#include "base/numerics/safe_math.h"
#include "base/optional.h"
#include "base/partition_alloc_buildflags.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "components/gwp_asan/client/guarded_page_allocator.h"
#include "components/gwp_asan/client/sampling_helpers.h"
#if BUILDFLAG(USE_ALLOCATOR_SHIM)
#include "components/gwp_asan/client/sampling_malloc_shims.h"
#endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
#if BUILDFLAG(USE_PARTITION_ALLOC)
#include "components/gwp_asan/client/sampling_partitionalloc_shims.h"
#endif // BUILDFLAG(USE_PARTITION_ALLOC)
namespace gwp_asan {
namespace internal {
namespace {
constexpr int kDefaultMaxAllocations = 70;
constexpr int kDefaultMaxMetadata = 255;
#if defined(ARCH_CPU_64_BITS)
constexpr int kDefaultTotalPages = 2048;
#else
// Use much less virtual memory on 32-bit builds (where OOMing due to lack of
// address space is a concern.)
constexpr int kDefaultTotalPages = kDefaultMaxMetadata * 2;
#endif
// The allocation sampling frequency is calculated using the formula:
// multiplier * range**rand
// where rand is a random real number in the range [0,1).
constexpr int kDefaultAllocationSamplingMultiplier = 1000;
constexpr int kDefaultAllocationSamplingRange = 16;
constexpr double kDefaultProcessSamplingProbability = 0.015;
// The multiplier to increase the ProcessSamplingProbability in scenarios where
// we want to perform additional testing (e.g., on canary/dev builds).
constexpr int kDefaultProcessSamplingBoost2 = 10;
#if defined(OS_WIN) || defined(OS_MACOSX)
constexpr base::FeatureState kDefaultEnabled = base::FEATURE_ENABLED_BY_DEFAULT;
#else
constexpr base::FeatureState kDefaultEnabled =
base::FEATURE_DISABLED_BY_DEFAULT;
#endif
const base::Feature kGwpAsanMalloc{"GwpAsanMalloc", kDefaultEnabled};
const base::Feature kGwpAsanPartitionAlloc{"GwpAsanPartitionAlloc",
kDefaultEnabled};
// Returns whether this process should be sampled to enable GWP-ASan.
bool SampleProcess(const base::Feature& feature, bool boost_sampling) {
double process_sampling_probability =
GetFieldTrialParamByFeatureAsDouble(feature, "ProcessSamplingProbability",
kDefaultProcessSamplingProbability);
if (process_sampling_probability < 0.0 ||
process_sampling_probability > 1.0) {
DLOG(ERROR) << "GWP-ASan ProcessSamplingProbability is out-of-range: "
<< process_sampling_probability;
return false;
}
int process_sampling_boost = GetFieldTrialParamByFeatureAsInt(
feature, "ProcessSamplingBoost2", kDefaultProcessSamplingBoost2);
if (process_sampling_boost < 1) {
DLOG(ERROR) << "GWP-ASan ProcessSampling multiplier is out-of-range: "
<< process_sampling_boost;
return false;
}
base::CheckedNumeric<double> sampling_prob_mult =
process_sampling_probability;
if (boost_sampling)
sampling_prob_mult *= process_sampling_boost;
if (!sampling_prob_mult.IsValid()) {
DLOG(ERROR) << "GWP-ASan multiplier caused out-of-range multiply: "
<< process_sampling_boost;
return false;
}
process_sampling_probability = sampling_prob_mult.ValueOrDie();
return (base::RandDouble() < process_sampling_probability);
}
// Returns the allocation sampling frequency, or 0 on error.
size_t AllocationSamplingFrequency(const base::Feature& feature) {
int multiplier =
GetFieldTrialParamByFeatureAsInt(feature, "AllocationSamplingMultiplier",
kDefaultAllocationSamplingMultiplier);
if (multiplier < 1) {
DLOG(ERROR) << "GWP-ASan AllocationSamplingMultiplier is out-of-range: "
<< multiplier;
return 0;
}
int range = GetFieldTrialParamByFeatureAsInt(
feature, "AllocationSamplingRange", kDefaultAllocationSamplingRange);
if (range < 1) {
DLOG(ERROR) << "GWP-ASan AllocationSamplingRange is out-of-range: "
<< range;
return 0;
}
base::CheckedNumeric<size_t> frequency = multiplier;
frequency *= std::pow(range, base::RandDouble());
if (!frequency.IsValid()) {
DLOG(ERROR) << "Out-of-range multiply " << multiplier << " " << range;
return 0;
}
return frequency.ValueOrDie();
}
} // namespace
// Exported for testing.
GWP_ASAN_EXPORT base::Optional<AllocatorSettings> GetAllocatorSettings(
const base::Feature& feature,
bool boost_sampling) {
if (!base::FeatureList::IsEnabled(feature))
return base::nullopt;
static_assert(AllocatorState::kMaxSlots <= std::numeric_limits<int>::max(),
"kMaxSlots out of range");
constexpr int kMaxSlots = static_cast<int>(AllocatorState::kMaxSlots);
static_assert(AllocatorState::kMaxMetadata <= std::numeric_limits<int>::max(),
"kMaxMetadata out of range");
constexpr int kMaxMetadata = static_cast<int>(AllocatorState::kMaxMetadata);
int total_pages = GetFieldTrialParamByFeatureAsInt(feature, "TotalPages",
kDefaultTotalPages);
if (total_pages < 1 || total_pages > kMaxSlots) {
DLOG(ERROR) << "GWP-ASan TotalPages is out-of-range: " << total_pages;
return base::nullopt;
}
int max_metadata = GetFieldTrialParamByFeatureAsInt(feature, "MaxMetadata",
kDefaultMaxMetadata);
if (max_metadata < 1 || max_metadata > std::min(total_pages, kMaxMetadata)) {
DLOG(ERROR) << "GWP-ASan MaxMetadata is out-of-range: " << max_metadata
<< " with TotalPages = " << total_pages;
return base::nullopt;
}
int max_allocations = GetFieldTrialParamByFeatureAsInt(
feature, "MaxAllocations", kDefaultMaxAllocations);
if (max_allocations < 1 || max_allocations > max_metadata) {
DLOG(ERROR) << "GWP-ASan MaxAllocations is out-of-range: "
<< max_allocations << " with MaxMetadata = " << max_metadata;
return base::nullopt;
}
size_t alloc_sampling_freq = AllocationSamplingFrequency(feature);
if (!alloc_sampling_freq)
return base::nullopt;
if (!SampleProcess(feature, boost_sampling))
return base::nullopt;
return AllocatorSettings{max_allocations, max_metadata, total_pages,
alloc_sampling_freq};
}
} // namespace internal
void EnableForMalloc(bool boost_sampling, const char* process_type) {
#if BUILDFLAG(USE_ALLOCATOR_SHIM)
static bool init_once = [&]() -> bool {
auto settings = internal::GetAllocatorSettings(internal::kGwpAsanMalloc,
boost_sampling);
if (!settings)
return false;
internal::InstallMallocHooks(
settings->max_allocated_pages, settings->num_metadata,
settings->total_pages, settings->sampling_frequency,
internal::CreateOomCallback("Malloc", process_type,
settings->sampling_frequency));
return true;
}();
ignore_result(init_once);
#else
ignore_result(internal::kGwpAsanMalloc);
DLOG(WARNING) << "base::allocator shims are unavailable for GWP-ASan.";
#endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
}
void EnableForPartitionAlloc(bool boost_sampling, const char* process_type) {
#if BUILDFLAG(USE_PARTITION_ALLOC)
static bool init_once = [&]() -> bool {
auto settings = internal::GetAllocatorSettings(
internal::kGwpAsanPartitionAlloc, boost_sampling);
if (!settings)
return false;
internal::InstallPartitionAllocHooks(
settings->max_allocated_pages, settings->num_metadata,
settings->total_pages, settings->sampling_frequency,
internal::CreateOomCallback("PartitionAlloc", process_type,
settings->sampling_frequency));
return true;
}();
ignore_result(init_once);
#else
ignore_result(internal::kGwpAsanPartitionAlloc);
DLOG(WARNING) << "PartitionAlloc hooks are unavailable for GWP-ASan.";
#endif // BUILDFLAG(USE_PARTITION_ALLOC)
}
} // namespace gwp_asan