[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