| From a5d25807a4d122b1cb4f933fd5feea9e256a4611 Mon Sep 17 00:00:00 2001 |
| From: Mel Gorman <mgorman@suse.de> |
| Date: Mon, 11 Feb 2013 14:52:36 +0000 |
| Subject: [PATCH] x86/mm: Check if PUD is large when validating a kernel |
| address |
| |
| commit 0ee364eb316348ddf3e0dfcd986f5f13f528f821 upstream. |
| |
| A user reported the following oops when a backup process reads |
| /proc/kcore: |
| |
| BUG: unable to handle kernel paging request at ffffbb00ff33b000 |
| IP: [<ffffffff8103157e>] kern_addr_valid+0xbe/0x110 |
| [...] |
| |
| Call Trace: |
| [<ffffffff811b8aaa>] read_kcore+0x17a/0x370 |
| [<ffffffff811ad847>] proc_reg_read+0x77/0xc0 |
| [<ffffffff81151687>] vfs_read+0xc7/0x130 |
| [<ffffffff811517f3>] sys_read+0x53/0xa0 |
| [<ffffffff81449692>] system_call_fastpath+0x16/0x1b |
| |
| Investigation determined that the bug triggered when reading |
| system RAM at the 4G mark. On this system, that was the first |
| address using 1G pages for the virt->phys direct mapping so the |
| PUD is pointing to a physical address, not a PMD page. |
| |
| The problem is that the page table walker in kern_addr_valid() is |
| not checking pud_large() and treats the physical address as if |
| it was a PMD. If it happens to look like pmd_none then it'll |
| silently fail, probably returning zeros instead of real data. If |
| the data happens to look like a present PMD though, it will be |
| walked resulting in the oops above. |
| |
| This patch adds the necessary pud_large() check. |
| |
| Unfortunately the problem was not readily reproducible and now |
| they are running the backup program without accessing |
| /proc/kcore so the patch has not been validated but I think it |
| makes sense. |
| |
| Signed-off-by: Mel Gorman <mgorman@suse.de> |
| Reviewed-by: Rik van Riel <riel@redhat.coM> |
| Reviewed-by: Michal Hocko <mhocko@suse.cz> |
| Acked-by: Johannes Weiner <hannes@cmpxchg.org> |
| Cc: linux-mm@kvack.org |
| Link: http://lkml.kernel.org/r/20130211145236.GX21389@suse.de |
| Signed-off-by: Ingo Molnar <mingo@kernel.org> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| --- |
| arch/x86/include/asm/pgtable.h | 5 +++++ |
| arch/x86/mm/init_64.c | 3 +++ |
| 2 files changed, 8 insertions(+) |
| |
| diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h |
| index a34c785c5a63..321d24d6a924 100644 |
| --- a/arch/x86/include/asm/pgtable.h |
| +++ b/arch/x86/include/asm/pgtable.h |
| @@ -132,6 +132,11 @@ static inline unsigned long pmd_pfn(pmd_t pmd) |
| return (pmd_val(pmd) & PTE_PFN_MASK) >> PAGE_SHIFT; |
| } |
| |
| +static inline unsigned long pud_pfn(pud_t pud) |
| +{ |
| + return (pud_val(pud) & PTE_PFN_MASK) >> PAGE_SHIFT; |
| +} |
| + |
| #define pte_page(pte) pfn_to_page(pte_pfn(pte)) |
| |
| static inline int pmd_large(pmd_t pte) |
| diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c |
| index ee41bba315d1..3cd243b8b01d 100644 |
| --- a/arch/x86/mm/init_64.c |
| +++ b/arch/x86/mm/init_64.c |
| @@ -864,6 +864,9 @@ int kern_addr_valid(unsigned long addr) |
| if (pud_none(*pud)) |
| return 0; |
| |
| + if (pud_large(*pud)) |
| + return pfn_valid(pud_pfn(*pud)); |
| + |
| pmd = pmd_offset(pud, addr); |
| if (pmd_none(*pmd)) |
| return 0; |
| -- |
| 1.8.5.2 |
| |