Skip to contentSkip to conversation

MAP_FIXED for ELF mappings

PATCH In review Michal Hocko 7 participants 35 messages Oct 4, 2017 – Oct 20, 2017

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

Hi,
while studying CVE-2017-1000253 and the MAP_FIXED usage in load_elf* code paths I have stumbled over MAP_FIXED usage for elf segments mapping. I am not really familiar with this area much so I might draw completely incorrect conclusions here but I am really wondering why we are doing MAP_FIXED there at all.

I can see why some segments really have to be mapped at a specific address but I wonder whether MAP_FIXED is the right tool to achieve that. It seems to me that MAP_FIXED is fundamentally dangerous because it unmaps any existing mapping. I assume that nothing should be really mapped in the requested range that early so we can only stumble over something when the address space randomization place things unexpectedly (which was the case of the above mentioned CVE AFAIU).

So my primary question is whether we can/should simply drop MAP_FIXED from elf_map at all. Instead we should test whether the mapping was successful for the requested address and fail otherwise. I realize that failing due to something that a user has no idea about sucks a lot but it seems to me safer to simply complain into the log and fail is a safer option.

Something like the following completely untested diff. Or am I completely missing the point of the MAP_FIXED purpose?
---

fs/binfmt_elf.c +26 -3
index 6466153f2bf0..09456e2add18 100644
@@ -341,6 +341,29 @@ create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
 
 #ifndef elf_map
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
 		struct elf_phdr *eppnt, int prot, int type,
 		unsigned long total_size)
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);
 	} else
-		map_addr = vm_mmap(filep, addr, size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
 	return(map_addr);
 }
@@ -1215,7 +1238,7 @@ static int load_elf_library(struct file *file)
 		eppnt++;
 
 	/* Now use mmap to map the library into memory. */
-	error = vm_mmap(file,
+	error = elf_vm_mmap(file,
 			ELF_PAGESTART(eppnt->p_vaddr),
 			(eppnt->p_filesz +
 			 ELF_PAGEOFFSET(eppnt->p_vaddr)),
Signature · 2 lines
Michal Hocko
SUSE Labs
#1 lore raw

Dohh, screwed up From. Sorry for spamming.

Quoted 85 lines · Michal Hocko

Hi,
while studying CVE-2017-1000253 and the MAP_FIXED usage in load_elf* code paths I have stumbled over MAP_FIXED usage for elf segments mapping. I am not really familiar with this area much so I might draw completely incorrect conclusions here but I am really wondering why we are doing MAP_FIXED there at all.

I can see why some segments really have to be mapped at a specific address but I wonder whether MAP_FIXED is the right tool to achieve that. It seems to me that MAP_FIXED is fundamentally dangerous because it unmaps any existing mapping. I assume that nothing should be really mapped in the requested range that early so we can only stumble over something when the address space randomization place things unexpectedly (which was the case of the above mentioned CVE AFAIU).

So my primary question is whether we can/should simply drop MAP_FIXED from elf_map at all. Instead we should test whether the mapping was successful for the requested address and fail otherwise. I realize that failing due to something that a user has no idea about sucks a lot but it seems to me safer to simply complain into the log and fail is a safer option.

Something like the following completely untested diff. Or am I completely missing the point of the MAP_FIXED purpose?
---

fs/binfmt_elf.c +26 -3
index 6466153f2bf0..09456e2add18 100644
@@ -341,6 +341,29 @@ create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
 
 #ifndef elf_map
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
 		struct elf_phdr *eppnt, int prot, int type,
 		unsigned long total_size)
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);
 	} else
-		map_addr = vm_mmap(filep, addr, size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
 	return(map_addr);
 }
@@ -1215,7 +1238,7 @@ static int load_elf_library(struct file *file)
 		eppnt++;
 
 	/* Now use mmap to map the library into memory. */
-	error = vm_mmap(file,
+	error = elf_vm_mmap(file,
 			ELF_PAGESTART(eppnt->p_vaddr),
 			(eppnt->p_filesz +
 			 ELF_PAGEOFFSET(eppnt->p_vaddr)),
Signature · 2 lines
Michal Hocko
SUSE Labs
Signature · 2 lines
Michal Hocko
SUSE Labs
#2 lore raw
In reply to Michal Hocko
Quoted 15 lines · Michal Hocko

Hi,
while studying CVE-2017-1000253 and the MAP_FIXED usage in load_elf* code paths I have stumbled over MAP_FIXED usage for elf segments mapping. I am not really familiar with this area much so I might draw completely incorrect conclusions here but I am really wondering why we are doing MAP_FIXED there at all.

I can see why some segments really have to be mapped at a specific address but I wonder whether MAP_FIXED is the right tool to achieve that. It seems to me that MAP_FIXED is fundamentally dangerous because it unmaps any existing mapping. I assume that nothing should be really mapped in the requested range that early so we can only stumble over something when the address space randomization place things unexpectedly (which was the case of the above mentioned CVE AFAIU).

Quoted 6 lines

So my primary question is whether we can/should simply drop MAP_FIXED from elf_map at all. Instead we should test whether the mapping was successful for the requested address and fail otherwise. I realize that failing due to something that a user has no idea about sucks a lot but it seems to me safer to simply complain into the log and fail is a safer option.

Sorry to interrupt. I tried below example.c and example2.c files and compile and link them with fpie and pie. Seems in the final PIE executable, the local global is pc relative. Means the data segment has to be put after the code segment though PIE program. Then MAP_FIXED is a good to have flag, especially we have counted in the total_size to search an area to cover the whole dynamic program and get the load_bias. It's no way to fail to get map agrea in case load_addr_set == 1. So with MAP_FIXED set, we won't take time to search the mm vma rb tree when load_addr_set == 1, but just return the specified addr directly, looks more efficient.

########### example2.c
int local_global_var=0x10;
int local_global_func(void)

{
        return 0x40;
}

########## example.c
#include <stdio.h>
extern int local_global_var;
extern int local_global_func(void);

int main(void)

{  
        int x = local_global_func();  
        local_global_var= 0x20;  
 
        while(getchar()!= 'q')  
                continue;  
        return 0;  
}

$gcc -fpie -c example.c example2.c
$ls example*.o
example.o example2.o
$objdump -d -r example.o
...
0000000000000000 <main>:

   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   e8 00 00 00 00          callq  d <main+0xd>
                        9: R_X86_64_PLT32       local_global_func-0x4
   d:   89 45 fc                mov    %eax,-0x4(%rbp)
  10:   c7 05 00 00 00 00 20    movl   $0x20,0x0(%rip)        # 1a
<main+0x1a>
  17:   00 00 00 
                        12: R_X86_64_PC32       local_global_var-0x8
  1a:   eb 01                   jmp    1d <main+0x1d>
  1c:   90                      nop
  1d:   e8 00 00 00 00          callq  22 <main+0x22>
                        1e: R_X86_64_PLT32      getchar-0x4
  22:   83 f8 71                cmp    $0x71,%eax
  25:   75 f5                   jne    1c <main+0x1c>
  27:   b8 00 00 00 00          mov    $0x0,%eax
  2c:   c9                      leaveq 
  2d:   c3                      retq

...
$gcc -pie -o test example.o example2.o
$objdump -d -r test
...
0000000000000655 <main>:

 655:   55                      push   %rbp
 656:   48 89 e5                mov    %rsp,%rbp
 659:   48 83 ec 10             sub    $0x10,%rsp
 65d:   e8 e8 ff ff ff          callq  64a <local_global_func>
 662:   89 45 fc                mov    %eax,-0x4(%rbp)
 665:   c7 05 b5 09 20 00 20    movl   $0x20,0x2009b5(%rip)
#201024 <local_global_var>
 66c:   00 00 00 
 66f:   eb 01                   jmp    672 <main+0x1d>
 671:   90                      nop
 672:   e8 a9 fe ff ff          callq  520 <getchar@plt>
 677:   83 f8 71                cmp    $0x71,%eax
 67a:   75 f5                   jne    671 <main+0x1c>
 67c:   b8 00 00 00 00          mov    $0x0,%eax
 681:   c9                      leaveq 
 682:   c3                      retq   
 683:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 68a:   00 00 00 
 68d:   0f 1f 00                nopl   (%rax)

...

Quoted 64 lines

Something like the following completely untested diff. Or am I completely missing the point of the MAP_FIXED purpose?
---

fs/binfmt_elf.c +26 -3
index 6466153f2bf0..09456e2add18 100644
@@ -341,6 +341,29 @@ create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
 
 #ifndef elf_map
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
 		struct elf_phdr *eppnt, int prot, int type,
 		unsigned long total_size)
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);
 	} else
-		map_addr = vm_mmap(filep, addr, size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
 	return(map_addr);
 }
@@ -1215,7 +1238,7 @@ static int load_elf_library(struct file *file)
 		eppnt++;
 
 	/* Now use mmap to map the library into memory. */
