mirror of
https://github.com/chromium/crashpad.git
synced 2025-01-03 03:45:25 +08:00
00491d58ee
The load bias is documented to be the difference between the preferred and actual load address for a module, but is declared as an unsigned number, and math using it relies on it being a pointer-precisioned two's complement number that might cause over- or under-flow. ElfImageReader and DebugRendezvous both provide ways to get the load bias for a module and are corroborated in tests. However, the load bias computed by DebugRendezvous does not have access to the preferred address, so there is not enough information to determine the signedness to use with a VMOffset. This patch compares the load biases modulo the numeric range for a pointer to ignore the signedness of the value. Also update the test module to trigger a negative load bias. Bug: chromium:1147922 Change-Id: I55bc49195cfb2def06777e26388380fb9bc0f710 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2569886 Commit-Queue: Joshua Peraza <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
263 lines
8.4 KiB
C++
263 lines
8.4 KiB
C++
// Copyright 2020 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/linux/test_modules.h"
|
|
|
|
#include <elf.h>
|
|
|
|
#include <limits>
|
|
|
|
#include "base/check_op.h"
|
|
#include "base/files/file_path.h"
|
|
#include "build/build_config.h"
|
|
#include "gtest/gtest.h"
|
|
#include "test/test_paths.h"
|
|
#include "util/file/filesystem.h"
|
|
#include "util/file/file_writer.h"
|
|
|
|
namespace crashpad {
|
|
namespace test {
|
|
|
|
bool WriteTestModule(const base::FilePath& module_path,
|
|
const std::string& soname) {
|
|
#if defined(ARCH_CPU_64_BITS)
|
|
using Ehdr = Elf64_Ehdr;
|
|
using Phdr = Elf64_Phdr;
|
|
using Shdr = Elf64_Shdr;
|
|
using Dyn = Elf64_Dyn;
|
|
using Sym = Elf64_Sym;
|
|
unsigned char elf_class = ELFCLASS64;
|
|
#else
|
|
using Ehdr = Elf32_Ehdr;
|
|
using Phdr = Elf32_Phdr;
|
|
using Shdr = Elf32_Shdr;
|
|
using Dyn = Elf32_Dyn;
|
|
using Sym = Elf32_Sym;
|
|
unsigned char elf_class = ELFCLASS32;
|
|
#endif
|
|
|
|
struct {
|
|
Ehdr ehdr;
|
|
struct {
|
|
Phdr load1;
|
|
Phdr load2;
|
|
Phdr dynamic;
|
|
} phdr_table;
|
|
struct {
|
|
Dyn hash;
|
|
Dyn strtab;
|
|
Dyn symtab;
|
|
Dyn strsz;
|
|
Dyn syment;
|
|
Dyn soname;
|
|
Dyn null;
|
|
} dynamic_array;
|
|
struct {
|
|
Elf32_Word nbucket;
|
|
Elf32_Word nchain;
|
|
Elf32_Word bucket;
|
|
Elf32_Word chain;
|
|
} hash_table;
|
|
char string_table[32];
|
|
struct {
|
|
} section_header_string_table;
|
|
struct {
|
|
Sym und_symbol;
|
|
} symbol_table;
|
|
struct {
|
|
Shdr null;
|
|
Shdr dynamic;
|
|
Shdr string_table;
|
|
Shdr section_header_string_table;
|
|
} shdr_table;
|
|
} module = {};
|
|
|
|
module.ehdr.e_ident[EI_MAG0] = ELFMAG0;
|
|
module.ehdr.e_ident[EI_MAG1] = ELFMAG1;
|
|
module.ehdr.e_ident[EI_MAG2] = ELFMAG2;
|
|
module.ehdr.e_ident[EI_MAG3] = ELFMAG3;
|
|
|
|
module.ehdr.e_ident[EI_CLASS] = elf_class;
|
|
|
|
#if defined(ARCH_CPU_LITTLE_ENDIAN)
|
|
module.ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
|
|
#else
|
|
module.ehdr.e_ident[EI_DATA] = ELFDATA2MSB;
|
|
#endif // ARCH_CPU_LITTLE_ENDIAN
|
|
|
|
module.ehdr.e_ident[EI_VERSION] = EV_CURRENT;
|
|
|
|
module.ehdr.e_type = ET_DYN;
|
|
|
|
#if defined(ARCH_CPU_X86)
|
|
module.ehdr.e_machine = EM_386;
|
|
#elif defined(ARCH_CPU_X86_64)
|
|
module.ehdr.e_machine = EM_X86_64;
|
|
#elif defined(ARCH_CPU_ARMEL)
|
|
module.ehdr.e_machine = EM_ARM;
|
|
#elif defined(ARCH_CPU_ARM64)
|
|
module.ehdr.e_machine = EM_AARCH64;
|
|
#elif defined(ARCH_CPU_MIPSEL) || defined(ARCH_CPU_MIPS64EL)
|
|
module.ehdr.e_machine = EM_MIPS;
|
|
#endif
|
|
|
|
module.ehdr.e_version = EV_CURRENT;
|
|
module.ehdr.e_ehsize = sizeof(module.ehdr);
|
|
|
|
module.ehdr.e_phoff = offsetof(decltype(module), phdr_table);
|
|
module.ehdr.e_phnum = sizeof(module.phdr_table) / sizeof(Phdr);
|
|
module.ehdr.e_phentsize = sizeof(Phdr);
|
|
|
|
module.ehdr.e_shoff = offsetof(decltype(module), shdr_table);
|
|
module.ehdr.e_shentsize = sizeof(Shdr);
|
|
module.ehdr.e_shnum = sizeof(module.shdr_table) / sizeof(Shdr);
|
|
module.ehdr.e_shstrndx =
|
|
offsetof(decltype(module.shdr_table), section_header_string_table) /
|
|
sizeof(Shdr);
|
|
|
|
const size_t page_size = getpagesize();
|
|
auto align = [page_size](uintptr_t addr) {
|
|
return (addr + page_size - 1) & ~(page_size - 1);
|
|
};
|
|
constexpr size_t segment_size = offsetof(decltype(module), shdr_table);
|
|
|
|
// This test module covers cases where:
|
|
// 1. Multiple segments are mapped from file offset 0.
|
|
// 2. Load bias is negative.
|
|
|
|
const uintptr_t load2_vaddr = align(std::numeric_limits<uintptr_t>::max() -
|
|
align(segment_size) - page_size);
|
|
const uintptr_t load1_vaddr = load2_vaddr - align(segment_size);
|
|
|
|
module.phdr_table.load1.p_type = PT_LOAD;
|
|
module.phdr_table.load1.p_offset = 0;
|
|
module.phdr_table.load1.p_vaddr = load1_vaddr;
|
|
module.phdr_table.load1.p_filesz = segment_size;
|
|
module.phdr_table.load1.p_memsz = segment_size;
|
|
module.phdr_table.load1.p_flags = PF_R;
|
|
module.phdr_table.load1.p_align = page_size;
|
|
|
|
module.phdr_table.load2.p_type = PT_LOAD;
|
|
module.phdr_table.load2.p_offset = 0;
|
|
module.phdr_table.load2.p_vaddr = load2_vaddr;
|
|
module.phdr_table.load2.p_filesz = segment_size;
|
|
module.phdr_table.load2.p_memsz = segment_size;
|
|
module.phdr_table.load2.p_flags = PF_R | PF_W;
|
|
module.phdr_table.load2.p_align = page_size;
|
|
|
|
module.phdr_table.dynamic.p_type = PT_DYNAMIC;
|
|
module.phdr_table.dynamic.p_offset =
|
|
offsetof(decltype(module), dynamic_array);
|
|
module.phdr_table.dynamic.p_vaddr =
|
|
load2_vaddr + module.phdr_table.dynamic.p_offset;
|
|
module.phdr_table.dynamic.p_filesz = sizeof(module.dynamic_array);
|
|
module.phdr_table.dynamic.p_memsz = sizeof(module.dynamic_array);
|
|
module.phdr_table.dynamic.p_flags = PF_R | PF_W;
|
|
module.phdr_table.dynamic.p_align = 8;
|
|
|
|
module.dynamic_array.hash.d_tag = DT_HASH;
|
|
module.dynamic_array.hash.d_un.d_ptr =
|
|
load1_vaddr + offsetof(decltype(module), hash_table);
|
|
module.dynamic_array.strtab.d_tag = DT_STRTAB;
|
|
module.dynamic_array.strtab.d_un.d_ptr =
|
|
load1_vaddr + offsetof(decltype(module), string_table);
|
|
module.dynamic_array.symtab.d_tag = DT_SYMTAB;
|
|
module.dynamic_array.symtab.d_un.d_ptr =
|
|
load1_vaddr + offsetof(decltype(module), symbol_table);
|
|
module.dynamic_array.strsz.d_tag = DT_STRSZ;
|
|
module.dynamic_array.strsz.d_un.d_val = sizeof(module.string_table);
|
|
module.dynamic_array.syment.d_tag = DT_SYMENT;
|
|
module.dynamic_array.syment.d_un.d_val = sizeof(Sym);
|
|
constexpr size_t kSonameOffset = 1;
|
|
module.dynamic_array.soname.d_tag = DT_SONAME;
|
|
module.dynamic_array.soname.d_un.d_val = kSonameOffset;
|
|
|
|
module.dynamic_array.null.d_tag = DT_NULL;
|
|
|
|
module.hash_table.nbucket = 1;
|
|
module.hash_table.nchain = 1;
|
|
module.hash_table.bucket = 0;
|
|
module.hash_table.chain = 0;
|
|
|
|
if (sizeof(module.string_table) < soname.size() + 2) {
|
|
ADD_FAILURE() << "string table too small";
|
|
return false;
|
|
}
|
|
module.string_table[0] = '\0';
|
|
memcpy(&module.string_table[kSonameOffset], soname.c_str(), soname.size());
|
|
|
|
module.shdr_table.null.sh_type = SHT_NULL;
|
|
|
|
module.shdr_table.dynamic.sh_name = 0;
|
|
module.shdr_table.dynamic.sh_type = SHT_DYNAMIC;
|
|
module.shdr_table.dynamic.sh_flags = SHF_WRITE | SHF_ALLOC;
|
|
module.shdr_table.dynamic.sh_addr = module.phdr_table.dynamic.p_vaddr;
|
|
module.shdr_table.dynamic.sh_offset = module.phdr_table.dynamic.p_offset;
|
|
module.shdr_table.dynamic.sh_size = module.phdr_table.dynamic.p_filesz;
|
|
module.shdr_table.dynamic.sh_link =
|
|
offsetof(decltype(module.shdr_table), string_table) / sizeof(Shdr);
|
|
|
|
module.shdr_table.string_table.sh_name = 0;
|
|
module.shdr_table.string_table.sh_type = SHT_STRTAB;
|
|
module.shdr_table.string_table.sh_offset =
|
|
offsetof(decltype(module), string_table);
|
|
module.shdr_table.string_table.sh_size = sizeof(module.string_table);
|
|
|
|
module.shdr_table.section_header_string_table.sh_name = 0;
|
|
module.shdr_table.section_header_string_table.sh_type = SHT_STRTAB;
|
|
module.shdr_table.section_header_string_table.sh_offset =
|
|
offsetof(decltype(module), section_header_string_table);
|
|
module.shdr_table.section_header_string_table.sh_size =
|
|
sizeof(module.section_header_string_table);
|
|
|
|
FileWriter writer;
|
|
if (!writer.Open(module_path,
|
|
FileWriteMode::kCreateOrFail,
|
|
FilePermissions::kWorldReadable)) {
|
|
ADD_FAILURE();
|
|
return false;
|
|
}
|
|
|
|
if (!writer.Write(&module, sizeof(module))) {
|
|
ADD_FAILURE();
|
|
LoggingRemoveFile(module_path);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ScopedModuleHandle LoadTestModule(const std::string& module_name,
|
|
const std::string& module_soname) {
|
|
base::FilePath module_path(
|
|
TestPaths::Executable().DirName().Append(module_name));
|
|
|
|
if (!WriteTestModule(module_path, module_soname)) {
|
|
return ScopedModuleHandle(nullptr);
|
|
}
|
|
EXPECT_TRUE(IsRegularFile(module_path));
|
|
|
|
ScopedModuleHandle handle(
|
|
dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL));
|
|
EXPECT_TRUE(handle.valid())
|
|
<< "dlopen: " << module_path.value() << " " << dlerror();
|
|
|
|
EXPECT_TRUE(LoggingRemoveFile(module_path));
|
|
|
|
return handle;
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace crashpad
|