munmap参数错误导致进程空间异常
程序员文章站
2022-07-14 18:25:30
...
去年在现场遇到一个问题,使用mmap操作文件的时候,总是莫名奇妙的core,用gdb 命令info file
查看进程空间,发现stack栈空间非常大,觉得莫名其妙。后来发现是munmap
传入的len
参数错误,导致系统删除了不该删除的内存。一直以为kernel会帮我校验地址空间是否合法,所以觉得奇怪,不过一直到这几天,才拿出来代码看了看。原来内核不会校验[addr,addr+len]范围是否合法,也不管中间是否有空洞,只是尽量删除更多的区域(vma)。
代码是3.13的,文件是mm/mmap.c
SYSCALL_DEFINE2(munmap, unsigned long, addr, size_t, len)
/* Munmap is split into 2 main parts -- this part which finds
* what needs doing, and the areas themselves, which do the
* work. This now handles partial unmappings.
* Jeremy Fitzhardinge <[email protected]>
*/
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
{
unsigned long end;
struct vm_area_struct *vma, *prev, *last;
// start已经要页对齐
// [start,start+len]一定在正确的范围内,即都大于TASK_SIZE
if ((start & ~PAGE_MASK) || start > TASK_SIZE || len > TASK_SIZE-start)
return -EINVAL;
// 长度不能是0
if ((len = PAGE_ALIGN(len)) == 0)
return -EINVAL;
/* Find the first overlapping VMA */
// 找到一个vma区域,他的vm_end要比start大。不过不能保证这个区域就包含了start,也可能vma.vm_start也在start后面,甚至在start+len后面
vma = find_vma(mm, start);
if (!vma)
return 0;
prev = vma->vm_prev;
/* we have start < vma->vm_end */
/* vma区域跟[start,end]没有重叠,也只有找到的这个vma可能与[start,end]重叠 */
end = start + len;
if (vma->vm_start >= end)
return 0;
// 如果vma与[start,end]重叠,就拆分vma。
// 只有start在(vma.vm_start, vma.vm_end)范围内时才会有交叉重叠,
// vma.vm_end肯定是比start大的,那么如果vma.vm_start比start小,
// 就能确定两个区域有重叠,需要拆分vma。
if (start > vma->vm_start) {
int error;
/*
* Make sure that map_count on return from munmap() will
* not exceed its limit; but let map_count go just above
* its limit temporarily, to help free resources as expected.
*/
if (end < vma->vm_end && mm->map_count >= sysctl_max_map_count)
return -ENOMEM;
error = __split_vma(mm, vma, start, 0);
if (error)
return error;
prev = vma;
}
// end的判断也是类似的,看看包含end的vma是否需要拆分
last = find_vma(mm, end);
if (last && end > last->vm_start) {
int error = __split_vma(mm, last, end, 1);
if (error)
return error;
}
vma = prev? prev->vm_next: mm->mmap;
// 尝试解锁某些加锁区域,如果存在的话
if (mm->locked_vm) {
struct vm_area_struct *tmp = vma;
while (tmp && tmp->vm_start < end) {
if (tmp->vm_flags & VM_LOCKED) {
mm->locked_vm -= vma_pages(tmp);
munlock_vma_pages_all(tmp);
}
tmp = tmp->vm_next;
}
}
// 把unmap需要删除的vma从mm中删除,并返回需要删除的vma链表
detach_vmas_to_be_unmapped(mm, vma, prev, end);
// 通知MMU,刷新TLB
unmap_region(mm, vma, prev, start, end);
// 对每个vma做vma.vm_ops.close(如果有的话),还要释放文件(vma.vm_file)
remove_vma_list(mm, vma);
return 0;
}
detach_vmas_to_be_unmapped
这个函数负责把[start,end]范围内的vma全部从进程空间中删除,并没有检查中间的区域之间是否有空洞。所以如果传入的长度比较大,超过了之前mmap的大小,也不会出现错误,导致undefined异常。
static void
detach_vmas_to_be_unmapped(struct mm_struct *mm, struct vm_area_struct *vma,
struct vm_area_struct *prev, unsigned long end)
{
struct vm_area_struct **insertion_point;
struct vm_area_struct *tail_vma = NULL;
insertion_point = (prev ? &prev->vm_next : &mm->mmap);
vma->vm_prev = NULL;
do {
vma_rb_erase(vma, &mm->mm_rb); // 从进程空间中删除所有的vma
mm->map_count--;
tail_vma = vma;
vma = vma->vm_next;
} while (vma && vma->vm_start < end);
*insertion_point = vma;
if (vma) {
vma->vm_prev = prev;
vma_gap_update(vma);
} else
mm->highest_vm_end = prev ? prev->vm_end : 0;
tail_vma->vm_next = NULL;
mm->mmap_cache = NULL; /* Kill the cache. */
}
上一篇: handler循环刷新UI
下一篇: @RequestBody传递多个对象
推荐阅读