-	error = vm_mmap(file,
+	error = elf_vm_mmap(file,
 			ELF_PAGESTART(eppnt->p_vaddr),
 			(eppnt->p_filesz +
 			 ELF_PAGEOFFSET(eppnt->p_vaddr)),
Signature · 2 lines
Michal Hocko
SUSE Labs
#3 lore raw
Quoted 23 lines · Baoquan He
Quoted 15 lines · Michal Hocko

Hi,
while studying CVE-2017-1000253 and the MAP_FIXED usage in load_elf* code paths I have stumbled over MAP_FIXED usage for elf segments mapping. I am not really familiar with this area much so I might draw completely incorrect conclusions here but I am really wondering why we are doing MAP_FIXED there at all.

I can see why some segments really have to be mapped at a specific address but I wonder whether MAP_FIXED is the right tool to achieve that. It seems to me that MAP_FIXED is fundamentally dangerous because it unmaps any existing mapping. I assume that nothing should be really mapped in the requested range that early so we can only stumble over something when the address space randomization place things unexpectedly (which was the case of the above mentioned CVE AFAIU).

Quoted 6 lines

So my primary question is whether we can/should simply drop MAP_FIXED from elf_map at all. Instead we should test whether the mapping was successful for the requested address and fail otherwise. I realize that failing due to something that a user has no idea about sucks a lot but it seems to me safer to simply complain into the log and fail is a safer option.

Quoted 10 lines

Sorry to interrupt. I tried below example.c and example2.c files and compile and link them with fpie and pie. Seems in the final PIE executable, the local global is pc relative. Means the data segment has to be put after the code segment though PIE program. Then MAP_FIXED is a good to have flag, especially we have counted in the total_size to search an area to cover the whole dynamic program and get the load_bias. It's no way to fail to get map agrea in case load_addr_set == 1. So with MAP_FIXED set, we won't take time to search the mm vma rb tree when load_addr_set == 1, but just return the specified addr directly, looks more efficient.

I am sorry, but I do not follow. elf_map should get the address hint to use. MAP_FIXED merely unmaps anything underneath if there is something which is exactly what I called dangerous. Without the flag we would just fail in that case. Or, are you suggesting that MAP_FIXED is a performance optimization because we are not doing find_vma in that case?

Signature · 2 lines
Michal Hocko
SUSE Labs
#4 lore raw
In reply to Baoquan He

I made a clean up patch according to Oleg's suggestion. It's trying to get an map area to cover total_size, then do mmap for for the 1st program segment only. Not sure if this way is correct.

Quoted 1 line

From 40f231bb78a74caebcb4a898089a9fa5323be05f Mon Sep 17 00:00:00 2001

From: Baoquan He <[email protected]>
Date: Fri, 29 Sep 2017 21:35:30 +0800
Subject: [PATCH] binfmt_elf: Clean up the elf_map

Oleg pointed out that it's really ugly to do mmap of the total_size, then unmap the region excluding the 1st segment. The right way should be search an unmapped area which can cover region of total_size, then map the 1st segment only.

And also update the code comment accordingly. In below commit, the relevant code comment is not changed to cover the ELF binary image case. commit a87938b2e2 ("fs/binfmt_elf.c: fix bug in loading of PIE binaries")

---
 fs/binfmt_elf.c | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)
fs/binfmt_elf.c +15 -12
index 72b7ecba7ead..43a47b2aa3f6 100644
@@ -357,22 +357,25 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 		return addr;
 
 	/*
-	* total_size is the size of the ELF (interpreter) image.
-	* The _first_ mmap needs to know the full size, otherwise
-	* randomization might put this image into an overlapping
-	* position with the ELF binary image. (since size < total_size)
-	* So we first map the 'big' image - and unmap the remainder at
-	* the end. (which unmap is needed for ELF images with holes.)
+	* total_size is the size of the ELF binary image or the ELF loader
+	* image. For loader image, the _first_ mmap needs to know the full
+	* size, otherwise randomization might put image into an overlapping
+	* position with the ELF binary image.(since size < total_size)
+	* So we use total_size to get an area to cover the whole loader image,
+	* then map the 1st progment segment only with its own size. For binary
+	* image, similarly, the _first_ mmap also needs to know the full size,
+	* otherwise randomization might put image above mm->mmap_base.
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, flags, off);
-		if (!BAD_ADDR(map_addr))
-			vm_munmap(map_addr+size, total_size-size);
-	} else
-		map_addr = vm_mmap(filep, addr, size, prot, flags, off);
+		addr = get_unmapped_area(file, addr, total_size, off, flags);
+		if (offset_in_page(addr))
+			return addr;
+	}
+
+	map_addr = vm_mmap(filep, addr, size, prot, flags, off);
 
-	return(map_addr);
+	return (map_addr);
 }
 
 #endif /* !elf_map */
Signature · 1 line
2.5.5
#5 lore raw
Quoted 44 lines · Baoquan He

I made a clean up patch according to Oleg's suggestion. It's trying to get an map area to cover total_size, then do mmap for for the 1st program segment only. Not sure if this way is correct.

Quoted 1 line

From 40f231bb78a74caebcb4a898089a9fa5323be05f Mon Sep 17 00:00:00 2001

From: Baoquan He <[email protected]>
Date: Fri, 29 Sep 2017 21:35:30 +0800
Subject: [PATCH] binfmt_elf: Clean up the elf_map

Oleg pointed out that it's really ugly to do mmap of the total_size, then unmap the region excluding the 1st segment. The right way should be search an unmapped area which can cover region of total_size, then map the 1st segment only.

And also update the code comment accordingly. In below commit, the relevant code comment is not changed to cover the ELF binary image case. commit a87938b2e2 ("fs/binfmt_elf.c: fix bug in loading of PIE binaries")

---
 fs/binfmt_elf.c | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)
fs/binfmt_elf.c +7 -6
index 72b7ecba7ead..43a47b2aa3f6 100644
@@ -357,22 +357,25 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 		return addr;
 
 	/*
-	* total_size is the size of the ELF (interpreter) image.
-	* The _first_ mmap needs to know the full size, otherwise
-	* randomization might put this image into an overlapping
-	* position with the ELF binary image. (since size < total_size)
-	* So we first map the 'big' image - and unmap the remainder at
-	* the end. (which unmap is needed for ELF images with holes.)
+	* total_size is the size of the ELF binary image or the ELF loader
+	* image. For loader image, the _first_ mmap needs to know the full
+	* size, otherwise randomization might put image into an overlapping
+	* position with the ELF binary image.(since size < total_size)
+	* So we use total_size to get an area to cover the whole loader image,
+	* then map the 1st progment segment only with its own size. For binary
+	* image, similarly, the _first_ mmap also needs to know the full size,

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 371

Quoted 10 lines
fs/binfmt_elf.c +2 -5
+	* otherwise randomization might put image above mm->mmap_base.
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, flags, off);
-		if (!BAD_ADDR(map_addr))
-			vm_munmap(map_addr+size, total_size-size);
-	} else
-		map_addr = vm_mmap(filep, addr, size, prot, flags, off);
+		addr = get_unmapped_area(file, addr, total_size, off, flags);

So how does this prevent clobbering an existing VMA when flags contains MAP_FIXED?

Quoted 13 lines
fs/binfmt_elf.c +6 -1
+		if (offset_in_page(addr))
+			return addr;
+	}
+
+	map_addr = vm_mmap(filep, addr, size, prot, flags, off);
 
-	return(map_addr);
+	return (map_addr);
 }
 
 #endif /* !elf_map */
Signature · 1 line
2.5.5
Signature · 2 lines
Michal Hocko
SUSE Labs
#6 lore raw
Quoted 31 lines · Michal Hocko
Quoted 30 lines · Baoquan He

I made a clean up patch according to Oleg's suggestion. It's trying to get an map area to cover total_size, then do mmap for for the 1st program segment only. Not sure if this way is correct.

Quoted 1 line

From 40f231bb78a74caebcb4a898089a9fa5323be05f Mon Sep 17 00:00:00 2001

From: Baoquan He <[email protected]>
Date: Fri, 29 Sep 2017 21:35:30 +0800
Subject: [PATCH] binfmt_elf: Clean up the elf_map

Oleg pointed out that it's really ugly to do mmap of the total_size, then unmap the region excluding the 1st segment. The right way should be search an unmapped area which can cover region of total_size, then map the 1st segment only.

And also update the code comment accordingly. In below commit, the relevant code comment is not changed to cover the ELF binary image case. commit a87938b2e2 ("fs/binfmt_elf.c: fix bug in loading of PIE binaries")

---
 fs/binfmt_elf.c | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)
fs/binfmt_elf.c +0 -0
index 72b7ecba7ead..43a47b2aa3f6 100644
@@ -357,22 +357,25 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 		return addr;
 
Quoted 15 lines
Quoted 5 lines
fs/binfmt_elf.c +0 -4
 	/*
-	* total_size is the size of the ELF (interpreter) image.
-	* The _first_ mmap needs to know the full size, otherwise
-	* randomization might put this image into an overlapping
-	* position with the ELF binary image. (since size < total_size)

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 367

Quoted 10 lines
fs/binfmt_elf.c +8 -2
-	* So we first map the 'big' image - and unmap the remainder at
-	* the end. (which unmap is needed for ELF images with holes.)
+	* total_size is the size of the ELF binary image or the ELF loader
+	* image. For loader image, the _first_ mmap needs to know the full
+	* size, otherwise randomization might put image into an overlapping
+	* position with the ELF binary image.(since size < total_size)
+	* So we use total_size to get an area to cover the whole loader image,
+	* then map the 1st progment segment only with its own size. For binary
+	* image, similarly, the _first_ mmap also needs to know the full size,
+	* otherwise randomization might put image above mm->mmap_base.

Oh, no, here, it won't include the PIE binary case. Since it must be from ELF_ET_DYN_BASE. Here should be the "ld.so program" case.

Quoted 4 lines
Quoted 4 lines
fs/binfmt_elf.c +0 -1
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, flags, off);
Quoted 8 lines

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 371

Quoted 5 lines
fs/binfmt_elf.c +1 -4
-		if (!BAD_ADDR(map_addr))
-			vm_munmap(map_addr+size, total_size-size);
-	} else
-		map_addr = vm_mmap(filep, addr, size, prot, flags, off);
+		addr = get_unmapped_area(file, addr, total_size, off, flags);

So how does this prevent clobbering an existing VMA when flags contains MAP_FIXED?

Earlier flush_old_exec() is called to clean all old VMAs. Here if total_size is non-zero only if it's the 1st prgoram segment of dynamic loader, either from load_elf_interp() or the "ld.so program" case. With my understanding, it can't be meeting an existing VMA. Not sure if it's correct. Currently, it haven't passed to ld.so to link other .so.

Quoted 18 lines
Quoted 14 lines
fs/binfmt_elf.c +6 -1
+		if (offset_in_page(addr))
+			return addr;
+	}
+
+	map_addr = vm_mmap(filep, addr, size, prot, flags, off);
 
-	return(map_addr);
+	return (map_addr);
 }
 
 #endif /* !elf_map */
