#include <link.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/utsname.h>
#include "base/compiler_specific.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/system/sys_info.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "base/android/linker/linker_jni.h"
extern char __executable_start;
extern "C" {
int dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data),
void* data) __attribute__((weak_import));
}
namespace chromium_android_linker {
namespace {
class LibraryRangeFinder {
public:
explicit LibraryRangeFinder(uintptr_t address) : load_address_(address) {}
uintptr_t load_address() const { return load_address_; }
size_t load_size() const { return load_size_; }
uintptr_t relro_start() const { return relro_start_; }
size_t relro_size() const { return relro_size_; }
static int VisitLibraryPhdrs(dl_phdr_info* info,
[[maybe_unused]] size_t size,
void* data);
private:
uintptr_t load_address_;
size_t load_size_ = 0;
uintptr_t relro_start_ = 0;
size_t relro_size_ = 0;
};
int LibraryRangeFinder::VisitLibraryPhdrs(dl_phdr_info* info,
[[maybe_unused]] size_t size,
void* data) {
auto* finder = reinterpret_cast<LibraryRangeFinder*>(data);
ElfW(Addr) lookup_address = static_cast<ElfW(Addr)>(finder->load_address());
auto min_vaddr = std::numeric_limits<ElfW(Addr)>::max();
ElfW(Addr) max_vaddr = 0;
ElfW(Addr) min_relro_vaddr = ~0;
ElfW(Addr) max_relro_vaddr = 0;
const size_t kPageSize = sysconf(_SC_PAGESIZE);
bool is_matching = false;
for (int i = 0; i < info->dlpi_phnum; ++i) {
const ElfW(Phdr)* phdr = &UNSAFE_TODO(info->dlpi_phdr[i]);
switch (phdr->p_type) {
case PT_LOAD:
if (lookup_address == info->dlpi_addr + phdr->p_vaddr) {
is_matching = true;
}
if (phdr->p_vaddr < min_vaddr) {
min_vaddr = phdr->p_vaddr;
}
if (phdr->p_vaddr + phdr->p_memsz > max_vaddr) {
max_vaddr = phdr->p_vaddr + phdr->p_memsz;
}
break;
case PT_GNU_RELRO:
min_relro_vaddr = PageStart(kPageSize, phdr->p_vaddr);
max_relro_vaddr = phdr->p_vaddr + phdr->p_memsz;
if (min_relro_vaddr < min_vaddr) {
min_vaddr = min_relro_vaddr;
}
if (max_vaddr < max_relro_vaddr) {
max_vaddr = max_relro_vaddr;
}
break;
default:
break;
}
}
if (is_matching) {
finder->load_size_ =
PageEnd(kPageSize, max_vaddr) - PageStart(kPageSize, min_vaddr);
finder->relro_size_ = PageEnd(kPageSize, max_relro_vaddr) -
PageStart(kPageSize, min_relro_vaddr);
finder->relro_start_ =
info->dlpi_addr + PageStart(kPageSize, min_relro_vaddr);
return 1;
}
return 0;
}
}
class LinkerTest : public testing::Test {
public:
LinkerTest() = default;
~LinkerTest() override = default;
};
TEST_F(LinkerTest, CreatedRegionIsSealed) {
constexpr size_t kRelroSize = 1 << 21;
void* relro_address = mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(MAP_FAILED, relro_address);
NativeLibInfo lib_info = {0, 0};
lib_info.set_relro_info_for_testing(
reinterpret_cast<uintptr_t>(relro_address), kRelroSize);
UNSAFE_TODO(memset(relro_address, 0xEE, kRelroSize));
ASSERT_EQ(true, lib_info.CreateSharedRelroFdForTesting());
int relro_fd = lib_info.get_relro_fd_for_testing();
ASSERT_NE(-1, relro_fd);
base::ScopedFD scoped_fd(relro_fd);
void* ro_address =
mmap(nullptr, kRelroSize, PROT_READ, MAP_SHARED, relro_fd, 0);
ASSERT_NE(MAP_FAILED, ro_address);
EXPECT_EQ(0xEEEEEEEEU, *reinterpret_cast<uint32_t*>(ro_address));
int not_equal = UNSAFE_TODO(memcmp(relro_address, ro_address, kRelroSize));
EXPECT_EQ(0, not_equal);
munmap(ro_address, kRelroSize);
EXPECT_EQ(MAP_FAILED, mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
MAP_SHARED, relro_fd, 0));
EXPECT_EQ(MAP_FAILED, mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE, relro_fd, 0));
EXPECT_EQ(MAP_FAILED,
mmap(nullptr, kRelroSize, PROT_WRITE, MAP_SHARED, relro_fd, 0));
EXPECT_EQ(MAP_FAILED,
mmap(nullptr, kRelroSize, PROT_WRITE, MAP_PRIVATE, relro_fd, 0));
}
TEST_F(LinkerTest, FindReservedMemoryRegion) {
size_t address, size;
bool found_reservation = FindWebViewReservation(&address, &size);
if (found_reservation) {
EXPECT_LE(130U * 1024 * 1024, size);
return;
}
static const size_t kSize = 19U * 1024 * 1024;
void* synthetic_region_start =
mmap(nullptr, kSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(MAP_FAILED, synthetic_region_start);
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, synthetic_region_start, kSize,
"[anon:libwebview reservation]");
EXPECT_TRUE(FindWebViewReservation(&address, &size));
EXPECT_EQ(kSize, size);
EXPECT_EQ(reinterpret_cast<void*>(address), synthetic_region_start);
munmap(synthetic_region_start, kSize);
}
TEST_F(LinkerTest, FindLibraryRanges) {
static int var_inside = 3;
NativeLibInfo lib_info = {0, 0};
uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
lib_info.set_load_address(executable_start);
EXPECT_TRUE(lib_info.FindRelroAndLibraryRangesInElfForTesting());
EXPECT_EQ(executable_start, lib_info.load_address());
uintptr_t inside_library = reinterpret_cast<uintptr_t>(&var_inside);
EXPECT_LE(executable_start, inside_library);
EXPECT_LE(inside_library,
lib_info.load_address() + lib_info.get_load_size_for_testing());
EXPECT_LE(lib_info.load_address(), lib_info.get_relro_start_for_testing());
EXPECT_LE(lib_info.get_relro_start_for_testing(),
lib_info.load_address() + lib_info.get_load_size_for_testing());
}
TEST_F(LinkerTest, FindLibraryRangesWhenLoadAddressWasReset) {
NativeLibInfo other_lib_info = {0, 0};
uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
other_lib_info.set_load_address(executable_start);
other_lib_info.set_relro_fd_for_testing(123);
NativeLibInfo lib_info = {0, 0};
EXPECT_FALSE(lib_info.CompareRelroAndReplaceItBy(other_lib_info));
}
TEST_F(LinkerTest, LibraryRangesViaIteratePhdr) {
if (!dl_iterate_phdr) {
ASSERT_TRUE(false) << "dl_iterate_phdr() not found";
}
uintptr_t executable_start = reinterpret_cast<uintptr_t>(&__executable_start);
LibraryRangeFinder finder(executable_start);
ASSERT_EQ(1, dl_iterate_phdr(&LibraryRangeFinder::VisitLibraryPhdrs,
reinterpret_cast<void*>(&finder)));
ASSERT_LE(finder.relro_start() + finder.relro_size(),
finder.load_address() + finder.load_size());
NativeLibInfo lib_info2 = {0, 0};
lib_info2.set_load_address(executable_start);
EXPECT_TRUE(lib_info2.FindRelroAndLibraryRangesInElfForTesting());
EXPECT_EQ(finder.load_address(), lib_info2.load_address());
EXPECT_EQ(finder.load_size(), lib_info2.get_load_size_for_testing());
EXPECT_EQ(finder.relro_start(), lib_info2.get_relro_start_for_testing());
}
}