diff mbox

[v3,1/5] arm64: mm: BUG on unsupported manipulations of live kernel mappings

Message ID 1476271425-19401-2-git-send-email-ard.biesheuvel@linaro.org
State Superseded
Headers show

Commit Message

Ard Biesheuvel Oct. 12, 2016, 11:23 a.m. UTC
Now that we take care not manipulate the live kernel page tables in a
way that may lead to TLB conflicts, the case where a table mapping is
replaced by a block mapping can no longer occur. So remove the handling
of this at the PUD and PMD levels, and instead, BUG() on any occurrence
of live kernel page table manipulations that modify anything other than
the permission bits.

Since mark_rodata_ro() is the only caller where the kernel mappings that
are being manipulated are actually live, drop the various conditional
flush_tlb_all() invocations, and add a single call to mark_rodata_ro()
instead.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>

---
 arch/arm64/mm/mmu.c | 68 ++++++++++++--------
 1 file changed, 41 insertions(+), 27 deletions(-)

-- 
2.7.4


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

Comments

Catalin Marinas Oct. 12, 2016, 3:04 p.m. UTC | #1
On Wed, Oct 12, 2016 at 12:23:41PM +0100, Ard Biesheuvel wrote:
> --- a/arch/arm64/mm/mmu.c

> +++ b/arch/arm64/mm/mmu.c

> @@ -28,8 +28,6 @@

>  #include <linux/memblock.h>

>  #include <linux/fs.h>

>  #include <linux/io.h>

> -#include <linux/slab.h>

> -#include <linux/stop_machine.h>

>  

>  #include <asm/barrier.h>

>  #include <asm/cputype.h>

> @@ -95,6 +93,12 @@ static phys_addr_t __init early_pgtable_alloc(void)

>  	return phys;

>  }

>  

> +/*

> + * The following mapping attributes may be updated in live

> + * kernel mappings without the need for break-before-make.

> + */

> +static const pteval_t modifiable_attr_mask = PTE_PXN | PTE_RDONLY | PTE_WRITE;

> +

