Skip to content
  • Oleg Nesterov's avatar
    mmap: fix the usage of ->vm_pgoff in special_mapping paths · 8a9cc3b5
    Oleg Nesterov authored
    
    
    Test-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.
    
    Signed-off-by: default avatarOleg Nesterov <oleg@redhat.com>
    Acked-by: default avatarKirill A. Shutemov <kirill.shutemov@linux.intel.com>
    Cc: Andy Lutomirski <luto@kernel.org>
    Cc: Hugh Dickins <hughd@google.com>
    Cc: Pavel Emelyanov <xemul@parallels.com>
    Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
    Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
    8a9cc3b5