* [PATCH stable 6.1, 5.15, 5.10 0/3] mm: backport mmap_min_addr handling fix to vm_unmapped_area()
@ 2026-10-30 17:22 Florian Fainelli
2026-10-30 17:22 ` [PATCH stable 5.10] mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area() Florian Fainelli
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Florian Fainelli @ 2026-10-30 17:22 UTC (permalink / raw)
To: stable
Cc: bcm-kernel-feedback-list, Liam R . Howlett, Linus Torvalds,
Andrew Morton, linux-mm, Greg Kroah-Hartman, Sasha Levin
To: stable@vger.kernel.org
Cc: Liam R. Howlett <Liam.Howlett@oracle.com>, Linus Torvalds <torvalds@linux-foundation.org>, Andrew Morton <akpm@linux-foundation.org>, linux-mm@kvack.org
This series backports
6b008640db73 ("mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area()")
to the 6.1, 5.15 and 5.10 stable trees. It is already part of v6.4-rc1
and therefore present in 6.6.y and later. The three patches are
independent of each other, one per tree, and are meant to be applied to
the respective stable branch only.
Problem
=======
An unprivileged process can get EPERM from mmap(MAP_ANONYMOUS) although
plenty of address space is still available. This happens when
vm.mmap_min_addr is larger than PAGE_SIZE (the usual case, distributions
and embedded systems set 32 KiB or 64 KiB):
- with the top-down layout, the search starts at mm->mmap_base and
walks down towards low_limit;
- several architectures pass a low_limit below mmap_min_addr to
vm_unmapped_area() (PAGE_SIZE, FIRST_USER_ADDRESS, ...), so the
search can return an address in [low_limit, mmap_min_addr);
- get_unmapped_area() gets that address back from the architecture
code, but security_mmap_addr() then refuses it, and the caller sees
-EPERM.
The right outcome is to treat the range below mmap_min_addr as not
available, so that the top-down search fails with -ENOMEM and the caller
falls back to the bottom-up search above mmap_base, as the generic code
already does.
Commit 2afc745f3e30 ("mm: ensure get_unmapped_area() returns higher
address than mmap_min_addr") fixed this only for the generic
arch_get_unmapped_area_topdown(). Architectures with their own
implementation were left alone. 6b008640db73 moves the clamp into
vm_unmapped_area() itself, which fixes all callers at once.
Affected code, determined by reading the 6.1, 5.15 and 5.10 trees and
confirmed by running the kernels below:
- x86-64 (arch/x86/kernel/sys_x86_64.c), including 32-bit processes
on a 64-bit kernel, and the x86 hugetlb search
(arch/x86/mm/hugetlbpage.c)
- arm (arch/arm/mm/mmap.c)
- mips (arch/mips/mm/mmap.c)
- sparc64 (arch/sparc/kernel/sys_sparc_64.c and
arch/sparc/mm/hugetlbpage.c); only 32-bit tasks use the top-down
layout there
- by code inspection only, not run: parisc, sh and, in 6.1, loongarch
arm64, s390 and powerpc already clamp to mmap_min_addr in their own or in
the generic code, so their behaviour does not change; they were run as
regression checks (see below). 32-bit x86 kernels use the generic code,
which already clamps; that was checked by reading the code only.
Backport notes
==============
6.1.y The patch applies cleanly.
5.15.y Same as 5.10.y, see below.
5.10.y These trees predate the maple tree, so the mm/mmap.c hunks do
not apply. unmapped_area() and unmapped_area_topdown() are the
rbtree based versions; the lower search bound is clamped
there with a local min_addr = max(info->low_limit,
mmap_min_addr), which is then used in place of
info->low_limit (including the early "low_limit > high_limit"
check, the final clipping of the gap and the VM_BUG_ON()). The
arch/s390 and fs/hugetlbfs hunks apply as in the original
commit. This is noted in the commit message of each patch.
Reproducer
==========
Run it as an unprivileged user, with vm.mmap_min_addr set to something
larger than PAGE_SIZE (for example 65536). It exits with status 1 and
prints "Operation not permitted" on an affected kernel, and exits with
status 0 and prints "Cannot allocate memory" on a fixed one.
- 32-bit process: ./repro
- 64-bit process: ./repro 1099511627776
(starting with 1 TiB chunks makes the 47/48 bit address space run out
after a few hundred mappings instead of billions)
--- mmap-minaddr-repro.c ---
// SPDX-License-Identifier: GPL-2.0
/*
* Map anonymous memory until mmap() fails and report why.
*
* Run as an unprivileged user (root has CAP_SYS_RAWIO, which bypasses
* vm.mmap_min_addr) with vm.mmap_min_addr larger than PAGE_SIZE.
*
* 32-bit process: ./repro (64 KiB first, halved on ENOMEM)
* 64-bit process: ./repro 1099511627776 (start with 1 TiB so that the
* address space is used up quickly)
*
* A kernel that handles vm.mmap_min_addr correctly ends with ENOMEM once the
* address space is exhausted. An affected kernel ends with EPERM while there
* is still free address space above mmap_base.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
int main(int argc, char **argv)
{
size_t size = argc > 1 ? strtoull(argv[1], NULL, 0) : 65536;
unsigned long count = 0;
for (;;) {
void *p = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p != MAP_FAILED) {
count++;
continue;
}
if (errno == ENOMEM && size > 4096) {
size /= 2;
continue;
}
printf("mmap(%zu) failed after %lu mappings: %s (errno %d)\n",
size, count, strerror(errno), errno);
return errno == EPERM; /* exit status 1: bug present */
}
}
--- end ---
Example output with vm.mmap_min_addr=65536, 6.1.y, x86-64 kernel:
without the patch, 32-bit process:
mmap(65536) failed after 63458 mappings: Operation not permitted (errno 1)
without the patch, 64-bit process (./repro 1099511627776):
mmap(32768) failed after 172 mappings: Operation not permitted (errno 1)
with the patch, 32-bit process:
mmap(4096) failed after 65513 mappings: Cannot allocate memory (errno 12)
with the patch, 64-bit process:
mmap(4096) failed after 184 mappings: Cannot allocate memory (errno 12)
The hugetlb paths can be exercised with a variant of the same loop: use
MAP_HUGETLB | MAP_NORESERVE (no huge pages need to be reserved), keep the
size a multiple of the huge page size and stop halving at the huge page
size, and set vm.mmap_min_addr (as root, before dropping privileges)
above the huge page size, for example to 256 MiB. Huge pages are
aligned, so with a smaller mmap_min_addr the search can never land below
it and the problem is not reachable.
Testing
=======
Every tree was built twice per architecture, without and with its patch,
and booted under QEMU (TCG) with a minimal initramfs. A process running
as uid 1000 was run three times per configuration (once on s390, where
a run takes about 17 million mmap() calls) with vm.mmap_min_addr=65536,
with 1 MiB on powerpc64 (64 KiB pages) and with 256 MiB for the hugetlb
runs. The results were identical for 6.1.y, 5.15.y and 5.10.y.
kernel / process without patch with patch
------------------------------------------------------------------------
x86_64, 32-bit and 64-bit process EPERM ENOMEM
x86_64, hugetlb, 64-bit process EPERM ENOMEM
arm (multi_v7), 32-bit process EPERM ENOMEM
mips (malta, big endian), 32-bit process EPERM ENOMEM
sparc64, 32-bit process EPERM ENOMEM
sparc64, hugetlb, 32-bit process EPERM ENOMEM
arm64, 64-bit and 32-bit (compat) process ENOMEM ENOMEM
arm64, hugetlb (generic hugetlbfs path) ENOMEM ENOMEM
s390 ENOMEM ENOMEM
powerpc64 hash (POWER8) and radix (POWER9) ENOMEM ENOMEM
powerpc64 radix, hugetlb ENOMEM ENOMEM
powerpc32 ENOMEM ENOMEM
The arch/s390 files touched by the patch were compiled and linked on all
three trees.
Not covered: parisc, sh and loongarch were not run at all. hugetlb on
s390 and on powerpc64 with the hash MMU could not be run, because QEMU
does not provide a usable default huge page size for them. The
generic hugetlbfs hunk was exercised through arm64 only (where the
behaviour is unchanged, as that path already clamped locally).
6.1.y: 4 files changed, 16 insertions(+), 9 deletions(-)
5.15.y: 4 files changed, 13 insertions(+), 11 deletions(-)
5.10.y: 4 files changed, 13 insertions(+), 11 deletions(-)
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH stable 5.10] mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area()
2026-10-30 17:22 [PATCH stable 6.1, 5.15, 5.10 0/3] mm: backport mmap_min_addr handling fix to vm_unmapped_area() Florian Fainelli
@ 2026-10-30 17:22 ` Florian Fainelli
2026-10-30 17:22 ` [PATCH stable 5.15] " Florian Fainelli
2026-10-30 17:22 ` [PATCH stable 6.1] " Florian Fainelli
2 siblings, 0 replies; 6+ messages in thread
From: Florian Fainelli @ 2026-10-30 17:22 UTC (permalink / raw)
To: stable
Cc: bcm-kernel-feedback-list, Linus Torvalds, Liam R . Howlett,
Russell King, Liam Howlett, Andrew Morton, Florian Fainelli,
linux-mm, Greg Kroah-Hartman, Sasha Levin
From: Linus Torvalds <torvalds@linux-foundation.org>
commit 6b008640db7355d8de6ac18f74cedd7ccc92684f upstream.
Instead of having callers care about the mmap_min_addr logic for the
lowest valid mapping address (and some of them getting it wrong), just
move the logic into vm_unmapped_area() itself. One less thing for various
architecture cases (and generic helpers) to worry about.
We should really try to make much more of this be common code, but baby
steps..
Without this, vm_unmapped_area() could return an address below
mmap_min_addr (because some caller forgot about that). That then causes
the mmap machinery to think it has found a workable address, but then
later security_mmap_addr(addr) is unhappy about it and the mmap() returns
with a nonsensical error (EPERM).
The proper action is to either return ENOMEM (if the virtual address space
is exhausted), or try to find another address (ie do a bottom-up search
for free addresses after the top-down one failed).
See commit 2afc745f3e30 ("mm: ensure get_unmapped_area() returns higher
address than mmap_min_addr"), which fixed this for one call site (the
generic arch_get_unmapped_area_topdown() fallback) but left other cases
alone.
Link: https://lkml.kernel.org/r/20230418214009.1142926-1-Liam.Howlett@oracle.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Liam R. Howlett <Liam.Howlett@oracle.com>
Cc: Russell King <linux@armlinux.org.uk>
Cc: Liam Howlett <liam.howlett@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
[ Adapted to the rbtree based unmapped_area() and unmapped_area_topdown()
used before the maple tree conversion: clamp the lower search bound to
mmap_min_addr there instead of in the maple tree search. ]
Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
---
arch/s390/mm/hugetlbpage.c | 2 +-
arch/s390/mm/mmap.c | 2 +-
fs/hugetlbfs/inode.c | 2 +-
mm/mmap.c | 18 ++++++++++--------
4 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/arch/s390/mm/hugetlbpage.c b/arch/s390/mm/hugetlbpage.c
index 0ca46f5d9438..96c04071a0d6 100644
--- a/arch/s390/mm/hugetlbpage.c
+++ b/arch/s390/mm/hugetlbpage.c
@@ -290,7 +290,7 @@ static unsigned long hugetlb_get_unmapped_area_topdown(struct file *file,
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = current->mm->mmap_base;
info.align_mask = PAGE_MASK & ~huge_page_mask(h);
info.align_offset = 0;
diff --git a/arch/s390/mm/mmap.c b/arch/s390/mm/mmap.c
index e54f928503c5..1cb7ec9f9e6f 100644
--- a/arch/s390/mm/mmap.c
+++ b/arch/s390/mm/mmap.c
@@ -136,7 +136,7 @@ unsigned long arch_get_unmapped_area_topdown(struct file *filp, unsigned long ad
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = mm->mmap_base;
if (filp || (flags & MAP_SHARED))
info.align_mask = MMAP_ALIGN_MASK << PAGE_SHIFT;
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index d2a3964daf70..0a5236d8d2db 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -232,7 +232,7 @@ hugetlb_get_unmapped_area_topdown(struct file *file, unsigned long addr,
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = arch_get_mmap_base(addr, current->mm->mmap_base);
info.align_mask = PAGE_MASK & ~huge_page_mask(h);
info.align_offset = 0;
diff --git a/mm/mmap.c b/mm/mmap.c
index f820bfbe5852..290822968e21 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1944,6 +1944,7 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long length, low_limit, high_limit, gap_start, gap_end;
+ unsigned long min_addr = max(info->low_limit, mmap_min_addr);
/* Adjust search length to account for worst case alignment overhead */
length = info->length + info->align_mask;
@@ -1955,9 +1956,9 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
return -ENOMEM;
high_limit = info->high_limit - length;
- if (info->low_limit > high_limit)
+ if (min_addr > high_limit)
return -ENOMEM;
- low_limit = info->low_limit + length;
+ low_limit = min_addr + length;
/* Check if rbtree root looks promising */
if (RB_EMPTY_ROOT(&mm->mm_rb))
@@ -2023,8 +2024,8 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
found:
/* We found a suitable gap. Clip it with the original low_limit. */
- if (gap_start < info->low_limit)
- gap_start = info->low_limit;
+ if (gap_start < min_addr)
+ gap_start = min_addr;
/* Adjust gap address to the desired alignment */
gap_start += (info->align_offset - gap_start) & info->align_mask;
@@ -2039,6 +2040,7 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long length, low_limit, high_limit, gap_start, gap_end;
+ unsigned long min_addr = max(info->low_limit, mmap_min_addr);
/* Adjust search length to account for worst case alignment overhead */
length = info->length + info->align_mask;
@@ -2054,9 +2056,9 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
return -ENOMEM;
high_limit = gap_end - length;
- if (info->low_limit > high_limit)
+ if (min_addr > high_limit)
return -ENOMEM;
- low_limit = info->low_limit + length;
+ low_limit = min_addr + length;
/* Check highest gap, which does not precede any rbtree node */
gap_start = mm->highest_vm_end;
@@ -2128,7 +2130,7 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
gap_end -= info->length;
gap_end -= (gap_end - info->align_offset) & info->align_mask;
- VM_BUG_ON(gap_end < info->low_limit);
+ VM_BUG_ON(gap_end < min_addr);
VM_BUG_ON(gap_end < gap_start);
return gap_end;
}
@@ -2235,7 +2237,7 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr,
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = arch_get_mmap_base(addr, mm->mmap_base);
info.align_mask = 0;
info.align_offset = 0;
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH stable 5.15] mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area()
2026-10-30 17:22 [PATCH stable 6.1, 5.15, 5.10 0/3] mm: backport mmap_min_addr handling fix to vm_unmapped_area() Florian Fainelli
2026-10-30 17:22 ` [PATCH stable 5.10] mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area() Florian Fainelli
@ 2026-10-30 17:22 ` Florian Fainelli
2026-10-31 17:23 ` sashiko-bot
2026-10-30 17:22 ` [PATCH stable 6.1] " Florian Fainelli
2 siblings, 1 reply; 6+ messages in thread
From: Florian Fainelli @ 2026-10-30 17:22 UTC (permalink / raw)
To: stable
Cc: bcm-kernel-feedback-list, Linus Torvalds, Liam R . Howlett,
Russell King, Liam Howlett, Andrew Morton, Florian Fainelli,
linux-mm, Greg Kroah-Hartman, Sasha Levin
From: Linus Torvalds <torvalds@linux-foundation.org>
commit 6b008640db7355d8de6ac18f74cedd7ccc92684f upstream.
Instead of having callers care about the mmap_min_addr logic for the
lowest valid mapping address (and some of them getting it wrong), just
move the logic into vm_unmapped_area() itself. One less thing for various
architecture cases (and generic helpers) to worry about.
We should really try to make much more of this be common code, but baby
steps..
Without this, vm_unmapped_area() could return an address below
mmap_min_addr (because some caller forgot about that). That then causes
the mmap machinery to think it has found a workable address, but then
later security_mmap_addr(addr) is unhappy about it and the mmap() returns
with a nonsensical error (EPERM).
The proper action is to either return ENOMEM (if the virtual address space
is exhausted), or try to find another address (ie do a bottom-up search
for free addresses after the top-down one failed).
See commit 2afc745f3e30 ("mm: ensure get_unmapped_area() returns higher
address than mmap_min_addr"), which fixed this for one call site (the
generic arch_get_unmapped_area_topdown() fallback) but left other cases
alone.
Link: https://lkml.kernel.org/r/20230418214009.1142926-1-Liam.Howlett@oracle.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Liam R. Howlett <Liam.Howlett@oracle.com>
Cc: Russell King <linux@armlinux.org.uk>
Cc: Liam Howlett <liam.howlett@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
[ Adapted to the rbtree based unmapped_area() and unmapped_area_topdown()
used before the maple tree conversion: clamp the lower search bound to
mmap_min_addr there instead of in the maple tree search. ]
Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
---
arch/s390/mm/hugetlbpage.c | 2 +-
arch/s390/mm/mmap.c | 2 +-
fs/hugetlbfs/inode.c | 2 +-
mm/mmap.c | 18 ++++++++++--------
4 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/arch/s390/mm/hugetlbpage.c b/arch/s390/mm/hugetlbpage.c
index 8631307d3def..01aaf530ee7a 100644
--- a/arch/s390/mm/hugetlbpage.c
+++ b/arch/s390/mm/hugetlbpage.c
@@ -290,7 +290,7 @@ static unsigned long hugetlb_get_unmapped_area_topdown(struct file *file,
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = current->mm->mmap_base;
info.align_mask = PAGE_MASK & ~huge_page_mask(h);
info.align_offset = 0;
diff --git a/arch/s390/mm/mmap.c b/arch/s390/mm/mmap.c
index e54f928503c5..1cb7ec9f9e6f 100644
--- a/arch/s390/mm/mmap.c
+++ b/arch/s390/mm/mmap.c
@@ -136,7 +136,7 @@ unsigned long arch_get_unmapped_area_topdown(struct file *filp, unsigned long ad
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = mm->mmap_base;
if (filp || (flags & MAP_SHARED))
info.align_mask = MMAP_ALIGN_MASK << PAGE_SHIFT;
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index f663657c237f..b41e79f964d2 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -232,7 +232,7 @@ hugetlb_get_unmapped_area_topdown(struct file *file, unsigned long addr,
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = arch_get_mmap_base(addr, current->mm->mmap_base);
info.align_mask = PAGE_MASK & ~huge_page_mask(h);
info.align_offset = 0;
diff --git a/mm/mmap.c b/mm/mmap.c
index 53fa4f5167dd..2c22f4bee04e 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1917,6 +1917,7 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long length, low_limit, high_limit, gap_start, gap_end;
+ unsigned long min_addr = max(info->low_limit, mmap_min_addr);
/* Adjust search length to account for worst case alignment overhead */
length = info->length + info->align_mask;
@@ -1928,9 +1929,9 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
return -ENOMEM;
high_limit = info->high_limit - length;
- if (info->low_limit > high_limit)
+ if (min_addr > high_limit)
return -ENOMEM;
- low_limit = info->low_limit + length;
+ low_limit = min_addr + length;
/* Check if rbtree root looks promising */
if (RB_EMPTY_ROOT(&mm->mm_rb))
@@ -1996,8 +1997,8 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
found:
/* We found a suitable gap. Clip it with the original low_limit. */
- if (gap_start < info->low_limit)
- gap_start = info->low_limit;
+ if (gap_start < min_addr)
+ gap_start = min_addr;
/* Adjust gap address to the desired alignment */
gap_start += (info->align_offset - gap_start) & info->align_mask;
@@ -2012,6 +2013,7 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long length, low_limit, high_limit, gap_start, gap_end;
+ unsigned long min_addr = max(info->low_limit, mmap_min_addr);
/* Adjust search length to account for worst case alignment overhead */
length = info->length + info->align_mask;
@@ -2027,9 +2029,9 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
return -ENOMEM;
high_limit = gap_end - length;
- if (info->low_limit > high_limit)
+ if (min_addr > high_limit)
return -ENOMEM;
- low_limit = info->low_limit + length;
+ low_limit = min_addr + length;
/* Check highest gap, which does not precede any rbtree node */
gap_start = mm->highest_vm_end;
@@ -2101,7 +2103,7 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
gap_end -= info->length;
gap_end -= (gap_end - info->align_offset) & info->align_mask;
- VM_BUG_ON(gap_end < info->low_limit);
+ VM_BUG_ON(gap_end < min_addr);
VM_BUG_ON(gap_end < gap_start);
return gap_end;
}
@@ -2208,7 +2210,7 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr,
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = arch_get_mmap_base(addr, mm->mmap_base);
info.align_mask = 0;
info.align_offset = 0;
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH stable 5.15] mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area()
2026-10-30 17:22 ` [PATCH stable 5.15] " Florian Fainelli
@ 2026-10-31 17:23 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-10-31 17:23 UTC (permalink / raw)
To: Florian Fainelli
Cc: bcm-kernel-feedback-list, Linus Torvalds, Liam R . Howlett,
Russell King, Andrew Morton, linux-mm, Greg Kroah-Hartman,
Sasha Levin
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] mm: unaligned return from unmapped_area() when mmap_min_addr is unaligned
--
commit 357809efe869317caba6039aa012c441fb76a0c6
Author: Linus Torvalds <torvalds@linux-foundation.org>
mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area()
This commit moves the mmap_min_addr lower-bound clamping out of individual
callers and into unmapped_area() and unmapped_area_topdown() so that
vm_unmapped_area() never returns an address below mmap_min_addr.
Link: https://lkml.kernel.org/r/20230418214009.1142926-1-Liam.Howlett@oracle.com
[ ... ]
> diff --git a/mm/mmap.c b/mm/mmap.c
> index 53fa4f5167dd0..2c22f4bee04ee 100644
> --- a/mm/mmap.c
> +++ b/mm/mmap.c
> @@ -1917,6 +1917,7 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
> struct mm_struct *mm = current->mm;
> struct vm_area_struct *vma;
> unsigned long length, low_limit, high_limit, gap_start, gap_end;
> + unsigned long min_addr = max(info->low_limit, mmap_min_addr);
>
> /* Adjust search length to account for worst case alignment overhead */
> length = info->length + info->align_mask;
> @@ -1928,9 +1929,9 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
> return -ENOMEM;
> high_limit = info->high_limit - length;
>
> - if (info->low_limit > high_limit)
> + if (min_addr > high_limit)
> return -ENOMEM;
> - low_limit = info->low_limit + length;
> + low_limit = min_addr + length;
>
> /* Check if rbtree root looks promising */
> if (RB_EMPTY_ROOT(&mm->mm_rb))
> @@ -1996,8 +1997,8 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
>
> found:
> /* We found a suitable gap. Clip it with the original low_limit. */
> - if (gap_start < info->low_limit)
> - gap_start = info->low_limit;
> + if (gap_start < min_addr)
> + gap_start = min_addr;
>
> /* Adjust gap address to the desired alignment */
> gap_start += (info->align_offset - gap_start) & info->align_mask;
[Severity: Medium]
Can unmapped_area() return a non-page-aligned address here when mmap_min_addr
is not page-aligned?
In security/min_addr.c, mmap_min_addr_handler() updates mmap_min_addr via
proc_doulongvec_minmax() without enforcing page alignment, which is why
round_hint_to_min() in mm/mmap.c explicitly uses PAGE_ALIGN(mmap_min_addr):
mm/mmap.c:round_hint_to_min() {
if (((void *)hint != NULL) &&
(hint < mmap_min_addr))
return PAGE_ALIGN(mmap_min_addr);
return hint;
}
In the upstream commit 6b008640db73 ("mm: move 'mmap_min_addr' logic from
callers into vm_unmapped_area()"), vm_unmapped_area() also aligned
mmap_min_addr to a page boundary:
info->low_limit = max(info->low_limit, PAGE_ALIGN(mmap_min_addr));
Here in unmapped_area(), min_addr is computed using the raw mmap_min_addr. If
mmap_min_addr is set to a non-page-aligned value above info->low_limit and a
bottom-up search finds a gap with gap_start < min_addr, gap_start is clipped
to the unaligned min_addr. Because info->align_mask has its low page-offset
bits cleared (or is 0), adjusting gap_start with info->align_mask leaves the
non-zero sub-page bits intact (and can also borrow from bit PAGE_SHIFT when
computing higher-order alignment).
Callers of vm_unmapped_area() then see offset_in_page(addr) or
(addr & ~PAGE_MASK) as non-zero and treat the returned address as an error,
causing get_unmapped_area() to fail with -EINVAL or callers checking
VM_BUG_ON(addr != -ENOMEM) to trigger.
Should unmapped_area() and unmapped_area_topdown() use
PAGE_ALIGN(mmap_min_addr) when computing min_addr?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030172216.368052-3-florian.fainelli@broadcom.com?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH stable 6.1] mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area()
2026-10-30 17:22 [PATCH stable 6.1, 5.15, 5.10 0/3] mm: backport mmap_min_addr handling fix to vm_unmapped_area() Florian Fainelli
2026-10-30 17:22 ` [PATCH stable 5.10] mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area() Florian Fainelli
2026-10-30 17:22 ` [PATCH stable 5.15] " Florian Fainelli
@ 2026-10-30 17:22 ` Florian Fainelli
2026-10-31 17:23 ` sashiko-bot
2 siblings, 1 reply; 6+ messages in thread
From: Florian Fainelli @ 2026-10-30 17:22 UTC (permalink / raw)
To: stable
Cc: bcm-kernel-feedback-list, Linus Torvalds, Liam R . Howlett,
Russell King, Liam Howlett, Andrew Morton, Florian Fainelli,
linux-mm, Greg Kroah-Hartman, Sasha Levin
From: Linus Torvalds <torvalds@linux-foundation.org>
commit 6b008640db7355d8de6ac18f74cedd7ccc92684f upstream.
Instead of having callers care about the mmap_min_addr logic for the
lowest valid mapping address (and some of them getting it wrong), just
move the logic into vm_unmapped_area() itself. One less thing for various
architecture cases (and generic helpers) to worry about.
We should really try to make much more of this be common code, but baby
steps..
Without this, vm_unmapped_area() could return an address below
mmap_min_addr (because some caller forgot about that). That then causes
the mmap machinery to think it has found a workable address, but then
later security_mmap_addr(addr) is unhappy about it and the mmap() returns
with a nonsensical error (EPERM).
The proper action is to either return ENOMEM (if the virtual address space
is exhausted), or try to find another address (ie do a bottom-up search
for free addresses after the top-down one failed).
See commit 2afc745f3e30 ("mm: ensure get_unmapped_area() returns higher
address than mmap_min_addr"), which fixed this for one call site (the
generic arch_get_unmapped_area_topdown() fallback) but left other cases
alone.
Link: https://lkml.kernel.org/r/20230418214009.1142926-1-Liam.Howlett@oracle.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Liam R. Howlett <Liam.Howlett@oracle.com>
Cc: Russell King <linux@armlinux.org.uk>
Cc: Liam Howlett <liam.howlett@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
---
arch/s390/mm/hugetlbpage.c | 2 +-
arch/s390/mm/mmap.c | 2 +-
fs/hugetlbfs/inode.c | 2 +-
mm/mmap.c | 19 +++++++++++++------
4 files changed, 16 insertions(+), 9 deletions(-)
diff --git a/arch/s390/mm/hugetlbpage.c b/arch/s390/mm/hugetlbpage.c
index 33ef6790114a..d646d81ead5b 100644
--- a/arch/s390/mm/hugetlbpage.c
+++ b/arch/s390/mm/hugetlbpage.c
@@ -273,7 +273,7 @@ static unsigned long hugetlb_get_unmapped_area_topdown(struct file *file,
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = current->mm->mmap_base;
info.align_mask = PAGE_MASK & ~huge_page_mask(h);
info.align_offset = 0;
diff --git a/arch/s390/mm/mmap.c b/arch/s390/mm/mmap.c
index 3327c47bc181..fc9a7dc26c5e 100644
--- a/arch/s390/mm/mmap.c
+++ b/arch/s390/mm/mmap.c
@@ -136,7 +136,7 @@ unsigned long arch_get_unmapped_area_topdown(struct file *filp, unsigned long ad
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = mm->mmap_base;
if (filp || (flags & MAP_SHARED))
info.align_mask = MMAP_ALIGN_MASK << PAGE_SHIFT;
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 3faad73c74ab..a7b6a97543b7 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -219,7 +219,7 @@ hugetlb_get_unmapped_area_topdown(struct file *file, unsigned long addr,
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = arch_get_mmap_base(addr, current->mm->mmap_base);
info.align_mask = PAGE_MASK & ~huge_page_mask(h);
info.align_offset = 0;
diff --git a/mm/mmap.c b/mm/mmap.c
index 1eb86aba7de5..17ec4d40edc1 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1598,7 +1598,8 @@ static inline int accountable_mapping(struct file *file, vm_flags_t vm_flags)
*/
static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
{
- unsigned long length, gap, low_limit;
+ unsigned long length, gap;
+ unsigned long low_limit, high_limit;
struct vm_area_struct *tmp;
MA_STATE(mas, ¤t->mm->mm_mt, 0, 0);
@@ -1609,8 +1610,11 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
return -ENOMEM;
low_limit = info->low_limit;
+ if (low_limit < mmap_min_addr)
+ low_limit = mmap_min_addr;
+ high_limit = info->high_limit;
retry:
- if (mas_empty_area(&mas, low_limit, info->high_limit - 1, length))
+ if (mas_empty_area(&mas, low_limit, high_limit - 1, length))
return -ENOMEM;
gap = mas.index;
@@ -1646,7 +1650,8 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
*/
static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
{
- unsigned long length, gap, high_limit, gap_end;
+ unsigned long length, gap, gap_end;
+ unsigned long low_limit, high_limit;
struct vm_area_struct *tmp;
MA_STATE(mas, ¤t->mm->mm_mt, 0, 0);
@@ -1655,10 +1660,12 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
if (length < info->length)
return -ENOMEM;
+ low_limit = info->low_limit;
+ if (low_limit < mmap_min_addr)
+ low_limit = mmap_min_addr;
high_limit = info->high_limit;
retry:
- if (mas_empty_area_rev(&mas, info->low_limit, high_limit - 1,
- length))
+ if (mas_empty_area_rev(&mas, low_limit, high_limit - 1, length))
return -ENOMEM;
gap = mas.last + 1 - info->length;
@@ -1793,7 +1800,7 @@ generic_get_unmapped_area_topdown(struct file *filp, unsigned long addr,
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.length = len;
- info.low_limit = max(PAGE_SIZE, mmap_min_addr);
+ info.low_limit = PAGE_SIZE;
info.high_limit = arch_get_mmap_base(addr, mm->mmap_base);
info.align_mask = 0;
info.align_offset = 0;
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH stable 6.1] mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area()
2026-10-30 17:22 ` [PATCH stable 6.1] " Florian Fainelli
@ 2026-10-31 17:23 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-10-31 17:23 UTC (permalink / raw)
To: Florian Fainelli
Cc: bcm-kernel-feedback-list, Linus Torvalds, Liam R . Howlett,
Russell King, Andrew Morton, linux-mm, Greg Kroah-Hartman,
Sasha Levin
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] mm: unaligned return from unmapped_area() when mmap_min_addr is unaligned
--
commit dd218bb48a347d4efd5770f53b201eae3a9f2336
Author: Linus Torvalds <torvalds@linux-foundation.org>
mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area()
This commit moves the mmap_min_addr lower-bound clamping out of individual
get_unmapped_area() callers and into unmapped_area() and
unmapped_area_topdown().
Link: https://lkml.kernel.org/r/20230418214009.1142926-1-Liam.Howlett@oracle.com
> diff --git a/mm/mmap.c b/mm/mmap.c
> index 1eb86aba7de56..17ec4d40edc11 100644
> --- a/mm/mmap.c
> +++ b/mm/mmap.c
> @@ -1598,7 +1598,8 @@ static inline int accountable_mapping(struct file *file, vm_flags_t vm_flags)
> */
> static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
> {
> - unsigned long length, gap, low_limit;
> + unsigned long length, gap;
> + unsigned long low_limit, high_limit;
> struct vm_area_struct *tmp;
>
> MA_STATE(mas, ¤t->mm->mm_mt, 0, 0);
> @@ -1609,8 +1610,11 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
> return -ENOMEM;
>
> low_limit = info->low_limit;
> + if (low_limit < mmap_min_addr)
> + low_limit = mmap_min_addr;
> + high_limit = info->high_limit;
> retry:
> - if (mas_empty_area(&mas, low_limit, info->high_limit - 1, length))
> + if (mas_empty_area(&mas, low_limit, high_limit - 1, length))
> return -ENOMEM;
>
> gap = mas.index;
> @@ -1646,7 +1650,8 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info)
> */
> static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
> {
> - unsigned long length, gap, high_limit, gap_end;
> + unsigned long length, gap, gap_end;
> + unsigned long low_limit, high_limit;
> struct vm_area_struct *tmp;
>
> MA_STATE(mas, ¤t->mm->mm_mt, 0, 0);
> @@ -1655,10 +1660,12 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info)
> if (length < info->length)
> return -ENOMEM;
>
> + low_limit = info->low_limit;
> + if (low_limit < mmap_min_addr)
> + low_limit = mmap_min_addr;
[Severity: Medium]
Can unmapped_area() return a non-page-aligned address when mmap_min_addr is
not a multiple of PAGE_SIZE?
In security/min_addr.c, mmap_min_addr_handler() updates dac_mmap_min_addr and
mmap_min_addr via proc_doulongvec_minmax() without enforcing page alignment,
which is why round_hint_to_min() in mm/mmap.c aligns it:
mm/mmap.c:round_hint_to_min() {
if (((void *)hint != NULL) &&
(hint < mmap_min_addr))
return PAGE_ALIGN(mmap_min_addr);
return hint;
}
When a caller invokes vm_unmapped_area() with info->low_limit < mmap_min_addr
and mmap_min_addr is not page-aligned, unmapped_area() sets low_limit to the
unaligned mmap_min_addr. If no VMA covers low_limit, mas_empty_area() sets
mas.index to low_limit, and unmapped_area() computes:
gap = mas.index;
gap += (info->align_offset - gap) & info->align_mask;
Because info->align_mask is 0 for normal mappings and has its low PAGE_SHIFT
bits masked off via PAGE_MASK for colour-aligned and hugetlb mappings, the
non-zero page offset bits of gap are never cleared (and when info->align_mask
is non-zero, borrowing from the unaligned low bits during
info->align_offset - gap also corrupts the upper alignment bits).
This causes get_unmapped_area() to reject the returned address with -EINVAL:
mm/mmap.c:get_unmapped_area() {
...
if (offset_in_page(addr))
return -EINVAL;
}
and triggers VM_BUG_ON(addr != -ENOMEM) in callers such as
hugetlb_get_unmapped_area_bottomup() and sparc64 arch_get_unmapped_area()
that check offset_in_page(addr) or addr & ~PAGE_MASK for errors.
Should unmapped_area() and unmapped_area_topdown() clamp low_limit to
PAGE_ALIGN(mmap_min_addr) instead of raw mmap_min_addr?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030172216.368052-4-florian.fainelli@broadcom.com?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-31 17:24 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 17:22 [PATCH stable 6.1, 5.15, 5.10 0/3] mm: backport mmap_min_addr handling fix to vm_unmapped_area() Florian Fainelli
2026-10-30 17:22 ` [PATCH stable 5.10] mm: move 'mmap_min_addr' logic from callers into vm_unmapped_area() Florian Fainelli
2026-10-30 17:22 ` [PATCH stable 5.15] " Florian Fainelli
2026-10-31 17:23 ` sashiko-bot
2026-10-30 17:22 ` [PATCH stable 6.1] " Florian Fainelli
2026-10-31 17:23 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox