blob: 3bfd570902e478d4e7628bd001d99f32e9282d63 [file] [log] [blame] [edit]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/elfldltl/container.h>
#include <lib/elfldltl/diagnostics.h>
#include <lib/elfldltl/load.h>
#include <lib/elfldltl/memory.h>
#include <lib/elfldltl/phdr.h>
#include <lib/elfldltl/static-vector.h>
#include <lib/stdcompat/source_location.h>
#include <lib/stdcompat/span.h>
#include <lib/symbolizer-markup/writer.h>
#include "tests.h"
namespace {
constexpr size_t kPageSize = 0x1000;
FORMAT_TYPED_TEST_SUITE(ElfldltlLoadTests);
TYPED_TEST(ElfldltlLoadTests, FailToAdd) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
ExpectedSingleError error("too many PT_LOAD segments", ": maximum 0 < requested ", 1);
elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container> loadInfo;
Phdr phdr{.memsz = 1};
EXPECT_FALSE(loadInfo.AddSegment(error.diag(), kPageSize, phdr));
}
TYPED_TEST(ElfldltlLoadTests, AddEmptyPhdr) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container> loadInfo;
Phdr phdr{};
EXPECT_TRUE(loadInfo.AddSegment(diag, kPageSize, phdr));
}
TYPED_TEST(ElfldltlLoadTests, CreateConstantSegment) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StaticVector<1>::Container> loadInfo;
using ConstantSegment = typename decltype(loadInfo)::ConstantSegment;
Phdr phdr{.memsz = kPageSize * 10};
EXPECT_TRUE(loadInfo.AddSegment(diag, kPageSize, phdr));
const auto& segments = loadInfo.segments();
ASSERT_EQ(segments.size(), 1u);
const auto& variant = segments[0];
ASSERT_TRUE(std::holds_alternative<ConstantSegment>(variant));
EXPECT_EQ(std::get<ConstantSegment>(variant).memsz(), phdr.memsz);
}
TYPED_TEST(ElfldltlLoadTests, CreateZeroFillSegment) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StaticVector<1>::Container> loadInfo;
using ZeroFillSegment = typename decltype(loadInfo)::ZeroFillSegment;
Phdr phdr{.memsz = kPageSize * 5};
phdr.flags = Phdr::kRead | Phdr::kWrite;
EXPECT_TRUE(loadInfo.AddSegment(diag, kPageSize, phdr));
const auto& segments = loadInfo.segments();
ASSERT_EQ(segments.size(), 1u);
const auto& variant = segments[0];
ASSERT_TRUE(std::holds_alternative<ZeroFillSegment>(variant));
EXPECT_EQ(std::get<ZeroFillSegment>(variant).memsz(), phdr.memsz);
}
TYPED_TEST(ElfldltlLoadTests, CreateDataWithZeroFillSegment) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StaticVector<1>::Container> loadInfo;
using DataWithZeroFillSegment = typename decltype(loadInfo)::DataWithZeroFillSegment;
Phdr phdr{.filesz = kPageSize, .memsz = kPageSize * 5};
phdr.flags = Phdr::kRead | Phdr::kWrite;
EXPECT_TRUE(loadInfo.AddSegment(diag, kPageSize, phdr));
const auto& segments = loadInfo.segments();
ASSERT_EQ(segments.size(), 1u);
const auto& variant = segments[0];
ASSERT_TRUE(std::holds_alternative<DataWithZeroFillSegment>(variant));
EXPECT_EQ(std::get<DataWithZeroFillSegment>(variant).memsz(), phdr.memsz());
}
TYPED_TEST(ElfldltlLoadTests, CreateDataSegment) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StaticVector<1>::Container> loadInfo;
using DataSegment = typename decltype(loadInfo)::DataSegment;
Phdr phdr{.filesz = kPageSize, .memsz = kPageSize};
phdr.flags = Phdr::kRead | Phdr::kWrite;
EXPECT_TRUE(loadInfo.AddSegment(diag, kPageSize, phdr));
const auto& segments = loadInfo.segments();
ASSERT_EQ(segments.size(), 1u);
const auto& variant = segments[0];
ASSERT_TRUE(std::holds_alternative<DataSegment>(variant));
EXPECT_EQ(std::get<DataSegment>(variant).memsz(), phdr.memsz());
}
template <class Elf, bool Merged, template <class ElfLayout> typename Segment1,
template <class ElfLayout> typename Segment2,
template <class ElfLayout> typename GetPhdr1,
template <class ElfLayout> typename GetPhdr2>
void DoMergeTest() {
using Segment1T = Segment1<Elf>;
using Segment2T = Segment2<Elf>;
constexpr unsigned totalSegments = Merged ? 1 : 2;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StaticVector<2>::Container> loadInfo;
const auto& segments = loadInfo.segments();
int offset = 0;
auto phdr1 = GetPhdr1<Elf>{}(offset);
auto phdr2 = GetPhdr2<Elf>{}(offset);
auto expectedSize = Merged ? phdr1.memsz() + phdr2.memsz() : phdr2.memsz();
loadInfo.AddSegment(diag, kPageSize, phdr1);
ASSERT_EQ(segments.size(), 1u);
ASSERT_TRUE(std::holds_alternative<Segment1T>(segments.back()));
EXPECT_EQ(std::get<Segment1T>(segments.back()).memsz(), phdr1.memsz());
loadInfo.AddSegment(diag, kPageSize, phdr2);
ASSERT_EQ(segments.size(), totalSegments);
ASSERT_TRUE(std::holds_alternative<Segment2T>(segments.back()));
EXPECT_EQ(std::get<Segment2T>(segments.back()).memsz(), expectedSize);
}
template <class Elf, template <class ElfLayout> typename Segment1,
template <class ElfLayout> typename Segment2,
template <class ElfLayout> typename GetPhdr1,
template <class ElfLayout> typename GetPhdr2>
void MergeTest() {
DoMergeTest<Elf, true, Segment1, Segment2, GetPhdr1, GetPhdr2>();
}
template <class Elf, template <class ElfLayout> typename Segment1,
template <class ElfLayout> typename Segment2,
template <class ElfLayout> typename GetPhdr1,
template <class ElfLayout> typename GetPhdr2>
void NotMergedTest() {
DoMergeTest<Elf, false, Segment1, Segment2, GetPhdr1, GetPhdr2>();
}
template <class Elf, template <class ElfLayout> typename Segment,
template <class ElfLayout> typename GetPhdr>
void MergeSameTest() {
MergeTest<Elf, Segment, Segment, GetPhdr, GetPhdr>();
}
template <uint64_t Flags, uint64_t FileSz = kPageSize, uint64_t MemSz = kPageSize>
struct CreatePhdr {
template <typename Elf>
struct type {
auto operator()(int& offset) {
using Phdr = typename Elf::Phdr;
Phdr phdr{.type = elfldltl::ElfPhdrType::kLoad,
.offset = offset,
.vaddr = offset,
.filesz = FileSz,
.memsz = MemSz};
phdr.flags = Flags;
offset += kPageSize;
return phdr;
}
};
};
template <typename Elf>
using ConstantSegment =
typename elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container>::ConstantSegment;
template <typename Elf>
using ConstantPhdr = CreatePhdr<elfldltl::PhdrBase::kRead>::type<Elf>;
template <typename Elf>
using ZeroFillSegment =
typename elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container>::ZeroFillSegment;
template <typename Elf>
using ZeroFillPhdr =
CreatePhdr<elfldltl::PhdrBase::kRead | elfldltl::PhdrBase::kWrite, 0>::type<Elf>;
template <typename Elf>
using DataWithZeroFillSegment =
typename elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container>::DataWithZeroFillSegment;
template <typename Elf>
using DataWithZeroFillPhdr = CreatePhdr<elfldltl::PhdrBase::kRead | elfldltl::PhdrBase::kWrite,
kPageSize, kPageSize * 2>::type<Elf>;
template <typename Elf>
using DataSegment =
typename elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container>::DataSegment;
template <typename Elf>
using DataPhdr = CreatePhdr<elfldltl::PhdrBase::kRead | elfldltl::PhdrBase::kWrite>::type<Elf>;
TYPED_TEST(ElfldltlLoadTests, MergeSameConstantSegment) {
MergeSameTest<typename TestFixture::Elf, ConstantSegment, ConstantPhdr>();
}
TYPED_TEST(ElfldltlLoadTests, MergeSameDataSegment) {
MergeSameTest<typename TestFixture::Elf, DataSegment, DataPhdr>();
}
TYPED_TEST(ElfldltlLoadTests, MergeDataAndZeroFill) {
MergeTest<typename TestFixture::Elf, DataSegment, DataWithZeroFillSegment, DataPhdr,
ZeroFillPhdr>();
}
TYPED_TEST(ElfldltlLoadTests, MergeDataAndDataWithZeroFill) {
MergeTest<typename TestFixture::Elf, DataSegment, DataWithZeroFillSegment, DataPhdr,
DataWithZeroFillPhdr>();
}
TYPED_TEST(ElfldltlLoadTests, CantMergeConstant) {
NotMergedTest<typename TestFixture::Elf, ConstantSegment, ZeroFillSegment, ConstantPhdr,
ZeroFillPhdr>();
NotMergedTest<typename TestFixture::Elf, ConstantSegment, DataWithZeroFillSegment, ConstantPhdr,
DataWithZeroFillPhdr>();
NotMergedTest<typename TestFixture::Elf, ConstantSegment, DataSegment, ConstantPhdr, DataPhdr>();
}
TYPED_TEST(ElfldltlLoadTests, CantMergeZeroFill) {
NotMergedTest<typename TestFixture::Elf, ZeroFillSegment, ConstantSegment, ZeroFillPhdr,
ConstantPhdr>();
// Logically two ZeroFillSegment's could be merged but we don't currently do
// this because these are unlikely to exist in the wild.
NotMergedTest<typename TestFixture::Elf, ZeroFillSegment, ZeroFillSegment, ZeroFillPhdr,
ZeroFillPhdr>();
NotMergedTest<typename TestFixture::Elf, ZeroFillSegment, DataWithZeroFillSegment, ZeroFillPhdr,
DataWithZeroFillPhdr>();
NotMergedTest<typename TestFixture::Elf, ZeroFillSegment, DataSegment, ZeroFillPhdr, DataPhdr>();
}
TYPED_TEST(ElfldltlLoadTests, CantMergeDataAndZeroFill) {
NotMergedTest<typename TestFixture::Elf, DataWithZeroFillSegment, ConstantSegment,
DataWithZeroFillPhdr, ConstantPhdr>();
NotMergedTest<typename TestFixture::Elf, DataWithZeroFillSegment, DataWithZeroFillSegment,
DataWithZeroFillPhdr, DataWithZeroFillPhdr>();
NotMergedTest<typename TestFixture::Elf, DataWithZeroFillSegment, DataSegment,
DataWithZeroFillPhdr, DataPhdr>();
}
TYPED_TEST(ElfldltlLoadTests, CantMergeData) {
NotMergedTest<typename TestFixture::Elf, DataSegment, ConstantSegment, DataPhdr, ConstantPhdr>();
}
TYPED_TEST(ElfldltlLoadTests, GetPhdrObserver) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> loadInfo;
using ConstantSegment = typename decltype(loadInfo)::ConstantSegment;
using DataWithZeroFillSegment = typename decltype(loadInfo)::DataWithZeroFillSegment;
int offset = 0;
const Phdr kPhdrs[] = {
ConstantPhdr<Elf>{}(offset), ConstantPhdr<Elf>{}(offset), DataPhdr<Elf>{}(offset),
DataPhdr<Elf>{}(offset), ZeroFillPhdr<Elf>{}(offset),
};
EXPECT_TRUE(
elfldltl::DecodePhdrs(diag, cpp20::span(kPhdrs), loadInfo.GetPhdrObserver(kPageSize)));
const auto& segments = loadInfo.segments();
EXPECT_EQ(segments.size(), 2u);
ASSERT_TRUE(std::holds_alternative<ConstantSegment>(segments[0]));
EXPECT_EQ(std::get<ConstantSegment>(segments[0]).memsz(), kPhdrs[0].memsz + kPhdrs[1].memsz);
ASSERT_TRUE(std::holds_alternative<DataWithZeroFillSegment>(segments[1]));
EXPECT_EQ(std::get<DataWithZeroFillSegment>(segments[1]).memsz(),
kPhdrs[2].memsz + kPhdrs[3].memsz + kPhdrs[4].memsz);
}
TYPED_TEST(ElfldltlLoadTests, VisitSegments) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> loadInfo;
ASSERT_EQ(loadInfo.segments().size(), 0u);
EXPECT_TRUE(loadInfo.VisitSegments([](auto&& segment) {
ADD_FAILURE();
return true;
}));
int offset = 0;
const Phdr kPhdrs[] = {
ConstantPhdr<Elf>{}(offset),
DataPhdr<Elf>{}(offset),
};
EXPECT_TRUE(
elfldltl::DecodePhdrs(diag, cpp20::span(kPhdrs), loadInfo.GetPhdrObserver(kPageSize)));
ASSERT_EQ(loadInfo.segments().size(), 2u);
int currentIndex = 0;
EXPECT_TRUE(loadInfo.VisitSegments([&](auto&& segment) {
EXPECT_EQ(segment.offset(), kPhdrs[currentIndex++].offset);
return true;
}));
currentIndex = 0;
EXPECT_FALSE(loadInfo.VisitSegments([&](auto&& segment) {
EXPECT_EQ(currentIndex++, 0);
return false;
}));
}
TYPED_TEST(ElfldltlLoadTests, RelroBounds) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> loadInfo;
using Region = typename decltype(loadInfo)::Region;
{
Region r = loadInfo.RelroBounds({}, kPageSize);
EXPECT_EQ(r.start, 0u);
EXPECT_EQ(r.end, 0u);
EXPECT_TRUE(r.empty());
}
{
Phdr phdr{.memsz = kPageSize - 1};
Region r = loadInfo.RelroBounds(phdr, kPageSize);
EXPECT_EQ(r.start, 0u);
EXPECT_EQ(r.end, 0u);
EXPECT_TRUE(r.empty());
}
{
Phdr phdr{.memsz = kPageSize};
Region r = loadInfo.RelroBounds(phdr, kPageSize);
EXPECT_EQ(r.start, 0u);
EXPECT_EQ(r.end, kPageSize);
}
{
Phdr phdr{.memsz = kPageSize + 1};
Region r = loadInfo.RelroBounds(phdr, kPageSize);
EXPECT_EQ(r.start, 0u);
EXPECT_EQ(r.end, kPageSize);
}
}
TYPED_TEST(ElfldltlLoadTests, ApplyRelroMissing) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> loadInfo;
int offset = kPageSize;
Phdr phdrs[] = {
DataPhdr<Elf>{}(offset),
{.type = elfldltl::ElfPhdrType::kRelro, .memsz = kPageSize},
};
ASSERT_FALSE(loadInfo.RelroBounds(phdrs[1], kPageSize).empty());
{
ASSERT_EQ(loadInfo.segments().size(), 0u);
ExpectedSingleError expected("PT_GNU_RELRO not in any data segment");
EXPECT_TRUE(loadInfo.ApplyRelro(expected.diag(), phdrs[1], kPageSize, false));
}
EXPECT_TRUE(elfldltl::DecodePhdrs(diag, cpp20::span<const Phdr>(phdrs),
loadInfo.GetPhdrObserver(kPageSize)));
{
ASSERT_EQ(loadInfo.segments().size(), 1u);
ExpectedSingleError expected("PT_GNU_RELRO not in any data segment");
EXPECT_TRUE(loadInfo.ApplyRelro(expected.diag(), phdrs[1], kPageSize, false));
}
}
TYPED_TEST(ElfldltlLoadTests, ApplyRelroBadStart) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> loadInfo;
Phdr phdrs[] = {
{.type = elfldltl::ElfPhdrType::kLoad, .filesz = 2 * kPageSize, .memsz = 2 * kPageSize},
{.type = elfldltl::ElfPhdrType::kRelro, .vaddr = kPageSize, .memsz = kPageSize},
};
phdrs[0].flags = elfldltl::PhdrBase::kRead | elfldltl::PhdrBase::kWrite;
ASSERT_EQ(loadInfo.RelroBounds(phdrs[1], kPageSize).start, kPageSize);
ASSERT_EQ(loadInfo.RelroBounds(phdrs[1], kPageSize).end, kPageSize * 2);
EXPECT_TRUE(elfldltl::DecodePhdrs(diag, cpp20::span<const Phdr>(phdrs),
loadInfo.GetPhdrObserver(kPageSize)));
ExpectedSingleError expected("PT_GNU_RELRO not at segment start");
EXPECT_TRUE(loadInfo.ApplyRelro(expected.diag(), phdrs[1], kPageSize, false));
}
TYPED_TEST(ElfldltlLoadTests, ApplyRelroTooManyLoads) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StaticVector<1>::Container> loadInfo;
Phdr phdrs[] = {
{.type = elfldltl::ElfPhdrType::kLoad, .filesz = 2 * kPageSize, .memsz = 2 * kPageSize},
{.type = elfldltl::ElfPhdrType::kRelro, .memsz = kPageSize},
};
phdrs[0].flags = elfldltl::PhdrBase::kRead | elfldltl::PhdrBase::kWrite;
EXPECT_TRUE(elfldltl::DecodePhdrs(diag, cpp20::span<const Phdr>(phdrs),
loadInfo.GetPhdrObserver(kPageSize)));
ASSERT_EQ(loadInfo.segments().size(), 1u);
auto expected = ExpectedSingleError("too many PT_LOAD segments", ": maximum 1 < requested ", 2);
loadInfo.ApplyRelro(expected.diag(), phdrs[1], kPageSize, false);
}
using SomeLI = elfldltl::LoadInfo<elfldltl::Elf<>, elfldltl::StdContainer<std::vector>::Container>;
enum SegmentType {
C = SomeLI::Segment(SomeLI::ConstantSegment(0, 0, 0, 0)).index(),
D = SomeLI::Segment(SomeLI::DataSegment(0, 0, 0, 0)).index(),
DWZF = SomeLI::Segment(SomeLI::DataWithZeroFillSegment(0, 0, 0, 0)).index(),
ZF = SomeLI::Segment(SomeLI::ZeroFillSegment(0, 0)).index(),
RO, // DataSegment that should overlaps with the relro region
};
// Can't be {RO} or {C}
using SplitStrategy = std::optional<SegmentType>;
// This class creates adjacent segments based on segment type.
// All segments except for 'RO' will have a memsz of `kPageSize`, the flags and filesz are changed
// depending on the `SegmentType`. The SplitStrategy defines how a 'RO' segment should be created
// such that it will be split into a ConstSegment and a segment defined by the strategy.
// For example:
// {C, RO, D} with a ZF split strategy will create the following Phdrs
// | Type | | C || RO(ZF) || D |
// | flags | | R || RW || RW |
// | offset | | 0 || kPagesize || kPagesize*3 |
// | {mem,file}sz | | kPagesize || kPagesize*2,kPagesize || kPagesize |
// get_relro_phdr will return a phdr that overlaps with the RO segment like:
// | RO |
// | ~RWX |
// | kPagesize |
// | kPagesize |
// Such that after ApplyRelro is called the 'RO(ZF)' segment will be split into a ConstantSegment
// and a ZeroFillSegment. The expected result then would be {C, C, ZF, D} with merge_ro false or
// {C, ZF, D} with merge_ro true.
template <typename Elf>
struct PhdrCreator {
using Phdr = typename Elf::Phdr;
using size_type = typename Elf::size_type;
SplitStrategy strategy;
size_type offset = 0;
size_type relro_offset = 0;
Phdr operator()(SegmentType type) {
Phdr phdr{.type = elfldltl::ElfPhdrType::kLoad, .offset = offset, .vaddr = offset};
auto w = {D, DWZF, ZF, RO};
if (std::any_of(w.begin(), w.end(), [type](auto t) { return type == t; })) {
phdr.flags = elfldltl::PhdrBase::kRead | elfldltl::PhdrBase::kWrite;
} else {
phdr.flags = elfldltl::PhdrBase::kRead;
}
size_type memsz = kPageSize;
size_type filesz = kPageSize;
if (type == DWZF || (type == RO && strategy && *strategy == DWZF)) {
filesz /= 2;
} else if (type == ZF || (type == RO && strategy && *strategy == ZF)) {
filesz = 0;
}
if (type == RO) {
relro_offset = offset;
if (strategy) {
memsz += kPageSize;
filesz += kPageSize;
}
}
offset += memsz;
phdr.memsz = memsz;
phdr.filesz = filesz;
return phdr;
}
Phdr get_relro_phdr() {
return {.type = elfldltl::ElfPhdrType::kRelro, .vaddr = relro_offset, .memsz = kPageSize};
}
};
using PhdrsPattern = std::initializer_list<SegmentType>;
template <typename Elf>
void RelroTest(PhdrsPattern input, PhdrsPattern expected, SplitStrategy strategy, bool merge_ro,
cpp20::source_location loc = cpp20::source_location::current()) {
using Phdr = typename Elf::Phdr;
std::vector<Phdr> input_phdrs;
PhdrCreator<Elf> creator{strategy};
std::transform(input.begin(), input.end(), std::back_inserter(input_phdrs), std::ref(creator));
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> loadInfo;
EXPECT_TRUE(elfldltl::DecodePhdrs(diag,
cpp20::span<const Phdr>(input_phdrs.data(), input_phdrs.size()),
loadInfo.GetPhdrObserver(kPageSize)));
ASSERT_TRUE(loadInfo.ApplyRelro(diag, creator.get_relro_phdr(), kPageSize, merge_ro))
<< "line " << loc.line();
auto& segments = loadInfo.segments();
ASSERT_EQ(segments.size(), expected.size()) << "line " << loc.line();
for (size_t i = 0; i < segments.size(); i++) {
EXPECT_EQ(segments[i].index(), std::data(expected)[i]) << "line " << loc.line();
}
}
template <typename Elf>
void RelroTest(PhdrsPattern input, PhdrsPattern expected, SplitStrategy strategy,
cpp20::source_location loc = cpp20::source_location::current()) {
RelroTest<Elf>(input, expected, strategy, true, loc);
RelroTest<Elf>(input, expected, strategy, false, loc);
}
TYPED_TEST(ElfldltlLoadTests, ApplyRelroBasic) {
using Elf = typename TestFixture::Elf;
RelroTest<Elf>({RO}, {C}, {});
RelroTest<Elf>({RO}, {C, D}, D);
RelroTest<Elf>({RO}, {C, DWZF}, DWZF);
RelroTest<Elf>({RO}, {C, ZF}, ZF);
}
TYPED_TEST(ElfldltlLoadTests, ApplyRelroMergeRight) {
using Elf = typename TestFixture::Elf;
RelroTest<Elf>({RO, C}, {C, C}, {}, false);
RelroTest<Elf>({RO, C}, {C}, {}, true);
RelroTest<Elf>({RO, C}, {C, D, C}, D);
RelroTest<Elf>({RO, C}, {C, DWZF, C}, DWZF);
RelroTest<Elf>({RO, C}, {C, ZF, C}, ZF);
RelroTest<Elf>({RO, D}, {C, D}, {});
RelroTest<Elf>({RO, D}, {C, D}, D);
RelroTest<Elf>({RO, D}, {C, DWZF, D}, DWZF);
RelroTest<Elf>({RO, D}, {C, ZF, D}, ZF);
RelroTest<Elf>({RO, DWZF}, {C, DWZF}, {});
RelroTest<Elf>({RO, DWZF}, {C, DWZF}, D);
RelroTest<Elf>({RO, DWZF}, {C, DWZF, DWZF}, DWZF);
RelroTest<Elf>({RO, DWZF}, {C, ZF, DWZF}, ZF);
RelroTest<Elf>({RO, ZF}, {C, ZF}, {});
RelroTest<Elf>({RO, ZF}, {C, DWZF}, D);
// The following could be:
// RelroTest<Elf>({RO, ZF}, {C, DWZF}, DWZF);
// RelroTest<Elf>({RO, ZF}, {C, ZF}, ZF);
// but we don't have Merge overloads for (*, ZF) because these are unlikely to exist in the wild.
RelroTest<Elf>({RO, ZF}, {C, DWZF, ZF}, DWZF);
RelroTest<Elf>({RO, ZF}, {C, ZF, ZF}, ZF);
}
TYPED_TEST(ElfldltlLoadTests, ApplyRelroMergeLeft) {
using Elf = typename TestFixture::Elf;
RelroTest<Elf>({C, RO}, {C, C}, {}, false);
RelroTest<Elf>({C, RO}, {C}, {}, true);
}
TYPED_TEST(ElfldltlLoadTests, ApplyRelroMergeBoth) {
using Elf = typename TestFixture::Elf;
RelroTest<Elf>({C, RO, C}, {C, C, C}, {}, false);
RelroTest<Elf>({C, RO, C}, {C}, {}, true);
}
TYPED_TEST(ElfldltlLoadTests, ApplyRelroCantMerge) {
using Elf = typename TestFixture::Elf;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
Phdr phdrs[] = {
{.type = elfldltl::ElfPhdrType::kLoad, .filesz = kPageSize, .memsz = kPageSize},
{.type = elfldltl::ElfPhdrType::kLoad,
.offset = kPageSize,
.vaddr = kPageSize,
.filesz = kPageSize,
.memsz = kPageSize},
};
phdrs[0].flags = elfldltl::PhdrBase::kRead | elfldltl::PhdrBase::kExecute;
phdrs[1].flags = elfldltl::PhdrBase::kRead | elfldltl::PhdrBase::kWrite;
Phdr relro = {.type = elfldltl::ElfPhdrType::kRelro, .vaddr = kPageSize, .memsz = kPageSize};
for (bool merge_ro : {true, false}) {
elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> loadInfo;
using ConstantSegment = typename decltype(loadInfo)::ConstantSegment;
EXPECT_TRUE(elfldltl::DecodePhdrs(diag, cpp20::span<const Phdr>(phdrs),
loadInfo.GetPhdrObserver(kPageSize)));
auto& segments = loadInfo.segments();
ASSERT_EQ(segments.size(), 2u);
EXPECT_TRUE(loadInfo.ApplyRelro(diag, relro, kPageSize, merge_ro));
ASSERT_EQ(segments.size(), 2u);
ASSERT_TRUE(std::holds_alternative<ConstantSegment>(segments[0]));
EXPECT_EQ(std::get<ConstantSegment>(segments[0]).flags(), phdrs[0].flags);
ASSERT_TRUE(std::holds_alternative<ConstantSegment>(segments[1]));
auto expected_flags = elfldltl::PhdrBase::kRead | (!merge_ro ? elfldltl::PhdrBase::kWrite : 0);
EXPECT_EQ(std::get<ConstantSegment>(segments[1]).flags(), expected_flags);
}
}
TYPED_TEST(ElfldltlLoadTests, SymbolizerContext) {
using Elf = typename TestFixture::Elf;
using size_type = typename Elf::size_type;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> info;
constexpr std::array kBuildId{std::byte{0x12}, std::byte{0x34}, std::byte{0xab}, std::byte{0xcd}};
size_type offset = 0;
for (uint32_t flags : (const uint32_t[]){
Phdr::kRead,
Phdr::kExecute,
Phdr::kRead | Phdr::kWrite,
}) {
Phdr phdr = {
.type = elfldltl::ElfPhdrType::kLoad,
.offset = offset,
.vaddr = offset,
.filesz = kPageSize,
.memsz = kPageSize,
};
phdr.flags = flags;
offset += kPageSize;
ASSERT_TRUE(info.AddSegment(diag, kPageSize, phdr));
};
constexpr char kExpectedContext[] = R"""(foo: {{{module:17:foo:elf:1234abcd}}}
foo: {{{mmap:0x12340000:0x1000:load:17:r:0x0}}}
foo: {{{mmap:0x12341000:0x1000:load:17:x:0x1000}}}
foo: {{{mmap:0x12342000:0x1000:load:17:rw:0x2000}}}
)""";
std::string markup;
symbolizer_markup::Writer writer([&markup](std::string_view str) { markup += str; });
EXPECT_EQ(&writer, &(info.SymbolizerContext(writer, 17, "foo", cpp20::span(kBuildId), 0x12340000,
"foo: ")));
EXPECT_EQ(kExpectedContext, markup);
}
} // namespace