Last year, I encountered a problem in the field. When using mmap to operate files, it was always a strange core. I used the gdb command info file to view the process space. I found that the stack stack space was very large, and I felt it was inexplicable. Later, it was found that the len parameter passed in by munmap was wrong, which caused the system to delete the memory that should not be deleted. I always thought that the kernel would help me check whether the address space is legal, so I thought it was strange, but I didn't take out the code until these days. The original kernel will not check whether the [addr,addr+len] range is legal or not, and whether there is a hole in the middle or not, it just tries to delete as many areas (Vmas) as possible.
Code is 3.13, file is 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 <jeremy@goop.org>
*/
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
{
unsigned long end;
struct vm_area_struct *vma, *prev, *last;
// start already wants page alignment
// [start,start+len] must be in the correct range, i.e. both are greater than task [size]
if ((start & ~PAGE_MASK) || start > TASK_SIZE || len > TASK_SIZE-start)
return -EINVAL;
// Length cannot be 0
if ((len = PAGE_ALIGN(len)) == 0)
return -EINVAL;
/* Find the first overlapping VMA */
// Find a VMA area whose VM end is larger than start. However, there is no guarantee that this area contains start, or vma.vm_start is also behind start, even after start+len
vma = find_vma(mm, start);
if (!vma)
return 0;
prev = vma->vm_prev;
/* we have start < vma->vm_end */
/* vma The region does not overlap with [start,end], and only the vma found may overlap with [start,end] */
end = start + len;
if (vma->vm_start >= end)
return 0;
// If the vma overlaps [start,end], the vma is split.
// Only when start is within the range of (vma.vm_start, vma.vm_end), there will be cross overlap,
// vma.vm_end must be larger than start, so if vma.vm_start is smaller than start,
// It can be determined that there is overlap between the two regions and vma needs to be split.
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;
}
// The end judgment is similar. See if the vma containing end needs to be split
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;
// Try to unlock some locked areas, if any
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;
}
}
// Remove the vma to be deleted from mm and return the list of vma to be deleted
detach_vmas_to_be_unmapped(mm, vma, prev, end);
// Notify MMU to refresh TLB
unmap_region(mm, vma, prev, start, end);
// For each VMA, do vma.vm_ops.close (if any), and release the file (vma.vm_file)
remove_vma_list(mm, vma);
return 0;
}
The function "detach" Vmas "to" be "unmapped is responsible for removing all Vmas in the range of [start,end] from the process space, and does not check whether there is a hole between the middle areas. Therefore, if the incoming length is larger than the size of the previous mmap, there will be no error, resulting in undefined exception.
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); // Remove all Vmas from process space
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. */
}