Signature · 1 line
2.5.5
Signature · 2 lines
Michal Hocko
SUSE Labs
#7 lore raw
Quoted 64 lines · Baoquan He
Quoted 31 lines · Michal Hocko
Quoted 30 lines · Baoquan He

I made a clean up patch according to Oleg's suggestion. It's trying to get an map area to cover total_size, then do mmap for for the 1st program segment only. Not sure if this way is correct.

Quoted 1 line

From 40f231bb78a74caebcb4a898089a9fa5323be05f Mon Sep 17 00:00:00 2001

From: Baoquan He <[email protected]>
Date: Fri, 29 Sep 2017 21:35:30 +0800
Subject: [PATCH] binfmt_elf: Clean up the elf_map

Oleg pointed out that it's really ugly to do mmap of the total_size, then unmap the region excluding the 1st segment. The right way should be search an unmapped area which can cover region of total_size, then map the 1st segment only.

And also update the code comment accordingly. In below commit, the relevant code comment is not changed to cover the ELF binary image case. commit a87938b2e2 ("fs/binfmt_elf.c: fix bug in loading of PIE binaries")

---
 fs/binfmt_elf.c | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)
fs/binfmt_elf.c +0 -0
index 72b7ecba7ead..43a47b2aa3f6 100644
@@ -357,22 +357,25 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 		return addr;
 
Quoted 15 lines
Quoted 5 lines
fs/binfmt_elf.c +0 -4
 	/*
-	* total_size is the size of the ELF (interpreter) image.
-	* The _first_ mmap needs to know the full size, otherwise
-	* randomization might put this image into an overlapping
-	* position with the ELF binary image. (since size < total_size)

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 367

Quoted 10 lines
fs/binfmt_elf.c +8 -2
-	* So we first map the 'big' image - and unmap the remainder at
-	* the end. (which unmap is needed for ELF images with holes.)
+	* total_size is the size of the ELF binary image or the ELF loader
+	* image. For loader image, the _first_ mmap needs to know the full
+	* size, otherwise randomization might put image into an overlapping
+	* position with the ELF binary image.(since size < total_size)
+	* So we use total_size to get an area to cover the whole loader image,
+	* then map the 1st progment segment only with its own size. For binary
+	* image, similarly, the _first_ mmap also needs to know the full size,
+	* otherwise randomization might put image above mm->mmap_base.

Oh, no, here, it won't include the PIE binary case. Since it must be from ELF_ET_DYN_BASE. Here should be the "ld.so program" case.

Quoted 4 lines
Quoted 4 lines
fs/binfmt_elf.c +0 -1
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, flags, off);
Quoted 8 lines

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 371

Quoted 5 lines
fs/binfmt_elf.c +1 -4
-		if (!BAD_ADDR(map_addr))
-			vm_munmap(map_addr+size, total_size-size);
-	} else
-		map_addr = vm_mmap(filep, addr, size, prot, flags, off);
+		addr = get_unmapped_area(file, addr, total_size, off, flags);

So how does this prevent clobbering an existing VMA when flags contains MAP_FIXED?

Quoted 1 line

Earlier flush_old_exec() is called to clean all old VMAs.

Yes, but we already have a new stack mapped and that was the point of the referenced CVE where the binary segments got mapped over the stack AFAIU.

Signature · 2 lines
Michal Hocko
SUSE Labs
#8 lore raw
Quoted 4 lines · Michal Hocko

Yes, but we already have a new stack mapped and that was the point of the referenced CVE where the binary segments got mapped over the stack AFAIU.

Well, if you control the binary to the point where you just make the ELF section map on top of the stack, what's the problem?

I mean, it's not a security issue. You could just have written the code to do mmap() instead.

So I think this is a "crazy users can do crazy things, we're not arbiters of taste" thing.

Linus

#9 lore raw
Quoted 6 lines · Linus Torvalds
Quoted 4 lines · Michal Hocko

Yes, but we already have a new stack mapped and that was the point of the referenced CVE where the binary segments got mapped over the stack AFAIU.

Quoted 8 lines

Well, if you control the binary to the point where you just make the ELF section map on top of the stack, what's the problem?

I mean, it's not a security issue. You could just have written the code to do mmap() instead.

So I think this is a "crazy users can do crazy things, we're not arbiters of taste" thing.

Well, my understanding of the CVE (fixed by a87938b2e246 ("fs/binfmt_elf.c: fix bug in loading of PIE binaries")) is that the elf and stack randomization worked against each other. 7ffbad995000-7ffbad996000 rw-p 00000000 00:00 0

7ffbad996000-7ffbad998000 r-xp 00000000 fd:00 4194375                    /tmp/PIE
7ffbad999000-7ffbadb97000 rw-p 00000000 00:00 0                          [stack]
7ffbadb97000-7ffbadb98000 r--p 00001000 fd:00 4194375                    /tmp/PIE
7ffbadb98000-7ffbadbb9000 rw-p 00002000 fd:00 4194375                    /tmp/PIE
7ffbadbba000-7ffc0d9ba000 rw-p 00000000 00:00 0                          [heap] # incorrectly marked stack

So while the issue is already fixed that made me think how MAP_FIXED silently corrupted the underlying stack and how it is inherently dangerous and also why do we need it in the first place. I would rather make any issues like that impossible for ever.

But as I've said I have hard time to wrap my head around the whole load_elf_binary so I can be missing something easily here.

Signature · 2 lines
Michal Hocko
SUSE Labs
#10 lore raw
In reply to Michal Hocko
Quoted 13 lines · Michal Hocko

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 371

Quoted 8 lines · Baoquan He
fs/binfmt_elf.c +1 -5
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, flags, off);
-		if (!BAD_ADDR(map_addr))
-			vm_munmap(map_addr+size, total_size-size);
-	} else
-		map_addr = vm_mmap(filep, addr, size, prot, flags, off);
+		addr = get_unmapped_area(file, addr, total_size, off, flags);

So how does this prevent clobbering an existing VMA when flags contains MAP_FIXED?

I got lost...

this is just cleanup, it should not change the behaviour, with or without MAP_FIXED. It just avoids the mmap(total_size) + munmap(extra_size).

Oleg.

#11 lore raw
Quoted 15 lines · Oleg Nesterov
Quoted 13 lines · Michal Hocko

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 371

Quoted 8 lines · Baoquan He
fs/binfmt_elf.c +1 -5
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, flags, off);
-		if (!BAD_ADDR(map_addr))
-			vm_munmap(map_addr+size, total_size-size);
-	} else
-		map_addr = vm_mmap(filep, addr, size, prot, flags, off);
+		addr = get_unmapped_area(file, addr, total_size, off, flags);

So how does this prevent clobbering an existing VMA when flags contains MAP_FIXED?

Quoted 4 lines

I got lost...

this is just cleanup, it should not change the behaviour, with or without MAP_FIXED. It just avoids the mmap(total_size) + munmap(extra_size).

OK, I have clearly misunderstood the intention then.

Signature · 2 lines
Michal Hocko
SUSE Labs
#12 lore raw

cover fs, elf: get rid of MAP_FIXED from the loader

In reply to Michal Hocko

Hi,
the previous discussion didn't really show any hard requirement for MAP_FIXED usage for the elf segments mapping. I have spent some more time studying the code (thanks to Qualys for their insight) and concluded that the current MAP_FIXED usage is rather fragile and not really needed. The first patch replaces it by a hint mmaping and failing rather than silently corrupt an existing memory and the second patch removes MAP_FIXED for the initial segment mapping because this shouldn't be really needed either, I would even call it wrong.
Anyway, more details are in the changelog of patches. I will really appreciate any feedback.

This has passed some testing with PIE/PIC binaries running in the loop without any negative side effects detected.

#13 lore raw

PATCH 1/2 fs, elf: drop MAP_FIXED usage from elf_map

From: Michal Hocko <[email protected]>

Both load_elf_interp and load_elf_binary rely on elf_map to map segments on a controlled address and they use MAP_FIXED to enforce that. This is however dangerous thing prone to silent data corruption which can be even exploitable. Let's take CVE-2017-1000253 as an example. At the time (before eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE")) ELF_ET_DYN_BASE was at TASK_SIZE / 3 * 2 which is not that far away from the stack top on 32b (legacy) memory layout (only 1GB away). Therefore we could end up mapping over the existing stack with some luck.

The issue has been fixed since then (a87938b2e246 ("fs/binfmt_elf.c:
fix bug in loading of PIE binaries")), ELF_ET_DYN_BASE moved moved much further from the stack (eab09532d400 and later by c715b72c1ba4 ("mm:
revert x86_64 and arm64 ELF_ET_DYN_BASE base changes")) and excessive stack consumption early during execve fully stopped by da029c11e6b1 ("exec: Limit arg stack to at most 75% of _STK_LIM"). So we should be safe and any attack should be impractical. On the other hand this is just too subtle assumption so it can break quite easily and hard to spot.

I believe that the MAP_FIXED usage in load_elf_binary (et. al) is still fundamentally dangerous. Moreover it shouldn't be even needed. We are at the early process stage and so there shouldn't be unrelated mappings (except for stack and loader) existing so mmap for a given address should succeed even without MAP_FIXED. Something is terribly wrong if this is not the case and we should rather fail than silently corrupt the underlying mapping.

Address this issue by adding a helper elf_vm_mmap used by elf_map which drops MAP_FIXED when asking for the mapping and check whether the returned address really matches what the caller asked for and complain loudly if this is not the case and fail. Such a failure would be a kernel bug and it should alarm us to look what has gone wrong.

---
 fs/binfmt_elf.c | 29 ++++++++++++++++++++++++++---
 1 file changed, 26 insertions(+), 3 deletions(-)
fs/binfmt_elf.c +26 -3
index 6466153f2bf0..09456e2add18 100644
@@ -341,6 +341,29 @@ create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
 
 #ifndef elf_map
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
 		struct elf_phdr *eppnt, int prot, int type,
 		unsigned long total_size)
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);
 	} else
-		map_addr = vm_mmap(filep, addr, size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
 	return(map_addr);
 }
@@ -1215,7 +1238,7 @@ static int load_elf_library(struct file *file)
 		eppnt++;
 
 	/* Now use mmap to map the library into memory. */
