aboutsummaryrefslogtreecommitdiffstats
path: root/arch/s390
diff options
context:
space:
mode:
authorHeiko Carstens <heiko.carstens@de.ibm.com>2013-03-21 07:50:39 -0400
committerMartin Schwidefsky <schwidefsky@de.ibm.com>2013-04-02 02:53:08 -0400
commitea81531de23cf92085e0601178fae920141caa5d (patch)
treee1978efab8d5b900ca5fe7d82ca0079c259cc8dc /arch/s390
parent1fcbba3d65dff8ce9d25e644fcc86fa8c629de7d (diff)
s390/uaccess: fix page table walk
When translating user space addresses to kernel addresses the follow_table() function had two bugs: - PROT_NONE mappings could be read accessed via the kernel mapping. That is e.g. putting a filename into a user page, then protecting the page with PROT_NONE and afterwards issuing the "open" syscall with a pointer to the filename would incorrectly succeed. - when walking the page tables it used the pgd/pud/pmd/pte primitives which with dynamic page tables give no indication which real level of page tables is being walked (region2, region3, segment or page table). So in case of an exception the translation exception code passed to __handle_fault() is not necessarily correct. This is not really an issue since __handle_fault() doesn't evaluate the code. Only in case of e.g. a SIGBUS this code gets passed to user space. If user space can do something sane with the value is a different question though. To fix these issues don't use any Linux primitives. Only walk the page tables like the hardware would do it, however we leave quite some checks away since we know that we only have full size page tables and each index is within bounds. In theory this should fix all issues... Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com> Reviewed-by: Gerald Schaefer <gerald.schaefer@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'arch/s390')
-rw-r--r--arch/s390/include/asm/pgtable.h1
-rw-r--r--arch/s390/lib/uaccess_pt.c81
2 files changed, 55 insertions, 27 deletions
diff --git a/arch/s390/include/asm/pgtable.h b/arch/s390/include/asm/pgtable.h
index 4a2930844d43..1686d8f0f878 100644
--- a/arch/s390/include/asm/pgtable.h
+++ b/arch/s390/include/asm/pgtable.h
@@ -344,6 +344,7 @@ extern unsigned long MODULES_END;
344#define _REGION3_ENTRY_CO 0x100 /* change-recording override */ 344#define _REGION3_ENTRY_CO 0x100 /* change-recording override */
345 345
346/* Bits in the segment table entry */ 346/* Bits in the segment table entry */
347#define _SEGMENT_ENTRY_ORIGIN_LARGE ~0xfffffUL /* large page address */
347#define _SEGMENT_ENTRY_ORIGIN ~0x7ffUL/* segment table origin */ 348#define _SEGMENT_ENTRY_ORIGIN ~0x7ffUL/* segment table origin */
348#define _SEGMENT_ENTRY_RO 0x200 /* page protection bit */ 349#define _SEGMENT_ENTRY_RO 0x200 /* page protection bit */
349#define _SEGMENT_ENTRY_INV 0x20 /* invalid segment table entry */ 350#define _SEGMENT_ENTRY_INV 0x20 /* invalid segment table entry */
diff --git a/arch/s390/lib/uaccess_pt.c b/arch/s390/lib/uaccess_pt.c
index 6771fdd89377..466fb3383960 100644
--- a/arch/s390/lib/uaccess_pt.c
+++ b/arch/s390/lib/uaccess_pt.c
@@ -77,42 +77,69 @@ static size_t copy_in_kernel(size_t count, void __user *to,
77 * >= -4095 (IS_ERR_VALUE(x) returns true), a fault has occured and the address 77 * >= -4095 (IS_ERR_VALUE(x) returns true), a fault has occured and the address
78 * contains the (negative) exception code. 78 * contains the (negative) exception code.
79 */ 79 */
80static __always_inline unsigned long follow_table(struct mm_struct *mm, 80#ifdef CONFIG_64BIT
81 unsigned long addr, int write) 81static unsigned long follow_table(struct mm_struct *mm,
82 unsigned long address, int write)
82{ 83{
83 pgd_t *pgd; 84 unsigned long *table = (unsigned long *)__pa(mm->pgd);
84 pud_t *pud; 85
85 pmd_t *pmd; 86 switch (mm->context.asce_bits & _ASCE_TYPE_MASK) {
86 pte_t *ptep; 87 case _ASCE_TYPE_REGION1:
88 table = table + ((address >> 53) & 0x7ff);
89 if (unlikely(*table & _REGION_ENTRY_INV))
90 return -0x39UL;
91 table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
92 case _ASCE_TYPE_REGION2:
93 table = table + ((address >> 42) & 0x7ff);
94 if (unlikely(*table & _REGION_ENTRY_INV))
95 return -0x3aUL;
96 table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
97 case _ASCE_TYPE_REGION3:
98 table = table + ((address >> 31) & 0x7ff);
99 if (unlikely(*table & _REGION_ENTRY_INV))
100 return -0x3bUL;
101 table = (unsigned long *)(*table & _REGION_ENTRY_ORIGIN);
102 case _ASCE_TYPE_SEGMENT:
103 table = table + ((address >> 20) & 0x7ff);
104 if (unlikely(*table & _SEGMENT_ENTRY_INV))
105 return -0x10UL;
106 if (unlikely(*table & _SEGMENT_ENTRY_LARGE)) {
107 if (write && (*table & _SEGMENT_ENTRY_RO))
108 return -0x04UL;
109 return (*table & _SEGMENT_ENTRY_ORIGIN_LARGE) +
110 (address & ~_SEGMENT_ENTRY_ORIGIN_LARGE);
111 }
112 table = (unsigned long *)(*table & _SEGMENT_ENTRY_ORIGIN);
113 }
114 table = table + ((address >> 12) & 0xff);
115 if (unlikely(*table & _PAGE_INVALID))
116 return -0x11UL;
117 if (write && (*table & _PAGE_RO))
118 return -0x04UL;
119 return (*table & PAGE_MASK) + (address & ~PAGE_MASK);
120}
87 121
88 pgd = pgd_offset(mm, addr); 122#else /* CONFIG_64BIT */
89 if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
90 return -0x3aUL;
91 123
92 pud = pud_offset(pgd, addr); 124static unsigned long follow_table(struct mm_struct *mm,
93 if (pud_none(*pud) || unlikely(pud_bad(*pud))) 125 unsigned long address, int write)
94 return -0x3bUL; 126{
127 unsigned long *table = (unsigned long *)__pa(mm->pgd);
95 128
96 pmd = pmd_offset(pud, addr); 129 table = table + ((address >> 20) & 0x7ff);
97 if (pmd_none(*pmd)) 130 if (unlikely(*table & _SEGMENT_ENTRY_INV))
98 return -0x10UL; 131 return -0x10UL;
99 if (pmd_large(*pmd)) { 132 table = (unsigned long *)(*table & _SEGMENT_ENTRY_ORIGIN);
100 if (write && (pmd_val(*pmd) & _SEGMENT_ENTRY_RO)) 133 table = table + ((address >> 12) & 0xff);
101 return -0x04UL; 134 if (unlikely(*table & _PAGE_INVALID))
102 return (pmd_val(*pmd) & HPAGE_MASK) + (addr & ~HPAGE_MASK);
103 }
104 if (unlikely(pmd_bad(*pmd)))
105 return -0x10UL;
106
107 ptep = pte_offset_map(pmd, addr);
108 if (!pte_present(*ptep))
109 return -0x11UL; 135 return -0x11UL;
110 if (write && (!pte_write(*ptep) || !pte_dirty(*ptep))) 136 if (write && (*table & _PAGE_RO))
111 return -0x04UL; 137 return -0x04UL;
112 138 return (*table & PAGE_MASK) + (address & ~PAGE_MASK);
113 return (pte_val(*ptep) & PAGE_MASK) + (addr & ~PAGE_MASK);
114} 139}
115 140
141#endif /* CONFIG_64BIT */
142
116static __always_inline size_t __user_copy_pt(unsigned long uaddr, void *kptr, 143static __always_inline size_t __user_copy_pt(unsigned long uaddr, void *kptr,
117 size_t n, int write_user) 144 size_t n, int write_user)
118{ 145{