From 8fdb3419a51ffeda64f9c811f22a42edf9c7f633 Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Wed, 1 Apr 2015 16:57:50 -0700 Subject: [PATCH] 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 --- linker/linker_phdr.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/linker/linker_phdr.cpp b/linker/linker_phdr.cpp index 2c4ca15cc..638c9d6ba 100644 --- a/linker/linker_phdr.cpp +++ b/linker/linker_phdr.cpp @@ -429,9 +429,15 @@ static int _phdr_table_set_load_prot(const ElfW(Phdr)* phdr_table, size_t phdr_c 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(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; }