| From 14ee036ade14829d69805dfd44e1127d2c92429c Mon Sep 17 00:00:00 2001 |
| From: Dave Hansen <dave@linux.vnet.ibm.com> |
| Date: Tue, 29 May 2012 15:06:46 -0700 |
| Subject: [PATCH] hugetlb: fix resv_map leak in error path |
| |
| commit c50ac050811d6485616a193eb0f37bfbd191cc89 upstream. |
| |
| When called for anonymous (non-shared) mappings, hugetlb_reserve_pages() |
| does a resv_map_alloc(). It depends on code in hugetlbfs's |
| vm_ops->close() to release that allocation. |
| |
| However, in the mmap() failure path, we do a plain unmap_region() without |
| the remove_vma() which actually calls vm_ops->close(). |
| |
| This is a decent fix. This leak could get reintroduced if new code (say, |
| after hugetlb_reserve_pages() in hugetlbfs_file_mmap()) decides to return |
| an error. But, I think it would have to unroll the reservation anyway. |
| |
| Christoph's test case: |
| |
| http://marc.info/?l=linux-mm&m=133728900729735 |
| |
| This patch applies to 3.4 and later. A version for earlier kernels is at |
| https://lkml.org/lkml/2012/5/22/418. |
| |
| Signed-off-by: Dave Hansen <dave@linux.vnet.ibm.com> |
| Acked-by: Mel Gorman <mel@csn.ul.ie> |
| Acked-by: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com> |
| Reported-by: Christoph Lameter <cl@linux.com> |
| Tested-by: Christoph Lameter <cl@linux.com> |
| Cc: Andrea Arcangeli <aarcange@redhat.com> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| --- |
| mm/hugetlb.c | 28 ++++++++++++++++++++++------ |
| 1 file changed, 22 insertions(+), 6 deletions(-) |
| |
| diff --git a/mm/hugetlb.c b/mm/hugetlb.c |
| index ca9ce49d71e3..f7b80540ba95 100644 |
| --- a/mm/hugetlb.c |
| +++ b/mm/hugetlb.c |
| @@ -2030,6 +2030,15 @@ static void hugetlb_vm_op_open(struct vm_area_struct *vma) |
| kref_get(&reservations->refs); |
| } |
| |
| +static void resv_map_put(struct vm_area_struct *vma) |
| +{ |
| + struct resv_map *reservations = vma_resv_map(vma); |
| + |
| + if (!reservations) |
| + return; |
| + kref_put(&reservations->refs, resv_map_release); |
| +} |
| + |
| static void hugetlb_vm_op_close(struct vm_area_struct *vma) |
| { |
| struct hstate *h = hstate_vma(vma); |
| @@ -2045,7 +2054,7 @@ static void hugetlb_vm_op_close(struct vm_area_struct *vma) |
| reserve = (end - start) - |
| region_count(&reservations->regions, start, end); |
| |
| - kref_put(&reservations->refs, resv_map_release); |
| + resv_map_put(vma); |
| |
| if (reserve) { |
| hugetlb_acct_memory(h, -reserve); |
| @@ -2744,12 +2753,16 @@ int hugetlb_reserve_pages(struct inode *inode, |
| set_vma_resv_flags(vma, HPAGE_RESV_OWNER); |
| } |
| |
| - if (chg < 0) |
| - return chg; |
| + if (chg < 0) { |
| + ret = chg; |
| + goto out_err; |
| + } |
| |
| /* There must be enough filesystem quota for the mapping */ |
| - if (hugetlb_get_quota(inode->i_mapping, chg)) |
| - return -ENOSPC; |
| + if (hugetlb_get_quota(inode->i_mapping, chg)) { |
| + ret = -ENOSPC; |
| + goto out_err; |
| + } |
| |
| /* |
| * Check enough hugepages are available for the reservation. |
| @@ -2758,7 +2771,7 @@ int hugetlb_reserve_pages(struct inode *inode, |
| ret = hugetlb_acct_memory(h, chg); |
| if (ret < 0) { |
| hugetlb_put_quota(inode->i_mapping, chg); |
| - return ret; |
| + goto out_err; |
| } |
| |
| /* |
| @@ -2775,6 +2788,9 @@ int hugetlb_reserve_pages(struct inode *inode, |
| if (!vma || vma->vm_flags & VM_MAYSHARE) |
| region_add(&inode->i_mapping->private_list, from, to); |
| return 0; |
| +out_err: |
| + resv_map_put(vma); |
| + return ret; |
| } |
| |
| void hugetlb_unreserve_pages(struct inode *inode, long offset, long freed) |
| -- |
| 1.8.5.2 |
| |