-	error = vm_mmap(file,
+	error = elf_vm_mmap(file,
 			ELF_PAGESTART(eppnt->p_vaddr),
 			(eppnt->p_filesz +
 			 ELF_PAGEOFFSET(eppnt->p_vaddr)),
Signature · 1 line
2.14.2
#14 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

Quoted 55 lines · Michal Hocko

From: Michal Hocko <[email protected]>

Both load_elf_interp and load_elf_binary rely on elf_map to map segments on a controlled address and they use MAP_FIXED to enforce that. This is however dangerous thing prone to silent data corruption which can be even exploitable. Let's take CVE-2017-1000253 as an example. At the time (before eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE")) ELF_ET_DYN_BASE was at TASK_SIZE / 3 * 2 which is not that far away from the stack top on 32b (legacy) memory layout (only 1GB away). Therefore we could end up mapping over the existing stack with some luck.

The issue has been fixed since then (a87938b2e246 ("fs/binfmt_elf.c:
fix bug in loading of PIE binaries")), ELF_ET_DYN_BASE moved moved much further from the stack (eab09532d400 and later by c715b72c1ba4 ("mm:
revert x86_64 and arm64 ELF_ET_DYN_BASE base changes")) and excessive stack consumption early during execve fully stopped by da029c11e6b1 ("exec: Limit arg stack to at most 75% of _STK_LIM"). So we should be safe and any attack should be impractical. On the other hand this is just too subtle assumption so it can break quite easily and hard to spot.

I believe that the MAP_FIXED usage in load_elf_binary (et. al) is still fundamentally dangerous. Moreover it shouldn't be even needed. We are at the early process stage and so there shouldn't be unrelated mappings (except for stack and loader) existing so mmap for a given address should succeed even without MAP_FIXED. Something is terribly wrong if this is not the case and we should rather fail than silently corrupt the underlying mapping.

Address this issue by adding a helper elf_vm_mmap used by elf_map which drops MAP_FIXED when asking for the mapping and check whether the returned address really matches what the caller asked for and complain loudly if this is not the case and fail. Such a failure would be a kernel bug and it should alarm us to look what has gone wrong.

---
 fs/binfmt_elf.c | 29 ++++++++++++++++++++++++++---
 1 file changed, 26 insertions(+), 3 deletions(-)
fs/binfmt_elf.c +7 -0
index 6466153f2bf0..09456e2add18 100644
@@ -341,6 +341,29 @@ create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
 
 #ifndef elf_map
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+               unsigned long size, int prot, int type, unsigned long off)
+{
+       unsigned long map_addr;
+
+       /*
+        * If caller requests the mapping at a specific place, make sure we fail

fs/binfmt_elf.c create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec, · line 360

Quoted 10 lines
fs/binfmt_elf.c +10 -0
+        * rather than potentially clobber an existing mapping which can have
+        * security consequences (e.g. smash over the stack area).
+        */
+       map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+       if (BAD_ADDR(map_addr))
+               return map_addr;
+
+       if ((type & MAP_FIXED) && map_addr != addr) {
+               pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+                               (void*)addr);

Is "info" loud enough? I actually think this should be a WARN_ONCE().

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr,

Quoted 10 lines
fs/binfmt_elf.c +6 -0
+               return -EAGAIN;
+       }
+
+       return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
                struct elf_phdr *eppnt, int prot, int type,
                unsigned long total_size)
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,

elf_map is redirected on metag -- it should probably have its vm_mmap calls adjust too.

Quoted 23 lines
fs/binfmt_elf.c +3 -4
        */
        if (total_size) {
                total_size = ELF_PAGEALIGN(total_size);
-               map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+               map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
                if (!BAD_ADDR(map_addr))
                        vm_munmap(map_addr+size, total_size-size);
        } else
-               map_addr = vm_mmap(filep, addr, size, prot, type, off);
+               map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
        return(map_addr);
 }
@@ -1215,7 +1238,7 @@ static int load_elf_library(struct file *file)
                eppnt++;
 
        /* Now use mmap to map the library into memory. */
-       error = vm_mmap(file,
+       error = elf_vm_mmap(file,
                        ELF_PAGESTART(eppnt->p_vaddr),
                        (eppnt->p_filesz +
                         ELF_PAGEOFFSET(eppnt->p_vaddr)),
--
Quoted 2 lines

2.14.2

Otherwise, yeah, this should be good.

-Kees

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

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

[CCing metag people for the metag elf_map implementation specific. The thread
starts here http://lkml.kernel.org/r/20171016134446.19910-1-mhocko-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org]

Quoted 1 line · Kees Cook

On Mon, Oct 16, 2017 at 6:44 AM, Michal Hocko <[email protected]> wrote:

[...]

Quoted 11 lines
Quoted 11 lines
fs/binfmt_elf.c +11 -0
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+               unsigned long size, int prot, int type, unsigned long off)
+{
+       unsigned long map_addr;
+
+       /*
+        * If caller requests the mapping at a specific place, make sure we fail
+        * rather than potentially clobber an existing mapping which can have
+        * security consequences (e.g. smash over the stack area).
+        */
+       map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
Quoted 8 lines

fs/binfmt_elf.c create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec, · line 360

Quoted 6 lines
fs/binfmt_elf.c +6 -0
+       if (BAD_ADDR(map_addr))
+               return map_addr;
+
+       if ((type & MAP_FIXED) && map_addr != addr) {
+               pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+                               (void*)addr);

Is "info" loud enough? I actually think this should be a WARN_ONCE().

Does it make any sense to taint the kernel just because of this condition? I can make it pr_error...

Quoted 5 lines
Quoted 5 lines
fs/binfmt_elf.c +5 -0
+               return -EAGAIN;
+       }
+
+       return map_addr;
+}
Quoted 8 lines

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr,

Quoted 5 lines
fs/binfmt_elf.c +1 -0
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
                struct elf_phdr *eppnt, int prot, int type,
                unsigned long total_size)
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,

elf_map is redirected on metag -- it should probably have its vm_mmap calls adjust too.

Thanks for spotting this. I am not really familiar with metag. It seems to clear MAP_FIXED already

	tcm_tag = tcm_lookup_tag(addr);

	if (tcm_tag != TCM_INVALID_TAG)
		type &= ~MAP_FIXED;

So if there is a tag the flag is cleared. I do not understand this code (and git log doesn't help) but why is this MAP_FIXED code really needed?

Quoted 16 lines
Quoted 16 lines
fs/binfmt_elf.c +2 -2
        */
        if (total_size) {
                total_size = ELF_PAGEALIGN(total_size);
-               map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+               map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
                if (!BAD_ADDR(map_addr))
                        vm_munmap(map_addr+size, total_size-size);
        } else
-               map_addr = vm_mmap(filep, addr, size, prot, type, off);
+               map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
        return(map_addr);
 }
@@ -1215,7 +1238,7 @@ static int load_elf_library(struct file *file)
                eppnt++;
 
Quoted 11 lines
Quoted 9 lines
fs/binfmt_elf.c +1 -2
        /* Now use mmap to map the library into memory. */
-       error = vm_mmap(file,
+       error = elf_vm_mmap(file,
                        ELF_PAGESTART(eppnt->p_vaddr),
                        (eppnt->p_filesz +
                         ELF_PAGEOFFSET(eppnt->p_vaddr)),
--

2.14.2

Otherwise, yeah, this should be good.

Thanks!

Signature · 2 lines
Michal Hocko
SUSE Labs
#16 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

Quoted 21 lines · Michal Hocko

[CCing metag people for the metag elf_map implementation specific. The thread starts here http://lkml.kernel.org/r/[email protected]]

Quoted 14 lines · Kees Cook

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr,

Quoted 10 lines · Michal Hocko
fs/binfmt_elf.c +6 -0
+               return -EAGAIN;
+       }
+
+       return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
                struct elf_phdr *eppnt, int prot, int type,
                unsigned long total_size)
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,

elf_map is redirected on metag -- it should probably have its vm_mmap calls adjust too.

Thanks for spotting this. I am not really familiar with metag. It seems to clear MAP_FIXED already

Quoted 7 lines
	tcm_tag = tcm_lookup_tag(addr);

	if (tcm_tag != TCM_INVALID_TAG)
		type &= ~MAP_FIXED;

So if there is a tag the flag is cleared. I do not understand this code (and git log doesn't help) but why is this MAP_FIXED code really needed?

This function was added to the metag port in mid-2010 to support ELFs with tightly coupled memory (TCM) segments, for example metag "core" memories are at fixed virtual addresses and aren't MMU mappable (i.e. globally accessible), and are outside of the usual userland address range, but are as fast as cache. The commit message says this:

Quoted 4 lines

Override the definition of the elf_map() function to special case sections that are loaded at the address of the internal memories. If we have such a section, map it at a different address and copy the contents of the section into the appropriate memory.

So yeh, it looks like if the section is meant to use TCM based on the virtual address, it drops MAP_FIXED so that the vm_mmap can succeed (because its outside the normally valid range), and then copies it directly to the desired TCM so the program can use it.

Hope that helps add some context to understand whats needed.

There was some description of this in an ELCE-2010 talk by the original author Will Newton that may also be of interest [1].

Cheers
James

[1] http://free-electrons.com/blog/elce-2010-videos/
"Exploiting On-chip Memories in Embedded Linux Applications" See slides about "core memories".

#17 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

Quoted 41 lines · James Hogan
Quoted 21 lines · Michal Hocko

[CCing metag people for the metag elf_map implementation specific. The thread
starts here http://lkml.kernel.org/r/20171016134446.19910-1-mhocko-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org]

Quoted 14 lines · Kees Cook

Review static unsigned long elf_map(struct file *filep, unsigned long addr,

Quoted 10 lines · Michal Hocko
(quoted diff) +6 -0
+               return -EAGAIN;
+       }
+
+       return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
                struct elf_phdr *eppnt, int prot, int type,
                unsigned long total_size)
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,

elf_map is redirected on metag -- it should probably have its vm_mmap calls adjust too.

Thanks for spotting this. I am not really familiar with metag. It seems to clear MAP_FIXED already

Quoted 7 lines
	tcm_tag = tcm_lookup_tag(addr);

	if (tcm_tag != TCM_INVALID_TAG)
		type &= ~MAP_FIXED;

So if there is a tag the flag is cleared. I do not understand this code (and git log doesn't help) but why is this MAP_FIXED code really needed?

This function was added to the metag port in mid-2010 to support ELFs with tightly coupled memory (TCM) segments, for example metag "core" memories are at fixed virtual addresses and aren't MMU mappable (i.e. globally accessible), and are outside of the usual userland address range, but are as fast as cache. The commit message says this:

Quoted 4 lines

Override the definition of the elf_map() function to special case sections that are loaded at the address of the internal memories. If we have such a section, map it at a different address and copy the contents of the section into the appropriate memory.

Quoted 6 lines

So yeh, it looks like if the section is meant to use TCM based on the virtual address, it drops MAP_FIXED so that the vm_mmap can succeed (because its outside the normally valid range), and then copies it directly to the desired TCM so the program can use it.

Hope that helps add some context to understand whats needed.

Hmm, so IIUC then we need the same fix as
http://lkml.kernel.org/r/20171016134446.19910-2-mhocko-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
right?

This would be something like. I wanted to share elf_vm_mmap but didn't find a proper place to not cause include dependency hell so I balied out to c&p.
---

arch/metag/kernel/process.c +25 -2
index c4606ce743d2..b20596b4c4c2 100644
@@ -378,6 +378,29 @@ int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpu)
 
 #define BAD_ADDR(x) ((unsigned long)(x) >= TASK_SIZE)
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 unsigned long __metag_elf_map(struct file *filep, unsigned long addr,
 			      struct elf_phdr *eppnt, int prot, int type,
 			      unsigned long total_size)
@@ -410,11 +433,11 @@ unsigned long __metag_elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);
 	} else
-		map_addr = vm_mmap(filep, addr, size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
 	if (!BAD_ADDR(map_addr) && tcm_tag != TCM_INVALID_TAG) {
 		struct tcm_allocation *tcm;
Signature · 2 lines
Michal Hocko
SUSE Labs
#18 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

Quoted 95 lines · Michal Hocko
Quoted 41 lines · James Hogan
Quoted 21 lines · Michal Hocko

[CCing metag people for the metag elf_map implementation specific. The thread starts here http://lkml.kernel.org/r/[email protected]]

Quoted 14 lines · Kees Cook

arch/metag/kernel/process.c static unsigned long elf_map(struct file *filep, unsigned long addr,

Quoted 10 lines · Michal Hocko
arch/metag/kernel/process.c +6 -0
+               return -EAGAIN;
+       }
+
+       return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
                struct elf_phdr *eppnt, int prot, int type,
                unsigned long total_size)
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,

elf_map is redirected on metag -- it should probably have its vm_mmap calls adjust too.

Thanks for spotting this. I am not really familiar with metag. It seems to clear MAP_FIXED already

Quoted 7 lines
	tcm_tag = tcm_lookup_tag(addr);

	if (tcm_tag != TCM_INVALID_TAG)
		type &= ~MAP_FIXED;

So if there is a tag the flag is cleared. I do not understand this code (and git log doesn't help) but why is this MAP_FIXED code really needed?

This function was added to the metag port in mid-2010 to support ELFs with tightly coupled memory (TCM) segments, for example metag "core" memories are at fixed virtual addresses and aren't MMU mappable (i.e. globally accessible), and are outside of the usual userland address range, but are as fast as cache. The commit message says this:

Quoted 4 lines

Override the definition of the elf_map() function to special case sections that are loaded at the address of the internal memories. If we have such a section, map it at a different address and copy the contents of the section into the appropriate memory.

Quoted 6 lines

So yeh, it looks like if the section is meant to use TCM based on the virtual address, it drops MAP_FIXED so that the vm_mmap can succeed (because its outside the normally valid range), and then copies it directly to the desired TCM so the program can use it.

Hope that helps add some context to understand whats needed.

Hmm, so IIUC then we need the same fix as
http://lkml.kernel.org/r/[email protected],
right?

This would be something like. I wanted to share elf_vm_mmap but didn't find a proper place to not cause include dependency hell so I balied out to c&p.
---

arch/metag/kernel/process.c +23 -0
index c4606ce743d2..b20596b4c4c2 100644
@@ -378,6 +378,29 @@ int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpu)
 
 #define BAD_ADDR(x) ((unsigned long)(x) >= TASK_SIZE)
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 unsigned long __metag_elf_map(struct file *filep, unsigned long addr,
 			      struct elf_phdr *eppnt, int prot, int type,
 			      unsigned long total_size)
@@ -410,11 +433,11 @@ unsigned long __metag_elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);

arch/metag/kernel/process.c unsigned long __metag_elf_map(struct file *filep, unsigned long addr, · line 443

Quoted 10 lines
arch/metag/kernel/process.c +2 -2
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);
 	} else
-		map_addr = vm_mmap(filep, addr, size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
 	if (!BAD_ADDR(map_addr) && tcm_tag != TCM_INVALID_TAG) {
 		struct tcm_allocation *tcm;

Yeh that looks reasonable to me.

Cheers
James

#19 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

Quoted 1 line · James Hogan

On Tue, Oct 17, 2017 at 09:37:48AM +0200, Michal Hocko wrote:

[...]

Quoted 50 lines
Quoted 50 lines

This would be something like. I wanted to share elf_vm_mmap but didn't find a proper place to not cause include dependency hell so I balied out to c&p.
---

arch/metag/kernel/process.c +25 -2
index c4606ce743d2..b20596b4c4c2 100644
@@ -378,6 +378,29 @@ int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpu)
 
 #define BAD_ADDR(x) ((unsigned long)(x) >= TASK_SIZE)
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 unsigned long __metag_elf_map(struct file *filep, unsigned long addr,
 			      struct elf_phdr *eppnt, int prot, int type,
 			      unsigned long total_size)
@@ -410,11 +433,11 @@ unsigned long __metag_elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);
 	} else
