aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCatalin Marinas <catalin.marinas@arm.com>2017-07-25 09:53:03 -0400
committerWill Deacon <will.deacon@arm.com>2017-08-04 08:26:11 -0400
commit6d332747fa5f0a6843b56b5b129168ba909336d1 (patch)
tree5d4029213884457d165c2bfd476ece7764490bdd
parentc6f97add0f2ac83b98b06dbdda58fa47638ae7b0 (diff)
arm64: Fix potential race with hardware DBM in ptep_set_access_flags()
In a system with DBM (dirty bit management) capable agents there is a possible race between a CPU executing ptep_set_access_flags() (maybe non-DBM capable) and a hardware update of the dirty state (clearing of PTE_RDONLY). The scenario: a) the pte is writable (PTE_WRITE set), clean (PTE_RDONLY set) and old (PTE_AF clear) b) ptep_set_access_flags() is called as a result of a read access and it needs to set the pte to writable, clean and young (PTE_AF set) c) a DBM-capable agent, as a result of a different write access, is marking the entry as young (setting PTE_AF) and dirty (clearing PTE_RDONLY) The current ptep_set_access_flags() implementation would set the PTE_RDONLY bit in the resulting value overriding the DBM update and losing the dirty state. This patch fixes such race by setting PTE_RDONLY to the most permissive (lowest value) of the current entry and the new one. Fixes: 66dbd6e61a52 ("arm64: Implement ptep_set_access_flags() for hardware AF/DBM") Cc: Will Deacon <will.deacon@arm.com> Acked-by: Mark Rutland <mark.rutland@arm.com> Acked-by: Steve Capper <steve.capper@arm.com> Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
-rw-r--r--arch/arm64/mm/fault.c15
1 files changed, 8 insertions, 7 deletions
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index c7861c9864e6..2509e4fe6992 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -163,26 +163,27 @@ int ptep_set_access_flags(struct vm_area_struct *vma,
163 /* only preserve the access flags and write permission */ 163 /* only preserve the access flags and write permission */
164 pte_val(entry) &= PTE_AF | PTE_WRITE | PTE_DIRTY; 164 pte_val(entry) &= PTE_AF | PTE_WRITE | PTE_DIRTY;
165 165
166 /* 166 /* set PTE_RDONLY if actual read-only or clean PTE */
167 * PTE_RDONLY is cleared by default in the asm below, so set it in
168 * back if necessary (read-only or clean PTE).
169 */
170 if (!pte_write(entry) || !pte_sw_dirty(entry)) 167 if (!pte_write(entry) || !pte_sw_dirty(entry))
171 pte_val(entry) |= PTE_RDONLY; 168 pte_val(entry) |= PTE_RDONLY;
172 169
173 /* 170 /*
174 * Setting the flags must be done atomically to avoid racing with the 171 * Setting the flags must be done atomically to avoid racing with the
175 * hardware update of the access/dirty state. 172 * hardware update of the access/dirty state. The PTE_RDONLY bit must
173 * be set to the most permissive (lowest value) of *ptep and entry
174 * (calculated as: a & b == ~(~a | ~b)).
176 */ 175 */
176 pte_val(entry) ^= PTE_RDONLY;
177 asm volatile("// ptep_set_access_flags\n" 177 asm volatile("// ptep_set_access_flags\n"
178 " prfm pstl1strm, %2\n" 178 " prfm pstl1strm, %2\n"
179 "1: ldxr %0, %2\n" 179 "1: ldxr %0, %2\n"
180 " and %0, %0, %3 // clear PTE_RDONLY\n" 180 " eor %0, %0, %3 // negate PTE_RDONLY in *ptep\n"
181 " orr %0, %0, %4 // set flags\n" 181 " orr %0, %0, %4 // set flags\n"
182 " eor %0, %0, %3 // negate final PTE_RDONLY\n"
182 " stxr %w1, %0, %2\n" 183 " stxr %w1, %0, %2\n"
183 " cbnz %w1, 1b\n" 184 " cbnz %w1, 1b\n"
184 : "=&r" (old_pteval), "=&r" (tmp), "+Q" (pte_val(*ptep)) 185 : "=&r" (old_pteval), "=&r" (tmp), "+Q" (pte_val(*ptep))
185 : "L" (~PTE_RDONLY), "r" (pte_val(entry))); 186 : "L" (PTE_RDONLY), "r" (pte_val(entry)));
186 187
187 flush_tlb_fix_spurious_fault(vma, address); 188 flush_tlb_fix_spurious_fault(vma, address);
188 return 1; 189 return 1;