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