Skip to contentSkip to conversation

binfmt_elf: Use ELF_ET_DYN_BASE only for PIE

PATCH Applied Kees Cook 4 participants 6 messages Jun 21, 2017 – Jul 4, 2017

Subsystems (3)
lore
Share & tools
lore

Kees Cook

author
#0 lore raw

PATCH v2 1/1 binfmt_elf: Use ELF_ET_DYN_BASE only for PIE

The ELF_ET_DYN_BASE position was originally intended to keep loaders away from ET_EXEC binaries. (For example, running "/lib/ld-linux.so.2 /bin/cat" might cause the subsequent load of /bin/cat into where the loader had been loaded.) With the advent of PIE (ET_DYN binaries with an INTERP Program Header), ELF_ET_DYN_BASE continued to be used since the kernel was only looking at ET_DYN. However, since ELF_ET_DYN_BASE is traditionally set at the top 1/3rd of the TASK_SIZE, a substantial portion of the address space is unused.

For 32-bit tasks when RLIMIT_STACK is set to RLIM_INFINITY, programs are loaded below the mmap region. This means they can be made to collide (CVE-2017-1000370) or nearly collide (CVE-2017-1000371) with pathological stack regions. Lowering ELF_ET_DYN_BASE solves both by moving programs above the mmap region in all cases, and will now additionally avoid programs falling back to the mmap region by enforcing MAP_FIXED for program loads (i.e. if it would have collided with the stack, now it will fail to load instead of falling back to the mmap region).

To allow for a lower ELF_ET_DYN_BASE, loaders (ET_DYN without INTERP) are loaded into the mmap region, leaving space available for either an ET_EXEC binary with a fixed location or PIE being loaded into mmap by the loader. Only PIE programs are loaded offset from ELF_ET_DYN_BASE, which means architectures can now safely lower their values without risk of loaders colliding with their subsequently loaded programs.

For 64-bit, ELF_ET_DYN_BASE is best set to 4GB to allow runtimes to use the entire 32-bit address space for 32-bit pointers.

Thanks to PaX Team, Daniel Micay, and Rik van Riel for inspiration and suggestions on how to implement this solution.

Fixes d1fd836dcf00 ("mm: split ET_DYN ASLR from mmap ASLR")

---
v2:
- bump x86_64 ELF_ET_DYN_BASE to 4GB, riel.
- moar comments

---
 arch/x86/include/asm/elf.h | 13 +++++-----
 fs/binfmt_elf.c            | 59 +++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 58 insertions(+), 14 deletions(-)
arch/x86/include/asm/elf.h +7 -6
index e8ab9a46bc68..1c18d83d3f09 100644
@@ -245,12 +245,13 @@ extern int force_personality32;
 #define CORE_DUMP_USE_REGSET
 #define ELF_EXEC_PAGESIZE	4096
 
