| From 3446c13b268af86391d06611327006b059b8bab1 Mon Sep 17 00:00:00 2001 |
| From: Martin Schwidefsky <schwidefsky@de.ibm.com> |
| Date: Mon, 15 Feb 2016 14:46:49 +0100 |
| Subject: s390/mm: four page table levels vs. fork |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| From: Martin Schwidefsky <schwidefsky@de.ibm.com> |
| |
| commit 3446c13b268af86391d06611327006b059b8bab1 upstream. |
| |
| The fork of a process with four page table levels is broken since |
| git commit 6252d702c5311ce9 "[S390] dynamic page tables." |
| |
| All new mm contexts are created with three page table levels and |
| an asce limit of 4TB. If the parent has four levels dup_mmap will |
| add vmas to the new context which are outside of the asce limit. |
| The subsequent call to copy_page_range will walk the three level |
| page table structure of the new process with non-zero pgd and pud |
| indexes. This leads to memory clobbers as the pgd_index *and* the |
| pud_index is added to the mm->pgd pointer without a pgd_deref |
| in between. |
| |
| The init_new_context() function is selecting the number of page |
| table levels for a new context. The function is used by mm_init() |
| which in turn is called by dup_mm() and mm_alloc(). These two are |
| used by fork() and exec(). The init_new_context() function can |
| distinguish the two cases by looking at mm->context.asce_limit, |
| for fork() the mm struct has been copied and the number of page |
| table levels may not change. For exec() the mm_alloc() function |
| set the new mm structure to zero, in this case a three-level page |
| table is created as the temporary stack space is located at |
| STACK_TOP_MAX = 4TB. |
| |
| This fixes CVE-2016-2143. |
| |
| Reported-by: Marcin Kościelnicki <koriakin@0x04.net> |
| Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com> |
| Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| arch/s390/include/asm/mmu_context.h | 16 +++++++++++----- |
| arch/s390/include/asm/pgalloc.h | 24 +++++++++++++++++++----- |
| 2 files changed, 30 insertions(+), 10 deletions(-) |
| |
| --- a/arch/s390/include/asm/mmu_context.h |
| +++ b/arch/s390/include/asm/mmu_context.h |
| @@ -15,17 +15,25 @@ |
| static inline int init_new_context(struct task_struct *tsk, |
| struct mm_struct *mm) |
| { |
| + spin_lock_init(&mm->context.list_lock); |
| + INIT_LIST_HEAD(&mm->context.pgtable_list); |
| + INIT_LIST_HEAD(&mm->context.gmap_list); |
| cpumask_clear(&mm->context.cpu_attach_mask); |
| atomic_set(&mm->context.attach_count, 0); |
| mm->context.flush_mm = 0; |
| - mm->context.asce_bits = _ASCE_TABLE_LENGTH | _ASCE_USER_BITS; |
| - mm->context.asce_bits |= _ASCE_TYPE_REGION3; |
| #ifdef CONFIG_PGSTE |
| mm->context.alloc_pgste = page_table_allocate_pgste; |
| mm->context.has_pgste = 0; |
| mm->context.use_skey = 0; |
| #endif |
| - mm->context.asce_limit = STACK_TOP_MAX; |
| + if (mm->context.asce_limit == 0) { |
| + /* context created by exec, set asce limit to 4TB */ |
| + mm->context.asce_bits = _ASCE_TABLE_LENGTH | |
| + _ASCE_USER_BITS | _ASCE_TYPE_REGION3; |
| + mm->context.asce_limit = STACK_TOP_MAX; |
| + } else if (mm->context.asce_limit == (1UL << 31)) { |
| + mm_inc_nr_pmds(mm); |
| + } |
| crst_table_init((unsigned long *) mm->pgd, pgd_entry_type(mm)); |
| return 0; |
| } |
| @@ -111,8 +119,6 @@ static inline void activate_mm(struct mm |
| static inline void arch_dup_mmap(struct mm_struct *oldmm, |
| struct mm_struct *mm) |
| { |
| - if (oldmm->context.asce_limit < mm->context.asce_limit) |
| - crst_table_downgrade(mm, oldmm->context.asce_limit); |
| } |
| |
| static inline void arch_exit_mmap(struct mm_struct *mm) |
| --- a/arch/s390/include/asm/pgalloc.h |
| +++ b/arch/s390/include/asm/pgalloc.h |
| @@ -100,12 +100,26 @@ static inline void pud_populate(struct m |
| |
| static inline pgd_t *pgd_alloc(struct mm_struct *mm) |
| { |
| - spin_lock_init(&mm->context.list_lock); |
| - INIT_LIST_HEAD(&mm->context.pgtable_list); |
| - INIT_LIST_HEAD(&mm->context.gmap_list); |
| - return (pgd_t *) crst_table_alloc(mm); |
| + unsigned long *table = crst_table_alloc(mm); |
| + |
| + if (!table) |
| + return NULL; |
| + if (mm->context.asce_limit == (1UL << 31)) { |
| + /* Forking a compat process with 2 page table levels */ |
| + if (!pgtable_pmd_page_ctor(virt_to_page(table))) { |
| + crst_table_free(mm, table); |
| + return NULL; |
| + } |
| + } |
| + return (pgd_t *) table; |
| +} |
| + |
| +static inline void pgd_free(struct mm_struct *mm, pgd_t *pgd) |
| +{ |
| + if (mm->context.asce_limit == (1UL << 31)) |
| + pgtable_pmd_page_dtor(virt_to_page(pgd)); |
| + crst_table_free(mm, (unsigned long *) pgd); |
| } |
| -#define pgd_free(mm, pgd) crst_table_free(mm, (unsigned long *) pgd) |
| |
| static inline void pmd_populate(struct mm_struct *mm, |
| pmd_t *pmd, pgtable_t pte) |