linker: LoadSegments: Load 4KiB ELFs on 16KiB page-sized systems
Firstly reserve additional space for aligning the permission boundary in
compat loading. Only kPageSize-kCompatForPageSize additional space is
needed, but reservation is done with mmap which can only give page sized
multiple reservations, so page-align the required size. This may result
in reserving more space than needed, but this is not a problem; since VA
space can only be reserved in page-sized (16KiB) multiples.
Make the initial reservation mapping RW, since in compat mode, the ELF
contents will be read into the mapping; instead of mapped over it, as done
in conventional loading.
For determining the segment extent/boundaries for loading(reading in)
the ELF contents, use the compat(legacy) page size (4096). The larger
16KiB page size will lead to overwriting adjacent segments since the
ELF was not built with 16KiB aligned segments.
In 16KiB compatibility mode only ELFs that follow the below layout
can be loaded successfully:
┌────────────┬─────────────────────────┬────────────┐
│ │ │ │
│ (RO|RX)* │ (RW - RELRO prefix)? │ (RW)* │
│ │ │ │
└────────────┴─────────────────────────┴────────────┘
In other words, compatible layouts have:
- zero or more RO or RX segments;
- followed by zero or one RELRO prefix;
- followed by zero or more RW segments (this can include the RW
suffix from the segment containing the RELRO prefix, if any)
In 16KiB compat mode, after relocation, the ELF is laid out in virtual
memory as is shown below:
┌──────────────────────────────────────┬────────────┐
│ │ │
│ (RX)? │ (RW)? │
│ │ │
└──────────────────────────────────────┴────────────┘
In compat mode:
- the RO and RX segments along with the RELRO prefix are protected
as RX;
- and the RW segments along with RW suffix from the relro segment,
if any; are RW protected.
This allows for the single RX|RW permission boundary to be aligned with
a 16KiB page boundary; since a single page cannot share multiple
permissions.
In compatibility mode, RELRO needs special handling. After relocation,
the relro segment shares RX permission with the preceding sections of
the ELF. This entire region i.e preceding RO/RX segments and the relro
prefix is referred to as the "compat relro section" -- it's permission
needs to be modified as a whole.
Introduce ElfReader compat_relro_[start|size] to save the extent of the
"compat relro section", when loading an ELF in 16KiB compat mode in
ElFReader and pipe it through to soinfo.
Once the RW|RX permission boundary of the ELF to be loaded in
compatibility mode is known, the RX|RW permission boundary is positioned
on a 16KiB page boundary by offseting from the load start; since a single
page cannot share multiple permissions.
For debuggability, the VMAs(mappings) for the loaded ELF in compat mode
are labelled with the file path followed by "(compat loaded)". This is
necessary since the segments were loaded in anonymous mappings.
Sample output from /proc/*/maps:
71d6b91ec000-71d6b91f0000 r-xp 00000000 00:00 0 [anon:/system/lib64/elf_max_page_size_4kib.so (compat loaded)]
71d6b91f0000-71d6b91f4000 rw-p 00000000 00:00 0 [anon:/system/lib64/elf_max_page_size_4kib.so (compat loaded)]
Bug: 339709616
Test: atest linker-unit-tests
Change-Id: I3b4e4293326a9adf06d429ebf38c86cf1d4276f6
Signed-off-by: Kalesh Singh <kaleshsingh@google.com>
diff --git a/linker/linker_phdr.h b/linker/linker_phdr.h
index a30fe33..2f159f3 100644
--- a/linker/linker_phdr.h
+++ b/linker/linker_phdr.h
@@ -39,6 +39,13 @@
#include "linker_mapped_file_fragment.h"
#include "linker_note_gnu_property.h"
+#define MAYBE_MAP_FLAG(x, from, to) (((x) & (from)) ? (to) : 0)
+#define PFLAGS_TO_PROT(x) (MAYBE_MAP_FLAG((x), PF_X, PROT_EXEC) | \
+ MAYBE_MAP_FLAG((x), PF_R, PROT_READ) | \
+ MAYBE_MAP_FLAG((x), PF_W, PROT_WRITE))
+
+static constexpr size_t kCompatPageSize = 0x1000;
+
class ElfReader {
public:
ElfReader();
@@ -60,6 +67,8 @@
ElfW(Addr) entry_point() const { return header_.e_entry + load_bias_; }
bool should_pad_segments() const { return should_pad_segments_; }
bool should_use_16kib_app_compat() const { return should_use_16kib_app_compat_; }
+ ElfW(Addr) compat_relro_start() const { return compat_relro_start_; }
+ ElfW(Addr) compat_relro_size() const { return compat_relro_size_; }
private:
[[nodiscard]] bool ReadElfHeader();
@@ -70,10 +79,14 @@
[[nodiscard]] bool ReadPadSegmentNote();
[[nodiscard]] bool ReserveAddressSpace(address_space_params* address_space);
[[nodiscard]] bool MapSegment(size_t seg_idx, size_t len);
+ [[nodiscard]] bool CompatMapSegment(size_t seg_idx, size_t len);
void ZeroFillSegment(const ElfW(Phdr)* phdr);
void DropPaddingPages(const ElfW(Phdr)* phdr, uint64_t seg_file_end);
[[nodiscard]] bool MapBssSection(const ElfW(Phdr)* phdr, ElfW(Addr) seg_page_end,
ElfW(Addr) seg_file_end);
+ [[nodiscard]] bool IsEligibleFor16KiBAppCompat(ElfW(Addr)* vaddr);
+ [[nodiscard]] bool HasAtMostOneRelroSegment(const ElfW(Phdr)** relro_phdr);
+ [[nodiscard]] bool Setup16KiBAppCompat();
[[nodiscard]] bool LoadSegments();
[[nodiscard]] bool FindPhdr();
[[nodiscard]] bool FindGnuPropertySection();
@@ -127,6 +140,10 @@
// Use app compat mode when loading 4KiB max-page-size ELFs on 16KiB page-size devices?
bool should_use_16kib_app_compat_ = false;
+ // RELRO region for 16KiB compat loading
+ ElfW(Addr) compat_relro_start_ = 0;
+ ElfW(Addr) compat_relro_size_ = 0;
+
// Only used by AArch64 at the moment.
GnuPropertySection note_gnu_property_ __unused;
};
@@ -150,6 +167,8 @@
ElfW(Addr) load_bias, bool should_pad_segments,
bool should_use_16kib_app_compat);
+int phdr_table_protect_gnu_relro_16kib_compat(ElfW(Addr) start, ElfW(Addr) size);
+
int phdr_table_serialize_gnu_relro(const ElfW(Phdr)* phdr_table, size_t phdr_count,
ElfW(Addr) load_bias, int fd, size_t* file_offset);