-/* This is the location that an ET_DYN program is loaded if exec'ed.  Typical
-   use of this is to invoke "./ld.so someprog" to test out a new version of
-   the loader.  We need to make sure that it is out of the way of the program
-   that it will "exec", and that there is sufficient room for the brk.  */
-
-#define ELF_ET_DYN_BASE		(TASK_SIZE / 3 * 2)
+/*
+ * This is the base location for PIE (ET_DYN with INTERP) loads. On
+ * 64-bit, this is raised to 4GB to leave the entire 32-bit address
+ * space open for things that want to use the area for 32-bit pointers.
+ */
+#define ELF_ET_DYN_BASE		(mmap_is_ia32() ? 0x000400000UL : \
+						  0x100000000UL)
 
 /* This yields a mask that user programs can use to figure out what
    instruction set this CPU supports.  This could be done in user space,
fs/binfmt_elf.c +51 -8

68 lines not on this page · raw message

Signature · 6 lines
2.7.4


-- 
Kees Cook
Pixel Security
Acked-by on v2 1/1
#1 lore raw
Quoted 14 lines · Kees Cook

To allow for a lower ELF_ET_DYN_BASE, loaders (ET_DYN without INTERP) are loaded into the mmap region, leaving space available for either an
ET_EXEC binary with a fixed location or PIE being loaded into mmap by the
loader. Only PIE programs are loaded offset from ELF_ET_DYN_BASE, which
means architectures can now safely lower their values without risk of loaders colliding with their subsequently loaded programs.

For 64-bit, ELF_ET_DYN_BASE is best set to 4GB to allow runtimes to use
the entire 32-bit address space for 32-bit pointers.

Quoted 7 lines

Thanks to PaX Team, Daniel Micay, and Rik van Riel for inspiration and
suggestions on how to implement this solution.

Fixes d1fd836dcf00 ("mm: split ET_DYN ASLR from mmap ASLR")
#2 lore raw
In reply to Kees Cook
Quoted 9 lines · Kees Cook

The ELF_ET_DYN_BASE position was originally intended to keep loaders away from ET_EXEC binaries. (For example, running "/lib/ld-linux.so.2 /bin/cat" might cause the subsequent load of /bin/cat into where the loader had been loaded.) With the advent of PIE (ET_DYN binaries with an INTERP Program Header), ELF_ET_DYN_BASE continued to be used since the kernel was only looking at ET_DYN. However, since ELF_ET_DYN_BASE is traditionally set at the top 1/3rd of the TASK_SIZE, a substantial portion of the address space is unused.

Quoted 8 lines

For 32-bit tasks when RLIMIT_STACK is set to RLIM_INFINITY, programs are loaded below the mmap region. This means they can be made to collide (CVE-2017-1000370) or nearly collide (CVE-2017-1000371) with pathological stack regions. Lowering ELF_ET_DYN_BASE solves both by moving programs above the mmap region in all cases, and will now additionally avoid programs falling back to the mmap region by enforcing MAP_FIXED for program loads (i.e. if it would have collided with the stack, now it will fail to load instead of falling back to the mmap region).

I do not understand this part. MAP_FIXED will simply unmap whatever was under the requested range, how it could help failing anything? So what would happen if something was mapped in that region, or is this impossible? Moreover MAP_FIXED close to stack will inhibit the stack gap protection.

Signature · 2 lines
Michal Hocko
SUSE Labs
#3 lore raw
Quoted 27 lines · Michal Hocko
Quoted 14 lines · Kees Cook

The ELF_ET_DYN_BASE position was originally intended to keep loaders away from ET_EXEC binaries. (For example, running "/lib/ld- linux.so.2
/bin/cat" might cause the subsequent load of /bin/cat into where the loader had been loaded.) With the advent of PIE (ET_DYN binaries with
an INTERP Program Header), ELF_ET_DYN_BASE continued to be used since
the kernel was only looking at ET_DYN. However, since ELF_ET_DYN_BASE
is traditionally set at the top 1/3rd of the TASK_SIZE, a substantial
portion of the address space is unused.

Quoted 11 lines

For 32-bit tasks when RLIMIT_STACK is set to RLIM_INFINITY, programs are loaded below the mmap region. This means they can be made to collide
(CVE-2017-1000370) or nearly collide (CVE-2017-1000371) with pathological
stack regions. Lowering ELF_ET_DYN_BASE solves both by moving programs
above the mmap region in all cases, and will now additionally avoid programs falling back to the mmap region by enforcing MAP_FIXED for program loads (i.e. if it would have collided with the stack, now it will fail to load instead of falling back to the mmap region).

Quoted 6 lines

I do not understand this part. MAP_FIXED will simply unmap whatever was under the requested range, how it could help failing anything? So what would happen if something was mapped in that region, or is this impossible? Moreover MAP_FIXED close to stack will inhibit the stack gap
protection.

I don't think there's a reason to use MAP_FIXED. PaX likely ignores the address hint with RANDMMAP in that code, which would explain it there.

author
#4 lore raw
In reply to Michal Hocko
Quoted 19 lines · Michal Hocko
Quoted 9 lines · Kees Cook

The ELF_ET_DYN_BASE position was originally intended to keep loaders away from ET_EXEC binaries. (For example, running "/lib/ld-linux.so.2 /bin/cat" might cause the subsequent load of /bin/cat into where the loader had been loaded.) With the advent of PIE (ET_DYN binaries with an INTERP Program Header), ELF_ET_DYN_BASE continued to be used since the kernel was only looking at ET_DYN. However, since ELF_ET_DYN_BASE is traditionally set at the top 1/3rd of the TASK_SIZE, a substantial portion of the address space is unused.

Quoted 8 lines

For 32-bit tasks when RLIMIT_STACK is set to RLIM_INFINITY, programs are loaded below the mmap region. This means they can be made to collide (CVE-2017-1000370) or nearly collide (CVE-2017-1000371) with pathological stack regions. Lowering ELF_ET_DYN_BASE solves both by moving programs above the mmap region in all cases, and will now additionally avoid programs falling back to the mmap region by enforcing MAP_FIXED for program loads (i.e. if it would have collided with the stack, now it will fail to load instead of falling back to the mmap region).

Quoted 5 lines

I do not understand this part. MAP_FIXED will simply unmap whatever was under the requested range, how it could help failing anything? So what would happen if something was mapped in that region, or is this impossible? Moreover MAP_FIXED close to stack will inhibit the stack gap protection.

Hmm, well, that's my misunderstanding. Regardless, it should still use MAP_FIXED otherwise we end up with potentially unpredictable results. (Note that MAP_FIXED is already used all all remaining allocations, it was just missing on the first one.)

-Kees

Signature · 2 lines
Kees Cook
Pixel Security
#5 lore raw

[Sorry for a late reply]

Quoted 20 lines · Kees Cook
Quoted 19 lines · Michal Hocko
Quoted 17 lines · Kees Cook

The ELF_ET_DYN_BASE position was originally intended to keep loaders away from ET_EXEC binaries. (For example, running "/lib/ld-linux.so.2 /bin/cat" might cause the subsequent load of /bin/cat into where the loader had been loaded.) With the advent of PIE (ET_DYN binaries with an INTERP Program Header), ELF_ET_DYN_BASE continued to be used since the kernel was only looking at ET_DYN. However, since ELF_ET_DYN_BASE is traditionally set at the top 1/3rd of the TASK_SIZE, a substantial portion of the address space is unused.

For 32-bit tasks when RLIMIT_STACK is set to RLIM_INFINITY, programs are loaded below the mmap region. This means they can be made to collide (CVE-2017-1000370) or nearly collide (CVE-2017-1000371) with pathological stack regions. Lowering ELF_ET_DYN_BASE solves both by moving programs above the mmap region in all cases, and will now additionally avoid programs falling back to the mmap region by enforcing MAP_FIXED for program loads (i.e. if it would have collided with the stack, now it will fail to load instead of falling back to the mmap region).

Quoted 8 lines
Quoted 5 lines

I do not understand this part. MAP_FIXED will simply unmap whatever was under the requested range, how it could help failing anything? So what would happen if something was mapped in that region, or is this impossible? Moreover MAP_FIXED close to stack will inhibit the stack gap protection.

Hmm, well, that's my misunderstanding. Regardless, it should still use MAP_FIXED otherwise we end up with potentially unpredictable results.

Why that matters? The base is random, right? This is quite early in the initialization so the address space should be pretty empty at this stage.

Quoted 2 lines

(Note that MAP_FIXED is already used all all remaining allocations, it was just missing on the first one.)

OK, so what happens when there was an existing mapping at the requested address and we just punched it off? Will things work properly? I can see some MAP_FIXED users in load_elf_binary but the function is simply unparsable for me, unfortunatelly.

Signature · 2 lines
Michal Hocko
SUSE Labs