diff --git a/util/linux/process_memory_range.cc b/util/linux/process_memory_range.cc new file mode 100644 index 00000000..bdf9adca --- /dev/null +++ b/util/linux/process_memory_range.cc @@ -0,0 +1,93 @@ +// Copyright 2017 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/linux/process_memory_range.h" + +#include +#include + +#include "base/logging.h" + +namespace crashpad { + +ProcessMemoryRange::ProcessMemoryRange() + : memory_(nullptr), range_(), initialized_() {} + +ProcessMemoryRange::~ProcessMemoryRange() {} + +bool ProcessMemoryRange::Initialize(const ProcessMemory* memory, + bool is_64_bit, + LinuxVMAddress base, + LinuxVMSize size) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + memory_ = memory; + range_.SetRange(is_64_bit, base, size); + if (!range_.IsValid()) { + LOG(ERROR) << "invalid range"; + return false; + } + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +bool ProcessMemoryRange::Initialize(const ProcessMemory* memory, + bool is_64_bit) { + LinuxVMSize max = is_64_bit ? std::numeric_limits::max() + : std::numeric_limits::max(); + return Initialize(memory, is_64_bit, 0, max); +} + +bool ProcessMemoryRange::Initialize(const ProcessMemoryRange& other) { + return Initialize(other.memory_, + other.range_.Is64Bit(), + other.range_.Base(), + other.range_.Size()); +} + +bool ProcessMemoryRange::RestrictRange(LinuxVMAddress base, LinuxVMSize size) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + CheckedLinuxAddressRange new_range(range_.Is64Bit(), base, size); + if (!new_range.IsValid() || !range_.ContainsRange(new_range)) { + LOG(ERROR) << "invalid range"; + return false; + } + range_ = new_range; + return true; +} + +bool ProcessMemoryRange::Read(LinuxVMAddress address, + size_t size, + void* buffer) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + CheckedLinuxAddressRange read_range(range_.Is64Bit(), address, size); + if (!read_range.IsValid() || !range_.ContainsRange(read_range)) { + LOG(ERROR) << "read out of range"; + return false; + } + return memory_->Read(address, size, buffer); +} + +bool ProcessMemoryRange::ReadCStringSizeLimited(LinuxVMAddress address, + size_t size, + std::string* string) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (!range_.ContainsValue(address)) { + LOG(ERROR) << "read out of range"; + return false; + } + size = std::min(static_cast(size), range_.End() - address); + return memory_->ReadCStringSizeLimited(address, size, string); +} + +} // namespace crashpad diff --git a/util/linux/process_memory_range.h b/util/linux/process_memory_range.h new file mode 100644 index 00000000..eb08de86 --- /dev/null +++ b/util/linux/process_memory_range.h @@ -0,0 +1,123 @@ +// Copyright 2017 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_LINUX_PROCESS_MEMORY_RANGE_H_ +#define CRASHPAD_UTIL_LINUX_PROCESS_MEMORY_RANGE_H_ + +#include + +#include + +#include "base/macros.h" +#include "util/linux/address_types.h" +#include "util/linux/checked_linux_address_range.h" +#include "util/linux/process_memory.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +//! \brief Provides range protected access to the memory of another process. +class ProcessMemoryRange { + public: + ProcessMemoryRange(); + ~ProcessMemoryRange(); + + //! \brief Initializes this object. + //! + //! One of the Initialize methods must be successfully called on this object + //! before calling any other. + //! + //! \param[in] memory The memory reader to delegate to. + //! \param[in] is_64_bit Whether the target process is 64-bit. + //! \param[in] base The base address of the initial range. + //! \param[in] size The size of the initial range. + //! \return `true` on success. `false` on failure with a message logged. + bool Initialize(const ProcessMemory* memory, + bool is_64_bit, + LinuxVMAddress base, + LinuxVMSize size); + + //! \brief Initializes this object with the maximum range for the address + //! space. + //! + //! One of the Initialize methods must be successfully called on this object + //! before calling any other. + //! + //! \param[in] memory The memory reader to delegate to. + //! \param[in] is_64_bit Whether the target process is 64-bit. + bool Initialize(const ProcessMemory* memory, bool is_64_bit); + + //! \brief Initializes this object from an existing memory range. + //! + //! One of the Initialize methods must be successfully called on this object + //! before calling any other. + //! + //! \param[in] other The memory range object to initialize from. + //! \return `true` on success. `false` on failure with a message logged. + bool Initialize(const ProcessMemoryRange& other); + + //! \brief Returns whether the range is part of a 64-bit address space. + bool Is64Bit() const { return range_.Is64Bit(); } + + //! \brief Shrinks the range to the new base and size. + //! + //! The new range must be contained within the existing range for this object. + //! + //! \param[in] base The new base of the range. + //! \param[in] size The new size of the range. + //! \return `true` on success. `false` on failure with a message logged. + bool RestrictRange(LinuxVMAddress base, LinuxVMSize size); + + //! \brief Copies memory from the target process into a caller-provided buffer + //! in the current process. + //! + //! \param[in] address The address, in the target process' address space, of + //! the memory region to copy. + //! \param[in] size The size, in bytes, of the memory region to copy. + //! \a buffer must be at least this size. + //! \param[out] buffer The buffer into which the contents of the other + //! process' memory will be copied. + //! + //! \return `true` on success, with \a buffer filled appropriately. `false` on + //! failure, with a message logged. + bool Read(LinuxVMAddress address, size_t size, void* buffer) const; + + //! \brief Reads a `NUL`-terminated C string from the target process into a + //! string in the current process. + //! + //! \param[in] address The address, in the target process’s address space, of + //! the string to copy. + //! \param[in] size The maximum number of bytes to read. The string is + //! required to be `NUL`-terminated within this many bytes. + //! \param[out] string The string read from the other process. + //! + //! \return `true` on success, with \a string set appropriately. `false` on + //! failure, with a message logged. Failures can occur, for example, when + //! a `NUL` terminator is not found within \a size bytes, or when + //! encountering unmapped or unreadable pages. + bool ReadCStringSizeLimited(LinuxVMAddress address, + size_t size, + std::string* string) const; + + private: + const ProcessMemory* memory_; // weak + CheckedLinuxAddressRange range_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessMemoryRange); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_PROCESS_MEMORY_RANGE_H_ diff --git a/util/linux/process_memory_range_test.cc b/util/linux/process_memory_range_test.cc new file mode 100644 index 00000000..bd23ae55 --- /dev/null +++ b/util/linux/process_memory_range_test.cc @@ -0,0 +1,91 @@ +// Copyright 2017 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/linux/process_memory_range.h" + +#include + +#include + +#include "base/logging.h" +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { +namespace test { +namespace { + +struct TestObject { + char string1[16]; + char string2[16]; +} kTestObject = {"string1", "string2"}; + +TEST(ProcessMemoryRange, Basic) { + pid_t pid = getpid(); +#if defined(ARCH_CPU_64_BITS) + constexpr bool is_64_bit = true; +#else + constexpr bool is_64_bit = false; +#endif // ARCH_CPU_64_BITS + + ProcessMemory memory; + ASSERT_TRUE(memory.Initialize(pid)); + + ProcessMemoryRange range; + ASSERT_TRUE(range.Initialize(&memory, is_64_bit)); + EXPECT_EQ(range.Is64Bit(), is_64_bit); + + // Both strings are accessible within the object's range. + auto object_addr = FromPointerCast(&kTestObject); + EXPECT_TRUE(range.RestrictRange(object_addr, sizeof(kTestObject))); + + TestObject object; + ASSERT_TRUE(range.Read(object_addr, sizeof(object), &object)); + EXPECT_EQ(memcmp(&object, &kTestObject, sizeof(object)), 0); + + std::string string; + auto string1_addr = FromPointerCast(kTestObject.string1); + auto string2_addr = FromPointerCast(kTestObject.string2); + ASSERT_TRUE(range.ReadCStringSizeLimited( + string1_addr, arraysize(kTestObject.string1), &string)); + EXPECT_STREQ(string.c_str(), kTestObject.string1); + + ASSERT_TRUE(range.ReadCStringSizeLimited( + string2_addr, arraysize(kTestObject.string2), &string)); + EXPECT_STREQ(string.c_str(), kTestObject.string2); + + // Limit the range to remove access to string2. + ProcessMemoryRange range2; + ASSERT_TRUE(range2.Initialize(range)); + ASSERT_TRUE( + range2.RestrictRange(string1_addr, arraysize(kTestObject.string1))); + EXPECT_TRUE(range2.ReadCStringSizeLimited( + string1_addr, arraysize(kTestObject.string1), &string)); + EXPECT_FALSE(range2.ReadCStringSizeLimited( + string2_addr, arraysize(kTestObject.string2), &string)); + EXPECT_FALSE(range2.Read(object_addr, sizeof(object), &object)); + + // String reads fail if the NUL terminator is outside the range. + ASSERT_TRUE(range2.RestrictRange(string1_addr, strlen(kTestObject.string1))); + EXPECT_FALSE(range2.ReadCStringSizeLimited( + string1_addr, arraysize(kTestObject.string1), &string)); + + // New range outside the old range. + EXPECT_FALSE(range2.RestrictRange(string1_addr - 1, 1)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/util.gyp b/util/util.gyp index 6a052273..0d2364b5 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -52,6 +52,8 @@ 'linux/memory_map.h', 'linux/process_memory.cc', 'linux/process_memory.h', + 'linux/process_memory_range.cc', + 'linux/process_memory_range.h', 'linux/thread_info.cc', 'linux/thread_info.h', 'linux/scoped_ptrace_attach.cc', diff --git a/util/util_test.gyp b/util/util_test.gyp index 1b4cff95..72f25533 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -41,6 +41,7 @@ 'file/string_file_test.cc', 'linux/auxiliary_vector_test.cc', 'linux/memory_map_test.cc', + 'linux/process_memory_range_test.cc', 'linux/process_memory_test.cc', 'linux/thread_info_test.cc', 'linux/scoped_ptrace_attach_test.cc',