Skip to content

Commit a564ccf

Browse files
Qi Zhengakpm00
authored andcommitted
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/[email protected] Fixes: fc9c45b ("arm: adjust_pte() use pte_offset_map_rw_nolock()") Signed-off-by: Qi Zheng <[email protected]> Reported-by: Ezra Buehler <[email protected]> Closes: https://lore.kernel.org/lkml/CAM1KZSmZ2T_riHvay+7cKEFxoPgeVpHkVFTzVVEQ1BO0cLkHEQ@mail.gmail.com/ Acked-by: David Hildenbrand <[email protected]> Tested-by: Ezra Buehler <[email protected]> Cc: Hugh Dickins <[email protected]> Cc: Muchun Song <[email protected]> Cc: Qi Zheng <[email protected]> Cc: Russel King <[email protected]> Cc: Ryan Roberts <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]>
1 parent 7277dd0 commit a564ccf

File tree

1 file changed

+25
-12
lines changed

1 file changed

+25
-12
lines changed

arch/arm/mm/fault-armv.c

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ static int do_adjust_pte(struct vm_area_struct *vma, unsigned long address,
6262
}
6363

6464
static int adjust_pte(struct vm_area_struct *vma, unsigned long address,
65-
unsigned long pfn, struct vm_fault *vmf)
65+
unsigned long pfn, bool need_lock)
6666
{
6767
spinlock_t *ptl;
6868
pgd_t *pgd;
@@ -99,12 +99,11 @@ static int adjust_pte(struct vm_area_struct *vma, unsigned long address,
9999
if (!pte)
100100
return 0;
101101

102-
/*
103-
* If we are using split PTE locks, then we need to take the page
104-
* lock here. Otherwise we are using shared mm->page_table_lock
105-
* which is already locked, thus cannot take it.
106-
*/
107-
if (ptl != vmf->ptl) {
102+
if (need_lock) {
103+
/*
104+
* Use nested version here to indicate that we are already
105+
* holding one similar spinlock.
106+
*/
108107
spin_lock_nested(ptl, SINGLE_DEPTH_NESTING);
109108
if (unlikely(!pmd_same(pmdval, pmdp_get_lockless(pmd)))) {
110109
pte_unmap_unlock(pte, ptl);
@@ -114,7 +113,7 @@ static int adjust_pte(struct vm_area_struct *vma, unsigned long address,
114113

115114
ret = do_adjust_pte(vma, address, pfn, pte);
116115

117-
if (ptl != vmf->ptl)
116+
if (need_lock)
118117
spin_unlock(ptl);
119118
pte_unmap(pte);
120119

@@ -123,9 +122,10 @@ static int adjust_pte(struct vm_area_struct *vma, unsigned long address,
123122

124123
static void
125124
make_coherent(struct address_space *mapping, struct vm_area_struct *vma,
126-
unsigned long addr, pte_t *ptep, unsigned long pfn,
127-
struct vm_fault *vmf)
125+
unsigned long addr, pte_t *ptep, unsigned long pfn)
128126
{
127+
const unsigned long pmd_start_addr = ALIGN_DOWN(addr, PMD_SIZE);
128+
const unsigned long pmd_end_addr = pmd_start_addr + PMD_SIZE;
129129
struct mm_struct *mm = vma->vm_mm;
130130
struct vm_area_struct *mpnt;
131131
unsigned long offset;
@@ -141,6 +141,14 @@ make_coherent(struct address_space *mapping, struct vm_area_struct *vma,
141141
*/
142142
flush_dcache_mmap_lock(mapping);
143143
vma_interval_tree_foreach(mpnt, &mapping->i_mmap, pgoff, pgoff) {
144+
/*
145+
* If we are using split PTE locks, then we need to take the pte
146+
* lock. Otherwise we are using shared mm->page_table_lock which
147+
* is already locked, thus cannot take it.
148+
*/
149+
bool need_lock = IS_ENABLED(CONFIG_SPLIT_PTE_PTLOCKS);
150+
unsigned long mpnt_addr;
151+
144152
/*
145153
* If this VMA is not in our MM, we can ignore it.
146154
* Note that we intentionally mask out the VMA
@@ -151,7 +159,12 @@ make_coherent(struct address_space *mapping, struct vm_area_struct *vma,
151159
if (!(mpnt->vm_flags & VM_MAYSHARE))
152160
continue;
153161
offset = (pgoff - mpnt->vm_pgoff) << PAGE_SHIFT;
154-
aliases += adjust_pte(mpnt, mpnt->vm_start + offset, pfn, vmf);
162+
mpnt_addr = mpnt->vm_start + offset;
163+
164+
/* Avoid deadlocks by not grabbing the same PTE lock again. */
165+
if (mpnt_addr >= pmd_start_addr && mpnt_addr < pmd_end_addr)
166+
need_lock = false;
167+
aliases += adjust_pte(mpnt, mpnt_addr, pfn, need_lock);
155168
}
156169
flush_dcache_mmap_unlock(mapping);
157170
if (aliases)
@@ -194,7 +207,7 @@ void update_mmu_cache_range(struct vm_fault *vmf, struct vm_area_struct *vma,
194207
__flush_dcache_folio(mapping, folio);
195208
if (mapping) {
196209
if (cache_is_vivt())
197-
make_coherent(mapping, vma, addr, ptep, pfn, vmf);
210+
make_coherent(mapping, vma, addr, ptep, pfn);
198211
else if (vma->vm_flags & VM_EXEC)
199212
__flush_icache_all();
200213
}

0 commit comments

Comments
 (0)