linker: never mark pages simultaneously writable / executable

When the Android dynamic linker handles a text relocation,
it first relaxes the permissions on the segment being modified,
performs the modifications, and then restores the page permissions.
The relaxation worked by adding PROT_WRITE to whatever protection
bits were set in the section. In effect, the pages were getting set
to PROT_READ|PROT_WRITE|PROT_EXEC, modified, then restored to
PROT_READ|PROT_EXEC

The SELinux kernel code differentiates between 4 different kinds
of executable memory:
* Executable stack (execstack)
* Executable heap (execheap)
* File-based executable code which has been modified (execmod)
* All other executable memory (execmem)

The execmod capability is only triggered by the kernel when a
dirty but non-executable mmap()ed page becomes executable. When that
occurs, an SELinux policy check is done to see if the execmod capability
is provided by policy.

However, if the page is already executable, and PROT_WRITE is added
to the page, it's considered an execmem permission check, not an execmod
permission check.

There are certain circumstances where we may want to distinguish between
execmod and execmem. This change adjusts the dynamic linker to avoid
using RWX pages, so that an RX -> RW -> RX transition will properly
be detected as an execmod permission check instead of an execmem permission
check.

Bug: 20013628
Change-Id: I14d7be29170b156942f9809023f3b2fc1f37846c
diff --git a/linker/linker_phdr.cpp b/linker/linker_phdr.cpp
index 2c4ca15..638c9d6 100644
--- a/linker/linker_phdr.cpp
+++ b/linker/linker_phdr.cpp
@@ -429,9 +429,15 @@
     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;
 
+    int prot = PFLAGS_TO_PROT(phdr->p_flags);
+    if ((extra_prot_flags & PROT_WRITE) != 0) {
+      // make sure we're never simultaneously writable / executable
+      prot &= ~PROT_EXEC;
+    }
+
     int ret = mprotect(reinterpret_cast<void*>(seg_page_start),
                        seg_page_end - seg_page_start,
-                       PFLAGS_TO_PROT(phdr->p_flags) | extra_prot_flags);
+                       prot | extra_prot_flags);
     if (ret < 0) {
       return -1;
     }