>  static void alloc_init_pte(pmd_t *pmd, unsigned long addr,

>  				  unsigned long end, unsigned long pfn,

>  				  pgprot_t prot,

> @@ -115,8 +119,18 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,

>  

>  	pte = pte_set_fixmap_offset(pmd, addr);

>  	do {

> +		pte_t old_pte = *pte;

> +

>  		set_pte(pte, pfn_pte(pfn, prot));

>  		pfn++;

> +

> +		/*

> +		 * After the PTE entry has been populated once, we

> +		 * only allow updates to the permission attributes.

> +		 */

> +		BUG_ON(pte_val(old_pte) != 0 &&

> +		       ((pte_val(old_pte) ^ pte_val(*pte)) &

> +			~modifiable_attr_mask) != 0);


Please turn this check into a single macro. You have it in three places
already (though with different types but a macro would do). Something
like below (feel free to come up with a better macro name):

		BUG_ON(!safe_pgattr_change(old_pte, *pte));

-- 
Catalin

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Ard Biesheuvel Oct. 13, 2016, 12:25 p.m. UTC | #2
On 12 October 2016 at 16:04, Catalin Marinas <catalin.marinas@arm.com> wrote:
> On Wed, Oct 12, 2016 at 12:23:41PM +0100, Ard Biesheuvel wrote:

>> --- a/arch/arm64/mm/mmu.c

>> +++ b/arch/arm64/mm/mmu.c

>> @@ -28,8 +28,6 @@

>>  #include <linux/memblock.h>

>>  #include <linux/fs.h>

>>  #include <linux/io.h>

>> -#include <linux/slab.h>

>> -#include <linux/stop_machine.h>

>>

>>  #include <asm/barrier.h>

>>  #include <asm/cputype.h>

>> @@ -95,6 +93,12 @@ static phys_addr_t __init early_pgtable_alloc(void)

>>       return phys;

>>  }

>>

>> +/*

>> + * The following mapping attributes may be updated in live

>> + * kernel mappings without the need for break-before-make.

>> + */

>> +static const pteval_t modifiable_attr_mask = PTE_PXN | PTE_RDONLY | PTE_WRITE;

>> +

>>  static void alloc_init_pte(pmd_t *pmd, unsigned long addr,

>>                                 unsigned long end, unsigned long pfn,

>>                                 pgprot_t prot,

>> @@ -115,8 +119,18 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,

>>

>>       pte = pte_set_fixmap_offset(pmd, addr);

>>       do {

>> +             pte_t old_pte = *pte;

>> +

>>               set_pte(pte, pfn_pte(pfn, prot));

>>               pfn++;

>> +

>> +             /*

>> +              * After the PTE entry has been populated once, we

>> +              * only allow updates to the permission attributes.

>> +              */

>> +             BUG_ON(pte_val(old_pte) != 0 &&

>> +                    ((pte_val(old_pte) ^ pte_val(*pte)) &

>> +                     ~modifiable_attr_mask) != 0);

>

> Please turn this check into a single macro. You have it in three places

> already (though with different types but a macro would do). Something

> like below (feel free to come up with a better macro name):

>

>                 BUG_ON(!safe_pgattr_change(old_pte, *pte));

>


Something like below? With that, I can also fold the PMD and PUD
versions of the BUG() together.

"""
/*
 * Returns whether updating a live page table entry is safe:
 * - if the old and new values are identical,
 * - if an invalid mapping is turned into a valid one (or vice versa),
 * - if the entry is a block or page mapping, and the old and new values
 *   only differ in the PXN/RDONLY/WRITE bits.
 *
 * NOTE: 'safe' does not imply that no TLB maintenance is required, it only
 *       means that no TLB conflicts should occur as a result of the update.
 */
#define __set_pgattr_is_safe(type, old, new, blocktype) \
(type ## _val(old) == type ## _val(new) || \
((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \
(((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \
 (((type ## _val(old) ^ type ## _val(new)) & \
   ~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

static inline bool set_live_pte_is_safe(pte_t old, pte_t new)
{
return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE);
}

static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new)
{
return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT);
}

static inline bool set_live_pud_is_safe(pud_t old, pud_t new)
{
return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT);
}
"""

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Catalin Marinas Oct. 13, 2016, 2:44 p.m. UTC | #3
On Thu, Oct 13, 2016 at 01:25:53PM +0100, Ard Biesheuvel wrote:
> On 12 October 2016 at 16:04, Catalin Marinas <catalin.marinas@arm.com> wrote:

> > On Wed, Oct 12, 2016 at 12:23:41PM +0100, Ard Biesheuvel wrote:

> >> +/*

> >> + * The following mapping attributes may be updated in live

> >> + * kernel mappings without the need for break-before-make.

> >> + */

> >> +static const pteval_t modifiable_attr_mask = PTE_PXN | PTE_RDONLY | PTE_WRITE;

> >> +

> >>  static void alloc_init_pte(pmd_t *pmd, unsigned long addr,

> >>                                 unsigned long end, unsigned long pfn,

> >>                                 pgprot_t prot,

> >> @@ -115,8 +119,18 @@ static void alloc_init_pte(pmd_t *pmd, unsigned long addr,

> >>

> >>       pte = pte_set_fixmap_offset(pmd, addr);

> >>       do {

> >> +             pte_t old_pte = *pte;

> >> +

> >>               set_pte(pte, pfn_pte(pfn, prot));

> >>               pfn++;

> >> +

> >> +             /*

> >> +              * After the PTE entry has been populated once, we

> >> +              * only allow updates to the permission attributes.

> >> +              */

> >> +             BUG_ON(pte_val(old_pte) != 0 &&

> >> +                    ((pte_val(old_pte) ^ pte_val(*pte)) &

> >> +                     ~modifiable_attr_mask) != 0);

> >

> > Please turn this check into a single macro. You have it in three places

> > already (though with different types but a macro would do). Something

> > like below (feel free to come up with a better macro name):

> >

> >                 BUG_ON(!safe_pgattr_change(old_pte, *pte));

> 

> Something like below? With that, I can also fold the PMD and PUD

> versions of the BUG() together.


(fixing up alignment to make it readable)

> """

> /*

>  * Returns whether updating a live page table entry is safe:

>  * - if the old and new values are identical,

>  * - if an invalid mapping is turned into a valid one (or vice versa),

>  * - if the entry is a block or page mapping, and the old and new values

>  *   only differ in the PXN/RDONLY/WRITE bits.

>  *

>  * NOTE: 'safe' does not imply that no TLB maintenance is required, it only

>  *       means that no TLB conflicts should occur as a result of the update.

>  */

> #define __set_pgattr_is_safe(type, old, new, blocktype) \

>	(type ## _val(old) == type ## _val(new) || \

>	 ((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \

>	 (((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \

>	  (((type ## _val(old) ^ type ## _val(new)) & \

>	 ~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

> 

> static inline bool set_live_pte_is_safe(pte_t old, pte_t new)

> {

>	return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE);

> }

> 

> static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new)

> {

>	return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT);

> }

> 

> static inline bool set_live_pud_is_safe(pud_t old, pud_t new)

> {

>	return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT);

> }


The set_ prefix is slightly confusing as it suggests (to me) having a
side effect. Maybe pgattr_set_is_safe()?

But it looks like we make it more complicated needed by using pte_t
instead of pteval_t as argument. How about just using the pteval_t as
argument (and it's fine to call it with pmdval_t, pudval_t as well):

#define pgattr_set_is_safe(oldval, newval) \
	...

-- 
Catalin

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Ard Biesheuvel Oct. 13, 2016, 2:48 p.m. UTC | #4
On 13 October 2016 at 15:44, Catalin Marinas <catalin.marinas@arm.com> wrote:
> On Thu, Oct 13, 2016 at 01:25:53PM +0100, Ard Biesheuvel wrote:

>

> (fixing up alignment to make it readable)

>


I apologise on Gmail's behalf

>> """

>> /*

>>  * Returns whether updating a live page table entry is safe:

>>  * - if the old and new values are identical,

>>  * - if an invalid mapping is turned into a valid one (or vice versa),

>>  * - if the entry is a block or page mapping, and the old and new values

>>  *   only differ in the PXN/RDONLY/WRITE bits.

>>  *

>>  * NOTE: 'safe' does not imply that no TLB maintenance is required, it only

>>  *       means that no TLB conflicts should occur as a result of the update.

>>  */

>> #define __set_pgattr_is_safe(type, old, new, blocktype) \

>>       (type ## _val(old) == type ## _val(new) || \

>>        ((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \

>>        (((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \

>>         (((type ## _val(old) ^ type ## _val(new)) & \

>>        ~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

>>

>> static inline bool set_live_pte_is_safe(pte_t old, pte_t new)

>> {

>>       return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE);

>> }

>>

>> static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new)

>> {

>>       return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT);

>> }

>>

>> static inline bool set_live_pud_is_safe(pud_t old, pud_t new)

>> {

>>       return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT);

>> }

>

> The set_ prefix is slightly confusing as it suggests (to me) having a

> side effect. Maybe pgattr_set_is_safe()?

>

> But it looks like we make it more complicated needed by using pte_t

> instead of pteval_t as argument. How about just using the pteval_t as

> argument (and it's fine to call it with pmdval_t, pudval_t as well):

>

> #define pgattr_set_is_safe(oldval, newval) \

>         ...

>


Well, the only problem there is that the permission bit check should
only apply to level 3 page mappings (bit[1] == 1) and level 1/2 block
mappings (bit[1] == 0), so we would still need two versions

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Catalin Marinas Oct. 13, 2016, 4:51 p.m. UTC | #5
On Thu, Oct 13, 2016 at 03:48:04PM +0100, Ard Biesheuvel wrote:
> On 13 October 2016 at 15:44, Catalin Marinas <catalin.marinas@arm.com> wrote:

> > On Thu, Oct 13, 2016 at 01:25:53PM +0100, Ard Biesheuvel wrote:

> >

> > (fixing up alignment to make it readable)

> >

> 

> I apologise on Gmail's behalf

> 

> >> """

> >> /*

> >>  * Returns whether updating a live page table entry is safe:

> >>  * - if the old and new values are identical,

> >>  * - if an invalid mapping is turned into a valid one (or vice versa),

> >>  * - if the entry is a block or page mapping, and the old and new values

> >>  *   only differ in the PXN/RDONLY/WRITE bits.

> >>  *

> >>  * NOTE: 'safe' does not imply that no TLB maintenance is required, it only

> >>  *       means that no TLB conflicts should occur as a result of the update.

> >>  */

> >> #define __set_pgattr_is_safe(type, old, new, blocktype) \

> >>       (type ## _val(old) == type ## _val(new) || \

> >>        ((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \

> >>        (((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \

> >>         (((type ## _val(old) ^ type ## _val(new)) & \

> >>        ~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

> >>

> >> static inline bool set_live_pte_is_safe(pte_t old, pte_t new)

> >> {

> >>       return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE);

> >> }

> >>

> >> static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new)

> >> {

> >>       return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT);

> >> }

> >>

> >> static inline bool set_live_pud_is_safe(pud_t old, pud_t new)

> >> {

> >>       return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT);

> >> }

> >

> > The set_ prefix is slightly confusing as it suggests (to me) having a

> > side effect. Maybe pgattr_set_is_safe()?

> >

> > But it looks like we make it more complicated needed by using pte_t

> > instead of pteval_t as argument. How about just using the pteval_t as

> > argument (and it's fine to call it with pmdval_t, pudval_t as well):

> >

> > #define pgattr_set_is_safe(oldval, newval) \

> >         ...

> 

> Well, the only problem there is that the permission bit check should

> only apply to level 3 page mappings (bit[1] == 1) and level 1/2 block

> mappings (bit[1] == 0), so we would still need two versions


I was suggesting that you only replace the "... & ~modifiable_attr_mask"
check in your patch to avoid writing it three times. The macro that you
proposed above does more but it is also more unreadable.

-- 
Catalin

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Ard Biesheuvel Oct. 13, 2016, 4:58 p.m. UTC | #6
On 13 October 2016 at 17:51, Catalin Marinas <catalin.marinas@arm.com> wrote:
> On Thu, Oct 13, 2016 at 03:48:04PM +0100, Ard Biesheuvel wrote:

>> On 13 October 2016 at 15:44, Catalin Marinas <catalin.marinas@arm.com> wrote:

>> > On Thu, Oct 13, 2016 at 01:25:53PM +0100, Ard Biesheuvel wrote:

>> >

>> > (fixing up alignment to make it readable)

>> >

>>

>> I apologise on Gmail's behalf

>>

>> >> """

>> >> /*

>> >>  * Returns whether updating a live page table entry is safe:

>> >>  * - if the old and new values are identical,

>> >>  * - if an invalid mapping is turned into a valid one (or vice versa),

>> >>  * - if the entry is a block or page mapping, and the old and new values

>> >>  *   only differ in the PXN/RDONLY/WRITE bits.

>> >>  *

>> >>  * NOTE: 'safe' does not imply that no TLB maintenance is required, it only

>> >>  *       means that no TLB conflicts should occur as a result of the update.

>> >>  */

>> >> #define __set_pgattr_is_safe(type, old, new, blocktype) \

>> >>       (type ## _val(old) == type ## _val(new) || \

>> >>        ((type ## _val(old) ^ type ## _val(new)) & PTE_VALID) != 0 || \

>> >>        (((type ## _val(old) & PTE_TYPE_MASK) == blocktype) && \

>> >>         (((type ## _val(old) ^ type ## _val(new)) & \

>> >>        ~(PTE_PXN | PTE_RDONLY | PTE_WRITE)) == 0)))

>> >>

>> >> static inline bool set_live_pte_is_safe(pte_t old, pte_t new)

>> >> {

>> >>       return __set_pgattr_is_safe(pte, old, new, PTE_TYPE_PAGE);

>> >> }

>> >>

>> >> static inline bool set_live_pmd_is_safe(pmd_t old, pmd_t new)

>> >> {

>> >>       return __set_pgattr_is_safe(pmd, old, new, PMD_TYPE_SECT);

>> >> }

>> >>

>> >> static inline bool set_live_pud_is_safe(pud_t old, pud_t new)

>> >> {

>> >>       return __set_pgattr_is_safe(pud, old, new, PUD_TYPE_SECT);

>> >> }

>> >

>> > The set_ prefix is slightly confusing as it suggests (to me) having a

>> > side effect. Maybe pgattr_set_is_safe()?

>> >

>> > But it looks like we make it more complicated needed by using pte_t

>> > instead of pteval_t as argument. How about just using the pteval_t as

>> > argument (and it's fine to call it with pmdval_t, pudval_t as well):

>> >

>> > #define pgattr_set_is_safe(oldval, newval) \

>> >         ...

>>

>> Well, the only problem there is that the permission bit check should

>> only apply to level 3 page mappings (bit[1] == 1) and level 1/2 block

>> mappings (bit[1] == 0), so we would still need two versions

>

> I was suggesting that you only replace the "... & ~modifiable_attr_mask"

> check in your patch to avoid writing it three times. The macro that you

> proposed above does more but it is also more unreadable.

>


Ah ok, fair enough

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
diff mbox

Patch

diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 05615a3fdc6f..e1c34e5a1d7d 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -28,8 +28,6 @@ 
 #include <linux/memblock.h>
 #include <linux/fs.h>
 #include <linux/io.h>
-#include <linux/slab.h>
-#include <linux/stop_machine.h>
 
 #include <asm/barrier.h>
 #include <asm/cputype.h>
@@ -95,6 +93,12 @@  static phys_addr_t __init early_pgtable_alloc(void)
 	return phys;
 }
 
+/*
+ * The following mapping attributes may be updated in live
+ * kernel mappings without the need for break-before-make.
+ */
+static const pteval_t modifiable_attr_mask = PTE_PXN | PTE_RDONLY | PTE_WRITE;
+
 static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 				  unsigned long end, unsigned long pfn,
 				  pgprot_t prot,
@@ -115,8 +119,18 @@  static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
 
 	pte = pte_set_fixmap_offset(pmd, addr);
 	do {
+		pte_t old_pte = *pte;
+
 		set_pte(pte, pfn_pte(pfn, prot));
 		pfn++;
+
+		/*
+		 * After the PTE entry has been populated once, we
+		 * only allow updates to the permission attributes.
+		 */
+		BUG_ON(pte_val(old_pte) != 0 &&
+		       ((pte_val(old_pte) ^ pte_val(*pte)) &
+			~modifiable_attr_mask) != 0);
 	} while (pte++, addr += PAGE_SIZE, addr != end);
 
 	pte_clear_fixmap();
@@ -146,27 +160,28 @@  static void alloc_init_pmd(pud_t *pud, unsigned long addr, unsigned long end,
 
 	pmd = pmd_set_fixmap_offset(pud, addr);
 	do {
+		pmd_t old_pmd = *pmd;
+
 		next = pmd_addr_end(addr, end);
+
 		/* try section mapping first */
 		if (((addr | next | phys) & ~SECTION_MASK) == 0 &&
 		      allow_block_mappings) {
-			pmd_t old_pmd =*pmd;
 			pmd_set_huge(pmd, phys, prot);
+
 			/*
-			 * Check for previous table entries created during
-			 * boot (__create_page_tables) and flush them.
+			 * After the PMD entry has been populated once, we
+			 * only allow updates to the permission attributes.
 			 */
-			if (!pmd_none(old_pmd)) {
-				flush_tlb_all();
-				if (pmd_table(old_pmd)) {
-					phys_addr_t table = pmd_page_paddr(old_pmd);
-					if (!WARN_ON_ONCE(slab_is_available()))
-						memblock_free(table, PAGE_SIZE);
-				}
-			}
+			BUG_ON(pmd_val(old_pmd) != 0 &&
+			       ((pmd_val(old_pmd) ^ pmd_val(*pmd)) &
+				~modifiable_attr_mask) != 0);
 		} else {
 			alloc_init_pte(pmd, addr, next, __phys_to_pfn(phys),
 				       prot, pgtable_alloc);
+
+			BUG_ON(pmd_val(old_pmd) != 0 &&
+			       pmd_val(old_pmd) != pmd_val(*pmd));
 		}
 		phys += next - addr;
 	} while (pmd++, addr = next, addr != end);
@@ -204,33 +219,29 @@  static void alloc_init_pud(pgd_t *pgd, unsigned long addr, unsigned long end,
 
 	pud = pud_set_fixmap_offset(pgd, addr);
 	do {
+		pud_t old_pud = *pud;
+
 		next = pud_addr_end(addr, end);
 
 		/*
 		 * For 4K granule only, attempt to put down a 1GB block
 		 */
 		if (use_1G_block(addr, next, phys) && allow_block_mappings) {
-			pud_t old_pud = *pud;
 			pud_set_huge(pud, phys, prot);
 
 			/*
-			 * If we have an old value for a pud, it will
-			 * be pointing to a pmd table that we no longer
-			 * need (from swapper_pg_dir).
-			 *
-			 * Look up the old pmd table and free it.
+			 * After the PUD entry has been populated once, we
+			 * only allow updates to the permission attributes.
 			 */
-			if (!pud_none(old_pud)) {
-				flush_tlb_all();
-				if (pud_table(old_pud)) {
-					phys_addr_t table = pud_page_paddr(old_pud);
-					if (!WARN_ON_ONCE(slab_is_available()))
-						memblock_free(table, PAGE_SIZE);
-				}
-			}
+			BUG_ON(pud_val(old_pud) != 0 &&
+			       ((pud_val(old_pud) ^ pud_val(*pud)) &
+				~modifiable_attr_mask) != 0);
 		} else {
 			alloc_init_pmd(pud, addr, next, phys, prot,
 				       pgtable_alloc, allow_block_mappings);
+
+			BUG_ON(pud_val(old_pud) != 0 &&
+			       pud_val(old_pud) != pud_val(*pud));
 		}
 		phys += next - addr;
 	} while (pud++, addr = next, addr != end);
@@ -396,6 +407,9 @@  void mark_rodata_ro(void)
 	section_size = (unsigned long)__init_begin - (unsigned long)__start_rodata;
 	create_mapping_late(__pa(__start_rodata), (unsigned long)__start_rodata,
 			    section_size, PAGE_KERNEL_RO);
+
+	/* flush the TLBs after updating live kernel mappings */
+	flush_tlb_all();
 }
 
 static void __init map_kernel_segment(pgd_t *pgd, void *va_start, void *va_end,