The patch titled Subject: mm: enforce a minimal stack gap even against inaccessible VMAs has been added to the -mm mm-hotfixes-unstable branch. Its filename is mm-enforce-a-minimal-stack-gap-even-against-inaccessible-vmas.patch This patch will shortly appear at https://git.kernel.org/pub/scm/linux/kernel/git/akpm/25-new.git/tree/patches/mm-enforce-a-minimal-stack-gap-even-against-inaccessible-vmas.patch This patch will later appear in the mm-hotfixes-unstable branch at git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm Before you just go and hit "reply", please: a) Consider who else should be cc'ed b) Prefer to cc a suitable mailing list as well c) Ideally: find the original patch on the mailing list and do a reply-to-all to that, adding suitable additional cc's *** Remember to use Documentation/process/submit-checklist.rst when testing your code *** The -mm tree is included into linux-next via the mm-everything branch at git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm and is updated there every 2-3 working days ------------------------------------------------------ From: Jann Horn <jannh@xxxxxxxxxx> Subject: mm: enforce a minimal stack gap even against inaccessible VMAs Date: Tue, 08 Oct 2024 00:55:39 +0200 As explained in the comment block this change adds, we can't tell what userspace's intent is when the stack grows towards an inaccessible VMA. I have a (highly contrived) C testcase for 32-bit x86 userspace with glibc that mixes malloc(), pthread creation, and recursion in just the right way such that the main stack overflows into malloc() arena memory. I don't know of any specific scenario where this is actually exploitable, but it seems like it could be a security problem for sufficiently unlucky userspace. I believe we should ensure that, as long as code is compiled with something like -fstack-check, a stack overflow in it can never cause the main stack to overflow into adjacent heap memory. My fix effectively reverts the behavior for !vma_is_accessible() VMAs to the behavior before commit 1be7107fbe18 ("mm: larger stack guard gap, between vmas"), so I think it should be a fairly safe change even in case A. Link: https://lkml.kernel.org/r/20241008-stack-gap-inaccessible-v1-1-848d4d891f21@xxxxxxxxxx Fixes: 561b5e0709e4 ("mm/mmap.c: do not blow on PROT_NONE MAP_FIXED holes in the stack") Signed-off-by: Jann Horn <jannh@xxxxxxxxxx> Cc: Ben Hutchings <ben@xxxxxxxxxxxxxxx> Cc: Helge Deller <deller@xxxxxx> Cc: Hugh Dickins <hughd@xxxxxxxxxx> Cc: Michal Hocko <mhocko@xxxxxxxx> Cc: Oleg Nesterov <oleg@xxxxxxxxxx> Cc: Rik van Riel <riel@xxxxxxxxxx> Cc: Vlastimil Babka <vbabka@xxxxxxx> Cc: Willy Tarreau <w@xxxxxx> Cc: <stable@xxxxxxxxxxxxxxx> Signed-off-by: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> --- mm/mmap.c | 53 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) --- a/mm/mmap.c~mm-enforce-a-minimal-stack-gap-even-against-inaccessible-vmas +++ a/mm/mmap.c @@ -1064,10 +1064,12 @@ static int expand_upwards(struct vm_area gap_addr = TASK_SIZE; next = find_vma_intersection(mm, vma->vm_end, gap_addr); - if (next && vma_is_accessible(next)) { - if (!(next->vm_flags & VM_GROWSUP)) + if (next && !(next->vm_flags & VM_GROWSUP)) { + /* see comments in expand_downwards() */ + if (vma_is_accessible(prev)) + return -ENOMEM; + if (address == next->vm_start) return -ENOMEM; - /* Check that both stack segments have the same anon_vma? */ } if (next) @@ -1155,10 +1157,47 @@ int expand_downwards(struct vm_area_stru /* Enforce stack_guard_gap */ prev = vma_prev(&vmi); /* Check that both stack segments have the same anon_vma? */ - if (prev) { - if (!(prev->vm_flags & VM_GROWSDOWN) && - vma_is_accessible(prev) && - (address - prev->vm_end < stack_guard_gap)) + if (prev && !(prev->vm_flags & VM_GROWSDOWN) && + (address - prev->vm_end < stack_guard_gap)) { + /* + * If the previous VMA is accessible, this is the normal case + * where the main stack is growing down towards some unrelated + * VMA. Enforce the full stack guard gap. + */ + if (vma_is_accessible(prev)) + return -ENOMEM; + + /* + * If the previous VMA is not accessible, we have a problem: + * We can't tell what userspace's intent is. + * + * Case A: + * Maybe userspace wants to use the previous VMA as a + * "guard region" at the bottom of the main stack, in which case + * userspace wants us to grow the stack until it is adjacent to + * the guard region. Apparently some Java runtime environments + * and Rust do that? + * That is kind of ugly, and in that case userspace really ought + * to ensure that the stack is fully expanded immediately, but + * we have to handle this case. + * + * Case B: + * But maybe the previous VMA is entirely unrelated to the stack + * and is only *temporarily* PROT_NONE. For example, glibc + * malloc arenas create a big PROT_NONE region and then + * progressively mark parts of it as writable. + * In that case, we must not let the stack become adjacent to + * the previous VMA. Otherwise, after the region later becomes + * writable, a stack overflow will cause the stack to grow into + * the previous VMA, and we won't have any stack gap to protect + * against this. + * + * As an ugly tradeoff, enforce a single-page gap. + * A single page will hopefully be small enough to not be + * noticed in case A, while providing the same level of + * protection in case B that normal userspace threads get. + */ + if (address == prev->vm_end) return -ENOMEM; } _ Patches currently in -mm which might be from jannh@xxxxxxxxxx are mm-mremap-prevent-racing-change-of-old-pmd-type.patch mm-enforce-a-minimal-stack-gap-even-against-inaccessible-vmas.patch