-		map_addr = vm_mmap(filep, addr, size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
Quoted 4 lines

arch/metag/kernel/process.c unsigned long __metag_elf_map(struct file *filep, unsigned long addr, · line 443

Quoted 2 lines
arch/metag/kernel/process.c +0 -0
 	if (!BAD_ADDR(map_addr) && tcm_tag != TCM_INVALID_TAG) {
 		struct tcm_allocation *tcm;

Yeh that looks reasonable to me.

Thanks for double checking. I will make sure to CC you when reposting the patch after other concerns sort out.

Signature · 2 lines
Michal Hocko
SUSE Labs
#20 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

In reply to Michal Hocko

Hi Michal,

Earlier I posted a patchset to clean up these in a different way, but patches sent to your below mail address are all rejected.

<[email protected]>: host mail.kerne.org[104.131.33.237] said: 454 4.1.1
<[email protected]>: Recipient address rejected: User unknown in virtual mailbox table (in reply to RCPT TO command)

Quoted 36 lines · Michal Hocko

From: Michal Hocko <[email protected]>

fs/binfmt_elf.c +23 -0
index 6466153f2bf0..09456e2add18 100644
@@ -341,6 +341,29 @@ create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
 
 #ifndef elf_map
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
 		struct elf_phdr *eppnt, int prot, int type,
 		unsigned long total_size)

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 394

Quoted 7 lines
fs/binfmt_elf.c +1 -1
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);

Here we will still take the map total, then unmap the rest way.

I am wondering why we don't fix those issues we figured out, but add another level of wrapper elf_vm_mmap() to hack it. So we will have elf_map() -> elf_vm_mmap() -> vm_mmap(), not even counting vm_mmap_pgoff(), then finally enter into do_mmap_pgoff(), to do the maping for elf program.

Thanks
Baoquan

Quoted 18 lines
fs/binfmt_elf.c +2 -2
 	} else
