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.cpp b/linker/linker_phdr.cpp
index 2a1c05b..7691031 100644
--- a/linker/linker_phdr.cpp
+++ b/linker/linker_phdr.cpp
@@ -140,11 +140,6 @@
 
  **/
 
-#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 const size_t kPageSize = page_size();
 
 /*
@@ -700,6 +695,13 @@
     return false;
   }
 
+  if (should_use_16kib_app_compat_) {
+    // Reserve additional space for aligning the permission boundary in compat loading
+    // Up to kPageSize-kCompatPageSize additional space is needed, but reservation
+    // is done with mmap which gives kPageSize multiple-sized reservations.
+    load_size_ += kPageSize;
+  }
+
   uint8_t* addr = reinterpret_cast<uint8_t*>(min_vaddr);
   void* start;
 
@@ -735,6 +737,13 @@
 
   load_start_ = start;
   load_bias_ = reinterpret_cast<uint8_t*>(start) - addr;
+
+  if (should_use_16kib_app_compat_) {
+    // In compat mode make the initial mapping RW since the ELF contents will be read
+    // into it; instead of mapped over it.
+    mprotect(reinterpret_cast<void*>(start), load_size_, PROT_READ | PROT_WRITE);
+  }
+
   return true;
 }
 
@@ -988,7 +997,11 @@
 }
 
 bool ElfReader::LoadSegments() {
-  size_t seg_align = kPageSize;
+  // NOTE: The compat(legacy) page size (4096) must be used when aligning
+  // the 4KiB segments for loading in compat mode. The larger 16KiB page size
+  // will lead to overwriting adjacent segments since the ELF's segment(s)
+  // are not 16KiB aligned.
+  size_t seg_align = should_use_16kib_app_compat_ ? kCompatPageSize : kPageSize;
 
   size_t min_palign = phdr_table_get_minimum_alignment(phdr_table_, phdr_num_);
   // Only enforce this on 16 KB systems with app compat disabled.
@@ -1000,6 +1013,11 @@
     return false;
   }
 
+  if (!Setup16KiBAppCompat()) {
+    DL_ERR("\"%s\" failed to setup 16KiB App Compat", name_.c_str());
+    return false;
+  }
+
   for (size_t i = 0; i < phdr_num_; ++i) {
     const ElfW(Phdr)* phdr = &phdr_table_[i];
 
@@ -1057,8 +1075,14 @@
       }
 
       // Pass the file_length, since it may have been extended by _extend_load_segment_vma().
-      if (!MapSegment(i, file_length)) {
-        return false;
+      if (should_use_16kib_app_compat_) {
+        if (!CompatMapSegment(i, file_length)) {
+          return false;
+        }
+      } else {
+        if (!MapSegment(i, file_length)) {
+          return false;
+        }
       }
     }
 
@@ -1304,6 +1328,20 @@
                                         should_pad_segments, should_use_16kib_app_compat);
 }
 
+/*
+ * Apply RX protection to the compat relro region of the ELF being loaded in
+ * 16KiB compat mode.
+ *
+ * Input:
+ *   start  -> start address of the compat relro region.
+ *   size   -> size of the compat relro region in bytes.
+ * Return:
+ *   0 on success, -1 on failure (error code in errno).
+ */
+int phdr_table_protect_gnu_relro_16kib_compat(ElfW(Addr) start, ElfW(Addr) size) {
+  return mprotect(reinterpret_cast<void*>(start), size, PROT_READ | PROT_EXEC);
+}
+
 /* Serialize the GNU relro segments to the given file descriptor. This can be
  * performed after relocations to allow another process to later share the
  * relocated segment, if it was loaded at the same address.