mirror of
git://git.yoctoproject.org/linux-yocto.git
synced 2026-01-27 12:47:24 +01:00
arm: pgtable: fix NULL pointer dereference issue
When update_mmu_cache_range() is called by update_mmu_cache(), the vmf
parameter is NULL, which will cause a NULL pointer dereference issue in
adjust_pte():
Unable to handle kernel NULL pointer dereference at virtual address 00000030 when read
Hardware name: Atmel AT91SAM9
PC is at update_mmu_cache_range+0x1e0/0x278
LR is at pte_offset_map_rw_nolock+0x18/0x2c
Call trace:
update_mmu_cache_range from remove_migration_pte+0x29c/0x2ec
remove_migration_pte from rmap_walk_file+0xcc/0x130
rmap_walk_file from remove_migration_ptes+0x90/0xa4
remove_migration_ptes from migrate_pages_batch+0x6d4/0x858
migrate_pages_batch from migrate_pages+0x188/0x488
migrate_pages from compact_zone+0x56c/0x954
compact_zone from compact_node+0x90/0xf0
compact_node from kcompactd+0x1d4/0x204
kcompactd from kthread+0x120/0x12c
kthread from ret_from_fork+0x14/0x38
Exception stack(0xc0d8bfb0 to 0xc0d8bff8)
To fix it, do not rely on whether 'ptl' is equal to decide whether to hold
the pte lock, but decide it by whether CONFIG_SPLIT_PTE_PTLOCKS is
enabled. In addition, if two vmas map to the same PTE page, there is no
need to hold the pte lock again, otherwise a deadlock will occur. Just
add the need_lock parameter to let adjust_pte() know this information.
Link: https://lkml.kernel.org/r/20250217024924.57996-1-zhengqi.arch@bytedance.com
Fixes: fc9c45b71f ("arm: adjust_pte() use pte_offset_map_rw_nolock()")
Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
Reported-by: Ezra Buehler <ezra.buehler@husqvarnagroup.com>
Closes: https://lore.kernel.org/lkml/CAM1KZSmZ2T_riHvay+7cKEFxoPgeVpHkVFTzVVEQ1BO0cLkHEQ@mail.gmail.com/
Acked-by: David Hildenbrand <david@redhat.com>
Tested-by: Ezra Buehler <ezra.buehler@husqvarnagroup.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Muchun Song <muchun.song@linux.dev>
Cc: Qi Zheng <zhengqi.arch@bytedance.com>
Cc: Russel King <linux@armlinux.org.uk>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
parent
7277dd0a0b
commit
a564ccfe30
|
|
@ -62,7 +62,7 @@ static int do_adjust_pte(struct vm_area_struct *vma, unsigned long address,
|
|||
}
|
||||
|
||||
static int adjust_pte(struct vm_area_struct *vma, unsigned long address,
|
||||
unsigned long pfn, struct vm_fault *vmf)
|
||||
unsigned long pfn, bool need_lock)
|
||||
{
|
||||
spinlock_t *ptl;
|
||||
pgd_t *pgd;
|
||||
|
|
@ -99,12 +99,11 @@ again:
|
|||
if (!pte)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If we are using split PTE locks, then we need to take the page
|
||||
* lock here. Otherwise we are using shared mm->page_table_lock
|
||||
* which is already locked, thus cannot take it.
|
||||
*/
|
||||
if (ptl != vmf->ptl) {
|
||||
if (need_lock) {
|
||||
/*
|
||||
* Use nested version here to indicate that we are already
|
||||
* holding one similar spinlock.
|
||||
*/
|
||||
spin_lock_nested(ptl, SINGLE_DEPTH_NESTING);
|
||||
if (unlikely(!pmd_same(pmdval, pmdp_get_lockless(pmd)))) {
|
||||
pte_unmap_unlock(pte, ptl);
|
||||
|
|
@ -114,7 +113,7 @@ again:
|
|||
|
||||
ret = do_adjust_pte(vma, address, pfn, pte);
|
||||
|
||||
if (ptl != vmf->ptl)
|
||||
if (need_lock)
|
||||
spin_unlock(ptl);
|
||||
pte_unmap(pte);
|
||||
|
||||
|
|
@ -123,9 +122,10 @@ again:
|
|||
|
||||
static void
|
||||
make_coherent(struct address_space *mapping, struct vm_area_struct *vma,
|
||||
unsigned long addr, pte_t *ptep, unsigned long pfn,
|
||||
struct vm_fault *vmf)
|
||||
unsigned long addr, pte_t *ptep, unsigned long pfn)
|
||||
{
|
||||
const unsigned long pmd_start_addr = ALIGN_DOWN(addr, PMD_SIZE);
|
||||
const unsigned long pmd_end_addr = pmd_start_addr + PMD_SIZE;
|
||||
struct mm_struct *mm = vma->vm_mm;
|
||||
struct vm_area_struct *mpnt;
|
||||
unsigned long offset;
|
||||
|
|
@ -141,6 +141,14 @@ make_coherent(struct address_space *mapping, struct vm_area_struct *vma,
|
|||
*/
|
||||
flush_dcache_mmap_lock(mapping);
|
||||
vma_interval_tree_foreach(mpnt, &mapping->i_mmap, pgoff, pgoff) {
|
||||
/*
|
||||
* If we are using split PTE locks, then we need to take the pte
|
||||
* lock. Otherwise we are using shared mm->page_table_lock which
|
||||
* is already locked, thus cannot take it.
|
||||
*/
|
||||
bool need_lock = IS_ENABLED(CONFIG_SPLIT_PTE_PTLOCKS);
|
||||
unsigned long mpnt_addr;
|
||||
|
||||
/*
|
||||
* If this VMA is not in our MM, we can ignore it.
|
||||
* Note that we intentionally mask out the VMA
|
||||
|
|
@ -151,7 +159,12 @@ make_coherent(struct address_space *mapping, struct vm_area_struct *vma,
|
|||
if (!(mpnt->vm_flags & VM_MAYSHARE))
|
||||
continue;
|
||||
offset = (pgoff - mpnt->vm_pgoff) << PAGE_SHIFT;
|
||||
aliases += adjust_pte(mpnt, mpnt->vm_start + offset, pfn, vmf);
|
||||
mpnt_addr = mpnt->vm_start + offset;
|
||||
|
||||
/* Avoid deadlocks by not grabbing the same PTE lock again. */
|
||||
if (mpnt_addr >= pmd_start_addr && mpnt_addr < pmd_end_addr)
|
||||
need_lock = false;
|
||||
aliases += adjust_pte(mpnt, mpnt_addr, pfn, need_lock);
|
||||
}
|
||||
flush_dcache_mmap_unlock(mapping);
|
||||
if (aliases)
|
||||
|
|
@ -194,7 +207,7 @@ void update_mmu_cache_range(struct vm_fault *vmf, struct vm_area_struct *vma,
|
|||
__flush_dcache_folio(mapping, folio);
|
||||
if (mapping) {
|
||||
if (cache_is_vivt())
|
||||
make_coherent(mapping, vma, addr, ptep, pfn, vmf);
|
||||
make_coherent(mapping, vma, addr, ptep, pfn);
|
||||
else if (vma->vm_flags & VM_EXEC)
|
||||
__flush_icache_all();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user