16k compat: Combine memory protection logic for RX|RW and RWX modes
In both modes the initial mapping is RW, and execute permission is only
applied after linking is done.
In RX|RW compat mode RX protection is applied to code (.text, .plt, ...)
and GNU RELRO segments.
In RWX compat mode RWX protection is applied to the whole ELF mapping.
Test: Manually verify that both RX|RW and RWX binaries load correctly.
Bug: 424598884
Change-Id: I96f5f07f984a0ab9fbd9d868d3531b0bfba3ac97
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 8117930..206a39f 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -645,9 +645,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());
+ si_->set_should_16kib_app_compat_use_rwx(elf_reader.should_16kib_app_compat_use_rwx());
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());
+ si_->set_compat_code_start(elf_reader.compat_code_start());
+ si_->set_compat_code_size(elf_reader.compat_code_size());
}
return true;
@@ -3422,6 +3423,12 @@
return false;
}
+ // Now that we've finished linking we can apply execute permission to code segments in compat
+ // loaded binaries, and remove write permission from .text and GNU RELRO in RX|RW compat mode.
+ if (!protect_16kib_app_compat_code()) {
+ return false;
+ }
+
if (should_tag_memtag_globals()) {
std::list<std::string>* vma_names_ptr = vma_names();
// should_tag_memtag_globals -> __aarch64__ -> vma_names() != nullptr
@@ -3452,18 +3459,29 @@
bool soinfo::protect_relro() {
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;
}
+
+ if (phdr_table_protect_gnu_relro(phdr, phnum, load_bias, should_pad_segments_) < 0) {
+ DL_ERR("can't enable GNU RELRO protection for \"%s\": %m", get_realpath());
+ return false;
+ }
+
+ return true;
+}
+
+bool soinfo::protect_16kib_app_compat_code() {
+ if (!should_use_16kib_app_compat_) {
+ return true;
+ }
+
+ if (phdr_table_protect_16kib_app_compat_code(compat_code_start_, compat_code_size_,
+ should_16kib_app_compat_use_rwx_) < 0) {
+ DL_ERR("failed to set execute permission for compat loaded binary \"%s\": %s", get_realpath(),
+ strerror(errno));
+ return false;
+ }
+
return true;
}
diff --git a/linker/linker_phdr.cpp b/linker/linker_phdr.cpp
index 62faaf0..c6b1127 100644
--- a/linker/linker_phdr.cpp
+++ b/linker/linker_phdr.cpp
@@ -1314,8 +1314,7 @@
static inline void _extend_gnu_relro_prot_end(const ElfW(Phdr)* relro_phdr,
const ElfW(Phdr)* phdr_table, size_t phdr_count,
ElfW(Addr) load_bias, ElfW(Addr)* seg_page_end,
- bool should_pad_segments,
- bool should_use_16kib_app_compat) {
+ bool should_pad_segments) {
// Find the index and phdr of the LOAD containing the GNU_RELRO segment
for (size_t index = 0; index < phdr_count; ++index) {
const ElfW(Phdr)* phdr = &phdr_table[index];
@@ -1363,7 +1362,7 @@
// mprotect will only RO protect a part of the extended RW LOAD segment, which
// will leave an extra split RW VMA (the gap).
_extend_load_segment_vma(phdr_table, phdr_count, index, &p_memsz, &p_filesz,
- should_pad_segments, should_use_16kib_app_compat);
+ should_pad_segments, /*should_use_16kib_app_compat=*/false);
*seg_page_end = page_end(phdr->p_vaddr + p_memsz + load_bias);
return;
@@ -1376,8 +1375,7 @@
*/
static int _phdr_table_set_gnu_relro_prot(const ElfW(Phdr)* phdr_table, size_t phdr_count,
ElfW(Addr) load_bias, int prot_flags,
- bool should_pad_segments,
- bool should_use_16kib_app_compat) {
+ bool should_pad_segments) {
const ElfW(Phdr)* phdr = phdr_table;
const ElfW(Phdr)* phdr_limit = phdr + phdr_count;
@@ -1405,7 +1403,7 @@
ElfW(Addr) seg_page_start = page_start(phdr->p_vaddr) + load_bias;
ElfW(Addr) seg_page_end = page_end(phdr->p_vaddr + phdr->p_memsz) + load_bias;
_extend_gnu_relro_prot_end(phdr, phdr_table, phdr_count, load_bias, &seg_page_end,
- should_pad_segments, should_use_16kib_app_compat);
+ should_pad_segments);
int ret = mprotect(reinterpret_cast<void*>(seg_page_start),
seg_page_end - seg_page_start,
@@ -1436,24 +1434,9 @@
* 0 on success, -1 on failure (error code in errno).
*/
int phdr_table_protect_gnu_relro(const ElfW(Phdr)* phdr_table, size_t phdr_count,
- ElfW(Addr) load_bias, bool should_pad_segments,
- bool should_use_16kib_app_compat) {
+ ElfW(Addr) load_bias, bool should_pad_segments) {
return _phdr_table_set_gnu_relro_prot(phdr_table, phdr_count, load_bias, PROT_READ,
- 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);
+ should_pad_segments);
}
/* Serialize the GNU relro segments to the given file descriptor. This can be
diff --git a/linker/linker_phdr.h b/linker/linker_phdr.h
index 399e3ed..d78f7c1 100644
--- a/linker/linker_phdr.h
+++ b/linker/linker_phdr.h
@@ -70,8 +70,9 @@
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_; }
+ bool should_16kib_app_compat_use_rwx() const { return should_16kib_app_compat_use_rwx_; }
+ ElfW(Addr) compat_code_start() const { return compat_code_start_; }
+ ElfW(Addr) compat_code_size() const { return compat_code_size_; }
const GnuPropertySection* note_gnu_property() const { return ¬e_gnu_property_; }
private:
@@ -94,7 +95,7 @@
void FixMinAlignFor16KiB();
void LabelCompatVma();
void SetupRXRWAppCompat(ElfW(Addr) rx_rw_boundary);
- [[nodiscard]] bool SetupRWXAppCompat();
+ void SetupRWXAppCompat();
[[nodiscard]] bool Setup16KiBAppCompat();
[[nodiscard]] bool LoadSegments();
[[nodiscard]] bool FindPhdr();
@@ -153,12 +154,17 @@
// Use app compat mode when loading 4KiB max-page-size ELFs on 16KiB page-size devices?
bool should_use_16kib_app_compat_ = false;
+ // Map ELF segments RWX in app compat mode?
+ bool should_16kib_app_compat_use_rwx_ = false;
+
// Should fail hard on 16KiB related dlopen() errors?
bool dlopen_16kib_err_is_fatal_ = false;
- // RELRO region for 16KiB compat loading
- ElfW(Addr) compat_relro_start_ = 0;
- ElfW(Addr) compat_relro_size_ = 0;
+ // Region that needs execute permission for 16KiB compat loading.
+ // RX|RW compat mode: Contains code and GNU RELRO sections.
+ // RWX compat mode: Contains the whole ELF.
+ ElfW(Addr) compat_code_start_ = 0;
+ ElfW(Addr) compat_code_size_ = 0;
// Only used by AArch64 at the moment.
GnuPropertySection note_gnu_property_ __unused;
@@ -177,10 +183,10 @@
bool should_use_16kib_app_compat);
int phdr_table_protect_gnu_relro(const ElfW(Phdr)* phdr_table, size_t phdr_count,
- ElfW(Addr) load_bias, bool should_pad_segments,
- bool should_use_16kib_app_compat);
+ ElfW(Addr) load_bias, bool should_pad_segments);
-int phdr_table_protect_gnu_relro_16kib_compat(ElfW(Addr) start, ElfW(Addr) size);
+int phdr_table_protect_16kib_app_compat_code(ElfW(Addr) start, ElfW(Addr) size,
+ bool should_16kib_app_compat_use_rwx);
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);
diff --git a/linker/linker_phdr_16kib_compat.cpp b/linker/linker_phdr_16kib_compat.cpp
index 570f766..7721dac 100644
--- a/linker/linker_phdr_16kib_compat.cpp
+++ b/linker/linker_phdr_16kib_compat.cpp
@@ -360,29 +360,20 @@
// RW region (.data, .bss ...)
ElfW(Addr) rw_start = load_bias_ + rx_rw_boundary;
- ElfW(Addr) rw_size = load_size_ - (rw_start - reinterpret_cast<ElfW(Addr)>(load_start_));
+ CHECK(rw_start % page_size() == 0);
- CHECK(rw_start % getpagesize() == 0);
- CHECK(rw_size % getpagesize() == 0);
-
- // Compat RELRO (RX) region (.text, .data.relro, ...)
- compat_relro_size_ = load_size_ - rw_size;
- compat_relro_start_ = reinterpret_cast<ElfW(Addr)>(load_start_);
+ // Compat code and RELRO (RX) region (.text, .data.relro, ...)
+ compat_code_start_ = load_start();
+ compat_code_size_ = rw_start - load_start();
}
-bool ElfReader::SetupRWXAppCompat() {
+void ElfReader::SetupRWXAppCompat() {
// Warn and fallback to RWX mapping
const std::string layout = elf_layout(phdr_table_, phdr_num_);
DL_WARN("\"%s\": RX|RW compat loading failed, falling back to RWX compat: load segments [%s]",
name_.c_str(), layout.c_str());
-
- // There is no RELRO protection in this mode
- CHECK(!compat_relro_start_);
- CHECK(!compat_relro_size_);
-
- // Make the reserved mapping RWX; the ELF contents will be read
- // into it, instead of mapped over it.
- return mprotect(load_start_, load_size_, PROT_READ | PROT_WRITE | PROT_EXEC) != -1;
+ compat_code_start_ = load_start();
+ compat_code_size_ = load_size();
}
bool ElfReader::Setup16KiBAppCompat() {
@@ -393,9 +384,9 @@
ElfW(Addr) rx_rw_boundary; // Permission boundary for RX|RW compat mode
if (IsEligibleForRXRWAppCompat(&rx_rw_boundary)) {
SetupRXRWAppCompat(rx_rw_boundary);
- } else if (!SetupRWXAppCompat()) {
- DL_ERR_AND_LOG("\"%s\": mprotect failed to setup RWX app compat: %m", name_.c_str());
- return false;
+ } else {
+ should_16kib_app_compat_use_rwx_ = true;
+ SetupRWXAppCompat();
}
LabelCompatVma();
@@ -479,3 +470,23 @@
}
}
}
+
+/*
+ * Apply RX or RWX protection to the code region of the ELF being loaded in
+ * 16KiB compat mode.
+ *
+ * Input:
+ * start -> start address of the compat code region.
+ * size -> size of the compat code region in bytes.
+ * should_16kib_app_compat_use_rwx -> use RWX or RX permission.
+ * Return:
+ * 0 on success, -1 on failure (error code in errno).
+ */
+int phdr_table_protect_16kib_app_compat_code(ElfW(Addr) start, ElfW(Addr) size,
+ bool should_16kib_app_compat_use_rwx) {
+ int prot = PROT_READ | PROT_EXEC;
+ if (should_16kib_app_compat_use_rwx) {
+ prot |= PROT_WRITE;
+ }
+ return mprotect(reinterpret_cast<void*>(start), size, prot);
+}
diff --git a/linker/linker_soinfo.h b/linker/linker_soinfo.h
index 8f56c9f..d107c73 100644
--- a/linker/linker_soinfo.h
+++ b/linker/linker_soinfo.h
@@ -254,6 +254,7 @@
bool link_image(const SymbolLookupList& lookup_list, soinfo* local_group_root,
const android_dlextinfo* extinfo, size_t* relro_fd_offset);
bool protect_relro();
+ bool protect_16kib_app_compat_code();
void tag_globals(bool deterministic_memtag_globals);
ElfW(Addr) apply_memtag_if_mte_globals(ElfW(Addr) sym_addr) const;
@@ -406,11 +407,16 @@
}
bool should_use_16kib_app_compat() const { return should_use_16kib_app_compat_; }
- void set_compat_relro_start(ElfW(Addr) start) { compat_relro_start_ = start; }
- ElfW(Addr) compat_relro_start() const { return compat_relro_start_; }
+ void set_should_16kib_app_compat_use_rwx(bool should_16kib_app_compat_use_rwx) {
+ should_16kib_app_compat_use_rwx_ = should_16kib_app_compat_use_rwx;
+ }
+ bool should_16kib_app_compat_use_rwx() const { return should_16kib_app_compat_use_rwx_; }
- void set_compat_relro_size(ElfW(Addr) size) { compat_relro_size_ = size; }
- ElfW(Addr) compat_relro_size() const { return compat_relro_start_; }
+ void set_compat_code_start(ElfW(Addr) start) { compat_code_start_ = start; }
+ ElfW(Addr) compat_code_start() const { return compat_code_start_; }
+
+ void set_compat_code_size(ElfW(Addr) size) { compat_code_size_ = size; }
+ ElfW(Addr) compat_code_size() const { return compat_code_size_; }
private:
bool is_image_linked() const;
@@ -503,9 +509,14 @@
// 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;
+ // Map ELF segments RWX in app compat mode?
+ bool should_16kib_app_compat_use_rwx_ = false;
+
+ // Region that needs execute permission for 16KiB compat loading.
+ // RX|RW compat mode: Contains code and GNU RELRO sections.
+ // RWX compat mode: Contains the whole ELF.
+ ElfW(Addr) compat_code_start_ = 0;
+ ElfW(Addr) compat_code_size_ = 0;
};
// This function is used by dlvsym() to calculate hash of sym_ver