diff --git a/util/BUILD.gn b/util/BUILD.gn index 86ae6793..3819b153 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -329,6 +329,8 @@ static_library("util") { "posix/process_info_linux.cc", "process/process_memory_linux.cc", "process/process_memory_linux.h", + "process/process_memory_sanitized.cc", + "process/process_memory_sanitized.h", ] } @@ -651,6 +653,7 @@ source_set("util_test") { "linux/scoped_ptrace_attach_test.cc", "linux/socket_test.cc", "misc/capture_context_test_util_linux.cc", + "process/process_memory_sanitized_test.cc", ] } diff --git a/util/process/process_memory.h b/util/process/process_memory.h index 32e7472f..eeb78e97 100644 --- a/util/process/process_memory.h +++ b/util/process/process_memory.h @@ -126,6 +126,9 @@ class ProcessMemory { bool has_size, VMSize size, std::string* string) const; + + // Allow ProcessMemorySanitized to call ReadUpTo. + friend class ProcessMemorySanitized; }; } // namespace crashpad diff --git a/util/process/process_memory_sanitized.cc b/util/process/process_memory_sanitized.cc new file mode 100644 index 00000000..cafe5aef --- /dev/null +++ b/util/process/process_memory_sanitized.cc @@ -0,0 +1,65 @@ +// Copyright 2019 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/process/process_memory_sanitized.h" + +#include +#include +#include +#include + +#include +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace crashpad { + +ProcessMemorySanitized::ProcessMemorySanitized() + : ProcessMemory(), memory_(nullptr), whitelist_() {} + +ProcessMemorySanitized::~ProcessMemorySanitized() {} + +bool ProcessMemorySanitized::Initialize( + const ProcessMemory* memory, + const std::vector>* whitelist) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + memory_ = memory; + if (whitelist) + whitelist_ = *whitelist; + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +ssize_t ProcessMemorySanitized::ReadUpTo(VMAddress address, + size_t size, + void* buffer) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + VMAddress end = address + size; + for (auto&& entry : whitelist_) { + if (address >= entry.first && address < entry.second && + end >= entry.first && end <= entry.second) { + return memory_->ReadUpTo(address, size, buffer); + } + } + + DLOG(ERROR) + << "ProcessMemorySanitized failed to read unwhitelisted region. address=" + << address << " size=" << size; + return 0; +} + +} // namespace crashpad diff --git a/util/process/process_memory_sanitized.h b/util/process/process_memory_sanitized.h new file mode 100644 index 00000000..44439636 --- /dev/null +++ b/util/process/process_memory_sanitized.h @@ -0,0 +1,62 @@ +// Copyright 2019 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_PROCESS_PROCESS_MEMORY_SANITIZED_H_ +#define CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_SANITIZED_H_ + +#include + +#include +#include + +#include "base/macros.h" +#include "util/misc/address_types.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_memory.h" + +namespace crashpad { + +//! \brief Sanitized access to the memory of another process. +class ProcessMemorySanitized final : public ProcessMemory { + public: + ProcessMemorySanitized(); + ~ProcessMemorySanitized(); + + //! \brief Initializes this object to read memory from the underlying + //! \a memory object if the memory range is in the provided \a whitelist. + //! + //! This method must be called successfully prior to calling any other method + //! in this class. + //! + //! \param[in] memory The memory object to read whitelisted regions from. + //! \param[in] whitelist A whitelist of memory regions. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool Initialize( + const ProcessMemory* memory, + const std::vector>* whitelist); + + private: + ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override; + + const ProcessMemory* memory_; + InitializationStateDcheck initialized_; + std::vector> whitelist_; + + DISALLOW_COPY_AND_ASSIGN(ProcessMemorySanitized); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_PROCESS_PROCESS_MEMORY_SANITIZED_H_ diff --git a/util/process/process_memory_sanitized_test.cc b/util/process/process_memory_sanitized_test.cc new file mode 100644 index 00000000..ff5c9445 --- /dev/null +++ b/util/process/process_memory_sanitized_test.cc @@ -0,0 +1,64 @@ +// Copyright 2019 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/process/process_memory_sanitized.h" + +#include "gtest/gtest.h" +#include "test/process_type.h" +#include "util/misc/from_pointer_cast.h" +#include "util/process/process_memory_native.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(ProcessMemorySanitized, DenyOnEmptyWhitelist) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(GetSelfProcess())); + + char c = 42; + char out; + + ProcessMemorySanitized san_null; + san_null.Initialize(&memory, nullptr); + EXPECT_FALSE(san_null.Read(FromPointerCast(&c), 1, &out)); + + std::vector> whitelist; + ProcessMemorySanitized san_blank; + san_blank.Initialize(&memory, &whitelist); + EXPECT_FALSE(san_blank.Read(FromPointerCast(&c), 1, &out)); +} + +TEST(ProcessMemorySanitized, WhitelistingWorks) { + ProcessMemoryNative memory; + ASSERT_TRUE(memory.Initialize(GetSelfProcess())); + + char str[4] = "ABC"; + char out[4]; + + std::vector> whitelist; + whitelist.push_back(std::make_pair(FromPointerCast(str + 1), + FromPointerCast(str + 2))); + + ProcessMemorySanitized sanitized; + sanitized.Initialize(&memory, &whitelist); + + EXPECT_FALSE(sanitized.Read(FromPointerCast(str), 1, &out)); + EXPECT_TRUE(sanitized.Read(FromPointerCast(str + 1), 1, &out)); + EXPECT_FALSE(sanitized.Read(FromPointerCast(str + 2), 1, &out)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/util.gyp b/util/util.gyp index a438fa69..c7d735fb 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -395,6 +395,8 @@ ['OS=="linux" or OS=="android"', { 'sources': [ 'net/http_transport_socket.cc', + 'util/process_memory_sanitized.cc', + 'util/process_memory_sanitized.h', ], }, { # else: OS!="linux" 'sources!': [ diff --git a/util/util_test.gyp b/util/util_test.gyp index 573162e6..c819e6db 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -154,6 +154,11 @@ ['exclude', '^net/http_transport_test\\.cc$'], ] }], + ['OS=="linux" or OS=="android"', { + 'sources': [ + 'util/process_memory_sanitized_test.cc', + ], + }], ['OS!="linux" and OS!="android"', { 'sources/': [ ['exclude', '^process/'],