| // 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 "load-tests.h" |
| |
| #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/elfldltl/testing/diagnostics.h> |
| #include <lib/elfldltl/testing/typed-test.h> |
| #include <lib/stdcompat/source_location.h> |
| #include <lib/stdcompat/span.h> |
| #include <lib/symbolizer-markup/writer.h> |
| |
| namespace { |
| |
| using elfldltl::testing::ConstantPhdr; |
| using elfldltl::testing::DataPhdr; |
| using elfldltl::testing::DataWithZeroFillPhdr; |
| using elfldltl::testing::ExpectedSingleError; |
| using elfldltl::testing::ExpectOkDiagnostics; |
| using elfldltl::testing::ZeroFillPhdr; |
| |
| 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> load_info; |
| |
| Phdr phdr{.memsz = 1}; |
| EXPECT_FALSE(load_info.AddSegment(error, 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> load_info; |
| |
| Phdr phdr{}; |
| EXPECT_TRUE(load_info.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> load_info; |
| using ConstantSegment = typename decltype(load_info)::ConstantSegment; |
| |
| Phdr phdr{.memsz = kPageSize * 10}; |
| EXPECT_TRUE(load_info.AddSegment(diag, kPageSize, phdr)); |
| |
| const auto& segments = load_info.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> load_info; |
| using ZeroFillSegment = typename decltype(load_info)::ZeroFillSegment; |
| |
| Phdr phdr{.memsz = kPageSize * 5}; |
| phdr.flags = Phdr::kRead | Phdr::kWrite; |
| EXPECT_TRUE(load_info.AddSegment(diag, kPageSize, phdr)); |
| |
| const auto& segments = load_info.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> load_info; |
| using DataWithZeroFillSegment = typename decltype(load_info)::DataWithZeroFillSegment; |
| |
| Phdr phdr{.filesz = kPageSize, .memsz = kPageSize * 5}; |
| phdr.flags = Phdr::kRead | Phdr::kWrite; |
| EXPECT_TRUE(load_info.AddSegment(diag, kPageSize, phdr)); |
| |
| const auto& segments = load_info.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> load_info; |
| using DataSegment = typename decltype(load_info)::DataSegment; |
| |
| Phdr phdr{.filesz = kPageSize, .memsz = kPageSize}; |
| phdr.flags = Phdr::kRead | Phdr::kWrite; |
| EXPECT_TRUE(load_info.AddSegment(diag, kPageSize, phdr)); |
| |
| const auto& segments = load_info.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>; |
| using size_type = typename Elf::size_type; |
| constexpr unsigned totalSegments = Merged ? 1 : 2; |
| |
| auto diag = ExpectOkDiagnostics(); |
| |
| elfldltl::LoadInfo<Elf, elfldltl::StaticVector<2>::Container> load_info; |
| const auto& segments = load_info.segments(); |
| |
| size_type offset = 0; |
| auto phdr1 = GetPhdr1<Elf>{}(offset); |
| auto phdr2 = GetPhdr2<Elf>{}(offset); |
| auto expectedSize = Merged ? phdr1.memsz() + phdr2.memsz() : phdr2.memsz(); |
| |
| load_info.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()); |
| load_info.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 <typename Elf> |
| using ConstantSegment = |
| typename elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container>::ConstantSegment; |
| |
| template <typename Elf> |
| using ZeroFillSegment = |
| typename elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container>::ZeroFillSegment; |
| |
| template <typename Elf> |
| using DataWithZeroFillSegment = |
| typename elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container>::DataWithZeroFillSegment; |
| |
| template <typename Elf> |
| using DataSegment = |
| typename elfldltl::LoadInfo<Elf, elfldltl::StaticVector<0>::Container>::DataSegment; |
| |
| 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; |
| using size_type = typename Elf::size_type; |
| |
| auto diag = ExpectOkDiagnostics(); |
| |
| elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> load_info; |
| using ConstantSegment = typename decltype(load_info)::ConstantSegment; |
| using DataWithZeroFillSegment = typename decltype(load_info)::DataWithZeroFillSegment; |
| |
| size_type 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), load_info.GetPhdrObserver(kPageSize))); |
| const auto& segments = load_info.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; |
| using size_type = typename Elf::size_type; |
| |
| auto diag = ExpectOkDiagnostics(); |
| |
| elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> load_info; |
| |
| ASSERT_EQ(load_info.segments().size(), 0u); |
| EXPECT_TRUE(load_info.VisitSegments([](auto&& segment) { |
| ADD_FAILURE(); |
| return true; |
| })); |
| |
| size_type offset = 0; |
| const Phdr kPhdrs[] = { |
| ConstantPhdr<Elf>{}(offset), |
| DataPhdr<Elf>{}(offset), |
| }; |
| |
| EXPECT_TRUE( |
| elfldltl::DecodePhdrs(diag, cpp20::span(kPhdrs), load_info.GetPhdrObserver(kPageSize))); |
| ASSERT_EQ(load_info.segments().size(), 2u); |
| |
| int currentIndex = 0; |
| EXPECT_TRUE(load_info.VisitSegments([&](auto&& segment) { |
| EXPECT_EQ(segment.offset(), kPhdrs[currentIndex++].offset); |
| return true; |
| })); |
| |
| currentIndex = 0; |
| EXPECT_FALSE(load_info.VisitSegments([&](auto&& segment) { |
| EXPECT_EQ(currentIndex++, 0); |
| return false; |
| })); |
| } |
| |
| TYPED_TEST(ElfldltlLoadTests, RemoveLastSegment) { |
| using Elf = typename TestFixture::Elf; |
| using Phdr = typename Elf::Phdr; |
| using size_type = typename Elf::size_type; |
| using LoadInfo = elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container>; |
| |
| auto diag = ExpectOkDiagnostics(); |
| |
| LoadInfo load_info; |
| |
| size_type offset = 0; |
| const Phdr kPhdrs[] = { |
| ConstantPhdr<Elf>{}(offset), |
| DataPhdr<Elf>{}(offset), |
| }; |
| EXPECT_TRUE( |
| elfldltl::DecodePhdrs(diag, cpp20::span(kPhdrs), load_info.GetPhdrObserver(kPageSize))); |
| ASSERT_EQ(load_info.segments().size(), 2u); |
| |
| EXPECT_EQ(load_info.vaddr_size(), 2 * kPageSize); |
| |
| auto segment = load_info.RemoveLastSegment(); |
| static_assert(std::is_same_v<decltype(segment), typename LoadInfo::Segment>); |
| |
| EXPECT_EQ(load_info.segments().size(), 1u); |
| EXPECT_TRUE( |
| std::holds_alternative<typename LoadInfo::ConstantSegment>(load_info.segments().front())); |
| EXPECT_EQ(load_info.vaddr_size(), kPageSize); |
| } |
| |
| TYPED_TEST(ElfldltlLoadTests, AddSegmentUpdatesVaddrSize) { |
| using Elf = typename TestFixture::Elf; |
| using LoadInfo = elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container>; |
| |
| auto diag = ExpectOkDiagnostics(); |
| |
| LoadInfo load_info; |
| EXPECT_EQ(load_info.vaddr_size(), 0u); |
| |
| EXPECT_TRUE(load_info.AddSegment(diag, typename LoadInfo::ZeroFillSegment(0, kPageSize))); |
| ASSERT_EQ(load_info.segments().size(), 1u); |
| EXPECT_EQ(load_info.vaddr_size(), kPageSize); |
| } |
| |
| TYPED_TEST(ElfldltlLoadTests, RelroBounds) { |
| using Elf = typename TestFixture::Elf; |
| using Phdr = typename Elf::Phdr; |
| |
| elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> load_info; |
| using Region = typename decltype(load_info)::Region; |
| |
| { |
| Region r = load_info.RelroBounds({}, kPageSize); |
| EXPECT_EQ(r.start, 0u); |
| EXPECT_EQ(r.end, 0u); |
| EXPECT_TRUE(r.empty()); |
| } |
| { |
| Phdr phdr{.memsz = kPageSize - 1}; |
| Region r = load_info.RelroBounds(phdr, kPageSize); |
| EXPECT_EQ(r.start, 0u); |
| EXPECT_EQ(r.end, 0u); |
| EXPECT_TRUE(r.empty()); |
| } |
| { |
| Phdr phdr{.memsz = kPageSize}; |
| Region r = load_info.RelroBounds(phdr, kPageSize); |
| EXPECT_EQ(r.start, 0u); |
| EXPECT_EQ(r.end, kPageSize); |
| } |
| { |
| Phdr phdr{.memsz = kPageSize + 1}; |
| Region r = load_info.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; |
| using size_type = typename Elf::size_type; |
| |
| auto diag = ExpectOkDiagnostics(); |
| |
| elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> load_info; |
| |
| size_type offset = kPageSize; |
| Phdr phdrs[] = { |
| DataPhdr<Elf>{}(offset), |
| {.type = elfldltl::ElfPhdrType::kRelro, .memsz = kPageSize}, |
| }; |
| |
| ASSERT_FALSE(load_info.RelroBounds(phdrs[1], kPageSize).empty()); |
| |
| { |
| ASSERT_EQ(load_info.segments().size(), 0u); |
| ExpectedSingleError expected("PT_GNU_RELRO not in any data segment"); |
| EXPECT_TRUE(load_info.ApplyRelro(expected, phdrs[1], kPageSize, false)); |
| } |
| |
| EXPECT_TRUE(elfldltl::DecodePhdrs(diag, cpp20::span<const Phdr>(phdrs), |
| load_info.GetPhdrObserver(kPageSize))); |
| |
| { |
| ASSERT_EQ(load_info.segments().size(), 1u); |
| ExpectedSingleError expected("PT_GNU_RELRO not in any data segment"); |
| EXPECT_TRUE(load_info.ApplyRelro(expected, 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> load_info; |
| |
| 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(load_info.RelroBounds(phdrs[1], kPageSize).start, kPageSize); |
| ASSERT_EQ(load_info.RelroBounds(phdrs[1], kPageSize).end, kPageSize * 2); |
| |
| EXPECT_TRUE(elfldltl::DecodePhdrs(diag, cpp20::span<const Phdr>(phdrs), |
| load_info.GetPhdrObserver(kPageSize))); |
| |
| ExpectedSingleError expected("PT_GNU_RELRO not at segment start"); |
| EXPECT_TRUE(load_info.ApplyRelro(expected, 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> load_info; |
| |
| 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), |
| load_info.GetPhdrObserver(kPageSize))); |
| |
| ASSERT_EQ(load_info.segments().size(), 1u); |
| |
| auto expected = ExpectedSingleError("too many PT_LOAD segments", ": maximum 1 < requested ", 2); |
| load_info.ApplyRelro(expected, 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 <class Elf, template <class> class SegmentWrapper = elfldltl::NoSegmentWrapper> |
| using RelroTestLoadInfo = elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container, |
| elfldltl::PhdrLoadPolicy::kBasic, SegmentWrapper>; |
| |
| template <typename Elf, template <class> class SegmentWrapper = elfldltl::NoSegmentWrapper> |
| 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(); |
| |
| RelroTestLoadInfo<Elf, SegmentWrapper> load_info; |
| EXPECT_TRUE(elfldltl::DecodePhdrs(diag, |
| cpp20::span<const Phdr>(input_phdrs.data(), input_phdrs.size()), |
| load_info.GetPhdrObserver(kPageSize))); |
| ASSERT_TRUE(load_info.ApplyRelro(diag, creator.get_relro_phdr(), kPageSize, merge_ro)) |
| << "line " << loc.line(); |
| auto& segments = load_info.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, template <class> class SegmentWrapper = elfldltl::NoSegmentWrapper> |
| void RelroTest(PhdrsPattern input, PhdrsPattern expected, SplitStrategy strategy, |
| cpp20::source_location loc = cpp20::source_location::current()) { |
| RelroTest<Elf, SegmentWrapper>(input, expected, strategy, true, loc); |
| RelroTest<Elf, SegmentWrapper>(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); |
| } |
| |
| template <class Segment> |
| class MoveOnlySegmentWrapper : public Segment { |
| public: |
| using Segment::Segment; |
| |
| MoveOnlySegmentWrapper(const MoveOnlySegmentWrapper&) = delete; |
| constexpr MoveOnlySegmentWrapper(MoveOnlySegmentWrapper&&) = default; |
| constexpr MoveOnlySegmentWrapper& operator=(MoveOnlySegmentWrapper&&) = default; |
| }; |
| |
| TYPED_TEST(ElfldltlLoadTests, ApplyRelroMoveOnly) { |
| using Elf = typename TestFixture::Elf; |
| |
| using DefaultSegment = typename RelroTestLoadInfo<Elf>::LoadInfo::Segment; |
| static_assert(std::is_copy_constructible_v<DefaultSegment>); |
| static_assert(std::is_copy_assignable_v<DefaultSegment>); |
| |
| using MoveOnlySegment = |
| typename RelroTestLoadInfo<Elf, MoveOnlySegmentWrapper>::LoadInfo::Segment; |
| static_assert(!std::is_copy_constructible_v<MoveOnlySegment>); |
| static_assert(!std::is_copy_assignable_v<MoveOnlySegment>); |
| static_assert(std::is_move_constructible_v<MoveOnlySegment>); |
| static_assert(std::is_move_assignable_v<MoveOnlySegment>); |
| |
| RelroTest<Elf, MoveOnlySegmentWrapper>({RO}, {C}, {}); |
| RelroTest<Elf, MoveOnlySegmentWrapper>({RO}, {C, D}, D); |
| RelroTest<Elf, MoveOnlySegmentWrapper>({RO}, {C, DWZF}, DWZF); |
| RelroTest<Elf, MoveOnlySegmentWrapper>({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); |
| } |
| |
| template <class Segment> |
| class CantMergeSegmentWrapper : public Segment { |
| public: |
| using Segment::Segment; |
| template <class Other> |
| constexpr std::false_type CanMergeWith(const Other& other) const { |
| return {}; |
| } |
| }; |
| |
| TYPED_TEST(ElfldltlLoadTests, ApplyRelroCantMergeSegmentWrapper) { |
| using Elf = typename TestFixture::Elf; |
| |
| RelroTest<Elf, CantMergeSegmentWrapper>({RO, C}, {C, C}, {}); |
| RelroTest<Elf, CantMergeSegmentWrapper>({C, RO, C}, {C, C, C}, {}); |
| RelroTest<Elf, CantMergeSegmentWrapper>({C, RO}, {C, C}, {}); |
| } |
| |
| 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> load_info; |
| using ConstantSegment = typename decltype(load_info)::ConstantSegment; |
| |
| EXPECT_TRUE(elfldltl::DecodePhdrs(diag, cpp20::span<const Phdr>(phdrs), |
| load_info.GetPhdrObserver(kPageSize))); |
| auto& segments = load_info.segments(); |
| ASSERT_EQ(segments.size(), 2u); |
| EXPECT_TRUE(load_info.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); |
| } |
| } |
| |
| template <class Segment> |
| class CantReplaceSegmentWrapper : public Segment { |
| public: |
| using Segment::Segment; |
| constexpr std::false_type CanReplace() const { return {}; } |
| }; |
| |
| TYPED_TEST(ElfldltlLoadTests, ApplyRelroCantReplaceSegmentWrapper) { |
| using Elf = typename TestFixture::Elf; |
| using Phdr = typename Elf::Phdr; |
| |
| 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}; |
| |
| std::string error; |
| auto diag = elfldltl::OneStringDiagnostics(error); |
| |
| RelroTestLoadInfo<Elf, CantReplaceSegmentWrapper> load_info; |
| EXPECT_TRUE(elfldltl::DecodePhdrs(diag, cpp20::span<const Phdr>(phdrs), |
| load_info.GetPhdrObserver(kPageSize))); |
| |
| for (bool merge_ro : {true, false}) { |
| EXPECT_FALSE(load_info.ApplyRelro(diag, relro, kPageSize, merge_ro)); |
| EXPECT_EQ(error, "Cannot split segment to apply PT_GNU_RELRO protections"); |
| } |
| } |
| |
| 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); |
| } |
| |
| TYPED_TEST(ElfldltlLoadTests, FindSegment) { |
| using Elf = typename TestFixture::Elf; |
| using Phdr = typename Elf::Phdr; |
| using size_type = typename Elf::size_type; |
| |
| auto diag = ExpectOkDiagnostics(); |
| |
| elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> load_info; |
| |
| // Expect the first lookup to an empty segment list to return not found. |
| ASSERT_TRUE(load_info.segments().empty()); |
| ASSERT_EQ(load_info.FindSegment(0u), load_info.segments().end()); |
| |
| size_type offset = kPageSize; |
| const std::array kPhdrs = {ConstantPhdr<Elf>{}(offset), DataPhdr<Elf>{}(offset), |
| ConstantPhdr<Elf>{}(offset)}; |
| // Load all segments first so we can search the segments container with |
| // multiple entries. |
| for (const Phdr& phdr : kPhdrs) { |
| ASSERT_TRUE(load_info.AddSegment(diag, kPageSize, phdr)); |
| } |
| |
| ASSERT_EQ(load_info.segments().size(), 3u); |
| |
| // Test finding a segment from its starting vaddr. |
| for (const Phdr& phdr : kPhdrs) { |
| size_type vaddr = phdr.vaddr; |
| const auto found = load_info.FindSegment(vaddr); |
| ASSERT_NE(found, load_info.segments().end()); |
| ASSERT_TRUE( |
| load_info.VisitSegment([vaddr](const auto& s) { return s.vaddr() == vaddr; }, *found)); |
| }; |
| |
| // Test finding a segment from a vaddr in its vaddr range. |
| for (const Phdr& phdr : kPhdrs) { |
| size_type vaddr = phdr.vaddr + (phdr.memsz / 2); |
| const auto found = load_info.FindSegment(vaddr); |
| ASSERT_NE(found, load_info.segments().end()); |
| ASSERT_TRUE(load_info.VisitSegment( |
| [vaddr](const auto& s) { return s.vaddr() < vaddr && vaddr < s.vaddr() + s.memsz(); }, |
| *found)); |
| }; |
| |
| // Test finding a segment out of bounds of the first and last segments |
| { |
| size_type under_bounds_vaddr = kPhdrs[0].vaddr / 2; |
| const auto found = load_info.FindSegment(under_bounds_vaddr); |
| ASSERT_EQ(found, load_info.segments().end()); |
| } |
| { |
| size_type over_bounds_vaddr = kPhdrs[2].vaddr * 2; |
| const auto found = load_info.FindSegment(over_bounds_vaddr); |
| ASSERT_EQ(found, load_info.segments().end()); |
| } |
| } |
| |
| } // namespace |