CVE-2025-21932
Publication date:
01/04/2025
In the Linux kernel, the following vulnerability has been resolved:<br />
<br />
mm: abort vma_modify() on merge out of memory failure<br />
<br />
The remainder of vma_modify() relies upon the vmg state remaining pristine<br />
after a merge attempt.<br />
<br />
Usually this is the case, however in the one edge case scenario of a merge<br />
attempt failing not due to the specified range being unmergeable, but<br />
rather due to an out of memory error arising when attempting to commit the<br />
merge, this assumption becomes untrue.<br />
<br />
This results in vmg->start, end being modified, and thus the proceeding<br />
attempts to split the VMA will be done with invalid start/end values.<br />
<br />
Thankfully, it is likely practically impossible for us to hit this in<br />
reality, as it would require a maple tree node pre-allocation failure that<br />
would likely never happen due to it being &#39;too small to fail&#39;, i.e. the<br />
kernel would simply keep retrying reclaim until it succeeded.<br />
<br />
However, this scenario remains theoretically possible, and what we are<br />
doing here is wrong so we must correct it.<br />
<br />
The safest option is, when this scenario occurs, to simply give up the<br />
operation. If we cannot allocate memory to merge, then we cannot allocate<br />
memory to split either (perhaps moreso!).<br />
<br />
Any scenario where this would be happening would be under very extreme<br />
(likely fatal) memory pressure, so it&#39;s best we give up early.<br />
<br />
So there is no doubt it is appropriate to simply bail out in this<br />
scenario.<br />
<br />
However, in general we must if at all possible never assume VMG state is<br />
stable after a merge attempt, since merge operations update VMG fields. <br />
As a result, additionally also make this clear by storing start, end in<br />
local variables.<br />
<br />
The issue was reported originally by syzkaller, and by Brad Spengler (via<br />
an off-list discussion), and in both instances it manifested as a<br />
triggering of the assert:<br />
<br />
VM_WARN_ON_VMG(start >= end, vmg);<br />
<br />
In vma_merge_existing_range().<br />
<br />
It seems at least one scenario in which this is occurring is one in which<br />
the merge being attempted is due to an madvise() across multiple VMAs<br />
which looks like this:<br />
<br />
start end<br />
||<br />
|----------|------|<br />
| vma | next |<br />
|----------|------|<br />
<br />
When madvise_walk_vmas() is invoked, we first find vma in the above<br />
(determining prev to be equal to vma as we are offset into vma), and then<br />
enter the loop.<br />
<br />
We determine the end of vma that forms part of the range we are<br />
madvise()&#39;ing by setting &#39;tmp&#39; to this value:<br />
<br />
/* Here vma->vm_start vm_end;<br />
<br />
We then invoke the madvise() operation via visit(), letting prev get<br />
updated to point to vma as part of the operation:<br />
<br />
/* Here vma->vm_start start, end get set to perhaps<br />
unintuitive values - we intended to shrink the middle VMA and expand the<br />
next.<br />
<br />
This means vmg->start, end are set to... vma->vm_start, start.<br />
<br />
Now the commit_merge() fails, and vmg->start, end are left like this. <br />
This means we return to the rest of vma_modify() with vmg->start, end<br />
(here denoted as start&#39;, end&#39;) set as:<br />
<br />
start&#39; end&#39;<br />
||<br />
|----------|------|<br />
| vma | next |<br />
|----------|------|<br />
<br />
So we now erroneously try to split accordingly. This is where the<br />
unfortunate<br />
---truncated---
Severity CVSS v4.0: Pending analysis
Last modification:
01/04/2025