Skip to content

Commit 657b514

Browse files
thejhtorvalds
authored andcommitted
mm: lock_vma_under_rcu() must check vma->anon_vma under vma lock
lock_vma_under_rcu() tries to guarantee that __anon_vma_prepare() can't be called in the VMA-locked page fault path by ensuring that vma->anon_vma is set. However, this check happens before the VMA is locked, which means a concurrent move_vma() can concurrently call unlink_anon_vmas(), which disassociates the VMA's anon_vma. This means we can get UAF in the following scenario: THREAD 1 THREAD 2 ======== ======== <page fault> lock_vma_under_rcu() rcu_read_lock() mas_walk() check vma->anon_vma mremap() syscall move_vma() vma_start_write() unlink_anon_vmas() <syscall end> handle_mm_fault() __handle_mm_fault() handle_pte_fault() do_pte_missing() do_anonymous_page() anon_vma_prepare() __anon_vma_prepare() find_mergeable_anon_vma() mas_walk() [looks up VMA X] munmap() syscall (deletes VMA X) reusable_anon_vma() [called on freed VMA X] This is a security bug if you can hit it, although an attacker would have to win two races at once where the first race window is only a few instructions wide. This patch is based on some previous discussion with Linus Torvalds on the security list. Cc: [email protected] Fixes: 5e31275 ("mm: add per-VMA lock and helper functions to control it") Signed-off-by: Jann Horn <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 0a8db05 commit 657b514

File tree

1 file changed

+16
-12
lines changed

1 file changed

+16
-12
lines changed

mm/memory.c

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5393,27 +5393,28 @@ struct vm_area_struct *lock_vma_under_rcu(struct mm_struct *mm,
53935393
if (!vma_is_anonymous(vma) && !vma_is_tcp(vma))
53945394
goto inval;
53955395

5396-
/* find_mergeable_anon_vma uses adjacent vmas which are not locked */
5397-
if (!vma->anon_vma && !vma_is_tcp(vma))
5398-
goto inval;
5399-
54005396
if (!vma_start_read(vma))
54015397
goto inval;
54025398

5399+
/*
5400+
* find_mergeable_anon_vma uses adjacent vmas which are not locked.
5401+
* This check must happen after vma_start_read(); otherwise, a
5402+
* concurrent mremap() with MREMAP_DONTUNMAP could dissociate the VMA
5403+
* from its anon_vma.
5404+
*/
5405+
if (unlikely(!vma->anon_vma && !vma_is_tcp(vma)))
5406+
goto inval_end_read;
5407+
54035408
/*
54045409
* Due to the possibility of userfault handler dropping mmap_lock, avoid
54055410
* it for now and fall back to page fault handling under mmap_lock.
54065411
*/
5407-
if (userfaultfd_armed(vma)) {
5408-
vma_end_read(vma);
5409-
goto inval;
5410-
}
5412+
if (userfaultfd_armed(vma))
5413+
goto inval_end_read;
54115414

54125415
/* Check since vm_start/vm_end might change before we lock the VMA */
5413-
if (unlikely(address < vma->vm_start || address >= vma->vm_end)) {
5414-
vma_end_read(vma);
5415-
goto inval;
5416-
}
5416+
if (unlikely(address < vma->vm_start || address >= vma->vm_end))
5417+
goto inval_end_read;
54175418

54185419
/* Check if the VMA got isolated after we found it */
54195420
if (vma->detached) {
@@ -5425,6 +5426,9 @@ struct vm_area_struct *lock_vma_under_rcu(struct mm_struct *mm,
54255426

54265427
rcu_read_unlock();
54275428
return vma;
5429+
5430+
inval_end_read:
5431+
vma_end_read(vma);
54285432
inval:
54295433
rcu_read_unlock();
54305434
count_vm_vma_lock_event(VMA_LOCK_ABORT);

0 commit comments

Comments
 (0)