aboutsummaryrefslogtreecommitdiffstats
path: root/include/linux
diff options
context:
space:
mode:
authorAuthor: David Gibson <david@gibson.dropbear.id.au>2012-05-02 20:47:25 -0400
committerLuis Henriques <luis.henriques@canonical.com>2012-05-25 12:24:33 -0400
commit4d110539738dabbf1cfbbbabe63bde08482b5e28 (patch)
tree4d2a9804d64172447ab34550886d997d94bc048a /include/linux
parent9dc153ffd3abf22e14d40fcc28591c212cb9224c (diff)
hugepages: fix use after free bug in "quota" handling
CVE-2012-2133 BugLink: http://bugs.launchpad.net/bugs/990368 hugetlbfs_{get,put}_quota() are badly named. They don't interact with the general quota handling code, and they don't much resemble its behaviour. Rather than being about maintaining limits on on-disk block usage by particular users, they are instead about maintaining limits on in-memory page usage (including anonymous MAP_PRIVATE copied-on-write pages) associated with a particular hugetlbfs filesystem instance. Worse, they work by having callbacks to the hugetlbfs filesystem code from the low-level page handling code, in particular from free_huge_page(). This is a layering violation of itself, but more importantly, if the kernel does a get_user_pages() on hugepages (which can happen from KVM amongst others), then the free_huge_page() can be delayed until after the associated inode has already been freed. If an unmount occurs at the wrong time, even the hugetlbfs superblock where the "quota" limits are stored may have been freed. Andrew Barry proposed a patch to fix this by having hugepages, instead of storing a pointer to their address_space and reaching the superblock from there, had the hugepages store pointers directly to the superblock, bumping the reference count as appropriate to avoid it being freed. Andrew Morton rejected that version, however, on the grounds that it made the existing layering violation worse. This is a reworked version of Andrew's patch, which removes the extra, and some of the existing, layering violation. It works by introducing the concept of a hugepage "subpool" at the lower hugepage mm layer - that is a finite logical pool of hugepages to allocate from. hugetlbfs now creates a subpool for each filesystem instance with a page limit set, and a pointer to the subpool gets added to each allocated hugepage, instead of the address_space pointer used now. The subpool has its own lifetime and is only freed once all pages in it _and_ all other references to it (i.e. superblocks) are gone. subpools are optional - a NULL subpool pointer is taken by the code to mean that no subpool limits are in effect. Previous discussion of this bug found in: "Fix refcounting in hugetlbfs quota handling.". See: https://lkml.org/lkml/2011/8/11/28 or http://marc.info/?l=linux-mm&m=126928970510627&w=1 v2: Fixed a bug spotted by Hillf Danton, and removed the extra parameter to alloc_huge_page() - since it already takes the vma, it is not necessary. Signed-off-by: Andrew Barry <abarry@cray.com> Signed-off-by: David Gibson <david@gibson.dropbear.id.au> Cc: Hugh Dickins <hughd@google.com> Cc: Mel Gorman <mgorman@suse.de> Cc: Minchan Kim <minchan.kim@gmail.com> Cc: Hillf Danton <dhillf@gmail.com> Cc: Paul Mackerras <paulus@samba.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Backported from upstream commit 90481622d75715bfcb68501280a917dbfe516029 Signed-off-by: Steve Conklin <sconklin@canonical.com> Signed-off-by: Tim Gardner <tim.gardner@canonical.com>
Diffstat (limited to 'include/linux')
-rw-r--r--include/linux/hugetlb.h14
1 files changed, 10 insertions, 4 deletions
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 59225ef27d1..8f17b892549 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -14,6 +14,15 @@ struct user_struct;
14#include <linux/shm.h> 14#include <linux/shm.h>
15#include <asm/tlbflush.h> 15#include <asm/tlbflush.h>
16 16
17struct hugepage_subpool {
18 spinlock_t lock;
19 long count;
20 long max_hpages, used_hpages;
21};
22
23struct hugepage_subpool *hugepage_new_subpool(long nr_blocks);
24void hugepage_put_subpool(struct hugepage_subpool *spool);
25
17int PageHuge(struct page *page); 26int PageHuge(struct page *page);
18 27
19void reset_vma_resv_huge_pages(struct vm_area_struct *vma); 28void reset_vma_resv_huge_pages(struct vm_area_struct *vma);
@@ -143,12 +152,11 @@ struct hugetlbfs_config {
143}; 152};
144 153
145struct hugetlbfs_sb_info { 154struct hugetlbfs_sb_info {
146 long max_blocks; /* blocks allowed */
147 long free_blocks; /* blocks free */
148 long max_inodes; /* inodes allowed */ 155 long max_inodes; /* inodes allowed */
149 long free_inodes; /* inodes free */ 156 long free_inodes; /* inodes free */
150 spinlock_t stat_lock; 157 spinlock_t stat_lock;
151 struct hstate *hstate; 158 struct hstate *hstate;
159 struct hugepage_subpool *spool;
152}; 160};
153 161
154 162
@@ -171,8 +179,6 @@ extern const struct file_operations hugetlbfs_file_operations;
171extern const struct vm_operations_struct hugetlb_vm_ops; 179extern const struct vm_operations_struct hugetlb_vm_ops;
172struct file *hugetlb_file_setup(const char *name, size_t size, vm_flags_t acct, 180struct file *hugetlb_file_setup(const char *name, size_t size, vm_flags_t acct,
173 struct user_struct **user, int creat_flags); 181 struct user_struct **user, int creat_flags);
174int hugetlb_get_quota(struct address_space *mapping, long delta);
175void hugetlb_put_quota(struct address_space *mapping, long delta);
176 182
177static inline int is_file_hugepages(struct file *file) 183static inline int is_file_hugepages(struct file *file)
178{ 184{