parisc: fix mmap(MAP_FIXED|MAP_SHARED) to already mmapped address

locale-gen on Debian showed a strange problem on parisc:
mmap2(NULL, 536870912, PROT_NONE, MAP_SHARED, 3, 0) = 0x42a54000
mmap2(0x42a54000, 103860, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, 3, 0) = -1 EINVAL (Invalid argument)

Basically it was just trying to re-mmap() a file at the same address
which it was given by a previous mmap() call. But this remapping failed
with EINVAL.

The problem is, that when MAP_FIXED and MAP_SHARED flags were used, we didn't
included the mapping-based offset when we verified the alignment of the given
fixed address against the offset which we calculated it in the previous call.

Signed-off-by: Helge Deller <deller@gmx.de>
Cc: <stable@vger.kernel.org> # 3.10+
This commit is contained in:
Helge Deller 2013-11-20 23:07:42 +01:00
parent dc1ccc4815
commit 0576da2c08
1 changed files with 15 additions and 10 deletions

View File

@ -61,8 +61,15 @@ static int get_offset(struct address_space *mapping)
return (unsigned long) mapping >> 8; return (unsigned long) mapping >> 8;
} }
static unsigned long get_shared_area(struct address_space *mapping, static unsigned long shared_align_offset(struct file *filp, unsigned long pgoff)
unsigned long addr, unsigned long len, unsigned long pgoff) {
struct address_space *mapping = filp ? filp->f_mapping : NULL;
return (get_offset(mapping) + pgoff) << PAGE_SHIFT;
}
static unsigned long get_shared_area(struct file *filp, unsigned long addr,
unsigned long len, unsigned long pgoff)
{ {
struct vm_unmapped_area_info info; struct vm_unmapped_area_info info;
@ -71,7 +78,7 @@ static unsigned long get_shared_area(struct address_space *mapping,
info.low_limit = PAGE_ALIGN(addr); info.low_limit = PAGE_ALIGN(addr);
info.high_limit = TASK_SIZE; info.high_limit = TASK_SIZE;
info.align_mask = PAGE_MASK & (SHMLBA - 1); info.align_mask = PAGE_MASK & (SHMLBA - 1);
info.align_offset = (get_offset(mapping) + pgoff) << PAGE_SHIFT; info.align_offset = shared_align_offset(filp, pgoff);
return vm_unmapped_area(&info); return vm_unmapped_area(&info);
} }
@ -82,20 +89,18 @@ unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr,
return -ENOMEM; return -ENOMEM;
if (flags & MAP_FIXED) { if (flags & MAP_FIXED) {
if ((flags & MAP_SHARED) && if ((flags & MAP_SHARED) &&
(addr - (pgoff << PAGE_SHIFT)) & (SHMLBA - 1)) (addr - shared_align_offset(filp, pgoff)) & (SHMLBA - 1))
return -EINVAL; return -EINVAL;
return addr; return addr;
} }
if (!addr) if (!addr)
addr = TASK_UNMAPPED_BASE; addr = TASK_UNMAPPED_BASE;
if (filp) { if (filp || (flags & MAP_SHARED))
addr = get_shared_area(filp->f_mapping, addr, len, pgoff); addr = get_shared_area(filp, addr, len, pgoff);
} else if(flags & MAP_SHARED) { else
addr = get_shared_area(NULL, addr, len, pgoff);
} else {
addr = get_unshared_area(addr, len); addr = get_unshared_area(addr, len);
}
return addr; return addr;
} }