diff options
author | Mitsuo Hayasaka <mitsuo.hayasaka.hu@hitachi.com> | 2011-10-31 20:08:13 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-10-31 20:30:47 -0400 |
commit | f5252e009d5b87071a919221e4f6624184005368 (patch) | |
tree | 4be380e99c468dcb10597c445eb6b801897eafea /mm/vmalloc.c | |
parent | 8c5fb8eadde41f67c61a7ac2d3246dab87bf7020 (diff) |
mm: avoid null pointer access in vm_struct via /proc/vmallocinfo
The /proc/vmallocinfo shows information about vmalloc allocations in
vmlist that is a linklist of vm_struct. It, however, may access pages
field of vm_struct where a page was not allocated. This results in a null
pointer access and leads to a kernel panic.
Why this happens: In __vmalloc_node_range() called from vmalloc(), newly
allocated vm_struct is added to vmlist at __get_vm_area_node() and then,
some fields of vm_struct such as nr_pages and pages are set at
__vmalloc_area_node(). In other words, it is added to vmlist before it is
fully initialized. At the same time, when the /proc/vmallocinfo is read,
it accesses the pages field of vm_struct according to the nr_pages field
at show_numa_info(). Thus, a null pointer access happens.
The patch adds the newly allocated vm_struct to the vmlist *after* it is
fully initialized. So, it can avoid accessing the pages field with
unallocated page when show_numa_info() is called.
Signed-off-by: Mitsuo Hayasaka <mitsuo.hayasaka.hu@hitachi.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Namhyung Kim <namhyung@gmail.com>
Cc: "Paul E. McKenney" <paulmck@linux.vnet.ibm.com>
Cc: Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com>
Cc: <stable@kernel.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm/vmalloc.c')
-rw-r--r-- | mm/vmalloc.c | 65 |
1 files changed, 48 insertions, 17 deletions
diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 5016f19e1661..56faf3163ee2 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c | |||
@@ -1253,18 +1253,22 @@ EXPORT_SYMBOL_GPL(map_vm_area); | |||
1253 | DEFINE_RWLOCK(vmlist_lock); | 1253 | DEFINE_RWLOCK(vmlist_lock); |
1254 | struct vm_struct *vmlist; | 1254 | struct vm_struct *vmlist; |
1255 | 1255 | ||
1256 | static void insert_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va, | 1256 | static void setup_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va, |
1257 | unsigned long flags, void *caller) | 1257 | unsigned long flags, void *caller) |
1258 | { | 1258 | { |
1259 | struct vm_struct *tmp, **p; | ||
1260 | |||
1261 | vm->flags = flags; | 1259 | vm->flags = flags; |
1262 | vm->addr = (void *)va->va_start; | 1260 | vm->addr = (void *)va->va_start; |
1263 | vm->size = va->va_end - va->va_start; | 1261 | vm->size = va->va_end - va->va_start; |
1264 | vm->caller = caller; | 1262 | vm->caller = caller; |
1265 | va->private = vm; | 1263 | va->private = vm; |
1266 | va->flags |= VM_VM_AREA; | 1264 | va->flags |= VM_VM_AREA; |
1265 | } | ||
1266 | |||
1267 | static void insert_vmalloc_vmlist(struct vm_struct *vm) | ||
1268 | { | ||
1269 | struct vm_struct *tmp, **p; | ||
1267 | 1270 | ||
1271 | vm->flags &= ~VM_UNLIST; | ||
1268 | write_lock(&vmlist_lock); | 1272 | write_lock(&vmlist_lock); |
1269 | for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) { | 1273 | for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) { |
1270 | if (tmp->addr >= vm->addr) | 1274 | if (tmp->addr >= vm->addr) |
@@ -1275,6 +1279,13 @@ static void insert_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va, | |||
1275 | write_unlock(&vmlist_lock); | 1279 | write_unlock(&vmlist_lock); |
1276 | } | 1280 | } |
1277 | 1281 | ||
1282 | static void insert_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va, | ||
1283 | unsigned long flags, void *caller) | ||
1284 | { | ||
1285 | setup_vmalloc_vm(vm, va, flags, caller); | ||
1286 | insert_vmalloc_vmlist(vm); | ||
1287 | } | ||
1288 | |||
1278 | static struct vm_struct *__get_vm_area_node(unsigned long size, | 1289 | static struct vm_struct *__get_vm_area_node(unsigned long size, |
1279 | unsigned long align, unsigned long flags, unsigned long start, | 1290 | unsigned long align, unsigned long flags, unsigned long start, |
1280 | unsigned long end, int node, gfp_t gfp_mask, void *caller) | 1291 | unsigned long end, int node, gfp_t gfp_mask, void *caller) |
@@ -1313,7 +1324,18 @@ static struct vm_struct *__get_vm_area_node(unsigned long size, | |||
1313 | return NULL; | 1324 | return NULL; |
1314 | } | 1325 | } |
1315 | 1326 | ||
1316 | insert_vmalloc_vm(area, va, flags, caller); | 1327 | /* |
1328 | * When this function is called from __vmalloc_node_range, | ||
1329 | * we do not add vm_struct to vmlist here to avoid | ||
1330 | * accessing uninitialized members of vm_struct such as | ||
1331 | * pages and nr_pages fields. They will be set later. | ||
1332 | * To distinguish it from others, we use a VM_UNLIST flag. | ||
1333 | */ | ||
1334 | if (flags & VM_UNLIST) | ||
1335 | setup_vmalloc_vm(area, va, flags, caller); | ||
1336 | else | ||
1337 | insert_vmalloc_vm(area, va, flags, caller); | ||
1338 | |||
1317 | return area; | 1339 | return area; |
1318 | } | 1340 | } |
1319 | 1341 | ||
@@ -1381,17 +1403,20 @@ struct vm_struct *remove_vm_area(const void *addr) | |||
1381 | va = find_vmap_area((unsigned long)addr); | 1403 | va = find_vmap_area((unsigned long)addr); |
1382 | if (va && va->flags & VM_VM_AREA) { | 1404 | if (va && va->flags & VM_VM_AREA) { |
1383 | struct vm_struct *vm = va->private; | 1405 | struct vm_struct *vm = va->private; |
1384 | struct vm_struct *tmp, **p; | 1406 | |
1385 | /* | 1407 | if (!(vm->flags & VM_UNLIST)) { |
1386 | * remove from list and disallow access to this vm_struct | 1408 | struct vm_struct *tmp, **p; |
1387 | * before unmap. (address range confliction is maintained by | 1409 | /* |
1388 | * vmap.) | 1410 | * remove from list and disallow access to |
1389 | */ | 1411 | * this vm_struct before unmap. (address range |
1390 | write_lock(&vmlist_lock); | 1412 | * confliction is maintained by vmap.) |
1391 | for (p = &vmlist; (tmp = *p) != vm; p = &tmp->next) | 1413 | */ |
1392 | ; | 1414 | write_lock(&vmlist_lock); |
1393 | *p = tmp->next; | 1415 | for (p = &vmlist; (tmp = *p) != vm; p = &tmp->next) |
1394 | write_unlock(&vmlist_lock); | 1416 | ; |
1417 | *p = tmp->next; | ||
1418 | write_unlock(&vmlist_lock); | ||
1419 | } | ||
1395 | 1420 | ||
1396 | vmap_debug_free_range(va->va_start, va->va_end); | 1421 | vmap_debug_free_range(va->va_start, va->va_end); |
1397 | free_unmap_vmap_area(va); | 1422 | free_unmap_vmap_area(va); |
@@ -1602,8 +1627,8 @@ void *__vmalloc_node_range(unsigned long size, unsigned long align, | |||
1602 | if (!size || (size >> PAGE_SHIFT) > totalram_pages) | 1627 | if (!size || (size >> PAGE_SHIFT) > totalram_pages) |
1603 | return NULL; | 1628 | return NULL; |
1604 | 1629 | ||
1605 | area = __get_vm_area_node(size, align, VM_ALLOC, start, end, node, | 1630 | area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNLIST, |
1606 | gfp_mask, caller); | 1631 | start, end, node, gfp_mask, caller); |
1607 | 1632 | ||
1608 | if (!area) | 1633 | if (!area) |
1609 | return NULL; | 1634 | return NULL; |
@@ -1611,6 +1636,12 @@ void *__vmalloc_node_range(unsigned long size, unsigned long align, | |||
1611 | addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller); | 1636 | addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller); |
1612 | 1637 | ||
1613 | /* | 1638 | /* |
1639 | * In this function, newly allocated vm_struct is not added | ||
1640 | * to vmlist at __get_vm_area_node(). so, it is added here. | ||
1641 | */ | ||
1642 | insert_vmalloc_vmlist(area); | ||
1643 | |||
1644 | /* | ||
1614 | * A ref_count = 3 is needed because the vm_struct and vmap_area | 1645 | * A ref_count = 3 is needed because the vm_struct and vmap_area |
1615 | * structures allocated in the __get_vm_area_node() function contain | 1646 | * structures allocated in the __get_vm_area_node() function contain |
1616 | * references to the virtual address of the vmalloc'ed block. | 1647 | * references to the virtual address of the vmalloc'ed block. |