diff --git a/snapshot/BUILD.gn b/snapshot/BUILD.gn index ac3125ce..f8a055bd 100644 --- a/snapshot/BUILD.gn +++ b/snapshot/BUILD.gn @@ -122,6 +122,14 @@ static_library("snapshot") { "linux/system_snapshot_linux.h", "linux/thread_snapshot_linux.cc", "linux/thread_snapshot_linux.h", + "sanitized/memory_snapshot_sanitized.cc", + "sanitized/memory_snapshot_sanitized.h", + "sanitized/module_snapshot_sanitized.cc", + "sanitized/module_snapshot_sanitized.h", + "sanitized/process_snapshot_sanitized.cc", + "sanitized/process_snapshot_sanitized.h", + "sanitized/thread_snapshot_sanitized.cc", + "sanitized/thread_snapshot_sanitized.h", ] } @@ -329,6 +337,7 @@ source_set("snapshot_test") { "linux/exception_snapshot_linux_test.cc", "linux/process_reader_linux_test.cc", "linux/system_snapshot_linux_test.cc", + "sanitized/process_snapshot_sanitized_test.cc", ] } else { sources += [ "crashpad_info_client_options_test.cc" ] diff --git a/snapshot/cpu_context.cc b/snapshot/cpu_context.cc index 6ce059e7..553a1a5d 100644 --- a/snapshot/cpu_context.cc +++ b/snapshot/cpu_context.cc @@ -190,4 +190,18 @@ uint64_t CPUContext::StackPointer() const { } } +bool CPUContext::Is64Bit() const { + switch (architecture) { + case kCPUArchitectureX86_64: + case kCPUArchitectureARM64: + return true; + case kCPUArchitectureX86: + case kCPUArchitectureARM: + return false; + default: + NOTREACHED(); + return false; + } +} + } // namespace crashpad diff --git a/snapshot/cpu_context.h b/snapshot/cpu_context.h index 9cb2aec9..b104217d 100644 --- a/snapshot/cpu_context.h +++ b/snapshot/cpu_context.h @@ -323,6 +323,9 @@ struct CPUContext { //! context structure. uint64_t StackPointer() const; + //! \brief Returns `true` if this context is for a 64-bit architecture. + bool Is64Bit() const; + //! \brief The CPU architecture of a context structure. This field controls //! the expression of the union. CPUArchitecture architecture; diff --git a/snapshot/sanitized/memory_snapshot_sanitized.cc b/snapshot/sanitized/memory_snapshot_sanitized.cc new file mode 100644 index 00000000..e71629df --- /dev/null +++ b/snapshot/sanitized/memory_snapshot_sanitized.cc @@ -0,0 +1,107 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/sanitized/memory_snapshot_sanitized.h" + +#include + +namespace crashpad { +namespace internal { + +namespace { + +class MemorySanitizer : public MemorySnapshot::Delegate { + public: + MemorySanitizer(MemorySnapshot::Delegate* delegate, + RangeSet* ranges, + VMAddress address, + bool is_64_bit) + : delegate_(delegate), + ranges_(ranges), + address_(address), + is_64_bit_(is_64_bit) {} + + ~MemorySanitizer() = default; + + bool MemorySnapshotDelegateRead(void* data, size_t size) override { + if (is_64_bit_) { + Sanitize(data, size); + } else { + Sanitize(data, size); + } + return delegate_->MemorySnapshotDelegateRead(data, size); + } + + private: + template + void Sanitize(void* data, size_t size) { + const Pointer defaced = + static_cast(MemorySnapshotSanitized::kDefaced); + + // Sanitize up to a word-aligned address. + const size_t aligned_offset = + ((address_ + sizeof(Pointer) - 1) & ~(sizeof(Pointer) - 1)) - address_; + memcpy(data, &defaced, aligned_offset); + + // Sanitize words that aren't small and don't look like pointers. + size_t word_count = (size - aligned_offset) / sizeof(Pointer); + auto words = + reinterpret_cast(static_cast(data) + aligned_offset); + for (size_t index = 0; index < word_count; ++index) { + if (words[index] > MemorySnapshotSanitized::kSmallWordMax && + !ranges_->Contains(words[index])) { + words[index] = defaced; + } + } + + // Sanitize trailing bytes beyond the word-sized items. + const size_t sanitized_bytes = + aligned_offset + word_count * sizeof(Pointer); + memcpy(static_cast(data) + sanitized_bytes, + &defaced, + size - sanitized_bytes); + } + + MemorySnapshot::Delegate* delegate_; + RangeSet* ranges_; + VMAddress address_; + bool is_64_bit_; + + DISALLOW_COPY_AND_ASSIGN(MemorySanitizer); +}; + +} // namespace + +MemorySnapshotSanitized::MemorySnapshotSanitized(const MemorySnapshot* snapshot, + RangeSet* ranges, + bool is_64_bit) + : snapshot_(snapshot), ranges_(ranges), is_64_bit_(is_64_bit) {} + +MemorySnapshotSanitized::~MemorySnapshotSanitized() = default; + +uint64_t MemorySnapshotSanitized::Address() const { + return snapshot_->Address(); +} + +size_t MemorySnapshotSanitized::Size() const { + return snapshot_->Size(); +} + +bool MemorySnapshotSanitized::Read(Delegate* delegate) const { + MemorySanitizer sanitizer(delegate, ranges_, Address(), is_64_bit_); + return snapshot_->Read(&sanitizer); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/sanitized/memory_snapshot_sanitized.h b/snapshot/sanitized/memory_snapshot_sanitized.h new file mode 100644 index 00000000..41e324f5 --- /dev/null +++ b/snapshot/sanitized/memory_snapshot_sanitized.h @@ -0,0 +1,74 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_SANITIZED_MEMORY_SNAPSHOT_SANITIZED_H_ +#define CRASHPAD_SNAPSHOT_SANITIZED_MEMORY_SNAPSHOT_SANITIZED_H_ + +#include + +#include "snapshot/memory_snapshot.h" +#include "util/misc/range_set.h" + +namespace crashpad { +namespace internal { + +//! \brief A MemorySnapshot which wraps and filters sensitive information from +//! another MemorySnapshot. +//! +//! This class redacts all data from the wrapped MemorySnapshot unless: +//! 1. The data is pointer aligned and points into a whitelisted address range. +//! 2. The data is pointer aligned and is a small integer. +class MemorySnapshotSanitized final : public MemorySnapshot { + public: + //! \brief Redacted data is replaced with this value, casted to the + //! appropriate size for a pointer in the target process. + static constexpr uint64_t kDefaced = 0x0defaced0defaced; + + //! \brief Pointer-aligned data smaller than this value is not redacted. + static constexpr uint64_t kSmallWordMax = 4096; + + //! \brief Constructs this object. + //! + //! \param[in] snapshot The MemorySnapshot to sanitize. + //! \param[in] ranges A set of whitelisted address ranges. + //! \param[in] is_64_bit `true` if this memory is for a 64-bit process. + MemorySnapshotSanitized(const MemorySnapshot* snapshot, + RangeSet* ranges, + bool is_64_bit); + + ~MemorySnapshotSanitized() override; + + // MemorySnapshot: + + uint64_t Address() const override; + size_t Size() const override; + bool Read(Delegate* delegate) const override; + + const MemorySnapshot* MergeWithOtherSnapshot( + const MemorySnapshot* other) const override { + return nullptr; + } + + private: + const MemorySnapshot* snapshot_; + RangeSet* ranges_; + bool is_64_bit_; + + DISALLOW_COPY_AND_ASSIGN(MemorySnapshotSanitized); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_SANITIZED_MEMORY_SNAPSHOT_SANITIZED_H_ diff --git a/snapshot/sanitized/module_snapshot_sanitized.cc b/snapshot/sanitized/module_snapshot_sanitized.cc new file mode 100644 index 00000000..94cb3d00 --- /dev/null +++ b/snapshot/sanitized/module_snapshot_sanitized.cc @@ -0,0 +1,132 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/sanitized/module_snapshot_sanitized.h" + +namespace crashpad { +namespace internal { + +namespace { + +bool KeyIsInWhitelist(const std::string& name, + const std::vector& whitelist) { + for (const auto& key : whitelist) { + if (name == key) { + return true; + } + } + return false; +} + +} // namespace + +ModuleSnapshotSanitized::ModuleSnapshotSanitized( + const ModuleSnapshot* snapshot, + const std::vector* annotations_whitelist) + : snapshot_(snapshot), annotations_whitelist_(annotations_whitelist) {} + +ModuleSnapshotSanitized::~ModuleSnapshotSanitized() = default; + +std::string ModuleSnapshotSanitized::Name() const { + return snapshot_->Name(); +} + +uint64_t ModuleSnapshotSanitized::Address() const { + return snapshot_->Address(); +} + +uint64_t ModuleSnapshotSanitized::Size() const { + return snapshot_->Size(); +} + +time_t ModuleSnapshotSanitized::Timestamp() const { + return snapshot_->Timestamp(); +} + +void ModuleSnapshotSanitized::FileVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const { + snapshot_->FileVersion(version_0, version_1, version_2, version_3); +} + +void ModuleSnapshotSanitized::SourceVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const { + snapshot_->SourceVersion(version_0, version_1, version_2, version_3); +} + +ModuleSnapshot::ModuleType ModuleSnapshotSanitized::GetModuleType() const { + return snapshot_->GetModuleType(); +} + +void ModuleSnapshotSanitized::UUIDAndAge(crashpad::UUID* uuid, + uint32_t* age) const { + snapshot_->UUIDAndAge(uuid, age); +} + +std::string ModuleSnapshotSanitized::DebugFileName() const { + return snapshot_->DebugFileName(); +} + +std::vector ModuleSnapshotSanitized::AnnotationsVector() const { + // TODO(jperaza): If/when AnnotationsVector() begins to be used, determine + // whether and how the content should be sanitized. + DCHECK(snapshot_->AnnotationsVector().empty()); + return std::vector(); +} + +std::map +ModuleSnapshotSanitized::AnnotationsSimpleMap() const { + std::map annotations = + snapshot_->AnnotationsSimpleMap(); + if (annotations_whitelist_) { + for (auto kv = annotations.begin(); kv != annotations.end(); ++kv) { + if (!KeyIsInWhitelist(kv->first, *annotations_whitelist_)) { + annotations.erase(kv); + } + } + } + return annotations; +} + +std::vector ModuleSnapshotSanitized::AnnotationObjects() + const { + std::vector annotations = snapshot_->AnnotationObjects(); + if (annotations_whitelist_) { + std::vector whitelisted; + for (const auto& anno : annotations) { + if (KeyIsInWhitelist(anno.name, *annotations_whitelist_)) { + whitelisted.push_back(anno); + } + } + annotations.swap(whitelisted); + } + return annotations; +} + +std::set> ModuleSnapshotSanitized::ExtraMemoryRanges() + const { + DCHECK(snapshot_->ExtraMemoryRanges().empty()); + return std::set>(); +} + +std::vector +ModuleSnapshotSanitized::CustomMinidumpStreams() const { + return snapshot_->CustomMinidumpStreams(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/sanitized/module_snapshot_sanitized.h b/snapshot/sanitized/module_snapshot_sanitized.h new file mode 100644 index 00000000..4f375ce0 --- /dev/null +++ b/snapshot/sanitized/module_snapshot_sanitized.h @@ -0,0 +1,75 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_SANITIZED_MODULE_SNAPSHOT_SANITIZED_H_ +#define CRASHPAD_SNAPSHOT_SANITIZED_MODULE_SNAPSHOT_SANITIZED_H_ + +#include +#include + +#include "base/macros.h" +#include "snapshot/module_snapshot.h" + +namespace crashpad { +namespace internal { + +//! \brief A ModuleSnapshot which wraps and filters sensitive information from +//! another ModuleSnapshot. +class ModuleSnapshotSanitized final : public ModuleSnapshot { + public: + //! \brief Constructs this object. + //! + //! \param[in] snapshot The ModuleSnapshot to sanitize. + //! \param[in] annotations_whitelist A list of annotation names to allow to be + //! returned by AnnotationsSimpleMap() or AnnotationObjects(). If + //! `nullptr`, all annotations will be returned. + ModuleSnapshotSanitized( + const ModuleSnapshot* snapshot, + const std::vector* annotations_whitelist); + ~ModuleSnapshotSanitized() override; + + // ModuleSnapshot: + + std::string Name() const override; + uint64_t Address() const override; + uint64_t Size() const override; + time_t Timestamp() const override; + void FileVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const override; + void SourceVersion(uint16_t* version_0, + uint16_t* version_1, + uint16_t* version_2, + uint16_t* version_3) const override; + ModuleType GetModuleType() const override; + void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override; + std::string DebugFileName() const override; + std::vector AnnotationsVector() const override; + std::map AnnotationsSimpleMap() const override; + std::vector AnnotationObjects() const override; + std::set> ExtraMemoryRanges() const override; + std::vector CustomMinidumpStreams() const override; + + private: + const ModuleSnapshot* snapshot_; + const std::vector* annotations_whitelist_; + + DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotSanitized); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_SANITIZED_MODULE_SNAPSHOT_SANITIZED_H_ diff --git a/snapshot/sanitized/process_snapshot_sanitized.cc b/snapshot/sanitized/process_snapshot_sanitized.cc new file mode 100644 index 00000000..76caeb5a --- /dev/null +++ b/snapshot/sanitized/process_snapshot_sanitized.cc @@ -0,0 +1,265 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/sanitized/process_snapshot_sanitized.h" + +#include + +#include "snapshot/cpu_context.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +namespace { + +class StackReferencesAddressRange : public MemorySnapshot::Delegate { + public: + // Returns true if stack contains a pointer aligned word in the range [low, + // high). The search starts at the first pointer aligned address greater than + // stack_pointer. + bool CheckStack(const MemorySnapshot* stack, + VMAddress stack_pointer, + VMAddress low, + VMAddress high, + bool is_64_bit) { + stack_ = stack; + stack_pointer_ = stack_pointer; + low_ = low; + high_ = high; + is_64_bit_ = is_64_bit; + return stack_->Read(this); + } + + // MemorySnapshot::Delegate + bool MemorySnapshotDelegateRead(void* data, size_t size) { + return is_64_bit_ ? ScanStackForPointers(data, size) + : ScanStackForPointers(data, size); + } + + private: + template + bool ScanStackForPointers(void* data, size_t size) { + size_t sp_offset; + if (!AssignIfInRange(&sp_offset, stack_pointer_ - stack_->Address())) { + return false; + } + const size_t aligned_sp_offset = + (sp_offset + sizeof(Pointer) - 1) & ~(sizeof(Pointer) - 1); + + auto words = reinterpret_cast(static_cast(data) + + aligned_sp_offset); + size_t word_count = (size - aligned_sp_offset) / sizeof(Pointer); + for (size_t index = 0; index < word_count; ++index) { + if (words[index] >= low_ && words[index] < high_) { + return true; + } + } + + return false; + } + + VMAddress stack_pointer_; + VMAddress low_; + VMAddress high_; + const MemorySnapshot* stack_; + bool is_64_bit_; +}; + +} // namespace + +ProcessSnapshotSanitized::ProcessSnapshotSanitized() = default; + +ProcessSnapshotSanitized::~ProcessSnapshotSanitized() = default; + +bool ProcessSnapshotSanitized::Initialize( + const ProcessSnapshot* snapshot, + const std::vector* annotations_whitelist, + VMAddress target_module_address, + bool sanitize_stacks) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + snapshot_ = snapshot; + annotations_whitelist_ = annotations_whitelist; + sanitize_stacks_ = sanitize_stacks; + + if (target_module_address) { + const ExceptionSnapshot* exception = snapshot_->Exception(); + if (!exception) { + return false; + } + + const ThreadSnapshot* exc_thread = nullptr; + for (const auto thread : snapshot_->Threads()) { + if (thread->ThreadID() == exception->ThreadID()) { + exc_thread = thread; + break; + } + } + if (!exc_thread) { + return false; + } + + const ModuleSnapshot* target_module = nullptr; + for (const auto module : snapshot_->Modules()) { + if (target_module_address >= module->Address() && + target_module_address < module->Address() + module->Size()) { + target_module = module; + break; + } + } + if (!target_module) { + return false; + } + + // Only allow the snapshot if the program counter or some address on the + // stack points into the target module. + VMAddress pc = exception->Context()->InstructionPointer(); + VMAddress module_address_low = target_module->Address(); + VMAddress module_address_high = module_address_low + target_module->Size(); + if ((pc < module_address_low || pc >= module_address_high) && + !StackReferencesAddressRange().CheckStack( + exc_thread->Stack(), + exception->Context()->StackPointer(), + module_address_low, + module_address_high, + exception->Context()->Is64Bit())) { + return false; + } + } + + if (annotations_whitelist_) { + for (const auto module : snapshot_->Modules()) { + modules_.emplace_back(std::make_unique( + module, annotations_whitelist_)); + } + } + + if (sanitize_stacks_) { + for (const auto module : snapshot_->Modules()) { + address_ranges_.Insert(module->Address(), module->Size()); + } + + for (const auto thread : snapshot_->Threads()) { + address_ranges_.Insert(thread->Stack()->Address(), + thread->Stack()->Size()); + threads_.emplace_back(std::make_unique( + thread, &address_ranges_)); + } + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +pid_t ProcessSnapshotSanitized::ProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->ProcessID(); +} + +pid_t ProcessSnapshotSanitized::ParentProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->ParentProcessID(); +} + +void ProcessSnapshotSanitized::SnapshotTime(timeval* snapshot_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->SnapshotTime(snapshot_time); +} + +void ProcessSnapshotSanitized::ProcessStartTime(timeval* start_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->ProcessStartTime(start_time); +} + +void ProcessSnapshotSanitized::ProcessCPUTimes(timeval* user_time, + timeval* system_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->ProcessCPUTimes(user_time, system_time); +} + +void ProcessSnapshotSanitized::ReportID(UUID* report_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->ReportID(report_id); +} + +void ProcessSnapshotSanitized::ClientID(UUID* client_id) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + snapshot_->ClientID(client_id); +} + +const std::map& +ProcessSnapshotSanitized::AnnotationsSimpleMap() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->AnnotationsSimpleMap(); +} + +const SystemSnapshot* ProcessSnapshotSanitized::System() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->System(); +} + +std::vector ProcessSnapshotSanitized::Threads() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (!sanitize_stacks_) { + return snapshot_->Threads(); + } + + std::vector threads; + for (const auto& thread : threads_) { + threads.push_back(thread.get()); + } + return threads; +} + +std::vector ProcessSnapshotSanitized::Modules() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (!annotations_whitelist_) { + return snapshot_->Modules(); + } + + std::vector modules; + for (const auto& module : modules_) { + modules.push_back(module.get()); + } + return modules; +} + +std::vector ProcessSnapshotSanitized::UnloadedModules() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->UnloadedModules(); +} + +const ExceptionSnapshot* ProcessSnapshotSanitized::Exception() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->Exception(); +} + +std::vector +ProcessSnapshotSanitized::MemoryMap() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->MemoryMap(); +} + +std::vector ProcessSnapshotSanitized::Handles() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->Handles(); +} + +std::vector ProcessSnapshotSanitized::ExtraMemory() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return snapshot_->ExtraMemory(); +} + +} // namespace crashpad diff --git a/snapshot/sanitized/process_snapshot_sanitized.h b/snapshot/sanitized/process_snapshot_sanitized.h new file mode 100644 index 00000000..f5cf5fa6 --- /dev/null +++ b/snapshot/sanitized/process_snapshot_sanitized.h @@ -0,0 +1,105 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_SANITIZED_PROCESS_SNAPSHOT_SANITIZED_H_ +#define CRASHPAD_SNAPSHOT_SANITIZED_PROCESS_SNAPSHOT_SANITIZED_H_ + +#include +#include +#include + +#include "base/macros.h" +#include "snapshot/exception_snapshot.h" +#include "snapshot/process_snapshot.h" +#include "snapshot/sanitized/module_snapshot_sanitized.h" +#include "snapshot/sanitized/thread_snapshot_sanitized.h" +#include "snapshot/thread_snapshot.h" +#include "snapshot/unloaded_module_snapshot.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/misc/range_set.h" + +namespace crashpad { + +//! \brief A ProcessSnapshot which wraps and filters sensitive information from +//! another ProcessSnapshot. +class ProcessSnapshotSanitized final : public ProcessSnapshot { + public: + ProcessSnapshotSanitized(); + ~ProcessSnapshotSanitized() override; + + //! \brief Initializes this object. + //! + //! This method must be successfully called before calling any other method on + //! this object. + //! + //! \param[in] snapshot The ProcessSnapshot to sanitize. + //! \param[in] annotations_whitelist A list of annotations names to allow to + //! be returned by AnnotationsSimpleMap() or from this object's module + //! snapshots. If `nullptr`, all annotations will be returned. + //! \param[in] target_module_address An address in the target process' + //! address space within the bounds of a module to target. If the + //! crashing thread's context and stack do not contain any pointers into + //! this module's address range, this method will return `false`. If this + //! value is 0, this method will not check the context or stack for + //! references to any particular module. + //! \param[in] sanitize_stacks If `true`, the MemorySnapshots for each + //! thread's stack will be filtered using an + //! internal::StackSnapshotSanitized. + //! \return `false` if \a snapshot does not meet sanitization requirements and + //! should be filtered entirely. Otherwise `true`. + bool Initialize(const ProcessSnapshot* snapshot, + const std::vector* annotations_whitelist, + VMAddress target_module_address, + bool sanitize_stacks); + + // ProcessSnapshot: + + pid_t ProcessID() const override; + pid_t ParentProcessID() const override; + void SnapshotTime(timeval* snapshot_time) const override; + void ProcessStartTime(timeval* start_time) const override; + void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; + void ReportID(UUID* report_id) const override; + void ClientID(UUID* client_id) const override; + const std::map& AnnotationsSimpleMap() + const override; + const SystemSnapshot* System() const override; + std::vector Threads() const override; + std::vector Modules() const override; + std::vector UnloadedModules() const override; + const ExceptionSnapshot* Exception() const override; + std::vector MemoryMap() const override; + std::vector Handles() const override; + std::vector ExtraMemory() const override; + + private: + // Only used when annotations_whitelist_ != nullptr. + std::vector> modules_; + + // Only used when sanitize_stacks_ == true. + std::vector> threads_; + + RangeSet address_ranges_; + const ProcessSnapshot* snapshot_; + const std::vector* annotations_whitelist_; + bool sanitize_stacks_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotSanitized); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_SANITIZED_PROCESS_SNAPSHOT_SANITIZED_H_ diff --git a/snapshot/sanitized/process_snapshot_sanitized_test.cc b/snapshot/sanitized/process_snapshot_sanitized_test.cc new file mode 100644 index 00000000..485ef87f --- /dev/null +++ b/snapshot/sanitized/process_snapshot_sanitized_test.cc @@ -0,0 +1,275 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/sanitized/process_snapshot_sanitized.h" + +#include "base/macros.h" +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "test/multiprocess_exec.h" +#include "util/file/file_io.h" +#include "util/numeric/safe_assignment.h" + +#if defined(OS_LINUX) || defined(OS_ANDROID) +#include + +#include "snapshot/linux/process_snapshot_linux.h" +#include "util/linux/direct_ptrace_connection.h" +#include "util/linux/exception_information.h" +#include "util/posix/signals.h" +#endif + +namespace crashpad { +namespace test { +namespace { + +class ExceptionGenerator { + public: + static ExceptionGenerator* Get() { + static ExceptionGenerator* instance = new ExceptionGenerator(); + return instance; + } + + bool Initialize(FileHandle in, FileHandle out) { + in_ = in; + out_ = out; + return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr); + } + + private: + ExceptionGenerator() = default; + ~ExceptionGenerator() = delete; + + static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { + auto state = Get(); + + ExceptionInformation info = {}; + info.siginfo_address = FromPointerCast(siginfo); + info.context_address = FromPointerCast(context); + info.thread_id = syscall(SYS_gettid); + + auto info_addr = FromPointerCast(&info); + ASSERT_TRUE(LoggingWriteFile(state->out_, &info_addr, sizeof(info_addr))); + + CheckedReadFileAtEOF(state->in_); + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); + } + + FileHandle in_; + FileHandle out_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionGenerator); +}; + +constexpr char kWhitelistedAnnotationName[] = "name_of_whitelisted_anno"; +constexpr char kWhitelistedAnnotationValue[] = "some_value"; +constexpr char kNonWhitelistedAnnotationName[] = "non_whitelisted_anno"; +constexpr char kNonWhitelistedAnnotationValue[] = "private_annotation"; +constexpr char kSensitiveStackData[] = "sensitive_stack_data"; + +struct ChildTestAddresses { + VMAddress string_address; + VMAddress module_address; + VMAddress non_module_address; + VMAddress code_pointer_address; + VMAddress code_pointer_value; +}; + +void ChildTestFunction() { + FileHandle in = StdioFileHandle(StdioStream::kStandardInput); + FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + + static StringAnnotation<32> whitelisted_annotation( + kWhitelistedAnnotationName); + whitelisted_annotation.Set(kWhitelistedAnnotationValue); + + static StringAnnotation<32> non_whitelisted_annotation( + kNonWhitelistedAnnotationName); + non_whitelisted_annotation.Set(kNonWhitelistedAnnotationValue); + + char string_data[strlen(kSensitiveStackData) + 1]; + strcpy(string_data, kSensitiveStackData); + + void (*code_pointer)(void) = ChildTestFunction; + + ChildTestAddresses addrs = {}; + addrs.string_address = FromPointerCast(string_data); + addrs.module_address = FromPointerCast(ChildTestFunction); + addrs.non_module_address = FromPointerCast(&addrs); + addrs.code_pointer_address = FromPointerCast(&code_pointer); + addrs.code_pointer_value = FromPointerCast(code_pointer); + ASSERT_TRUE(LoggingWriteFile(out, &addrs, sizeof(addrs))); + + auto gen = ExceptionGenerator::Get(); + ASSERT_TRUE(gen->Initialize(in, out)); + + __builtin_trap(); +} + +CRASHPAD_CHILD_TEST_MAIN(ChildToBeSanitized) { + ChildTestFunction(); + NOTREACHED(); + return EXIT_SUCCESS; +} + +void ExpectAnnotations(ProcessSnapshot* snapshot, bool sanitized) { + bool found_whitelisted = false; + bool found_non_whitelisted = false; + for (auto module : snapshot->Modules()) { + for (const auto& anno : module->AnnotationObjects()) { + if (anno.name == kWhitelistedAnnotationName) { + found_whitelisted = true; + } else if (anno.name == kNonWhitelistedAnnotationName) { + found_non_whitelisted = true; + } + } + } + + EXPECT_TRUE(found_whitelisted); + if (sanitized) { + EXPECT_FALSE(found_non_whitelisted); + } else { + EXPECT_TRUE(found_non_whitelisted); + } +} + +class StackSanitizationChecker : public MemorySnapshot::Delegate { + public: + StackSanitizationChecker() = default; + ~StackSanitizationChecker() = default; + + void CheckStack(const MemorySnapshot* stack, + const ChildTestAddresses& addrs, + bool is_64_bit, + bool sanitized) { + stack_ = stack; + addrs_ = addrs; + is_64_bit_ = is_64_bit; + sanitized_ = sanitized; + EXPECT_TRUE(stack_->Read(this)); + } + + // MemorySnapshot::Delegate + bool MemorySnapshotDelegateRead(void* data, size_t size) override { + size_t pointer_offset; + if (!AssignIfInRange(&pointer_offset, + addrs_.code_pointer_address - stack_->Address())) { + ADD_FAILURE(); + return false; + } + + const auto data_c = static_cast(data); + VMAddress pointer_value; + if (is_64_bit_) { + pointer_value = *reinterpret_cast(data_c + pointer_offset); + } else { + pointer_value = *reinterpret_cast(data_c + pointer_offset); + } + EXPECT_EQ(pointer_value, addrs_.code_pointer_value); + + size_t string_offset; + if (!AssignIfInRange(&string_offset, + addrs_.string_address - stack_->Address())) { + ADD_FAILURE(); + return false; + } + + auto string = data_c + string_offset; + if (sanitized_) { + EXPECT_STRNE(string, kSensitiveStackData); + } else { + EXPECT_STREQ(string, kSensitiveStackData); + } + return true; + } + + private: + const MemorySnapshot* stack_; + ChildTestAddresses addrs_; + bool is_64_bit_; + bool sanitized_; +}; + +void ExpectStackData(ProcessSnapshot* snapshot, + const ChildTestAddresses& addrs, + bool sanitized) { + const ThreadSnapshot* crasher = nullptr; + for (const auto thread : snapshot->Threads()) { + if (thread->ThreadID() == snapshot->Exception()->ThreadID()) { + crasher = thread; + break; + } + } + ASSERT_TRUE(crasher); + + const MemorySnapshot* stack = crasher->Stack(); + StackSanitizationChecker().CheckStack( + stack, addrs, crasher->Context()->Is64Bit(), sanitized); +} + +class SanitizeTest : public MultiprocessExec { + public: + SanitizeTest() : MultiprocessExec() { + SetChildTestMainFunction("ChildToBeSanitized"); + SetExpectedChildTerminationBuiltinTrap(); + } + + ~SanitizeTest() = default; + + private: + void MultiprocessParent() { + ChildTestAddresses addrs = {}; + ASSERT_TRUE( + LoggingReadFileExactly(ReadPipeHandle(), &addrs, sizeof(addrs))); + + VMAddress exception_info_addr; + ASSERT_TRUE(LoggingReadFileExactly( + ReadPipeHandle(), &exception_info_addr, sizeof(exception_info_addr))); + + DirectPtraceConnection connection; + ASSERT_TRUE(connection.Initialize(ChildProcess())); + + ProcessSnapshotLinux snapshot; + ASSERT_TRUE(snapshot.Initialize(&connection)); + ASSERT_TRUE(snapshot.InitializeException(exception_info_addr)); + + ExpectAnnotations(&snapshot, /* sanitized= */ false); + ExpectStackData(&snapshot, addrs, /* sanitized= */ false); + + std::vector whitelist; + whitelist.push_back(kWhitelistedAnnotationName); + + ProcessSnapshotSanitized sanitized; + ASSERT_TRUE(sanitized.Initialize( + &snapshot, &whitelist, addrs.module_address, true)); + + ExpectAnnotations(&sanitized, /* sanitized= */ true); + ExpectStackData(&sanitized, addrs, /* sanitized= */ true); + + ProcessSnapshotSanitized screened_snapshot; + EXPECT_FALSE(screened_snapshot.Initialize( + &snapshot, nullptr, addrs.non_module_address, false)); + } + + DISALLOW_COPY_AND_ASSIGN(SanitizeTest); +}; + +TEST(ProcessSnapshotSanitized, Sanitize) { + SanitizeTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/snapshot/sanitized/thread_snapshot_sanitized.cc b/snapshot/sanitized/thread_snapshot_sanitized.cc new file mode 100644 index 00000000..186776ea --- /dev/null +++ b/snapshot/sanitized/thread_snapshot_sanitized.cc @@ -0,0 +1,63 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/sanitized/thread_snapshot_sanitized.h" + +#include "snapshot/cpu_context.h" + +namespace crashpad { +namespace internal { + +ThreadSnapshotSanitized::ThreadSnapshotSanitized(const ThreadSnapshot* snapshot, + RangeSet* ranges) + : ThreadSnapshot(), + snapshot_(snapshot), + stack_(snapshot_->Stack(), ranges, snapshot_->Context()->Is64Bit()) {} + +ThreadSnapshotSanitized::~ThreadSnapshotSanitized() = default; + +const CPUContext* ThreadSnapshotSanitized::Context() const { + return snapshot_->Context(); +} + +const MemorySnapshot* ThreadSnapshotSanitized::Stack() const { + return &stack_; +} + +uint64_t ThreadSnapshotSanitized::ThreadID() const { + return snapshot_->ThreadID(); +} + +int ThreadSnapshotSanitized::SuspendCount() const { + return snapshot_->SuspendCount(); +} + +int ThreadSnapshotSanitized::Priority() const { + return snapshot_->Priority(); +} + +uint64_t ThreadSnapshotSanitized::ThreadSpecificDataAddress() const { + return snapshot_->ThreadSpecificDataAddress(); +} + +std::vector ThreadSnapshotSanitized::ExtraMemory() + const { + // TODO(jperaza): If/when ExtraMemory() is used, decide whether and how it + // should be sanitized. + DCHECK(snapshot_->ExtraMemory().empty()); + return std::vector(); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/sanitized/thread_snapshot_sanitized.h b/snapshot/sanitized/thread_snapshot_sanitized.h new file mode 100644 index 00000000..081627a6 --- /dev/null +++ b/snapshot/sanitized/thread_snapshot_sanitized.h @@ -0,0 +1,59 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_SANITIZED_THREAD_SNAPSHOT_SANITIZED_H_ +#define CRASHPAD_SNAPSHOT_SANITIZED_THREAD_SNAPSHOT_SANITIZED_H_ + +#include "snapshot/thread_snapshot.h" + +#include "snapshot/sanitized/memory_snapshot_sanitized.h" +#include "util/misc/range_set.h" + +namespace crashpad { +namespace internal { + +//! \brief A ThreadSnapshot which wraps and filters sensitive information from +//! another ThreadSnapshot. +class ThreadSnapshotSanitized final : public ThreadSnapshot { + public: + //! \brief Constructs this object. + //! + //! \param[in] snapshot The ThreadSnapshot to sanitize. + //! \param[in] ranges A set of address ranges with which to sanitize this + //! thread's stacks. \see internal::MemorySnapshotSanitized. + ThreadSnapshotSanitized(const ThreadSnapshot* snapshot, RangeSet* ranges); + + ~ThreadSnapshotSanitized() override; + + // ThreadSnapshot: + + const CPUContext* Context() const override; + const MemorySnapshot* Stack() const override; + uint64_t ThreadID() const override; + int SuspendCount() const override; + int Priority() const override; + uint64_t ThreadSpecificDataAddress() const override; + std::vector ExtraMemory() const override; + + private: + const ThreadSnapshot* snapshot_; + MemorySnapshotSanitized stack_; + + DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotSanitized); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_SANITIZED_THREAD_SNAPSHOT_SANITIZED_H_ diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index b199841b..3aad7bae 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -123,6 +123,14 @@ 'posix/timezone.cc', 'posix/timezone.h', 'process_snapshot.h', + 'sanitized/memory_snapshot_sanitized.cc', + 'sanitized/memory_snapshot_sanitized.h', + 'sanitized/module_snapshot_sanitized.cc', + 'sanitized/module_snapshot_sanitized.h', + 'sanitized/process_snapshot_sanitized.cc', + 'sanitized/process_snapshot_sanitized.h', + 'sanitized/thread_snapshot_sanitized.cc', + 'sanitized/thread_snapshot_sanitized.h', 'snapshot_constants.h', 'system_snapshot.h', 'thread_snapshot.h', @@ -176,6 +184,7 @@ 'sources/': [ ['exclude', '^elf/'], ['exclude', '^crashpad_types/'], + ['exclude', '^sanitized/'], ], }], ['target_arch!="ia32" and target_arch!="x64"', { diff --git a/snapshot/snapshot_test.gyp b/snapshot/snapshot_test.gyp index 6f0fc90e..c2ec97d3 100644 --- a/snapshot/snapshot_test.gyp +++ b/snapshot/snapshot_test.gyp @@ -92,6 +92,7 @@ 'mac/system_snapshot_mac_test.cc', 'minidump/process_snapshot_minidump_test.cc', 'posix/timezone_test.cc', + 'sanitized/process_snapshot_sanitized_test.cc', 'win/cpu_context_win_test.cc', 'win/exception_snapshot_win_test.cc', 'win/extra_memory_ranges_test.cc', @@ -145,6 +146,7 @@ 'sources/': [ ['exclude', '^elf/'], ['exclude', '^crashpad_types/'], + ['exclude', '^sanitized/'], ], }], ], diff --git a/util/BUILD.gn b/util/BUILD.gn index 514f4838..abb55d5f 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -104,6 +104,8 @@ static_library("util") { "misc/pdb_structures.h", "misc/random_string.cc", "misc/random_string.h", + "misc/range_set.cc", + "misc/range_set.h", "misc/reinterpret_bytes.cc", "misc/reinterpret_bytes.h", "misc/scoped_forbid_return.cc", @@ -256,9 +258,7 @@ static_library("util") { defines = [ "CRASHPAD_USE_BORINGSSL" ] if (crashpad_is_in_fuchsia) { - deps += [ - "//third_party/boringssl" - ] + deps += [ "//third_party/boringssl" ] } else { libs = [ "crypto", @@ -490,13 +490,13 @@ if (!crashpad_is_android) { } if (use_boringssl_for_http_transport_socket) { - data_deps = [ ":generate_test_server_key" ] + data_deps = [ + ":generate_test_server_key", + ] defines = [ "CRASHPAD_USE_BORINGSSL" ] if (crashpad_is_in_fuchsia) { - deps += [ - "//third_party/boringssl" - ] + deps += [ "//third_party/boringssl" ] } else { libs = [ "crypto", @@ -526,6 +526,7 @@ source_set("util_test") { "misc/initialization_state_test.cc", "misc/paths_test.cc", "misc/random_string_test.cc", + "misc/range_set_test.cc", "misc/reinterpret_bytes_test.cc", "misc/scoped_forbid_return_test.cc", "misc/time_test.cc", diff --git a/util/misc/range_set.cc b/util/misc/range_set.cc new file mode 100644 index 00000000..5a7040fe --- /dev/null +++ b/util/misc/range_set.cc @@ -0,0 +1,55 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/misc/range_set.h" + +#include + +namespace crashpad { + +RangeSet::RangeSet() = default; + +RangeSet::~RangeSet() = default; + +void RangeSet::Insert(VMAddress base, VMSize size) { + if (!size) { + return; + } + + VMAddress last = base + size - 1; + + auto overlapping_range = ranges_.lower_bound(base); +#define OVERLAPPING_RANGES_BASE overlapping_range->second +#define OVERLAPPING_RANGES_LAST overlapping_range->first + while (overlapping_range != ranges_.end() && + OVERLAPPING_RANGES_BASE <= last) { + base = std::min(base, OVERLAPPING_RANGES_BASE); + last = std::max(last, OVERLAPPING_RANGES_LAST); + auto tmp = overlapping_range; + ++overlapping_range; + ranges_.erase(tmp); + } +#undef OVERLAPPING_RANGES_BASE +#undef OVERLAPPING_RANGES_LAST + + ranges_[last] = base; +} + +bool RangeSet::Contains(VMAddress address) const { + auto range_above_address = ranges_.lower_bound(address); + return range_above_address != ranges_.end() && + range_above_address->second <= address; +} + +} // namespace crashpad diff --git a/util/misc/range_set.h b/util/misc/range_set.h new file mode 100644 index 00000000..573705ec --- /dev/null +++ b/util/misc/range_set.h @@ -0,0 +1,51 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_MISC_RANGE_SET_H_ +#define CRASHPAD_UTIL_MISC_RANGE_SET_H_ + +#include + +#include "base/macros.h" +#include "util/misc/address_types.h" + +namespace crashpad { + +//! \brief A set of VMAddress ranges. +class RangeSet { + public: + RangeSet(); + ~RangeSet(); + + //! \brief Inserts a range into the set. + //! + //! \param[in] base The low address of the range. + //! \param[in] size The size of the range. + void Insert(VMAddress base, VMSize size); + + //! \brief Returns `true` if \a address falls within a range in this set. + bool Contains(VMAddress address) const; + + private: + // Keys are the highest address in the range. Values are the base address of + // the range. Overlapping ranges are merged on insertion. Adjacent ranges may + // be merged. + std::map ranges_; + + DISALLOW_COPY_AND_ASSIGN(RangeSet); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_RANGE_SET_H_ diff --git a/util/misc/range_set_test.cc b/util/misc/range_set_test.cc new file mode 100644 index 00000000..d5a3cd14 --- /dev/null +++ b/util/misc/range_set_test.cc @@ -0,0 +1,116 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/misc/range_set.h" + +#include + +#include + +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" +#include "util/misc/address_types.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { +namespace test { +namespace { + +void ExpectRangeIsContained(const RangeSet& ranges, + VMAddress base, + VMSize size) { + for (VMAddress addr = base; addr < base + size; ++addr) { + SCOPED_TRACE(base::StringPrintf("0x%" PRIx64 " in range 0x%" PRIx64 + ":0x%" PRIx64, + addr, + base, + base + size)); + EXPECT_TRUE(ranges.Contains(addr)); + } +} + +TEST(RangeSet, Basic) { + RangeSet ranges; + auto base = FromPointerCast(&ranges); + VMSize size = sizeof(ranges); + ranges.Insert(base, size); + ExpectRangeIsContained(ranges, base, size); + EXPECT_FALSE(ranges.Contains(base - 1)); + EXPECT_FALSE(ranges.Contains(base + size)); +} + +TEST(RangeSet, ZeroSizedRange) { + RangeSet ranges; + auto addr = FromPointerCast(&ranges); + ranges.Insert(addr, 0); + EXPECT_FALSE(ranges.Contains(addr)); +} + +TEST(RangeSet, DuplicateRanges) { + RangeSet ranges; + auto base = FromPointerCast(&ranges); + VMSize size = sizeof(ranges); + ranges.Insert(base, size); + ranges.Insert(base, size); + ExpectRangeIsContained(ranges, base, size); +} + +TEST(RangeSet, OverlappingRanges) { + RangeSet ranges; + ranges.Insert(37, 16); + ranges.Insert(9, 9); + ranges.Insert(17, 42); + + EXPECT_TRUE(ranges.Contains(9)); + EXPECT_TRUE(ranges.Contains(17)); + EXPECT_TRUE(ranges.Contains(36)); + EXPECT_TRUE(ranges.Contains(37)); + EXPECT_TRUE(ranges.Contains(52)); + EXPECT_TRUE(ranges.Contains(58)); +} + +TEST(RangeSet, SubRangeInLargeRange) { + constexpr size_t kBufferSize = 2 << 22; + auto buf = std::make_unique(kBufferSize); + + RangeSet ranges; + auto addr = FromPointerCast(buf.get()); + + ranges.Insert(addr, kBufferSize); + EXPECT_TRUE(ranges.Contains(addr)); + EXPECT_TRUE(ranges.Contains(addr + kBufferSize - 1)); + + ranges.Insert(addr, kBufferSize / 2); + EXPECT_TRUE(ranges.Contains(addr)); + EXPECT_TRUE(ranges.Contains(addr + kBufferSize / 2 - 1)); + EXPECT_TRUE(ranges.Contains(addr + kBufferSize - 1)); +} + +TEST(RangeSet, LargeOverlappingRanges) { + constexpr size_t kBufferSize = 2 << 23; + auto buf = std::make_unique(kBufferSize); + + RangeSet ranges; + auto addr = FromPointerCast(buf.get()); + + ranges.Insert(addr, 3 * kBufferSize / 4); + ranges.Insert(addr + kBufferSize / 4, 3 * kBufferSize / 4); + EXPECT_TRUE(ranges.Contains(addr)); + EXPECT_TRUE(ranges.Contains(addr + kBufferSize - 1)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/util.gyp b/util/util.gyp index 5590b1f3..4cbc13e1 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -153,6 +153,8 @@ 'misc/pdb_structures.h', 'misc/random_string.cc', 'misc/random_string.h', + 'misc/range_set.cc', + 'misc/range_set.h', 'misc/reinterpret_bytes.cc', 'misc/reinterpret_bytes.h', 'misc/scoped_forbid_return.cc', diff --git a/util/util_test.gyp b/util/util_test.gyp index d28fe9ee..ff3559e3 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -79,6 +79,7 @@ 'misc/paths_test.cc', 'misc/scoped_forbid_return_test.cc', 'misc/random_string_test.cc', + 'misc/range_set_test.cc', 'misc/reinterpret_bytes_test.cc', 'misc/time_test.cc', 'misc/uuid_test.cc',