FAILED: patch "[PATCH] mm/rmap: Fix anon_vma->degree ambiguity leading to" failed to apply to 4.9-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 4.9-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 2555283eb40df89945557273121e9393ef9b542b Mon Sep 17 00:00:00 2001
From: Jann Horn <[email protected]>
Date: Wed, 31 Aug 2022 19:06:00 +0200
Subject: [PATCH] mm/rmap: Fix anon_vma->degree ambiguity leading to
double-reuse
anon_vma->degree tracks the combined number of child anon_vmas and VMAs that use the anon_vma as their ->anon_vma.
anon_vma_clone() then assumes that for any anon_vma attached to src->anon_vma_chain other than src->anon_vma, it is impossible for it to be a leaf node of the VMA tree, meaning that for such VMAs ->degree is elevated by 1 because of a child anon_vma, meaning that if ->degree equals 1 there are no VMAs that use the anon_vma as their ->anon_vma.
This assumption is wrong because the ->degree optimization leads to leaf nodes being abandoned on anon_vma_clone() - an existing anon_vma is reused and no new parent-child relationship is created. So it is possible to reuse an anon_vma for one VMA while it is still tied to another VMA.
This is an issue because is_mergeable_anon_vma() and its callers assume that if two VMAs have the same ->anon_vma, the list of anon_vmas attached to the VMAs is guaranteed to be the same. When this assumption is violated, vma_merge() can merge pages into a VMA that is not attached to the corresponding anon_vma, leading to dangling page->mapping pointers that will be dereferenced during rmap walks.
Fix it by separately tracking the number of child anon_vmas and the number of VMAs using the anon_vma as their ->anon_vma.
include/linux/rmap.h +5 -2
@@ -41,12 +41,15 @@ struct anon_vma {atomic_t refcount;/* -* Count of child anon_vmas and VMAs which points to this anon_vma. +* Count of child anon_vmas. Equals to the count of all anon_vmas that +* have ->parent pointing to this one, including itself. * * This counter is used for making decision about reusing anon_vma * instead of forking new one. See comments in function anon_vma_clone. */ -unsigned degree; +unsigned long num_children; +/* Count of VMAs whose ->anon_vma pointer points to this object. */ +unsigned long num_active_vmas;struct anon_vma *parent;/* Parent of this anon_vma */
mm/rmap.c +16 -13
@@ -93,7 +93,8 @@ static inline struct anon_vma *anon_vma_alloc(void) anon_vma =kmem_cache_alloc (anon_vma_cachep,GFP_KERNEL );if (anon_vma) {atomic_set (&anon_vma->refcount,1 ); - anon_vma->degree =1 ;/* Reference for first vma */ + anon_vma->num_children =0 ; + anon_vma->num_active_vmas =0 ; anon_vma->parent = anon_vma;/* * Initialise the anon_vma root to point to itself. If called
@@ -201,6 +202,7 @@ int __anon_vma_prepare(struct vm_area_struct *vma) anon_vma =anon_vma_alloc ();if (unlikely (!anon_vma)) goto out_enomem_free_avc; + anon_vma->num_children++;/* self-parent link for new root */ allocated = anon_vma; }
@@ -210,8 +212,7 @@ int __anon_vma_prepare(struct vm_area_struct *vma)if (likely (!vma->anon_vma)) { vma->anon_vma = anon_vma;anon_vma_chain_link (vma, avc, anon_vma); -/* vma reference or self-parent link for new root */ - anon_vma->degree++; + anon_vma->num_active_vmas++; allocated =NULL ; avc =NULL ; }
@@ -296,19 +297,19 @@ int anon_vma_clone(struct vm_area_struct *dst, struct vm_area_struct *src)anon_vma_chain_link (dst, avc, anon_vma);/* -* Reuse existing anon_vma if its degree lower than two, -* that means it has no vma and only one anon_vma child. +* Reuse existing anon_vma if it has no vma and only one +* anon_vma child. * -* Do not choose parent anon_vma, otherwise first child -* will always reuse it. Root anon_vma is never reused: +* Root anon_vma is never reused: * it has self-parent reference and at least one child. */ if (!dst->anon_vma && src->anon_vma && - anon_vma != src->anon_vma && anon_vma->degree <2 ) + anon_vma->num_children <2 && + anon_vma->num_active_vmas ==0 ) dst->anon_vma = anon_vma; }if (dst->anon_vma) - dst->anon_vma->degree++; + dst->anon_vma->num_active_vmas++;unlock_anon_vma_root (root);return 0 ;
@@ -358,6 +359,7 @@ int anon_vma_fork(struct vm_area_struct *vma, struct vm_area_struct *pvma) anon_vma =anon_vma_alloc ();if (!anon_vma) goto out_error; + anon_vma->num_active_vmas++; avc =anon_vma_chain_alloc (GFP_KERNEL );if (!avc) goto out_error_free_anon_vma;
@@ -378,7 +380,7 @@ int anon_vma_fork(struct vm_area_struct *vma, struct vm_area_struct *pvma) vma->anon_vma = anon_vma;anon_vma_lock_write (anon_vma);anon_vma_chain_link (vma, avc, anon_vma); - anon_vma->parent->degree++; + anon_vma->parent->num_children++;anon_vma_unlock_write (anon_vma);return 0 ;
@@ -410,7 +412,7 @@ void unlink_anon_vmas(struct vm_area_struct *vma)* to free them outside the lock. */ if (RB_EMPTY_ROOT (&anon_vma->rb_root.rb_root)) { - anon_vma->parent->degree--; + anon_vma->parent->num_children--;continue ; }
@@ -418,7 +420,7 @@ void unlink_anon_vmas(struct vm_area_struct *vma)anon_vma_chain_free (avc); }if (vma->anon_vma) { - vma->anon_vma->degree--; + vma->anon_vma->num_active_vmas--;/* * vma would still be needed after unlink, and anon_vma will be prepared
@@ -436,7 +438,8 @@ void unlink_anon_vmas(struct vm_area_struct *vma)list_for_each_entry_safe (avc, next, &vma->anon_vma_chain, same_vma) {struct anon_vma *anon_vma = avc->anon_vma; -VM_WARN_ON (anon_vma->degree); +VM_WARN_ON (anon_vma->num_children); +VM_WARN_ON (anon_vma->num_active_vmas);put_anon_vma (anon_vma);list_del (&avc->same_vma);