-		map_addr = vm_mmap(filep, addr, size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, size, prot, type, off);
 
 	return(map_addr);
 }
@@ -1215,7 +1238,7 @@ static int load_elf_library(struct file *file)
 		eppnt++;
 
 	/* Now use mmap to map the library into memory. */
-	error = vm_mmap(file,
+	error = elf_vm_mmap(file,
 			ELF_PAGESTART(eppnt->p_vaddr),
 			(eppnt->p_filesz +
 			 ELF_PAGEOFFSET(eppnt->p_vaddr)),
Signature · 1 line
2.14.2
#21 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

Quoted 8 lines · Baoquan He

Hi Michal,

Earlier I posted a patchset to clean up these in a different way, but patches sent to your below mail address are all rejected.

<[email protected]>: host mail.kerne.org[104.131.33.237] said: 454 4.1.1
<[email protected]>: Recipient address rejected: User unknown in virtual mailbox table (in reply to RCPT TO command)

yes, there was a typo in the email address which I've fixed up in the reply... Your cleanup was mostly about replacing vm_mmap by get_unmaped_area which is an independent issue to what I am trying to achieve here.

Quoted 47 lines · Michal Hocko
Quoted 36 lines

From: Michal Hocko <[email protected]>

fs/binfmt_elf.c +23 -0
index 6466153f2bf0..09456e2add18 100644
@@ -341,6 +341,29 @@ create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
 
 #ifndef elf_map
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
 		struct elf_phdr *eppnt, int prot, int type,
 		unsigned long total_size)

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 394

Quoted 7 lines
fs/binfmt_elf.c +1 -1
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);

Here we will still take the map total, then unmap the rest way.

Quoted 2 lines

I am wondering why we don't fix those issues we figured out, but add another level of wrapper elf_vm_mmap() to hack it.

Could you be more specific please?

Quoted 4 lines

So we will have
elf_map() -> elf_vm_mmap() -> vm_mmap(), not even counting vm_mmap_pgoff(), then finally enter into do_mmap_pgoff(), to do the maping for elf program.

I've added another level of helper to keep the code in elf_map saner. It is quite complex already.

Signature · 2 lines
Michal Hocko
SUSE Labs
#22 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

Quoted 6 lines · Michal Hocko
Quoted 5 lines · Baoquan He

Hi Michal,

Earlier I posted a patchset to clean up these in a different way, but patches sent to your below mail address are all rejected.

Quoted 8 lines
Quoted 3 lines

<[email protected]>: host mail.kerne.org[104.131.33.237] said: 454 4.1.1
<[email protected]>: Recipient address rejected: User unknown in virtual mailbox table (in reply to RCPT TO command)

yes, there was a typo in the email address which I've fixed up in the reply... Your cleanup was mostly about replacing vm_mmap by get_unmaped_area which is an independent issue to what I am trying to achieve here.

Oops. Yes, I must have typed your address by hand, but not copying from the previous discussion thread. Sorry for that.

Quoted 48 lines
Quoted 48 lines · Michal Hocko
Quoted 36 lines

From: Michal Hocko <[email protected]>

fs/binfmt_elf.c +23 -0
index 6466153f2bf0..09456e2add18 100644
@@ -341,6 +341,29 @@ create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
 
 #ifndef elf_map
 
+static unsigned long elf_vm_mmap(struct file *filep, unsigned long addr,
+		unsigned long size, int prot, int type, unsigned long off)
+{
+	unsigned long map_addr;
+
+	/*
+	 * If caller requests the mapping at a specific place, make sure we fail
+	 * rather than potentially clobber an existing mapping which can have
+	 * security consequences (e.g. smash over the stack area).
+	 */
+	map_addr = vm_mmap(filep, addr, size, prot, type & ~MAP_FIXED, off);
+	if (BAD_ADDR(map_addr))
+		return map_addr;
+
+	if ((type & MAP_FIXED) && map_addr != addr) {
+		pr_info("Uhuuh, elf segement at %p requested but the memory is mapped already\n",
+				(void*)addr);
+		return -EAGAIN;
+	}
+
+	return map_addr;
+}
+
 static unsigned long elf_map(struct file *filep, unsigned long addr,
 		struct elf_phdr *eppnt, int prot, int type,
 		unsigned long total_size)

fs/binfmt_elf.c static unsigned long elf_map(struct file *filep, unsigned long addr, · line 394

Quoted 7 lines
fs/binfmt_elf.c +1 -1
@@ -366,11 +389,11 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
 	*/
 	if (total_size) {
 		total_size = ELF_PAGEALIGN(total_size);
-		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
+		map_addr = elf_vm_mmap(filep, addr, total_size, prot, type, off);
 		if (!BAD_ADDR(map_addr))
 			vm_munmap(map_addr+size, total_size-size);

Here we will still take the map total, then unmap the rest way.

Quoted 4 lines
Quoted 2 lines

I am wondering why we don't fix those issues we figured out, but add another level of wrapper elf_vm_mmap() to hack it.

Could you be more specific please?

In the current code, obviously the total size for PIE loading is unnecessary since it's MAP_FIXED, and it's initial mapping from ELF_ET_DYN_BASE, I can't see any chance it will fail to map. Though you use a trick in this new elf_vm_mmap() to check if it failed on mapping, in essence it's a MAP_FIXED.

And as I said before, then we will still have the ungraceful 'mapping total'|'unmapping the rest' method.

If from a new code reader's point of view, the newly added elf_vm_mmap() plus the existing elf_map() may confuse people more.

Just personnal opinion. Anyway, your patches are better, my patches have been resting there for several days but no comment. :-) I may still need strength the understanding about elf loading code.

Thanks
Baoquan

Quoted 11 lines
Quoted 5 lines

So we will have
elf_map() -> elf_vm_mmap() -> vm_mmap(), not even counting vm_mmap_pgoff(), then finally enter into do_mmap_pgoff(), to do the maping for elf program.

I've added another level of helper to keep the code in elf_map saner. It is quite complex already.

Signature · 2 lines
Michal Hocko
SUSE Labs
#23 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

On Tue 17-10-17 21:22:46, Baoquan He wrote:
[...]

Quoted 2 lines

And as I said before, then we will still have the ungraceful 'mapping total'|'unmapping the rest' method.

yes I haven't touched this part and I guess you are right it can be removed now, but this was not my primary concern to adddress so there is still some room for improvements on top. If you want to refactor that part, then feel free to do so but I think that we should address the MAP_FIXED part first.

Quoted 2 lines

If from a new code reader's point of view, the newly added elf_vm_mmap() plus the existing elf_map() may confuse people more.

Heh, I strongly suspect that this indirection is the last thing that would make the code hard to grasp. The whole load_elf_binary is a giant and incomprehensible mess.

Signature · 2 lines
Michal Hocko
SUSE Labs
#24 lore raw

Re: [PATCH 1/2] fs, elf: drop MAP_FIXED usage from elf_map

Quoted 10 lines · Michal Hocko

On Tue 17-10-17 21:22:46, Baoquan He wrote:
[...]

Quoted 2 lines

And as I said before, then we will still have the ungraceful 'mapping total'|'unmapping the rest' method.

yes I haven't touched this part and I guess you are right it can be removed now, but this was not my primary concern to adddress so there is still some room for improvements on top. If you want to refactor that part, then feel free to do so but I think that we should address the MAP_FIXED part first.

OK, I am fine. Then let's focus on this issue you are addressing. Sorry for the noise. :-)

Thanks
Baoquan

#25 lore raw

PATCH 2/2 fs, elf: drop MAP_FIXED from initial ET_DYN segment

In reply to Michal Hocko

From: Michal Hocko <[email protected]>

eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE") has added MAP_FIXED flag to the initial ET_DYN segment mapping which defines the randomized base for the PIE ELF segments. The thing is that MAP_FIXED shouldn't be really needed because the address is essentially random anyway. All other segments are mapped relatively to this base. elf_map makes sure that all segments will fit into the address space by enforcing total_mapping_size initial map.

Why do we want to drop MAP_FIXED in the first place? Because it is error prone. If we happen to have an existing mapping in the requested range then we do not want to corrupt it silently. Without MAP_FIXED vm_mmap will simply fallback to another range. In reality there shouldn't be any conflicting mappings at this early exec stage so the mmap should succeed even without MAP_FIXED but subtle changes to the randomization can break this assumption so we should rather be careful here.

Fixes eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE")
---
 fs/binfmt_elf.c | 1 -
 1 file changed, 1 deletion(-)
fs/binfmt_elf.c +0 -1
index 09456e2add18..244cc30dfa24 100644
@@ -988,7 +988,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
 				load_bias = ELF_ET_DYN_BASE;
 				if (current->flags & PF_RANDOMIZE)
 					load_bias += arch_mmap_rnd();
-				elf_flags |= MAP_FIXED;
 			} else
 				load_bias = 0;
 
Signature · 1 line
2.14.2
#26 lore raw

Re: [PATCH 2/2] fs, elf: drop MAP_FIXED from initial ET_DYN segment

Quoted 29 lines · Michal Hocko

From: Michal Hocko <[email protected]>

eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE") has added MAP_FIXED flag to the initial ET_DYN segment mapping which defines the randomized base for the PIE ELF segments. The thing is that MAP_FIXED shouldn't be really needed because the address is essentially random anyway. All other segments are mapped relatively to this base. elf_map makes sure that all segments will fit into the address space by enforcing total_mapping_size initial map.

Why do we want to drop MAP_FIXED in the first place? Because it is error prone. If we happen to have an existing mapping in the requested range then we do not want to corrupt it silently. Without MAP_FIXED vm_mmap will simply fallback to another range. In reality there shouldn't be any conflicting mappings at this early exec stage so the mmap should succeed even without MAP_FIXED but subtle changes to the randomization can break this assumption so we should rather be careful here.

