android/linux: Support modules with shared relros on Android

Add MemoryMap::Iterator to support different strategies for locating
the start of module mappings on Android and Linux.

Beginning with API 21, Bionic provides android_dlopen_ext() which
allows passing a file descriptor with an existing relro segment to the
loader. This means that the mapping containing the dynamic segment
could have a name, device, and inode which are different than the
other mappings for the module.

The revised strategy for Android at API 21+ is to search all mappings
in reverse order from they dynamic array mapping until a module is
parsed with the expected dynamic array address.

Linux and Android 20- continue to select mappings using the device,
inode, and file offsets of the mappings.

Bug: crashpad:268
Change-Id: I30e95e51cb6874c00875d2a9c57f1249877736d4
Reviewed-on: https://chromium-review.googlesource.com/c/1374375
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2018-12-17 14:27:46 -08:00 committed by Commit Bot
parent 8b6f158d20
commit 2afe6dc210
11 changed files with 256 additions and 86 deletions

View File

@ -75,6 +75,8 @@ compat_target("compat") {
if (crashpad_is_android) {
sources += [
"android/android/api-level.cc",
"android/android/api-level.h",
"android/dlfcn_internal.cc",
"android/dlfcn_internal.h",
"android/elf.h",

View File

@ -0,0 +1,50 @@
// 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 <android/api-level.h>
#include <dlfcn.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/system_properties.h>
#include "dlfcn_internal.h"
#if __ANDROID_API__ < 29
extern "C" {
int android_get_device_api_level() {
using FuncType = int (*)();
static const FuncType bionic_get_device_api_level =
reinterpret_cast<FuncType>(
crashpad::internal::Dlsym(RTLD_NEXT, "android_get_device_api_level"));
if (bionic_get_device_api_level) {
return bionic_get_device_api_level();
}
char api_string[PROP_VALUE_MAX];
int length = __system_property_get("ro.build.version.sdk", api_string);
if (length <= 0) {
return -1;
}
int api_level = atoi(api_string);
return api_level > 0 ? api_level : -1;
}
} // extern "C"
#endif // __ANDROID_API__ < 29

View File

@ -0,0 +1,38 @@
// 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_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
#define CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
#include_next <android/api-level.h>
#include <sys/cdefs.h>
#if __ANDROID_API__ < 29
#ifdef __cplusplus
extern "C" {
#endif
// Returns the API level of the device or -1 if it can't be determined. This
// function is provided by Bionic at API 29.
int android_get_device_api_level();
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __ANDROID_API__ < 29
#endif // CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_

View File

@ -20,6 +20,8 @@
{
'target_name': 'crashpad_compat',
'sources': [
'android/android/api-level.cc',
'android/android/api-level.h',
'android/dlfcn_internal.cc',
'android/dlfcn_internal.h',
'android/elf.h',

View File

@ -100,10 +100,9 @@ void LocateExecutable(PtraceConnection* connection,
ASSERT_TRUE(memory_map.Initialize(connection));
const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs);
ASSERT_TRUE(phdr_mapping);
std::vector<const MemoryMap::Mapping*> possible_mappings =
memory_map.FindFilePossibleMmapStarts(*phdr_mapping);
ASSERT_EQ(possible_mappings.size(), 1u);
*elf_address = possible_mappings[0]->range.Base();
auto possible_mappings = memory_map.FindFilePossibleMmapStarts(*phdr_mapping);
ASSERT_EQ(possible_mappings->Count(), 1u);
*elf_address = possible_mappings->Next()->range.Base();
}
#endif // OS_FUCHSIA

View File

@ -37,28 +37,13 @@
#include "util/process/process_memory_range.h"
#if defined(OS_ANDROID)
#include <sys/system_properties.h>
#include <android/api-level.h>
#endif
namespace crashpad {
namespace test {
namespace {
#if defined(OS_ANDROID)
int AndroidRuntimeAPI() {
char api_string[PROP_VALUE_MAX];
int length = __system_property_get("ro.build.version.sdk", api_string);
if (length <= 0) {
return -1;
}
int api_level;
bool success =
base::StringToInt(base::StringPiece(api_string, length), &api_level);
return success ? api_level : -1;
}
#endif // OS_ANDROID
void TestAgainstTarget(PtraceConnection* connection) {
// Use ElfImageReader on the main executable which can tell us the debug
// address. glibc declares the symbol _r_debug in link.h which we can use to
@ -74,10 +59,10 @@ void TestAgainstTarget(PtraceConnection* connection) {
const MemoryMap::Mapping* phdr_mapping = mappings.FindMapping(phdrs);
ASSERT_TRUE(phdr_mapping);
std::vector<const MemoryMap::Mapping*> exe_mappings =
mappings.FindFilePossibleMmapStarts(*phdr_mapping);
ASSERT_EQ(exe_mappings.size(), 1u);
LinuxVMAddress elf_address = exe_mappings[0]->range.Base();
auto exe_mappings = mappings.FindFilePossibleMmapStarts(*phdr_mapping);
ASSERT_EQ(exe_mappings->Count(), 1u);
LinuxVMAddress elf_address = exe_mappings->Next()->range.Base();
ProcessMemoryLinux memory;
ASSERT_TRUE(memory.Initialize(connection->GetProcessID()));
@ -94,7 +79,7 @@ void TestAgainstTarget(PtraceConnection* connection) {
ASSERT_TRUE(debug.Initialize(range, debug_address));
#if defined(OS_ANDROID)
const int android_runtime_api = AndroidRuntimeAPI();
const int android_runtime_api = android_get_device_api_level();
ASSERT_GE(android_runtime_api, 1);
EXPECT_NE(debug.Executable()->name.find("crashpad_snapshot_test"),
@ -143,13 +128,13 @@ void TestAgainstTarget(PtraceConnection* connection) {
mappings.FindMapping(module.dynamic_array);
ASSERT_TRUE(dyn_mapping);
std::vector<const MemoryMap::Mapping*> possible_mappings =
mappings.FindFilePossibleMmapStarts(*dyn_mapping);
ASSERT_GE(possible_mappings.size(), 1u);
auto possible_mappings = mappings.FindFilePossibleMmapStarts(*dyn_mapping);
ASSERT_GE(possible_mappings->Count(), 1u);
std::unique_ptr<ElfImageReader> module_reader;
const MemoryMap::Mapping* module_mapping = nullptr;
for (const auto mapping : possible_mappings) {
const MemoryMap::Mapping* mapping = nullptr;
while ((mapping = possible_mappings->Next())) {
auto parsed_module = std::make_unique<ElfImageReader>();
VMAddress dynamic_address;
if (parsed_module->Initialize(range, mapping->range.Base()) &&

View File

@ -29,6 +29,10 @@
#include "util/linux/auxiliary_vector.h"
#include "util/linux/proc_stat_reader.h"
#if defined(OS_ANDROID)
#include <android/api-level.h>
#endif
namespace crashpad {
namespace {
@ -352,17 +356,15 @@ void ProcessReaderLinux::InitializeModules() {
return;
}
std::vector<const MemoryMap::Mapping*> possible_mappings =
auto possible_mappings =
memory_map_.FindFilePossibleMmapStarts(*phdr_mapping);
for (auto riter = possible_mappings.rbegin();
riter != possible_mappings.rend();
++riter) {
auto mapping = *riter;
const MemoryMap::Mapping* mapping = nullptr;
while ((mapping = possible_mappings->Next())) {
auto parsed_exe = std::make_unique<ElfImageReader>();
if (parsed_exe->Initialize(
range,
mapping->range.Base(),
/* verbose= */ possible_mappings.size() == 1) &&
/* verbose= */ possible_mappings->Count() == 1) &&
parsed_exe->GetProgramHeaderTableAddress() == phdrs) {
exe_mapping = mapping;
exe_reader = std::move(parsed_exe);
@ -370,7 +372,8 @@ void ProcessReaderLinux::InitializeModules() {
}
}
if (!exe_mapping) {
LOG(ERROR) << "no exe mappings " << possible_mappings.size();
LOG(ERROR) << "no exe mappings 0x" << std::hex
<< phdr_mapping->range.Base();
return;
}
}
@ -407,18 +410,30 @@ void ProcessReaderLinux::InitializeModules() {
continue;
}
std::vector<const MemoryMap::Mapping*> possible_mappings =
#if defined(OS_ANDROID)
// Beginning at API 21, Bionic provides android_dlopen_ext() which allows
// passing a file descriptor with an existing relro segment to the loader.
// This means that the mapping attributes of dyn_mapping may be unrelated
// to the attributes of the other mappings for the module. In this case,
// search all mappings in reverse order from dyn_mapping until a module is
// parsed whose dynamic address matches the value in the debug link.
static int api_level = android_get_device_api_level();
auto possible_mappings =
(api_level >= 21 || api_level < 0)
? memory_map_.ReverseIteratorFrom(*dyn_mapping)
: memory_map_.FindFilePossibleMmapStarts(*dyn_mapping);
#else
auto possible_mappings =
memory_map_.FindFilePossibleMmapStarts(*dyn_mapping);
for (auto riter = possible_mappings.rbegin();
riter != possible_mappings.rend();
++riter) {
auto mapping = *riter;
#endif
const MemoryMap::Mapping* mapping = nullptr;
while ((mapping = possible_mappings->Next())) {
auto parsed_module = std::make_unique<ElfImageReader>();
VMAddress dynamic_address;
if (parsed_module->Initialize(
range,
mapping->range.Base(),
/* verbose= */ possible_mappings.size() == 1) &&
/* verbose= */ possible_mappings->Count() == 1) &&
parsed_module->GetDynamicArrayAddress(&dynamic_address) &&
dynamic_address == entry.dynamic_array) {
module_mapping = mapping;
@ -427,7 +442,8 @@ void ProcessReaderLinux::InitializeModules() {
}
}
if (!module_mapping) {
LOG(ERROR) << "no module mappings " << possible_mappings.size();
LOG(ERROR) << "no module mappings 0x" << std::hex
<< dyn_mapping->range.Base();
continue;
}
}

View File

@ -715,7 +715,7 @@ void ExpectTestModule(ProcessReaderLinux* reader,
auto dynamic_mapping = reader->GetMemoryMap()->FindMapping(dynamic_addr);
auto mappings =
reader->GetMemoryMap()->FindFilePossibleMmapStarts(*dynamic_mapping);
EXPECT_EQ(mappings.size(), 2u);
EXPECT_EQ(mappings->Count(), 2u);
return;
}
}

View File

@ -204,6 +204,48 @@ ParseResult ParseMapsLine(DelimitedFileReader* maps_file_reader,
return ParseResult::kSuccess;
}
class SparseReverseIterator : public MemoryMap::Iterator {
public:
SparseReverseIterator(const std::vector<const MemoryMap::Mapping*>& mappings)
: mappings_(mappings), riter_(mappings_.rbegin()){};
SparseReverseIterator() : mappings_(), riter_(mappings_.rend()) {}
// Iterator:
const MemoryMap::Mapping* Next() override {
return riter_ == mappings_.rend() ? nullptr : *(riter_++);
}
unsigned int Count() override { return mappings_.rend() - riter_; }
private:
std::vector<const MemoryMap::Mapping*> mappings_;
std::vector<const MemoryMap::Mapping*>::reverse_iterator riter_;
DISALLOW_COPY_AND_ASSIGN(SparseReverseIterator);
};
class FullReverseIterator : public MemoryMap::Iterator {
public:
FullReverseIterator(
std::vector<MemoryMap::Mapping>::const_reverse_iterator rbegin,
std::vector<MemoryMap::Mapping>::const_reverse_iterator rend)
: riter_(rbegin), rend_(rend) {}
// Iterator:
const MemoryMap::Mapping* Next() override {
return riter_ == rend_ ? nullptr : &*riter_++;
}
unsigned int Count() override { return rend_ - riter_; }
private:
std::vector<MemoryMap::Mapping>::const_reverse_iterator riter_;
std::vector<MemoryMap::Mapping>::const_reverse_iterator rend_;
DISALLOW_COPY_AND_ASSIGN(FullReverseIterator);
};
} // namespace
MemoryMap::Mapping::Mapping()
@ -296,7 +338,7 @@ const MemoryMap::Mapping* MemoryMap::FindMappingWithName(
return nullptr;
}
std::vector<const MemoryMap::Mapping*> MemoryMap::FindFilePossibleMmapStarts(
std::unique_ptr<MemoryMap::Iterator> MemoryMap::FindFilePossibleMmapStarts(
const Mapping& mapping) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
@ -308,12 +350,12 @@ std::vector<const MemoryMap::Mapping*> MemoryMap::FindFilePossibleMmapStarts(
for (const auto& candidate : mappings_) {
if (mapping.Equals(candidate)) {
possible_starts.push_back(&candidate);
return possible_starts;
return std::make_unique<SparseReverseIterator>(possible_starts);
}
}
LOG(ERROR) << "mapping not found";
return std::vector<const Mapping*>();
return std::make_unique<SparseReverseIterator>();
}
#if defined(OS_ANDROID)
@ -341,7 +383,7 @@ std::vector<const MemoryMap::Mapping*> MemoryMap::FindFilePossibleMmapStarts(
possible_starts.push_back(&candidate);
}
if (mapping.Equals(candidate)) {
return possible_starts;
return std::make_unique<SparseReverseIterator>(possible_starts);
}
}
}
@ -359,12 +401,23 @@ std::vector<const MemoryMap::Mapping*> MemoryMap::FindFilePossibleMmapStarts(
possible_starts.push_back(&candidate);
}
if (mapping.Equals(candidate)) {
return possible_starts;
return std::make_unique<SparseReverseIterator>(possible_starts);
}
}
LOG(ERROR) << "mapping not found";
return std::vector<const Mapping*>();
return std::make_unique<SparseReverseIterator>();
}
std::unique_ptr<MemoryMap::Iterator> MemoryMap::ReverseIteratorFrom(
const Mapping& target) const {
for (auto riter = mappings_.crbegin(); riter != mappings_.rend(); ++riter) {
if (riter->Equals(target)) {
return std::make_unique<FullReverseIterator>(riter, mappings_.rend());
}
}
return std::make_unique<FullReverseIterator>(mappings_.rend(),
mappings_.rend());
}
} // namespace crashpad

View File

@ -17,6 +17,7 @@
#include <sys/types.h>
#include <memory>
#include <string>
#include <vector>
@ -76,6 +77,24 @@ class MemoryMap {
//! it was obtained from.
const Mapping* FindMappingWithName(const std::string& name) const;
//! \brief An abstract base class for iterating over ordered sets of mappings
//! in a MemoryMap.
class Iterator {
public:
virtual ~Iterator() = default;
//! \return the mapping pointed to by the iterator and advance the iterator
//! to the next mapping. If there are no more mappings, this method
//! returns `nullptr` on all subsequent invocations.
virtual const Mapping* Next() = 0;
//! \return the number of mappings remaining.
virtual unsigned int Count() = 0;
protected:
Iterator() = default;
};
//! \brief Find possible initial mappings of files mapped over several
//! segments.
//!
@ -99,10 +118,15 @@ class MemoryMap {
//! map a file, \a mapping is returned in \a possible_starts.
//!
//! \param[in] mapping A Mapping whose series to find the start of.
//! \return a vector of the possible mapping starts.
std::vector<const Mapping*> FindFilePossibleMmapStarts(
//! \return a reverse iterator over the possible mapping starts, starting from
//! the mapping with highest base address.
std::unique_ptr<Iterator> FindFilePossibleMmapStarts(
const Mapping& mapping) const;
//! \return A reverse iterator over all mappings in the MemoryMap from \a
//! mapping to the start of the MemoryMap.
std::unique_ptr<Iterator> ReverseIteratorFrom(const Mapping& mapping) const;
private:
std::vector<Mapping> mappings_;
InitializationStateDcheck initialized_;

View File

@ -373,22 +373,20 @@ void ExpectFindFilePossibleMmapStarts(LinuxVMAddress mapping_start,
ASSERT_NE(mapping1, mapping2);
ASSERT_NE(mapping2, mapping3);
std::vector<const MemoryMap::Mapping*> mappings;
mappings = map.FindFilePossibleMmapStarts(*mapping1);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
auto mappings = map.FindFilePossibleMmapStarts(*mapping1);
ASSERT_EQ(mappings->Count(), 1u);
EXPECT_EQ(mappings->Next(), mapping1);
mappings = map.FindFilePossibleMmapStarts(*mapping2);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping2);
ASSERT_EQ(mappings->Count(), 1u);
EXPECT_EQ(mappings->Next(), mapping2);
mappings = map.FindFilePossibleMmapStarts(*mapping3);
#if defined(OS_ANDROID)
EXPECT_EQ(mappings.size(), 2u);
EXPECT_EQ(mappings->Count(), 2u);
#else
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
ASSERT_EQ(mappings->Count(), 1u);
EXPECT_EQ(mappings->Next(), mapping1);
#endif
}
@ -432,29 +430,30 @@ TEST(MemoryMap, FindFilePossibleMmapStarts) {
ASSERT_NE(mapping1, mapping2);
ASSERT_NE(mapping2, mapping3);
std::vector<const MemoryMap::Mapping*> mappings;
#if defined(OS_ANDROID)
mappings = map.FindFilePossibleMmapStarts(*mapping1);
EXPECT_EQ(mappings.size(), 1u);
auto mappings = map.FindFilePossibleMmapStarts(*mapping1);
EXPECT_EQ(mappings->Count(), 1u);
EXPECT_EQ(mappings->Next(), mapping1);
EXPECT_EQ(mappings->Next(), nullptr);
mappings = map.FindFilePossibleMmapStarts(*mapping2);
EXPECT_EQ(mappings.size(), 2u);
EXPECT_EQ(mappings->Count(), 2u);
mappings = map.FindFilePossibleMmapStarts(*mapping3);
EXPECT_EQ(mappings.size(), 3u);
EXPECT_EQ(mappings->Count(), 3u);
#else
mappings = map.FindFilePossibleMmapStarts(*mapping1);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
auto mappings = map.FindFilePossibleMmapStarts(*mapping1);
ASSERT_EQ(mappings->Count(), 1u);
EXPECT_EQ(mappings->Next(), mapping1);
EXPECT_EQ(mappings->Next(), nullptr);
mappings = map.FindFilePossibleMmapStarts(*mapping2);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
ASSERT_EQ(mappings->Count(), 1u);
EXPECT_EQ(mappings->Next(), mapping1);
mappings = map.FindFilePossibleMmapStarts(*mapping3);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
ASSERT_EQ(mappings->Count(), 1u);
EXPECT_EQ(mappings->Next(), mapping1);
#endif
#if defined(ARCH_CPU_64_BITS)
@ -464,7 +463,9 @@ TEST(MemoryMap, FindFilePossibleMmapStarts) {
#endif
MemoryMap::Mapping bad_mapping;
bad_mapping.range.SetRange(is_64_bit, 0, 1);
EXPECT_EQ(map.FindFilePossibleMmapStarts(bad_mapping).size(), 0u);
mappings = map.FindFilePossibleMmapStarts(bad_mapping);
EXPECT_EQ(mappings->Count(), 0u);
EXPECT_EQ(mappings->Next(), nullptr);
}
// Make the second page an anonymous mapping
@ -578,45 +579,45 @@ TEST(MemoryMap, FindFilePossibleMmapStarts_MultipleStarts) {
ASSERT_TRUE(mapping);
auto possible_starts = map.FindFilePossibleMmapStarts(*mapping);
#if defined(OS_ANDROID)
EXPECT_EQ(possible_starts.size(), 1u);
EXPECT_EQ(possible_starts->Count(), 1u);
#else
EXPECT_EQ(possible_starts.size(), 0u);
EXPECT_EQ(possible_starts->Count(), 0u);
#endif
mapping = map.FindMapping(file_mapping1.addr_as<VMAddress>());
ASSERT_TRUE(mapping);
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
#if defined(OS_ANDROID)
EXPECT_EQ(possible_starts.size(), 2u);
EXPECT_EQ(possible_starts->Count(), 2u);
#else
EXPECT_EQ(possible_starts.size(), 1u);
EXPECT_EQ(possible_starts->Count(), 1u);
#endif
mapping = map.FindMapping(file_mapping2.addr_as<VMAddress>());
ASSERT_TRUE(mapping);
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
#if defined(OS_ANDROID)
EXPECT_EQ(possible_starts.size(), 3u);
EXPECT_EQ(possible_starts->Count(), 3u);
#else
EXPECT_EQ(possible_starts.size(), 2u);
EXPECT_EQ(possible_starts->Count(), 2u);
#endif
mapping = map.FindMapping(file_mapping3.addr_as<VMAddress>());
ASSERT_TRUE(mapping);
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
#if defined(OS_ANDROID)
EXPECT_EQ(possible_starts.size(), 4u);
EXPECT_EQ(possible_starts->Count(), 4u);
#else
EXPECT_EQ(possible_starts.size(), 3u);
EXPECT_EQ(possible_starts->Count(), 3u);
#endif
mapping = map.FindMapping(file_mapping4.addr_as<VMAddress>());
ASSERT_TRUE(mapping);
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
#if defined(OS_ANDROID)
EXPECT_EQ(possible_starts.size(), 5u);
EXPECT_EQ(possible_starts->Count(), 5u);
#else
EXPECT_EQ(possible_starts.size(), 4u);
EXPECT_EQ(possible_starts->Count(), 4u);
#endif
}