linux
Wrong length units lead to buffer overflow and cause kernel panic
Field `len' units are bytes while field `pos' is an array index; thus, units are bytes/sizeof(pagemap_entry_t). Pagemap walks are using `len' as the upper limit for `pos' to iterate the buffer, but clearly `len' is larger than the actual buffer length. If a function tries to fill the buffer taking `len' as a boundary, that will cause a buffer overflow. Note that PROC_PAGE_MONITOR enables monitoring of memory utilization via the /proc file system, one of these files is /proc/pid/pagemap.
Bug fixed by commit 8c8296223f3
| Type | BufferOverflow |
| Config | "PROC_PAGE_MONITOR" (1st degree) |
| Fix-in | code |
| Location | fs/proc/ |
#include <errno.h>
#include <stdlib.h>
#ifdef CONFIG_PROC_PAGE_MONITOR
#define PM_ENTRY_BYTES sizeof(long)
#define PM_END_OF_BUFFER 1
#define NUM_ENTRIES 10
long *pm_buffer;
unsigned long pm_pos, pm_len;
static int add_to_pagemap()
{
pm_buffer[pm_pos++] = 0; // (5,8) ERROR
if (pm_pos >= pm_len) // (6) comparing array positions against bytes !!!
return PM_END_OF_BUFFER;
return 0;
}
static int pagemap_pte_range()
{
int err = 0;
while (1) {
err = add_to_pagemap(); // (4,7)
if (err)
return err;
}
return err;
}
int walk_page_range()
{
return pagemap_pte_range(); // (3)
}
static int pagemap_read() {
pm_len = PM_ENTRY_BYTES * NUM_ENTRIES;
pm_buffer = malloc(pm_len);
if (!pm_buffer)
return -ENOMEM;
pm_pos = 0;
walk_page_range(); // (2): note that pm_len units are bytes
return 0;
}
#endif /* CONFIG_PROC_PAGE_MONITOR */
int main(int argc, char** argv) {
#ifdef CONFIG_PROC_PAGE_MONITOR
pagemap_read(); // (1)
#endif
return 0;
}
diff --git a/simple/8c82962.c b/simple/8c82962.c
--- a/simple/8c82962.c
+++ b/simple/8c82962.c
@@ -38,8 +42,8 @@
}
static int pagemap_read() {
- pm_len = PM_ENTRY_BYTES * NUM_ENTRIES;
- pm_buffer = malloc(pm_len);
+ pm_len = NUM_ENTRIES;
+ pm_buffer = malloc(pm_len * PM_ENTRY_BYTES);
if (!pm_buffer)
return -ENOMEM;
#include <errno.h>
#include <stdlib.h>
#ifdef CONFIG_PROC_PAGE_MONITOR
#define PM_ENTRY_BYTES sizeof(long)
#define PM_END_OF_BUFFER 1
#define NUM_ENTRIES 10
long *pm_buffer;
unsigned long pm_pos, pm_len;
#endif
int main(int argc, char** argv) {
#ifdef CONFIG_PROC_PAGE_MONITOR
// pagemap_read();
pm_len = PM_ENTRY_BYTES * NUM_ENTRIES;
pm_buffer = malloc(pm_len);
if (!pm_buffer)
return -ENOMEM;
pm_pos = 0;
int err = 0;
while (1) {
//err = add_to_pagemap();
pm_buffer[pm_pos++] = 0; // ERROR
if (pm_pos >= pm_len)
err = PM_END_OF_BUFFER;
if (err)
return err;
}
#endif
return 0;
}
. call fs/proc/task_mmu.c:1103:pagemap_read()
. 1130: pm.len = PM_ENTRY_BYTES * (PAGEMAP_WALK_SIZE >> PAGE_SHIFT);
// pm.len is set to the size of pm.buffer in bytes
// PM_ENTRY_BYTES = sizeof(pagemap_entry_t)
// PAGEMAP_WALK_SIZE = PMD_SIZE
// PMD_SIZE is the size of a page table, while 2^PAGE_SHIFT is the size of a page, therefore PMD_SIZE / 2^PAGE_SHIFT is the number of page entries in a page table
. 1131: pm.buffer = kmalloc(pm.len, GFP_TEMPORARY);
. 1165: while (count && (start_vaddr < end_vaddr)) {
. 1175: ret = walk_page_range(start_vaddr, end, &pagemap_walk);
.. call mm/pagewalk.c:167:walk_page_range()
.. 241: err = walk_pud_range(pgd, addr, next, walk);
... call mm/pagewalk.c:72:walk_pud_range()
... 92: err = walk_pmd_range(pud, addr, next, walk);
.... call mm/pagewalk.c:27:walk_pmd_range()
.... 50: err = walk->pmd_entry(pmd, addr, next, walk);
..... dyn-call fs/proc/task_mmu.c:986:pagemap_pte_range()
..... 1007: err = add_to_pagemap(addr, &pme, pm);
// add_to_pagemap() 905 is the iterator step function for setting entries by walking through the buffer
...... call fs/proc/task_mmu.c:905:add_to_pagemap()
...... 908: pm->buffer[pm->pos++] = *pme;
...... 909: if (pm->pos >= pm->len)
// it compares pos and len, but pos is the index in the array while len is the size of the array in bytes
...... ERROR 908: pm->buffer[pm->pos++] = *pme;
// eventually we get a buffer overflow, when pos is incremented beyond the array bounds