Fixes eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE")
---
 fs/binfmt_elf.c | 1 -
 1 file changed, 1 deletion(-)
fs/binfmt_elf.c +0 -0
index 09456e2add18..244cc30dfa24 100644

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 991

Quoted 4 lines
fs/binfmt_elf.c +0 -1
@@ -988,7 +988,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
                                load_bias = ELF_ET_DYN_BASE;
                                if (current->flags & PF_RANDOMIZE)
                                        load_bias += arch_mmap_rnd();
-                               elf_flags |= MAP_FIXED;

If MAP_FIXED is being masked out in patch 1 (but used as a check for correct position, I think this MAP_FIXED should _not_ be removed). This provides for checking for the initial mapping. The failure mode here would be to allow an attack to "push" a mapping away from some overlapping region. This should not be allowed either: if the initial mapping is "wrong", we should absolutely fail, otherwise we can be introducing a silent reduction in PIE entropy.

So, yes to patch 1, that makes sense to not allow silent overlap, but no to patch 2: we do not want silent entropy reduction.

-Kees

Quoted 6 lines
fs/binfmt_elf.c +0 -1
                        } else
                                load_bias = 0;
 
--

2.14.2

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

Re: [PATCH 2/2] fs, elf: drop MAP_FIXED from initial ET_DYN segment

Quoted 35 lines · Kees Cook
Quoted 29 lines · Michal Hocko

From: Michal Hocko <[email protected]>

eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE") has added MAP_FIXED flag to the initial ET_DYN segment mapping which defines the randomized base for the PIE ELF segments. The thing is that MAP_FIXED shouldn't be really needed because the address is essentially random anyway. All other segments are mapped relatively to this base. elf_map makes sure that all segments will fit into the address space by enforcing total_mapping_size initial map.

Why do we want to drop MAP_FIXED in the first place? Because it is error prone. If we happen to have an existing mapping in the requested range then we do not want to corrupt it silently. Without MAP_FIXED vm_mmap will simply fallback to another range. In reality there shouldn't be any conflicting mappings at this early exec stage so the mmap should succeed even without MAP_FIXED but subtle changes to the randomization can break this assumption so we should rather be careful here.

Fixes eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE")
---
 fs/binfmt_elf.c | 1 -
 1 file changed, 1 deletion(-)
fs/binfmt_elf.c +0 -0
index 09456e2add18..244cc30dfa24 100644

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 991

Quoted 4 lines
fs/binfmt_elf.c +0 -1
@@ -988,7 +988,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
                                load_bias = ELF_ET_DYN_BASE;
                                if (current->flags & PF_RANDOMIZE)
                                        load_bias += arch_mmap_rnd();
-                               elf_flags |= MAP_FIXED;
Quoted 7 lines

If MAP_FIXED is being masked out in patch 1 (but used as a check for correct position, I think this MAP_FIXED should _not_ be removed). This provides for checking for the initial mapping. The failure mode here would be to allow an attack to "push" a mapping away from some overlapping region. This should not be allowed either: if the initial mapping is "wrong", we should absolutely fail, otherwise we can be introducing a silent reduction in PIE entropy.

Do we really lose any entropy? We are using standard randomized mmap in that case. So we are randomized in either case. Are you worried that an attacker could tell the two cases and abuse some sort of offset2lib attack?

Signature · 2 lines
Michal Hocko
SUSE Labs
#28 lore raw

Re: [PATCH 2/2] fs, elf: drop MAP_FIXED from initial ET_DYN segment

Quoted 44 lines · Michal Hocko
Quoted 35 lines · Kees Cook
Quoted 29 lines · Michal Hocko

From: Michal Hocko <[email protected]>

eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE") has added MAP_FIXED flag to the initial ET_DYN segment mapping which defines the randomized base for the PIE ELF segments. The thing is that MAP_FIXED shouldn't be really needed because the address is essentially random anyway. All other segments are mapped relatively to this base. elf_map makes sure that all segments will fit into the address space by enforcing total_mapping_size initial map.

Why do we want to drop MAP_FIXED in the first place? Because it is error prone. If we happen to have an existing mapping in the requested range then we do not want to corrupt it silently. Without MAP_FIXED vm_mmap will simply fallback to another range. In reality there shouldn't be any conflicting mappings at this early exec stage so the mmap should succeed even without MAP_FIXED but subtle changes to the randomization can break this assumption so we should rather be careful here.

Fixes eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE")
---
 fs/binfmt_elf.c | 1 -
 1 file changed, 1 deletion(-)
fs/binfmt_elf.c +0 -0
index 09456e2add18..244cc30dfa24 100644

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 991

Quoted 4 lines
fs/binfmt_elf.c +0 -1
@@ -988,7 +988,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
                                load_bias = ELF_ET_DYN_BASE;
                                if (current->flags & PF_RANDOMIZE)
                                        load_bias += arch_mmap_rnd();
-                               elf_flags |= MAP_FIXED;
Quoted 7 lines

If MAP_FIXED is being masked out in patch 1 (but used as a check for correct position, I think this MAP_FIXED should _not_ be removed). This provides for checking for the initial mapping. The failure mode here would be to allow an attack to "push" a mapping away from some overlapping region. This should not be allowed either: if the initial mapping is "wrong", we should absolutely fail, otherwise we can be introducing a silent reduction in PIE entropy.

Quoted 4 lines

Do we really lose any entropy? We are using standard randomized mmap in that case. So we are randomized in either case. Are you worried that an attacker could tell the two cases and abuse some sort of offset2lib attack?

Not in the regular case. I'm suggesting that what your changes are preparing for is an _unknown_ way to collide mappings. In that case, we should be as defensive as we know how. And if we were to remove MAP_FIXED here, it would allow an attacker (with some future method) to potentially collapse a range of ASLR for execution, since missing MAP_FIXED here would silently move a mapping somewhere else. So we should keep MAP_FIXED, as any collision would indicate an unknown method of crashing an exec into something else.

-Kees

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

Re: [PATCH 2/2] fs, elf: drop MAP_FIXED from initial ET_DYN segment

Quoted 50 lines · Kees Cook
Quoted 44 lines · Michal Hocko
Quoted 35 lines · Kees Cook
Quoted 29 lines · Michal Hocko

From: Michal Hocko <[email protected]>

eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE") has added MAP_FIXED flag to the initial ET_DYN segment mapping which defines the randomized base for the PIE ELF segments. The thing is that MAP_FIXED shouldn't be really needed because the address is essentially random anyway. All other segments are mapped relatively to this base. elf_map makes sure that all segments will fit into the address space by enforcing total_mapping_size initial map.

Why do we want to drop MAP_FIXED in the first place? Because it is error prone. If we happen to have an existing mapping in the requested range then we do not want to corrupt it silently. Without MAP_FIXED vm_mmap will simply fallback to another range. In reality there shouldn't be any conflicting mappings at this early exec stage so the mmap should succeed even without MAP_FIXED but subtle changes to the randomization can break this assumption so we should rather be careful here.

Fixes eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE")
---
 fs/binfmt_elf.c | 1 -
 1 file changed, 1 deletion(-)
fs/binfmt_elf.c +0 -0
index 09456e2add18..244cc30dfa24 100644

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 991

Quoted 4 lines
fs/binfmt_elf.c +0 -1
@@ -988,7 +988,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
                                load_bias = ELF_ET_DYN_BASE;
                                if (current->flags & PF_RANDOMIZE)
                                        load_bias += arch_mmap_rnd();
-                               elf_flags |= MAP_FIXED;
Quoted 7 lines

If MAP_FIXED is being masked out in patch 1 (but used as a check for correct position, I think this MAP_FIXED should _not_ be removed). This provides for checking for the initial mapping. The failure mode here would be to allow an attack to "push" a mapping away from some overlapping region. This should not be allowed either: if the initial mapping is "wrong", we should absolutely fail, otherwise we can be introducing a silent reduction in PIE entropy.

Quoted 4 lines

Do we really lose any entropy? We are using standard randomized mmap in that case. So we are randomized in either case. Are you worried that an attacker could tell the two cases and abuse some sort of offset2lib attack?

Quoted 8 lines

Not in the regular case. I'm suggesting that what your changes are preparing for is an _unknown_ way to collide mappings. In that case, we should be as defensive as we know how. And if we were to remove MAP_FIXED here, it would allow an attacker (with some future method) to potentially collapse a range of ASLR for execution, since missing MAP_FIXED here would silently move a mapping somewhere else. So we should keep MAP_FIXED, as any collision would indicate an unknown method of crashing an exec into something else.

I am sorry but I do not follow. I could see how offset2lib would be a concern but you seem to be thinking about a different scenario. Could you be more specific please.

I am not insisting on this patch but it seems to me is just makes a recoverable state a failure.

Signature · 2 lines
Michal Hocko
SUSE Labs
#30 lore raw

Re: [PATCH 2/2] fs, elf: drop MAP_FIXED from initial ET_DYN segment

Quoted 60 lines · Michal Hocko
Quoted 50 lines · Kees Cook
Quoted 44 lines · Michal Hocko
Quoted 35 lines · Kees Cook
Quoted 29 lines · Michal Hocko

From: Michal Hocko <[email protected]>

eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE") has added MAP_FIXED flag to the initial ET_DYN segment mapping which defines the randomized base for the PIE ELF segments. The thing is that MAP_FIXED shouldn't be really needed because the address is essentially random anyway. All other segments are mapped relatively to this base. elf_map makes sure that all segments will fit into the address space by enforcing total_mapping_size initial map.

