[PATCH 3/3] arm64: gcs: Do not map the guarded control stack as THP
Catalin Marinas
catalin.marinas at arm.com
Fri Feb 20 07:13:51 PST 2026
On Fri, Feb 20, 2026 at 02:34:08PM +0000, Mark Brown wrote:
> On Fri, Feb 20, 2026 at 02:05:31PM +0000, Catalin Marinas wrote:
> > The default GCS size allocated on first prctl() for the main thread or
> > subsequently on clone() is either half of RLIMIT_STACK or half of a
> > thread's stack size. Both of these are likely to be suitable for a THP
> > allocation and the kernel is more aggressive in creating such mappings.
> > However, it does not make much sense to use a huge page as it didn't
> > make sense for the normal stacks either. See commit c4608d1bf7c6 ("mm:
> > mmap: map MAP_STACK to VM_NOHUGEPAGE").
>
> > Force VM_NOHUGEPAGE when allocating/mapping the GCS. As per commit
> > 7190b3c8bd2b ("mm: mmap: map MAP_STACK to VM_NOHUGEPAGE only if THP is
> > enabled"), only pass this flag if TRANSPARENT_HUGEPAGE is enabled as not
> > to confuse CRIU tools.
>
> I agree that this is sensible however I'm fairly sure this will also
> apply to the other shadow stack implementations so I think it would be
> better to either do it cross architecture (ideally factoring this out of
> the arch code entirely) or put a note in the commit log that it's likely
> going to apply to other architectures. There's a bunch of stuff that we
> should start factoring out into common code now that RISC-V landed and
> it looks like the clone3() stuff ran it's course, we should make it as
> easy as possible to understand why any divergences we're adding.
Something like below (not tested yet and not addressing riscv, waiting
for -rc1):
diff --git a/arch/arm64/mm/gcs.c b/arch/arm64/mm/gcs.c
index bbdb62ae47cd..21ed78d129de 100644
--- a/arch/arm64/mm/gcs.c
+++ b/arch/arm64/mm/gcs.c
@@ -12,23 +12,7 @@
static unsigned long alloc_gcs(unsigned long addr, unsigned long size)
{
- int flags = MAP_ANONYMOUS | MAP_PRIVATE;
- vm_flags_t vm_flags = VM_SHADOW_STACK;
- struct mm_struct *mm = current->mm;
- unsigned long mapped_addr, unused;
-
- if (addr)
- flags |= MAP_FIXED_NOREPLACE;
-
- if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
- vm_flags |= VM_NOHUGEPAGE;
-
- mmap_write_lock(mm);
- mapped_addr = do_mmap(NULL, addr, size, PROT_READ | PROT_WRITE,
- flags, vm_flags, 0, &unused, NULL);
- mmap_write_unlock(mm);
-
- return mapped_addr;
+ return vm_mmap_shadow_stack(addr, size, 0);
}
static unsigned long gcs_size(unsigned long size)
diff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c
index 978232b6d48d..9725e7d89b1e 100644
--- a/arch/x86/kernel/shstk.c
+++ b/arch/x86/kernel/shstk.c
@@ -100,17 +100,9 @@ static int create_rstor_token(unsigned long ssp, unsigned long *token_addr)
static unsigned long alloc_shstk(unsigned long addr, unsigned long size,
unsigned long token_offset, bool set_res_tok)
{
- int flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_ABOVE4G;
- struct mm_struct *mm = current->mm;
- unsigned long mapped_addr, unused;
+ unsigned long mapped_addr;
- if (addr)
- flags |= MAP_FIXED_NOREPLACE;
-
- mmap_write_lock(mm);
- mapped_addr = do_mmap(NULL, addr, size, PROT_READ, flags,
- VM_SHADOW_STACK | VM_WRITE, 0, &unused, NULL);
- mmap_write_unlock(mm);
+ mapped_addr = vm_mmap_shadow_stack(addr, size, MAP_ABOVE4G);
if (!set_res_tok || IS_ERR_VALUE(mapped_addr))
goto out;
diff --git a/include/linux/mm.h b/include/linux/mm.h
index f0d5be9dc736..4bde7539adc8 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3711,6 +3711,8 @@ static inline void mm_populate(unsigned long addr, unsigned long len) {}
/* This takes the mm semaphore itself */
extern int __must_check vm_brk_flags(unsigned long, unsigned long, unsigned long);
extern int vm_munmap(unsigned long, size_t);
+extern unsigned long __must_check vm_mmap_shadow_stack(unsigned long addr,
+ unsigned long len, unsigned long flags);
extern unsigned long __must_check vm_mmap(struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long, unsigned long);
diff --git a/mm/util.c b/mm/util.c
index 97cae40c0209..5c0d92f52157 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -588,6 +588,24 @@ unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr,
return ret;
}
+unsigned long vm_mmap_shadow_stack(unsigned long addr, unsigned long len,
+ unsigned long flags)
+{
+ struct mm_struct *mm = current->mm;
+ unsigned long ret, unused;
+
+ flags |= MAP_ANONYMOUS | MAP_PRIVATE;
+ if (addr)
+ flags |= MAP_FIXED_NOREPLACE;
+
+ mmap_write_lock(mm);
+ ret = do_mmap(NULL, addr, len, PROT_READ | PROT_WRITE, flags,
+ VM_SHADOW_STACK | VM_NOHUGEPAGE, 0, &unused, NULL);
+ mmap_write_unlock(mm);
+
+ return ret;
+}
+
/*
* Perform a userland memory mapping into the current process address space. See
* the comment for do_mmap() for more details on this operation in general.
--
Catalin
More information about the linux-arm-kernel
mailing list