s390/mm: fix pxd_bad with folded page tables
authorMartin Schwidefsky <schwidefsky@de.ibm.com>
Wed, 24 Apr 2019 10:49:44 +0000 (12:49 +0200)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Wed, 24 Apr 2019 11:28:50 +0000 (13:28 +0200)
With git commit d1874a0c2805fcfa9162c972d6b7541e57adb542
"s390/mm: make the pxd_offset functions more robust" and a 2-level page
table it can now happen that pgd_bad() gets asked to verify a large
segment table entry. If the entry is marked as dirty pgd_bad() will
incorrectly return true.

Change the pgd_bad(), p4d_bad(), pud_bad() and pmd_bad() functions to
first verify the table type, return false if the table level is lower
than what the function is suppossed to check, return true if the table
level is too high, and otherwise check the relevant region and segment
table bits. pmd_bad() has to check against ~SEGMENT_ENTRY_BITS for
normal page table pointers or ~SEGMENT_ENTRY_BITS_LARGE for large
segment table entries. Same for pud_bad() which has to check against
~_REGION_ENTRY_BITS or ~_REGION_ENTRY_BITS_LARGE.

Fixes: d1874a0c2805 ("s390/mm: make the pxd_offset functions more robust")
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
arch/s390/include/asm/pgtable.h

index 394bec31cb97d929b3fd3d1cedbb02c7e64797d1..9f0195d5fa167f5d7817f2a231c6e9f8f31f9b9f 100644 (file)
@@ -238,7 +238,7 @@ static inline int is_module_addr(void *addr)
 #define _REGION_ENTRY_NOEXEC   0x100   /* region no-execute bit            */
 #define _REGION_ENTRY_OFFSET   0xc0    /* region table offset              */
 #define _REGION_ENTRY_INVALID  0x20    /* invalid region table entry       */
-#define _REGION_ENTRY_TYPE_MASK        0x0c    /* region/segment table type mask   */
+#define _REGION_ENTRY_TYPE_MASK        0x0c    /* region table type mask           */
 #define _REGION_ENTRY_TYPE_R1  0x0c    /* region first table type          */
 #define _REGION_ENTRY_TYPE_R2  0x08    /* region second table type         */
 #define _REGION_ENTRY_TYPE_R3  0x04    /* region third table type          */
@@ -277,6 +277,7 @@ static inline int is_module_addr(void *addr)
 #define _SEGMENT_ENTRY_PROTECT 0x200   /* segment protection bit           */
 #define _SEGMENT_ENTRY_NOEXEC  0x100   /* segment no-execute bit           */
 #define _SEGMENT_ENTRY_INVALID 0x20    /* invalid segment table entry      */
+#define _SEGMENT_ENTRY_TYPE_MASK 0x0c  /* segment table type mask          */
 
 #define _SEGMENT_ENTRY         (0)
 #define _SEGMENT_ENTRY_EMPTY   (_SEGMENT_ENTRY_INVALID)
@@ -614,15 +615,9 @@ static inline int pgd_none(pgd_t pgd)
 
 static inline int pgd_bad(pgd_t pgd)
 {
-       /*
-        * With dynamic page table levels the pgd can be a region table
-        * entry or a segment table entry. Check for the bit that are
-        * invalid for either table entry.
-        */
-       unsigned long mask =
-               ~_SEGMENT_ENTRY_ORIGIN & ~_REGION_ENTRY_INVALID &
-               ~_REGION_ENTRY_TYPE_MASK & ~_REGION_ENTRY_LENGTH;
-       return (pgd_val(pgd) & mask) != 0;
+       if ((pgd_val(pgd) & _REGION_ENTRY_TYPE_MASK) < _REGION_ENTRY_TYPE_R1)
+               return 0;
+       return (pgd_val(pgd) & ~_REGION_ENTRY_BITS) != 0;
 }
 
 static inline unsigned long pgd_pfn(pgd_t pgd)
@@ -703,6 +698,8 @@ static inline int pmd_large(pmd_t pmd)
 
 static inline int pmd_bad(pmd_t pmd)
 {
+       if ((pmd_val(pmd) & _SEGMENT_ENTRY_TYPE_MASK) > 0)
+               return 1;
        if (pmd_large(pmd))
                return (pmd_val(pmd) & ~_SEGMENT_ENTRY_BITS_LARGE) != 0;
        return (pmd_val(pmd) & ~_SEGMENT_ENTRY_BITS) != 0;
@@ -710,8 +707,12 @@ static inline int pmd_bad(pmd_t pmd)
 
 static inline int pud_bad(pud_t pud)
 {
-       if ((pud_val(pud) & _REGION_ENTRY_TYPE_MASK) < _REGION_ENTRY_TYPE_R3)
-               return pmd_bad(__pmd(pud_val(pud)));
+       unsigned long type = pud_val(pud) & _REGION_ENTRY_TYPE_MASK;
+
+       if (type > _REGION_ENTRY_TYPE_R3)
+               return 1;
+       if (type < _REGION_ENTRY_TYPE_R3)
+               return 0;
        if (pud_large(pud))
                return (pud_val(pud) & ~_REGION_ENTRY_BITS_LARGE) != 0;
        return (pud_val(pud) & ~_REGION_ENTRY_BITS) != 0;
@@ -719,8 +720,12 @@ static inline int pud_bad(pud_t pud)
 
 static inline int p4d_bad(p4d_t p4d)
 {
-       if ((p4d_val(p4d) & _REGION_ENTRY_TYPE_MASK) < _REGION_ENTRY_TYPE_R2)
-               return pud_bad(__pud(p4d_val(p4d)));
+       unsigned long type = p4d_val(p4d) & _REGION_ENTRY_TYPE_MASK;
+
+       if (type > _REGION_ENTRY_TYPE_R2)
+               return 1;
+       if (type < _REGION_ENTRY_TYPE_R2)
+               return 0;
        return (p4d_val(p4d) & ~_REGION_ENTRY_BITS) != 0;
 }