Why do we want to drop MAP_FIXED in the first place? Because it is error prone. If we happen to have an existing mapping in the requested range then we do not want to corrupt it silently. Without MAP_FIXED vm_mmap will simply fallback to another range. In reality there shouldn't be any conflicting mappings at this early exec stage so the mmap should succeed even without MAP_FIXED but subtle changes to the randomization can break this assumption so we should rather be careful here.

Fixes eab09532d400 ("binfmt_elf: use ELF_ET_DYN_BASE only for PIE")
---
 fs/binfmt_elf.c | 1 -
 1 file changed, 1 deletion(-)
fs/binfmt_elf.c +0 -0
index 09456e2add18..244cc30dfa24 100644

fs/binfmt_elf.c static int load_elf_binary(struct linux_binprm *bprm) · line 991

Quoted 4 lines
fs/binfmt_elf.c +0 -1
@@ -988,7 +988,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
                                load_bias = ELF_ET_DYN_BASE;
                                if (current->flags & PF_RANDOMIZE)
                                        load_bias += arch_mmap_rnd();
-                               elf_flags |= MAP_FIXED;
Quoted 7 lines

If MAP_FIXED is being masked out in patch 1 (but used as a check for correct position, I think this MAP_FIXED should _not_ be removed). This provides for checking for the initial mapping. The failure mode here would be to allow an attack to "push" a mapping away from some overlapping region. This should not be allowed either: if the initial mapping is "wrong", we should absolutely fail, otherwise we can be introducing a silent reduction in PIE entropy.

Quoted 4 lines

Do we really lose any entropy? We are using standard randomized mmap in that case. So we are randomized in either case. Are you worried that an attacker could tell the two cases and abuse some sort of offset2lib attack?

Quoted 8 lines

Not in the regular case. I'm suggesting that what your changes are preparing for is an _unknown_ way to collide mappings. In that case, we should be as defensive as we know how. And if we were to remove MAP_FIXED here, it would allow an attacker (with some future method) to potentially collapse a range of ASLR for execution, since missing MAP_FIXED here would silently move a mapping somewhere else. So we should keep MAP_FIXED, as any collision would indicate an unknown method of crashing an exec into something else.

Quoted 3 lines

I am sorry but I do not follow. I could see how offset2lib would be a concern but you seem to be thinking about a different scenario. Could you be more specific please.

Sorry, I will try a specific example below.

Quoted 2 lines

I am not insisting on this patch but it seems to me is just makes a recoverable state a failure.

Right, I understand you're trying to make it recoverable. I'm suggesting that making it recoverable provides a way for an attack to abuse it, and that what we'd be recovering from is a case we should never ever see.

Consider the case where through some future bug/feature, it's possible to put the stack at an arbitrary location during an exec. (We've worked to fix that already, but who knows what the future holds either through misfeatures or bugs.) If an attacker maps the stack over a large portion of the PIE exec range, patch 2 will result in vmmap searching out a location that isn't already allocated. This means that instead of the PIE ASLR choosing from the entire possible range, it will get limited to only the area where something isn't already overlapping. This would give an attacker the ability to control the PIE ASLR, possibly forcing it into a fixed location.

Without patch 2, an unexpected overlap will fail the exec, which is _good_, because we should never see an overlap. If we do, something has gone very wrong, and we shouldn't paper over that with a silent loss of ASLR entropy.

-Kees

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

Re: [PATCH 2/2] fs, elf: drop MAP_FIXED from initial ET_DYN segment

Quoted 1 line · Kees Cook

On Tue, Oct 17, 2017 at 2:04 AM, Michal Hocko <[email protected]> wrote:

[...]

Quoted 8 lines
Quoted 2 lines

I am not insisting on this patch but it seems to me is just makes a recoverable state a failure.

Right, I understand you're trying to make it recoverable. I'm suggesting that making it recoverable provides a way for an attack to abuse it, and that what we'd be recovering from is a case we should never ever see.

Quoted 10 lines

Consider the case where through some future bug/feature, it's possible to put the stack at an arbitrary location during an exec. (We've worked to fix that already, but who knows what the future holds either through misfeatures or bugs.) If an attacker maps the stack over a large portion of the PIE exec range, patch 2 will result in vmmap searching out a location that isn't already allocated. This means that instead of the PIE ASLR choosing from the entire possible range, it will get limited to only the area where something isn't already overlapping. This would give an attacker the ability to control the PIE ASLR, possibly forcing it into a fixed location.

Yes, I guess I understand that part. What is not clear to me exactly is why this matters as we have the mmap_base randomized and not under the control of the attacker.

Signature · 2 lines
Michal Hocko
SUSE Labs
#32 lore raw

Re: [PATCH 2/2] fs, elf: drop MAP_FIXED from initial ET_DYN segment

Quoted 22 lines · Michal Hocko
Quoted 1 line · Kees Cook

On Tue, Oct 17, 2017 at 2:04 AM, Michal Hocko <[email protected]> wrote:

[...]

Quoted 8 lines
Quoted 2 lines

I am not insisting on this patch but it seems to me is just makes a recoverable state a failure.

Right, I understand you're trying to make it recoverable. I'm suggesting that making it recoverable provides a way for an attack to abuse it, and that what we'd be recovering from is a case we should never ever see.

Quoted 10 lines

Consider the case where through some future bug/feature, it's possible to put the stack at an arbitrary location during an exec. (We've worked to fix that already, but who knows what the future holds either through misfeatures or bugs.) If an attacker maps the stack over a large portion of the PIE exec range, patch 2 will result in vmmap searching out a location that isn't already allocated. This means that instead of the PIE ASLR choosing from the entire possible range, it will get limited to only the area where something isn't already overlapping. This would give an attacker the ability to control the PIE ASLR, possibly forcing it into a fixed location.

Quoted 3 lines

Yes, I guess I understand that part. What is not clear to me exactly is why this matters as we have the mmap_base randomized and not under the control of the attacker.

mmap_base is separate from the PIE base, so patch 2 would allow for a reduction of the PIE ASLR entropy in the case of a novel overlap attack.

-Kees

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

Re: [PATCH 2/2] fs, elf: drop MAP_FIXED from initial ET_DYN segment

Quoted 23 lines · Kees Cook
Quoted 22 lines · Michal Hocko
Quoted 1 line · Kees Cook

On Tue, Oct 17, 2017 at 2:04 AM, Michal Hocko <[email protected]> wrote:

[...]

Quoted 18 lines
Quoted 2 lines

I am not insisting on this patch but it seems to me is just makes a recoverable state a failure.

Right, I understand you're trying to make it recoverable. I'm suggesting that making it recoverable provides a way for an attack to abuse it, and that what we'd be recovering from is a case we should never ever see.

Consider the case where through some future bug/feature, it's possible to put the stack at an arbitrary location during an exec. (We've worked to fix that already, but who knows what the future holds either through misfeatures or bugs.) If an attacker maps the stack over a large portion of the PIE exec range, patch 2 will result in vmmap searching out a location that isn't already allocated. This means that instead of the PIE ASLR choosing from the entire possible range, it will get limited to only the area where something isn't already overlapping. This would give an attacker the ability to control the PIE ASLR, possibly forcing it into a fixed location.

Quoted 7 lines
Quoted 3 lines

Yes, I guess I understand that part. What is not clear to me exactly is why this matters as we have the mmap_base randomized and not under the control of the attacker.

mmap_base is separate from the PIE base, so patch 2 would allow for a reduction of the PIE ASLR entropy in the case of a novel overlap attack.

OK, it seems that I am just too dull see through your concerns here. Anyway, are you willing to ack the patch 1 (when metag fix is included)? I would resubmit in that case and ask for merging without patch 2.

Signature · 2 lines
Michal Hocko
SUSE Labs
#34 lore raw

Re: [PATCH 2/2] fs, elf: drop MAP_FIXED from initial ET_DYN segment

Quoted 28 lines · Michal Hocko
Quoted 27 lines · Kees Cook
Quoted 25 lines · Michal Hocko
Quoted 1 line · Kees Cook

On Tue, Oct 17, 2017 at 2:04 AM, Michal Hocko <[email protected]> wrote:

[...]

Quoted 8 lines
Quoted 2 lines

I am not insisting on this patch but it seems to me is just makes a recoverable state a failure.

Right, I understand you're trying to make it recoverable. I'm suggesting that making it recoverable provides a way for an attack to abuse it, and that what we'd be recovering from is a case we should never ever see.

Quoted 10 lines

Consider the case where through some future bug/feature, it's possible to put the stack at an arbitrary location during an exec. (We've worked to fix that already, but who knows what the future holds either through misfeatures or bugs.) If an attacker maps the stack over a large portion of the PIE exec range, patch 2 will result in vmmap searching out a location that isn't already allocated. This means that instead of the PIE ASLR choosing from the entire possible range, it will get limited to only the area where something isn't already overlapping. This would give an attacker the ability to control the PIE ASLR, possibly forcing it into a fixed location.

Yes, I guess I understand that part. What is not clear to me exactly is why this matters as we have the mmap_base randomized and not under the control of the attacker.

Quoted 5 lines
Quoted 3 lines

mmap_base is separate from the PIE base, so patch 2 would allow for a reduction of the PIE ASLR entropy in the case of a novel overlap attack.

OK, it seems that I am just too dull see through your concerns here.

I'm probably not explaining it well enough! :(

Quoted 2 lines

Anyway, are you willing to ack the patch 1 (when metag fix is included)? I would resubmit in that case and ask for merging without patch 2.

Yup, I really like patch 1: it protects us from "impossible" situations, which we know rarely stay impossible. :)

-Kees

Signature · 2 lines
Kees Cook
Pixel Security