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.cpp b/linker/linker.cpp
index 5146584..88d02dc 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -641,6 +641,10 @@
si_->set_gap_size(elf_reader.gap_size());
si_->set_should_pad_segments(elf_reader.should_pad_segments());
si_->set_should_use_16kib_app_compat(elf_reader.should_use_16kib_app_compat());
+ if (si_->should_use_16kib_app_compat()) {
+ si_->set_compat_relro_start(elf_reader.compat_relro_start());
+ si_->set_compat_relro_size(elf_reader.compat_relro_size());
+ }
return true;
}
@@ -3415,10 +3419,18 @@
}
bool soinfo::protect_relro() {
- if (phdr_table_protect_gnu_relro(phdr, phnum, load_bias, should_pad_segments_,
- should_use_16kib_app_compat_) < 0) {
- DL_ERR("can't enable GNU RELRO protection for \"%s\": %m", get_realpath());
- return false;
+ if (should_use_16kib_app_compat_) {
+ if (phdr_table_protect_gnu_relro_16kib_compat(compat_relro_start_, compat_relro_size_) < 0) {
+ DL_ERR("can't enable COMPAT GNU RELRO protection for \"%s\": %s", get_realpath(),
+ strerror(errno));
+ return false;
+ }
+ } else {
+ if (phdr_table_protect_gnu_relro(phdr, phnum, load_bias, should_pad_segments_,
+ should_use_16kib_app_compat_) < 0) {
+ DL_ERR("can't enable GNU RELRO protection for \"%s\": %m", get_realpath());
+ return false;
+ }
}
return true;
}