From d5b3a1d531255ef240e8c1caa6fbaa808de9743c Mon Sep 17 00:00:00 2001 From: Justin Cohen Date: Fri, 7 May 2021 14:32:20 -0400 Subject: [PATCH] ios: Add support for ScopedVMRead and RAWLOG Adds wrapper to vm_read and vm_deallocate memory to allow for safe in-process memory reads during crashes. Also adds a logging utility safe for in-process exception handling. Bug: crashpad: 31 Change-Id: I658f3181cbec40a79e304b7306466e10c003564f Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2875349 Reviewed-by: Mark Mentovai Commit-Queue: Justin Cohen --- util/BUILD.gn | 9 +++- util/ios/raw_logging.cc | 66 +++++++++++++++++++++++ util/ios/raw_logging.h | 37 +++++++++++++ util/ios/scoped_vm_read.cc | 62 +++++++++++++++++++++ util/ios/scoped_vm_read.h | 96 +++++++++++++++++++++++++++++++++ util/ios/scoped_vm_read_test.cc | 83 ++++++++++++++++++++++++++++ 6 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 util/ios/raw_logging.cc create mode 100644 util/ios/raw_logging.h create mode 100644 util/ios/scoped_vm_read.cc create mode 100644 util/ios/scoped_vm_read.h create mode 100644 util/ios/scoped_vm_read_test.cc diff --git a/util/BUILD.gn b/util/BUILD.gn index 48bf8e2e..71bf1ce6 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -384,6 +384,10 @@ crashpad_static_library("util") { "ios/exception_processor.mm", "ios/ios_system_data_collector.h", "ios/ios_system_data_collector.mm", + "ios/raw_logging.cc", + "ios/raw_logging.h", + "ios/scoped_vm_read.cc", + "ios/scoped_vm_read.h", ] } @@ -787,7 +791,10 @@ source_set("util_test") { } if (crashpad_is_ios) { - sources += [ "ios/exception_processor_test.mm" ] + sources += [ + "ios/exception_processor_test.mm", + "ios/scoped_vm_read_test.cc", + ] sources -= [ "process/process_memory_range_test.cc", diff --git a/util/ios/raw_logging.cc b/util/ios/raw_logging.cc new file mode 100644 index 00000000..f037c452 --- /dev/null +++ b/util/ios/raw_logging.cc @@ -0,0 +1,66 @@ +// Copyright 2021 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/ios/raw_logging.h" + +#include +#include +#include + +#include "base/posix/eintr_wrapper.h" + +namespace crashpad { +namespace internal { + +void RawLogString(const char* message) { + const size_t message_len = strlen(message); + size_t bytes_written = 0; + while (bytes_written < message_len) { + int rv = HANDLE_EINTR(write( + STDERR_FILENO, message + bytes_written, message_len - bytes_written)); + if (rv < 0) { + // Give up, nothing we can do now. + break; + } + bytes_written += rv; + } +} + +void RawLogInt(unsigned int number) { + char buffer[20]; + char* digit = &buffer[sizeof(buffer) - 1]; + *digit = '\0'; + do { + *(--digit) = (number % 10) + '0'; + number /= 10; + } while (number != 0); + RawLogString(digit); +} + +// Prints `path:linenum message:error` (with optional `:error`). +void RawLog(const char* file, int line, const char* message, int error) { + RawLogString(file); + HANDLE_EINTR(write(STDERR_FILENO, ":", 1)); + RawLogInt(line); + HANDLE_EINTR(write(STDERR_FILENO, " ", 1)); + RawLogString(message); + if (error) { + RawLogString(": "); + RawLogInt(error); + } + HANDLE_EINTR(write(STDERR_FILENO, "\n", 1)); +} + +} // namespace internal +} // namespace crashpad diff --git a/util/ios/raw_logging.h b/util/ios/raw_logging.h new file mode 100644 index 00000000..505aa671 --- /dev/null +++ b/util/ios/raw_logging.h @@ -0,0 +1,37 @@ +// Copyright 2021 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_IOS_EXCEPTION_LOGGING_H_ +#define CRASHPAD_UTIL_IOS_EXCEPTION_LOGGING_H_ + +namespace crashpad { +namespace internal { + +//! \brief Log \a message to stderr in a way that is safe to run during an +//! in-process crash. Also prints the given file, line number and an +//! optional error code. +//! +//! Note: RUNS-DURING-CRASH. +void RawLog(const char* file, int line, const char* message, int error); + +} // namespace internal +} // namespace crashpad + +#define RAW_LOG(message) \ + ::crashpad::internal::RawLog(__FILE__, __LINE__, message, 0) + +#define RAW_LOG_ERROR(error, message) \ + ::crashpad::internal::RawLog(__FILE__, __LINE__, message, error) + +#endif // CRASHPAD_UTIL_IOS_EXCEPTION_LOGGING_H_ diff --git a/util/ios/scoped_vm_read.cc b/util/ios/scoped_vm_read.cc new file mode 100644 index 00000000..33c2ad05 --- /dev/null +++ b/util/ios/scoped_vm_read.cc @@ -0,0 +1,62 @@ +// Copyright 2021 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/ios/scoped_vm_read.h" + +#include "util/ios/raw_logging.h" + +namespace crashpad { +namespace internal { + +ScopedVMReadInternal::ScopedVMReadInternal() + : data_(0), vm_read_data_(0), vm_read_data_count_(0) {} + +ScopedVMReadInternal::~ScopedVMReadInternal() { + if (data_) { + kern_return_t kr = + vm_deallocate(mach_task_self(), vm_read_data_, vm_read_data_count_); + if (kr != KERN_SUCCESS) + RAW_LOG_ERROR(kr, "vm_deallocate"); + } +} + +bool ScopedVMReadInternal::Read(const void* data, const size_t data_length) { + if (data_) { + kern_return_t kr = + vm_deallocate(mach_task_self(), vm_read_data_, vm_read_data_count_); + if (kr != KERN_SUCCESS) + RAW_LOG_ERROR(kr, "vm_deallocate"); + data_ = 0; + } + vm_address_t data_address = reinterpret_cast(data); + vm_address_t page_region_address = trunc_page(data_address); + vm_size_t page_region_size = + round_page(data_address - page_region_address + data_length); + kern_return_t kr = vm_read(mach_task_self(), + page_region_address, + page_region_size, + &vm_read_data_, + &vm_read_data_count_); + + if (kr == KERN_SUCCESS) { + data_ = vm_read_data_ + (data_address - page_region_address); + return true; + } else { + RAW_LOG_ERROR(kr, "vm_read"); + return false; + } +} + +} // namespace internal +} // namespace crashpad diff --git a/util/ios/scoped_vm_read.h b/util/ios/scoped_vm_read.h new file mode 100644 index 00000000..86d80393 --- /dev/null +++ b/util/ios/scoped_vm_read.h @@ -0,0 +1,96 @@ +// Copyright 2021 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_IOS_SCOPED_VM_READ_H_ +#define CRASHPAD_UTIL_IOS_SCOPED_VM_READ_H_ + +#include + +#include "base/macros.h" + +namespace crashpad { +namespace internal { + +//! \brief Non-templated internal class to be used by ScopedVMRead. +//! +//! Note: RUNS-DURING-CRASH. +class ScopedVMReadInternal { + public: + ScopedVMReadInternal(); + ~ScopedVMReadInternal(); + + //! \brief Releases any previously read data and vm_reads \a data. Logs an + //! error on failure. + //! + //! \param[in] data Memory to be read by vm_read. + //! \param[in] data_length Length of \a data. + //! + //! \return `true` if all the data was read. Logs an error and returns false + //! on failure + bool Read(const void* data, size_t data_length); + + vm_address_t data() const { return data_; } + + private: + // The address of the requested data. + vm_address_t data_; + + // The rounded down page boundary of the requested data. + vm_address_t vm_read_data_; + + // The size of the pages that were actually read. + mach_msg_type_number_t vm_read_data_count_; + + DISALLOW_COPY_AND_ASSIGN(ScopedVMReadInternal); +}; + +//! \brief A scoped wrapper for calls to `vm_read` and `vm_deallocate`. Allows +//! in-process handler to safely read memory for the intermediate dump. +//! +//! Note: RUNS-DURING-CRASH. +template +class ScopedVMRead { + public: + ScopedVMRead() : internal_() {} + ~ScopedVMRead() {} + + //! \brief Releases any previously read data and vm_reads data. + //! + //! \param[in] data Memory to be read by vm_read. + //! \param[in] count Length of \a data. + //! + //! \return `true` if all the data was read. Logs an error and returns false + //! on failure + bool Read(const void* data, size_t count = 1) { + size_t data_length = count * sizeof(T); + return internal_.Read(data, data_length); + } + + //! \brief Returns the pointer to memory safe to read during the in-process + //! crash handler. + T* operator->() const { return get(); } + + //! \brief Returns the pointer to memory safe to read during the in-process + //! crash handler. + T* get() const { return reinterpret_cast(internal_.data()); } + + private: + ScopedVMReadInternal internal_; + DISALLOW_COPY_AND_ASSIGN(ScopedVMRead); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_UTIL_IOS_SCOPED_VM_READ_H_ diff --git a/util/ios/scoped_vm_read_test.cc b/util/ios/scoped_vm_read_test.cc new file mode 100644 index 00000000..80edf042 --- /dev/null +++ b/util/ios/scoped_vm_read_test.cc @@ -0,0 +1,83 @@ +// Copyright 2021 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/ios/scoped_vm_read.h" + +#include + +#include "base/mac/scoped_mach_vm.h" +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(ScopedVMReadTest, BasicFunctionality) { + // bad data or count. + internal::ScopedVMRead vmread_bad; + ASSERT_FALSE(vmread_bad.Read(nullptr, 100)); + ASSERT_FALSE(vmread_bad.Read(reinterpret_cast(0x1000), 100)); + vm_address_t address = 1; + ASSERT_FALSE(vmread_bad.Read(&address, 1000000000)); + + // array + constexpr char read_me[] = "read me"; + internal::ScopedVMRead vmread_string; + ASSERT_TRUE(vmread_string.Read(read_me, strlen(read_me))); + EXPECT_STREQ(read_me, vmread_string.get()); + + // struct + timeval time_of_day; + EXPECT_TRUE(gettimeofday(&time_of_day, nullptr) == 0); + internal::ScopedVMRead vmread_time; + ASSERT_TRUE(vmread_time.Read(&time_of_day)); + EXPECT_EQ(time_of_day.tv_sec, vmread_time->tv_sec); + EXPECT_EQ(time_of_day.tv_usec, vmread_time->tv_usec); + + // reset. + timeval time_of_day2; + EXPECT_TRUE(gettimeofday(&time_of_day2, nullptr) == 0); + ASSERT_TRUE(vmread_time.Read(&time_of_day2)); + EXPECT_EQ(time_of_day2.tv_sec, vmread_time->tv_sec); + EXPECT_EQ(time_of_day2.tv_usec, vmread_time->tv_usec); +} + +TEST(ScopedVMReadTest, MissingMiddleVM) { + char* region; + vm_size_t page_size = getpagesize(); + vm_size_t region_size = page_size * 3; + ASSERT_EQ(vm_allocate(mach_task_self(), + reinterpret_cast(®ion), + region_size, + VM_FLAGS_ANYWHERE), + 0); + base::mac::ScopedMachVM vm_owner(reinterpret_cast(region), + region_size); + + internal::ScopedVMRead vmread_missing_middle; + ASSERT_TRUE(vmread_missing_middle.Read(region, region_size)); + + // Dealloc middle page. + ASSERT_EQ(vm_deallocate(mach_task_self(), + reinterpret_cast(region + page_size), + page_size), + 0); + + ASSERT_FALSE(vmread_missing_middle.Read(region, region_size)); + ASSERT_TRUE(vmread_missing_middle.Read(region, page_size)); +} + +} // namespace +} // namespace test +} // namespace crashpad