@@ -619,7 +619,8 @@ static inline pgprot_t static_protections(pgprot_t prot, unsigned long start,
619619 * Validate strict W^X semantics.
620620 */
621621static inline pgprot_t verify_rwx (pgprot_t old , pgprot_t new , unsigned long start ,
622- unsigned long pfn , unsigned long npg )
622+ unsigned long pfn , unsigned long npg ,
623+ bool nx , bool rw )
623624{
624625 unsigned long end ;
625626
@@ -641,6 +642,10 @@ static inline pgprot_t verify_rwx(pgprot_t old, pgprot_t new, unsigned long star
641642 if ((pgprot_val (new ) & (_PAGE_RW | _PAGE_NX )) != _PAGE_RW )
642643 return new ;
643644
645+ /* Non-leaf translation entries can disable writing or execution. */
646+ if (!rw || nx )
647+ return new ;
648+
644649 end = start + npg * PAGE_SIZE - 1 ;
645650 WARN_ONCE (1 , "CPA detected W^X violation: %016llx -> %016llx range: 0x%016lx - 0x%016lx PFN %lx\n" ,
646651 (unsigned long long )pgprot_val (old ),
@@ -657,20 +662,26 @@ static inline pgprot_t verify_rwx(pgprot_t old, pgprot_t new, unsigned long star
657662
658663/*
659664 * Lookup the page table entry for a virtual address in a specific pgd.
660- * Return a pointer to the entry and the level of the mapping.
665+ * Return a pointer to the entry, the level of the mapping, and the effective
666+ * NX and RW bits of all page table levels.
661667 */
662- pte_t * lookup_address_in_pgd (pgd_t * pgd , unsigned long address ,
663- unsigned int * level )
668+ pte_t * lookup_address_in_pgd_attr (pgd_t * pgd , unsigned long address ,
669+ unsigned int * level , bool * nx , bool * rw )
664670{
665671 p4d_t * p4d ;
666672 pud_t * pud ;
667673 pmd_t * pmd ;
668674
669675 * level = PG_LEVEL_NONE ;
676+ * nx = false;
677+ * rw = true;
670678
671679 if (pgd_none (* pgd ))
672680 return NULL ;
673681
682+ * nx |= pgd_flags (* pgd ) & _PAGE_NX ;
683+ * rw &= pgd_flags (* pgd ) & _PAGE_RW ;
684+
674685 p4d = p4d_offset (pgd , address );
675686 if (p4d_none (* p4d ))
676687 return NULL ;
@@ -679,6 +690,9 @@ pte_t *lookup_address_in_pgd(pgd_t *pgd, unsigned long address,
679690 if (p4d_leaf (* p4d ) || !p4d_present (* p4d ))
680691 return (pte_t * )p4d ;
681692
693+ * nx |= p4d_flags (* p4d ) & _PAGE_NX ;
694+ * rw &= p4d_flags (* p4d ) & _PAGE_RW ;
695+
682696 pud = pud_offset (p4d , address );
683697 if (pud_none (* pud ))
684698 return NULL ;
@@ -687,6 +701,9 @@ pte_t *lookup_address_in_pgd(pgd_t *pgd, unsigned long address,
687701 if (pud_leaf (* pud ) || !pud_present (* pud ))
688702 return (pte_t * )pud ;
689703
704+ * nx |= pud_flags (* pud ) & _PAGE_NX ;
705+ * rw &= pud_flags (* pud ) & _PAGE_RW ;
706+
690707 pmd = pmd_offset (pud , address );
691708 if (pmd_none (* pmd ))
692709 return NULL ;
@@ -695,11 +712,26 @@ pte_t *lookup_address_in_pgd(pgd_t *pgd, unsigned long address,
695712 if (pmd_leaf (* pmd ) || !pmd_present (* pmd ))
696713 return (pte_t * )pmd ;
697714
715+ * nx |= pmd_flags (* pmd ) & _PAGE_NX ;
716+ * rw &= pmd_flags (* pmd ) & _PAGE_RW ;
717+
698718 * level = PG_LEVEL_4K ;
699719
700720 return pte_offset_kernel (pmd , address );
701721}
702722
723+ /*
724+ * Lookup the page table entry for a virtual address in a specific pgd.
725+ * Return a pointer to the entry and the level of the mapping.
726+ */
727+ pte_t * lookup_address_in_pgd (pgd_t * pgd , unsigned long address ,
728+ unsigned int * level )
729+ {
730+ bool nx , rw ;
731+
732+ return lookup_address_in_pgd_attr (pgd , address , level , & nx , & rw );
733+ }
734+
703735/*
704736 * Lookup the page table entry for a virtual address. Return a pointer
705737 * to the entry and the level of the mapping.
@@ -715,13 +747,16 @@ pte_t *lookup_address(unsigned long address, unsigned int *level)
715747EXPORT_SYMBOL_GPL (lookup_address );
716748
717749static pte_t * _lookup_address_cpa (struct cpa_data * cpa , unsigned long address ,
718- unsigned int * level )
750+ unsigned int * level , bool * nx , bool * rw )
719751{
720- if (cpa -> pgd )
721- return lookup_address_in_pgd (cpa -> pgd + pgd_index (address ),
722- address , level );
752+ pgd_t * pgd ;
753+
754+ if (!cpa -> pgd )
755+ pgd = pgd_offset_k (address );
756+ else
757+ pgd = cpa -> pgd + pgd_index (address );
723758
724- return lookup_address ( address , level );
759+ return lookup_address_in_pgd_attr ( pgd , address , level , nx , rw );
725760}
726761
727762/*
@@ -849,12 +884,13 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address,
849884 pgprot_t old_prot , new_prot , req_prot , chk_prot ;
850885 pte_t new_pte , * tmp ;
851886 enum pg_level level ;
887+ bool nx , rw ;
852888
853889 /*
854890 * Check for races, another CPU might have split this page
855891 * up already:
856892 */
857- tmp = _lookup_address_cpa (cpa , address , & level );
893+ tmp = _lookup_address_cpa (cpa , address , & level , & nx , & rw );
858894 if (tmp != kpte )
859895 return 1 ;
860896
@@ -965,7 +1001,8 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address,
9651001 new_prot = static_protections (req_prot , lpaddr , old_pfn , numpages ,
9661002 psize , CPA_DETECT );
9671003
968- new_prot = verify_rwx (old_prot , new_prot , lpaddr , old_pfn , numpages );
1004+ new_prot = verify_rwx (old_prot , new_prot , lpaddr , old_pfn , numpages ,
1005+ nx , rw );
9691006
9701007 /*
9711008 * If there is a conflict, split the large page.
@@ -1046,14 +1083,15 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,
10461083 pte_t * pbase = (pte_t * )page_address (base );
10471084 unsigned int i , level ;
10481085 pgprot_t ref_prot ;
1086+ bool nx , rw ;
10491087 pte_t * tmp ;
10501088
10511089 spin_lock (& pgd_lock );
10521090 /*
10531091 * Check for races, another CPU might have split this page
10541092 * up for us already:
10551093 */
1056- tmp = _lookup_address_cpa (cpa , address , & level );
1094+ tmp = _lookup_address_cpa (cpa , address , & level , & nx , & rw );
10571095 if (tmp != kpte ) {
10581096 spin_unlock (& pgd_lock );
10591097 return 1 ;
@@ -1594,10 +1632,11 @@ static int __change_page_attr(struct cpa_data *cpa, int primary)
15941632 int do_split , err ;
15951633 unsigned int level ;
15961634 pte_t * kpte , old_pte ;
1635+ bool nx , rw ;
15971636
15981637 address = __cpa_addr (cpa , cpa -> curpage );
15991638repeat :
1600- kpte = _lookup_address_cpa (cpa , address , & level );
1639+ kpte = _lookup_address_cpa (cpa , address , & level , & nx , & rw );
16011640 if (!kpte )
16021641 return __cpa_process_fault (cpa , address , primary );
16031642
@@ -1619,7 +1658,8 @@ static int __change_page_attr(struct cpa_data *cpa, int primary)
16191658 new_prot = static_protections (new_prot , address , pfn , 1 , 0 ,
16201659 CPA_PROTECT );
16211660
1622- new_prot = verify_rwx (old_prot , new_prot , address , pfn , 1 );
1661+ new_prot = verify_rwx (old_prot , new_prot , address , pfn , 1 ,
1662+ nx , rw );
16231663
16241664 new_prot = pgprot_clear_protnone_bits (new_prot );
16251665
0 commit comments