special_mapping_fault() is broken
Subsystems (3)
v2 cover special_mapping_fault() is broken
Hello,
special_mapping_fault() is absolutely broken. It seems it was always wrong, but this didn't matter until vdso/vvar started to use more than one page.
The patches are the same, just 1/3 was re-diffed on top of the recent 6b7339f4c31ad "mm: avoid setting up anonymous pages into file mapping" from Kirill.
And after this change vma_is_anonymous() becomes really trivial, it simply checks vm_ops == NULL. However, I do think the helper makes sense. There are a lot of ->vm_ops != NULL checks, the helper makes the caller's code more understandable (self-documented) and this is more grep-friendly.
Oleg.
include/linux/mm.h | 5 +++++ mm/memory.c | 8 ++++---- mm/mmap.c | 14 +++----------- 3 files changed, 12 insertions(+), 15 deletions(-)
PATCH v2 1/3 mm: introduce vma_is_anonymous(vma) helper
Preparation. Add the new simple helper, vma_is_anonymous(vma), and change handle_pte_fault() to use it. It will have more users.
The name is not accurate, say a hpet_mmap()'ed vma is not anonymous. Perhaps it should be named vma_has_fault() instead. But it matches the logic in mmap.c/memory.c (see next changes). "True" just means that a page fault will use do_anonymous_page().
--- include/linux/mm.h | 5 +++++ mm/memory.c | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-)
include/linux/mm.h +5 -0
@@ -1225,6 +1225,11 @@ static inline int vma_growsdown(struct vm_area_struct *vma, unsigned long addr)return vma && (vma->vm_end == addr) && (vma->vm_flags &VM_GROWSDOWN ); } +static inline bool vma_is_anonymous (struct vm_area_struct *vma) +{ +return !vma->vm_ops; +} +static inline int stack_guard_page_start (struct vm_area_struct *vma,unsigned long addr) {
mm/memory.c +4 -4
@@ -3249,12 +3249,12 @@ static int handle_pte_fault(struct mm_struct *mm,barrier ();if (!pte_present (entry)) {if (pte_none (entry)) { -if (vma->vm_ops) +if (vma_is_anonymous (vma)) +return do_anonymous_page (mm, vma, address, + pte, pmd, flags); +else return do_fault (mm, vma, address, pte, pmd, flags, entry); - -return do_anonymous_page (mm, vma, address, pte, pmd, - flags); }return do_swap_page (mm, vma, address, pte, pmd, flags, entry);
Signature · 1 line
1.5.5.1
PATCH v2 2/3 mmap: fix the usage of ->vm_pgoff in special_mapping paths
In reply to Oleg NesterovTest-case:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <assert.h>
void *find_vdso_vaddr(void)
{
FILE *perl;
char buf[32] = {};
perl = popen("perl -e 'open STDIN,qq|/proc/@{[getppid]}/maps|;"
"/^(.*?)-.*vdso/ && print hex $1 while <>'", "r");
fread(buf, sizeof(buf), 1, perl);
fclose(perl);
return (void *)atol(buf);
}
#define PAGE_SIZE 4096
int main(void)
{
void *vdso = find_vdso_vaddr();
assert(vdso);
// of course they should differ, and they do so far
printf("vdso pages differ: %d\n",
!!memcmp(vdso, vdso + PAGE_SIZE, PAGE_SIZE));
// split into 2 vma's
assert(mprotect(vdso, PAGE_SIZE, PROT_READ) == 0);
// force another fault on the next check
assert(madvise(vdso, 2 * PAGE_SIZE, MADV_DONTNEED) == 0);
// now they no longer differ, the 2nd vm_pgoff is wrong
printf("vdso pages differ: %d\n",
!!memcmp(vdso, vdso + PAGE_SIZE, PAGE_SIZE));
return 0;
}Output:
vdso pages differ: 1
vdso pages differ: 0
This is because split_vma() correctly updates ->vm_pgoff, but the logic in insert_vm_struct() and special_mapping_fault() is absolutely broken, so the fault at vdso + PAGE_SIZE return the 1st page. The same happens if you simply unmap the 1st page.
special_mapping_fault() does:
pgoff = vmf->pgoff - vma->vm_pgoff;
and this is _only_ correct if vma->vm_start mmaps the first page from ->vm_private_data array.
vdso or any other user of install_special_mapping() is not anonymous, it has the "backing storage" even if it is just the array of pages. So we actually need to make vm_pgoff work as an offset in this array.
Note: this also allows to fix another problem: currently gdb can't access "[vvar]" memory because in this case special_mapping_fault() doesn't work. Now that we can use ->vm_pgoff we can implement ->access() and fix this.
--- mm/mmap.c | 12 ++---------- 1 files changed, 2 insertions(+), 10 deletions(-)
mm/mmap.c +2 -10
@@ -2871,7 +2871,7 @@ int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma)* using the existing file pgoff checks and manipulations. * Similarly in do_mmap_pgoff and in do_brk. */ -if (!vma->vm_file) { +if (vma_is_anonymous (vma)) {BUG_ON (vma->anon_vma); vma->vm_pgoff = vma->vm_start >>PAGE_SHIFT ; }
@@ -3013,21 +3013,13 @@ static int special_mapping_fault(struct vm_area_struct *vma,pgoff_t pgoff;struct page **pages; -/* -* special mappings have no vm_file, and in that case, the mm -* uses vm_pgoff internally. So we have to subtract it from here. -* We are allowed to do this because we are the mm; do not copy -* this code into drivers! -*/ - pgoff = vmf->pgoff - vma->vm_pgoff; -if (vma->vm_ops == &legacy_special_mapping_vmops) pages = vma->vm_private_data;else pages = ((struct vm_special_mapping *)vma->vm_private_data)-> pages; -for (; pgoff && *pages; ++pages) +for (pgoff = vmf->pgoff; pgoff && *pages; ++pages) pgoff--;if (*pages) {
Signature · 1 line
1.5.5.1
PATCH v2 3/3 mremap: fix the wrong !vma->vm_file check in copy_vma()
In reply to Oleg NesterovTest-case:
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <assert.h>
void *find_vdso_vaddr(void)
{
FILE *perl;
char buf[32] = {};
perl = popen("perl -e 'open STDIN,qq|/proc/@{[getppid]}/maps|;"
"/^(.*?)-.*vdso/ && print hex $1 while <>'", "r");
fread(buf, sizeof(buf), 1, perl);
fclose(perl);
return (void *)atol(buf);
}
#define PAGE_SIZE 4096
void *get_unmapped_area(void)
{
void *p = mmap(0, PAGE_SIZE, PROT_NONE,
MAP_PRIVATE|MAP_ANONYMOUS, -1,0);
assert(p != MAP_FAILED);
munmap(p, PAGE_SIZE);
return p;
}
char save[2][PAGE_SIZE];
int main(void)
{
void *vdso = find_vdso_vaddr();
void *page[2];
assert(vdso);
memcpy(save, vdso, sizeof (save));
// force another fault on the next check
assert(madvise(vdso, 2 * PAGE_SIZE, MADV_DONTNEED) == 0);
page[0] = mremap(vdso,
PAGE_SIZE, PAGE_SIZE, MREMAP_FIXED | MREMAP_MAYMOVE,
get_unmapped_area());
page[1] = mremap(vdso + PAGE_SIZE,
PAGE_SIZE, PAGE_SIZE, MREMAP_FIXED | MREMAP_MAYMOVE,
get_unmapped_area());
assert(page[0] != MAP_FAILED && page[1] != MAP_FAILED);
printf("match: %d %d\n",
!memcmp(save[0], page[0], PAGE_SIZE),
!memcmp(save[1], page[1], PAGE_SIZE));
return 0;
}fails without this patch. Before the previous commit it gets the wrong page, now it segfaults (which is imho better).
This is because copy_vma() wrongly assumes that if vma->vm_file == NULL is irrelevant until the first fault which will use do_anonymous_page(). This is obviously wrong for the special mapping.
--- mm/mmap.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
mm/mmap.c +1 -1
@@ -2905,7 +2905,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,* If anonymous vma has not yet been faulted, update new pgoff * to match new location, to increase its chance of merging. */ -if (unlikely (!vma->vm_file && !vma->anon_vma)) { +if (unlikely (vma_is_anonymous (vma) && !vma->anon_vma)) { pgoff = addr >>PAGE_SHIFT ; faulted_in_anon_vma = false; }
Signature · 1 line
1.5.5.1
Quoted 10 lines · Oleg Nesterov
Hello,
special_mapping_fault()is absolutely broken. It seems it was always wrong, but this didn't matter until vdso/vvar started to use more than one page.The patches are the same, just 1/3 was re-diffed on top of the recent
6b7339f4c31ad"mm: avoid setting up anonymous pages into file mapping" from Kirill.
Quoted 5 lines
And after this change
vma_is_anonymous()becomes really trivial, it simply checksvm_ops== NULL. However, I do think the helper makes sense. There are a lot of ->vm_ops != NULL checks, the helper makes the caller's code more understandable (self-documented) and this is more grep-friendly.
For all three:
Signature · 1 line
Kirill A. Shutemov
Quoted 5 lines · Oleg Nesterov
And after this change
vma_is_anonymous()becomes really trivial, it simply checksvm_ops== NULL. However, I do think the helper makes sense. There are a lot of ->vm_ops != NULL checks, the helper makes the caller's code more understandable (self-documented) and this is more grep-friendly.
Yes, I for one, very much welcome this helper.
Andrew Morton
maintainerQuoted 8 lines · On Fri, 10 Jul 2015 18:51:21 +0200 Oleg Nesterov <[email protected]> wrote:
special_mapping_fault()is absolutely broken. It seems it was always wrong, but this didn't matter until vdso/vvar started to use more than one page.The patches are the same, just 1/3 was re-diffed on top of the recent
6b7339f4c31ad"mm: avoid setting up anonymous pages into file mapping" from Kirill.
Quoted 5 lines
And after this change
vma_is_anonymous()becomes really trivial, it simply checksvm_ops== NULL. However, I do think the helper makes sense. There are a lot of ->vm_ops != NULL checks, the helper makes the caller's code more understandable (self-documented) and this is more grep-friendly.
I'm trying to work out which kernel version(s) this should go into, without a lot of success.
What do we think the worst-case effects of the bug?
Quoted 17 lines · Andrew Morton
Quoted 8 lines · On Fri, 10 Jul 2015 18:51:21 +0200 Oleg Nesterov <[email protected]> wrote:
special_mapping_fault()is absolutely broken. It seems it was always wrong, but this didn't matter until vdso/vvar started to use more than one page.The patches are the same, just 1/3 was re-diffed on top of the recent
6b7339f4c31ad"mm: avoid setting up anonymous pages into file mapping" from Kirill.Quoted 5 lines
And after this change
vma_is_anonymous()becomes really trivial, it simply checksvm_ops== NULL. However, I do think the helper makes sense. There are a lot of ->vm_ops != NULL checks, the helper makes the caller's code more understandable (self-documented) and this is more grep-friendly.
Quoted 4 lines
I'm trying to work out which kernel version(s) this should go into, without a lot of success.
What do we think the worst-case effects of the bug?
Ah, I should have mentioned this. And when I re-read my messages I see that "absolutely broken" looks like "should be fixed asap". Sorry for confusion.
No, this bug is not serious. Nothing bad can happen from the kernel perspective. And I doubt that some application will ever unmap/remap the part of vdso or any other install_special_mapping() user. So this is just correctness fix. In fact, to me the main problem is that I was totally confused when I tried to read/understand this code ;)
Oleg.