mm: riscv: fix an unsafe pte read in huge_pte_alloc()
authorJohn Hubbard <jhubbard@nvidia.com>
Mon, 3 Jul 2023 19:00:44 +0000 (12:00 -0700)
committerPalmer Dabbelt <palmer@rivosinc.com>
Wed, 5 Jul 2023 14:24:17 +0000 (07:24 -0700)
The WARN_ON_ONCE() statement in riscv's huge_pte_alloc() is susceptible
to false positives, because the pte is read twice at the C language
level, locklessly, within the same conditional statement. Depending on
compiler behavior, this can lead to generated machine code that actually
reads the pte just once, or twice. Reading twice will expose the code to
changing pte values and cause incorrect behavior.

In [1], similar code actually caused a kernel crash on 64-bit x86, when
using clang to build the kernel, but only after the conversion from *pte
reads, to ptep_get(pte). The latter uses READ_ONCE(), which forced a
double read of *pte.

Rather than waiting for the upcoming ptep_get() conversion, just convert
this part of the code now, but in a way that avoids the above problem:
take a single snapshot of the pte before using it in the WARN
conditional.

As expected, this preparatory step does not actually change the
generated code ("make mm/hugetlbpage.s"), on riscv64, when using a gcc
12.2 cross compiler.

[1] https://lore.kernel.org/20230630013203.1955064-1-jhubbard@nvidia.com

Suggested-by: James Houghton <jthoughton@google.com>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Reviewed-by: Andrew Jones <ajones@ventanamicro.com>
Reviewed-by: Ryan Roberts <ryan.roberts@arm.com>
Link: https://lore.kernel.org/r/20230703190044.311730-1-jhubbard@nvidia.com
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
arch/riscv/mm/hugetlbpage.c

index 542883b3b49b37d52a802bf80d80bd879035dad5..96225a8533ad8002e39518f87c6bab9d0e8ffb5a 100644 (file)
@@ -73,7 +73,11 @@ pte_t *huge_pte_alloc(struct mm_struct *mm,
        }
 
 out:
-       WARN_ON_ONCE(pte && pte_present(*pte) && !pte_huge(*pte));
+       if (pte) {
+               pte_t pteval = ptep_get_lockless(pte);
+
+               WARN_ON_ONCE(pte_present(pteval) && !pte_huge(pteval));
+       }
        return pte;
 }