Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/mm/pat: clear VM_PAT if copy_p4d_range failed

Syzbot reports a warning in untrack_pfn(). Digging into the root we found
that this is due to memory allocation failure in pmd_alloc_one. And this
failure is produced due to failslab.

In copy_page_range(), memory alloaction for pmd failed. During the error
handling process in copy_page_range(), mmput() is called to remove all
vmas. While untrack_pfn this empty pfn, warning happens.

Here's a simplified flow:

dup_mm
dup_mmap
copy_page_range
copy_p4d_range
copy_pud_range
copy_pmd_range
pmd_alloc
__pmd_alloc
pmd_alloc_one
page = alloc_pages(gfp, 0);
if (!page)
return NULL;
mmput
exit_mmap
unmap_vmas
unmap_single_vma
untrack_pfn
follow_phys
WARN_ON_ONCE(1);

Since this vma is not generate successfully, we can clear flag VM_PAT. In
this case, untrack_pfn() will not be called while cleaning this vma.

Function untrack_pfn_moved() has also been renamed to fit the new logic.

Link: https://lkml.kernel.org/r/20230217025615.1595558-1-mawupeng1@huawei.com
Signed-off-by: Ma Wupeng <mawupeng1@huawei.com>
Reported-by: <syzbot+5f488e922d047d8f00cc@syzkaller.appspotmail.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Borislav Petkov <bp@suse.de>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Suresh Siddha <suresh.b.siddha@intel.com>
Cc: Toshi Kani <toshi.kani@hp.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Ma Wupeng and committed by
Andrew Morton
d155df53 a1b92a3f

+14 -8
+8 -4
arch/x86/mm/pat/memtype.c
··· 1073 1073 } 1074 1074 1075 1075 /* 1076 - * untrack_pfn_moved is called, while mremapping a pfnmap for a new region, 1077 - * with the old vma after its pfnmap page table has been removed. The new 1078 - * vma has a new pfnmap to the same pfn & cache type with VM_PAT set. 1076 + * untrack_pfn_clear is called if the following situation fits: 1077 + * 1078 + * 1) while mremapping a pfnmap for a new region, with the old vma after 1079 + * its pfnmap page table has been removed. The new vma has a new pfnmap 1080 + * to the same pfn & cache type with VM_PAT set. 1081 + * 2) while duplicating vm area, the new vma fails to copy the pgtable from 1082 + * old vma. 1079 1083 */ 1080 - void untrack_pfn_moved(struct vm_area_struct *vma) 1084 + void untrack_pfn_clear(struct vm_area_struct *vma) 1081 1085 { 1082 1086 vm_flags_clear(vma, VM_PAT); 1083 1087 }
+4 -3
include/linux/pgtable.h
··· 1191 1191 } 1192 1192 1193 1193 /* 1194 - * untrack_pfn_moved is called while mremapping a pfnmap for a new region. 1194 + * untrack_pfn_clear is called while mremapping a pfnmap for a new region 1195 + * or fails to copy pgtable during duplicate vm area. 1195 1196 */ 1196 - static inline void untrack_pfn_moved(struct vm_area_struct *vma) 1197 + static inline void untrack_pfn_clear(struct vm_area_struct *vma) 1197 1198 { 1198 1199 } 1199 1200 #else ··· 1206 1205 extern int track_pfn_copy(struct vm_area_struct *vma); 1207 1206 extern void untrack_pfn(struct vm_area_struct *vma, unsigned long pfn, 1208 1207 unsigned long size, bool mm_wr_locked); 1209 - extern void untrack_pfn_moved(struct vm_area_struct *vma); 1208 + extern void untrack_pfn_clear(struct vm_area_struct *vma); 1210 1209 #endif 1211 1210 1212 1211 #ifdef CONFIG_MMU
+1
mm/memory.c
··· 1290 1290 continue; 1291 1291 if (unlikely(copy_p4d_range(dst_vma, src_vma, dst_pgd, src_pgd, 1292 1292 addr, next))) { 1293 + untrack_pfn_clear(dst_vma); 1293 1294 ret = -ENOMEM; 1294 1295 break; 1295 1296 }
+1 -1
mm/mremap.c
··· 683 683 684 684 /* Tell pfnmap has moved from this vma */ 685 685 if (unlikely(vma->vm_flags & VM_PFNMAP)) 686 - untrack_pfn_moved(vma); 686 + untrack_pfn_clear(vma); 687 687 688 688 if (unlikely(!err && (flags & MREMAP_DONTUNMAP))) { 689 689 /* We always clear VM_LOCKED[ONFAULT] on the old vma */