All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
@ 2026-06-26 17:34 Xiang Mei
  2026-06-26 17:46 ` Xiang Mei
  2026-06-26 19:39 ` Pedro Falcato
  0 siblings, 2 replies; 13+ messages in thread
From: Xiang Mei @ 2026-06-26 17:34 UTC (permalink / raw)
  To: Kees Cook, Andrew Morton, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, linux-hardening
  Cc: Uladzislau Rezki, Gustavo A . R . Silva, H . Peter Anvin,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili, Xiang Mei

With CONFIG_VMAP_STACK, kernel stacks are allocated in the vmalloc area,
which an unprivileged user can surround with attacker-controlled data by
spraying vmap allocations adjacent to a target stack (for example via
XDP_UMEM_REG, though other vmalloc spray paths work too). Today each
guarded vmalloc allocation is followed by a single unmapped guard page.

A single guard page is not enough to contain the x86_64 ENTER
instruction used as a one-instruction stack pivot. ENTER imm16, imm8
builds a stack frame and lowers RSP by:

	imm16 + 8 * (L + 1),  L = imm8 & 0x1f

imm16 is an unsigned 16-bit operand (ENTER never raises RSP), and L is
in [0, 31], so the maximum displacement of a single ENTER is:

	0xffff + 8 * 0x20 = 0x100ff bytes

That is more than enough to step off the current stack, across the
one-page guard, and into the adjacent sprayed pages. When those pages
contain a return sled feeding a ROP chain, reaching any ENTER gadget
(opcode 0xc8, abundant as both intended and unintended gadgets) turns a
control-flow hijack into full ROP execution without any register control
at the hijack site, making it a one-gadget-style primitive that
significantly eases exploitation. The pivot happens after the control
transfer, so it is not constrained by CFI (kCFI/FineIBT).

Widen the guard region from one page to VMAP_GUARD_PAGES (0x11 pages,
0x11000 bytes), which is the smallest whole-page span exceeding the
0x100ff-byte maximum single-ENTER pivot. A pivot off the top of the
stack now lands in the unmapped guard and faults, instead of in mapped,
attacker-controlled memory. RANDOMIZE_KSTACK_OFFSET only perturbs RSP by
a sub-page amount, so it does not change the required width.

Introduce a VMAP_GUARD_PAGES knob that defaults to a single page (no
change for current architectures) and can be overridden per arch via
asm/vmalloc.h, and set it to 0x11 on x86_64. This is deliberately scoped
to x86_64: the 0x100ff bound is a property of the ENTER opcode, and ENTER
is also a one-byte opcode (0xc8) that appears as abundant unintended
gadgets. Other architectures (e.g. arm64) have no equivalent
single-instruction, immediate-controlled pivot reachable as an unaligned
unintended gadget, so they keep the one-page guard and pay no cost.

The override is gated on CONFIG_X86_64 rather than applying to all of x86:
VMAP_STACK is selected only on x86_64, so 32-bit kernel stacks are not in
the vmalloc area and the technique does not apply there. 32-bit x86 also
has a far smaller vmalloc window, where widening every guarded area by 16
pages would needlessly pressure the address space.

The guard pages are never populated, so there is no extra physical
memory and no additional page-table population beyond the larger virtual
span; the cost is virtual address space and vmap_area bookkeeping, which
is negligible against the 64-bit vmalloc window. get_vm_area_size() is
adjusted by the same VMAP_GUARD_SIZE so the usable size reported to
callers is unchanged.

On x86 this widens the guard for all guarded vmap areas, not only thread
stacks. ret2enter targets the stack specifically, so a narrower
alternative is to apply the wider guard only on the thread-stack
allocation path via a dedicated VM_ flag; we kept the change in the
common path as defense in depth for any vmalloc-adjacent pivot target,
but are happy to scope it to stacks if maintainers prefer.

Signed-off-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Jennifer Miller <jmill@asu.edu>
---
 arch/x86/include/asm/vmalloc.h | 21 +++++++++++++++++++++
 include/linux/vmalloc.h        | 16 ++++++++++++++--
 mm/vmalloc.c                   |  2 +-
 3 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/arch/x86/include/asm/vmalloc.h b/arch/x86/include/asm/vmalloc.h
index 49ce331f3ac6..2c341f398227 100644
--- a/arch/x86/include/asm/vmalloc.h
+++ b/arch/x86/include/asm/vmalloc.h
@@ -5,6 +5,27 @@
 #include <asm/page.h>
 #include <asm/pgtable_areas.h>
 
+/*
+ * The x86 ENTER instruction can be used as a one-instruction stack pivot:
+ * ENTER imm16, imm8 lowers RSP by imm16 + 8 * (L + 1), L = imm8 & 0x1f.
+ * imm16 is an unsigned 16-bit operand (ENTER never raises RSP) and L is in
+ * [0, 31], so a single ENTER can lower RSP by at most
+ * 0xffff + 8 * 0x20 = 0x100ff bytes. With CONFIG_VMAP_STACK the kernel
+ * stack lives in the vmalloc area, where an unprivileged user can spray
+ * adjacent allocations; a single-page guard is too small to contain such a
+ * pivot. Use 0x11 guard pages (0x11000 bytes), the smallest whole-page
+ * span exceeding 0x100ff, so the pivot faults in the guard instead of
+ * landing in attacker-controlled memory.
+ *
+ * Restrict this to 64-bit: VMAP_STACK is selected only on x86_64, so 32-bit
+ * kernel stacks are not in the vmalloc area and the technique does not apply.
+ * 32-bit also has a far smaller vmalloc window, where a 16-page-per-area
+ * widening would needlessly pressure the address space.
+ */
+#ifdef CONFIG_X86_64
+#define VMAP_GUARD_PAGES	0x11
+#endif
+
 #ifdef CONFIG_HAVE_ARCH_HUGE_VMAP
 
 #ifdef CONFIG_X86_64
diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h
index 3b02c0c6b371..b8546e519deb 100644
--- a/include/linux/vmalloc.h
+++ b/include/linux/vmalloc.h
@@ -49,6 +49,18 @@ struct iov_iter;		/* in uio.h */
 #define IOREMAP_MAX_ORDER	(7 + PAGE_SHIFT)	/* 128 pages */
 #endif
 
+/*
+ * Number of unmapped guard pages appended to each guarded vmalloc
+ * allocation. The default is a single page; an architecture may override
+ * VMAP_GUARD_PAGES (via asm/vmalloc.h) when a wider guard is needed to
+ * contain a worst-case single-instruction stack pivot into an adjacent,
+ * attacker-controlled vmap allocation (see arch/x86 for the ENTER case).
+ */
+#ifndef VMAP_GUARD_PAGES
+#define VMAP_GUARD_PAGES	1
+#endif
+#define VMAP_GUARD_SIZE		(VMAP_GUARD_PAGES * PAGE_SIZE)
+
 struct vm_struct {
 	union {
 		struct vm_struct *next;	  /* Early registration of vm_areas. */
@@ -236,8 +248,8 @@ int vmap_pages_range(unsigned long addr, unsigned long end, pgprot_t prot,
 static inline size_t get_vm_area_size(const struct vm_struct *area)
 {
 	if (!(area->flags & VM_NO_GUARD))
-		/* return actual size without guard page */
-		return area->size - PAGE_SIZE;
+		/* return actual size without guard region */
+		return area->size - VMAP_GUARD_SIZE;
 	else
 		return area->size;
 
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index bb6ae08d18f5..8bb2b3ef40a8 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -3217,7 +3217,7 @@ struct vm_struct *__get_vm_area_node(unsigned long size,
 		return NULL;
 
 	if (!(flags & VM_NO_GUARD))
-		size += PAGE_SIZE;
+		size += VMAP_GUARD_SIZE;
 
 	area->flags = flags;
 	area->caller = caller;
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-26 17:34 [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot Xiang Mei
@ 2026-06-26 17:46 ` Xiang Mei
  2026-06-26 17:48   ` Xiang Mei
  2026-06-26 19:39 ` Pedro Falcato
  1 sibling, 1 reply; 13+ messages in thread
From: Xiang Mei @ 2026-06-26 17:46 UTC (permalink / raw)
  To: Kees Cook, Andrew Morton, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, linux-hardening
  Cc: Uladzislau Rezki, Gustavo A . R . Silva, H . Peter Anvin,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On Fri, Jun 26, 2026 at 10:34:44AM -0700, Xiang Mei wrote:
> With CONFIG_VMAP_STACK, kernel stacks are allocated in the vmalloc area,
> which an unprivileged user can surround with attacker-controlled data by
> spraying vmap allocations adjacent to a target stack (for example via
> XDP_UMEM_REG, though other vmalloc spray paths work too). Today each
> guarded vmalloc allocation is followed by a single unmapped guard page.
> 
> A single guard page is not enough to contain the x86_64 ENTER
> instruction used as a one-instruction stack pivot. ENTER imm16, imm8
> builds a stack frame and lowers RSP by:
> 
> 	imm16 + 8 * (L + 1),  L = imm8 & 0x1f
> 
> imm16 is an unsigned 16-bit operand (ENTER never raises RSP), and L is
> in [0, 31], so the maximum displacement of a single ENTER is:
> 
> 	0xffff + 8 * 0x20 = 0x100ff bytes
> 
> That is more than enough to step off the current stack, across the
> one-page guard, and into the adjacent sprayed pages. When those pages
> contain a return sled feeding a ROP chain, reaching any ENTER gadget
> (opcode 0xc8, abundant as both intended and unintended gadgets) turns a
> control-flow hijack into full ROP execution without any register control
> at the hijack site, making it a one-gadget-style primitive that
> significantly eases exploitation. The pivot happens after the control
> transfer, so it is not constrained by CFI (kCFI/FineIBT).
> 
> Widen the guard region from one page to VMAP_GUARD_PAGES (0x11 pages,
> 0x11000 bytes), which is the smallest whole-page span exceeding the
> 0x100ff-byte maximum single-ENTER pivot. A pivot off the top of the
> stack now lands in the unmapped guard and faults, instead of in mapped,
> attacker-controlled memory. RANDOMIZE_KSTACK_OFFSET only perturbs RSP by
> a sub-page amount, so it does not change the required width.
> 
> Introduce a VMAP_GUARD_PAGES knob that defaults to a single page (no
> change for current architectures) and can be overridden per arch via
> asm/vmalloc.h, and set it to 0x11 on x86_64. This is deliberately scoped
> to x86_64: the 0x100ff bound is a property of the ENTER opcode, and ENTER
> is also a one-byte opcode (0xc8) that appears as abundant unintended
> gadgets. Other architectures (e.g. arm64) have no equivalent
> single-instruction, immediate-controlled pivot reachable as an unaligned
> unintended gadget, so they keep the one-page guard and pay no cost.
> 
> The override is gated on CONFIG_X86_64 rather than applying to all of x86:
> VMAP_STACK is selected only on x86_64, so 32-bit kernel stacks are not in
> the vmalloc area and the technique does not apply there. 32-bit x86 also
> has a far smaller vmalloc window, where widening every guarded area by 16
> pages would needlessly pressure the address space.
> 
> The guard pages are never populated, so there is no extra physical
> memory and no additional page-table population beyond the larger virtual
> span; the cost is virtual address space and vmap_area bookkeeping, which
> is negligible against the 64-bit vmalloc window. get_vm_area_size() is
> adjusted by the same VMAP_GUARD_SIZE so the usable size reported to
> callers is unchanged.
> 
> On x86 this widens the guard for all guarded vmap areas, not only thread
> stacks. ret2enter targets the stack specifically, so a narrower
> alternative is to apply the wider guard only on the thread-stack
> allocation path via a dedicated VM_ flag; we kept the change in the
> common path as defense in depth for any vmalloc-adjacent pivot target,
> but are happy to scope it to stacks if maintainers prefer.
> 
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
> Signed-off-by: Jennifer Miller <jmill@asu.edu>
> ---
>  arch/x86/include/asm/vmalloc.h | 21 +++++++++++++++++++++
>  include/linux/vmalloc.h        | 16 ++++++++++++++--
>  mm/vmalloc.c                   |  2 +-
>  3 files changed, 36 insertions(+), 3 deletions(-)
> 
> diff --git a/arch/x86/include/asm/vmalloc.h b/arch/x86/include/asm/vmalloc.h
> index 49ce331f3ac6..2c341f398227 100644
> --- a/arch/x86/include/asm/vmalloc.h
> +++ b/arch/x86/include/asm/vmalloc.h
> @@ -5,6 +5,27 @@
>  #include <asm/page.h>
>  #include <asm/pgtable_areas.h>
>  
> +/*
> + * The x86 ENTER instruction can be used as a one-instruction stack pivot:
> + * ENTER imm16, imm8 lowers RSP by imm16 + 8 * (L + 1), L = imm8 & 0x1f.
> + * imm16 is an unsigned 16-bit operand (ENTER never raises RSP) and L is in
> + * [0, 31], so a single ENTER can lower RSP by at most
> + * 0xffff + 8 * 0x20 = 0x100ff bytes. With CONFIG_VMAP_STACK the kernel
> + * stack lives in the vmalloc area, where an unprivileged user can spray
> + * adjacent allocations; a single-page guard is too small to contain such a
> + * pivot. Use 0x11 guard pages (0x11000 bytes), the smallest whole-page
> + * span exceeding 0x100ff, so the pivot faults in the guard instead of
> + * landing in attacker-controlled memory.
> + *
> + * Restrict this to 64-bit: VMAP_STACK is selected only on x86_64, so 32-bit
> + * kernel stacks are not in the vmalloc area and the technique does not apply.
> + * 32-bit also has a far smaller vmalloc window, where a 16-page-per-area
> + * widening would needlessly pressure the address space.
> + */
> +#ifdef CONFIG_X86_64
> +#define VMAP_GUARD_PAGES	0x11
> +#endif
> +
>  #ifdef CONFIG_HAVE_ARCH_HUGE_VMAP
>  
>  #ifdef CONFIG_X86_64
> diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h
> index 3b02c0c6b371..b8546e519deb 100644
> --- a/include/linux/vmalloc.h
> +++ b/include/linux/vmalloc.h
> @@ -49,6 +49,18 @@ struct iov_iter;		/* in uio.h */
>  #define IOREMAP_MAX_ORDER	(7 + PAGE_SHIFT)	/* 128 pages */
>  #endif
>  
> +/*
> + * Number of unmapped guard pages appended to each guarded vmalloc
> + * allocation. The default is a single page; an architecture may override
> + * VMAP_GUARD_PAGES (via asm/vmalloc.h) when a wider guard is needed to
> + * contain a worst-case single-instruction stack pivot into an adjacent,
> + * attacker-controlled vmap allocation (see arch/x86 for the ENTER case).
> + */
> +#ifndef VMAP_GUARD_PAGES
> +#define VMAP_GUARD_PAGES	1
> +#endif
> +#define VMAP_GUARD_SIZE		(VMAP_GUARD_PAGES * PAGE_SIZE)
> +
>  struct vm_struct {
>  	union {
>  		struct vm_struct *next;	  /* Early registration of vm_areas. */
> @@ -236,8 +248,8 @@ int vmap_pages_range(unsigned long addr, unsigned long end, pgprot_t prot,
>  static inline size_t get_vm_area_size(const struct vm_struct *area)
>  {
>  	if (!(area->flags & VM_NO_GUARD))
> -		/* return actual size without guard page */
> -		return area->size - PAGE_SIZE;
> +		/* return actual size without guard region */
> +		return area->size - VMAP_GUARD_SIZE;
>  	else
>  		return area->size;
>  
> diff --git a/mm/vmalloc.c b/mm/vmalloc.c
> index bb6ae08d18f5..8bb2b3ef40a8 100644
> --- a/mm/vmalloc.c
> +++ b/mm/vmalloc.c
> @@ -3217,7 +3217,7 @@ struct vm_struct *__get_vm_area_node(unsigned long size,
>  		return NULL;
>  
>  	if (!(flags & VM_NO_GUARD))
> -		size += PAGE_SIZE;
> +		size += VMAP_GUARD_SIZE;
>  
>  	area->flags = flags;
>  	area->caller = caller;
> -- 
> 2.43.0
>


Hi hardening team and maintainers,

This patch widens the vmalloc guard region on x86_64 to close a
class of stack-pivot primitive against CONFIG_VMAP_STACK kernels. We would
like the hardening and mm/x86 maintainers' view on whether this belongs in
the common guard path or should be scoped to thread stacks.

The description below is intentionally theoretical: it explains why a single
guard page is insufficient, without operational exploitation detail.

Background / threat model
=========================

With CONFIG_VMAP_STACK (the default on x86_64), kernel thread stacks live in
the vmalloc area, which allocates roughly linearly. In principle an
unprivileged user can place attacker-controlled vmalloc allocations near a
target stack, the vmalloc area exposes several allocation paths reachable
from userspace, so at the moment of a control-flow hijack the live stack
can be adjacent to attacker-controlled mapped pages.

Today each guarded vmalloc allocation is followed by a single unmapped guard
page. The guard relies on the assumption that a corrupted RSP cannot jump
far enough, in one step, to clear the guard and land in the next mapped
allocation. On x86_64 that assumption does not hold.

The primitive
=============

The x86_64 ENTER instruction (ENTER imm16, imm8) builds a stack frame in a
single instruction and lowers RSP by:

    imm16 + 8 * (L + 1),  L = imm8 & 0x1f

imm16 is an unsigned 16-bit operand and ENTER never raises RSP, so a single
ENTER lowers RSP by up to 0xffff + 8 * 0x20 = 0x100ff bytes, which is more than a
page. A single instruction can therefore move RSP off the current stack,
across the one-page guard, and into an adjacent vmalloc allocation.

Theoretically this matters because:

  - The displacement is attacker-chosen (via the immediates) up to 0x100ff,
    so the pivot can clear any guard narrower than that in one step.
  - ENTER is reachable as a gadget, so a pivot of this size is available
    without depending on register state at the hijack site.
  - The pivot happens after the control transfer, so it is not constrained
    by forward-edge CFI (kCFI / FineIBT).

Taken together these make it a one-gadget-style pivot that generalizes
across control-flow hijacks: because no register control is required at the
hijack site, any control-flow hijack able to reach a single ENTER gadget can
move RSP into an adjacent vmalloc allocation. The net effect is that a single
guard page is not a reliable boundary between a pivoted RSP and the next
mapped allocation on x86_64.

Because this lifts a generic primitive across control-flow hijacks rather
than easing one specific bug, we think the guard itself should be widened so
the boundary holds regardless of the originating hijack.

We have a working proof-of-concept and are happy to share it privately with
maintainers; we are keeping the offensive details off the public list.

The fix
=======

Widen the guard region from one page to the smallest whole-page span that
exceeds the worst-case single-ENTER displacement: 0x11 pages (0x11000 bytes
> 0x100ff). A pivot off the top of the stack then lands in unmapped guard
memory and faults, instead of in mapped, attacker-controlled pages.

This is scoped to x86_64 via a VMAP_GUARD_PAGES knob that defaults to one
page (no change for any other architecture) and is overridden only under
CONFIG_X86_64. The 0x100ff bound is a property of the ENTER opcode, which is
also a one-byte unintended gadget; other architectures have no equivalent
single-instruction, immediate-controlled pivot reachable as an unaligned
gadget, so they keep the one-page guard and pay no cost. VMAP_STACK is
selected only on x86_64, so 32-bit x86 is excluded as well.

Cost: the guard pages are never populated, so there is no extra physical
memory and no extra page-table population beyond the larger virtual span --
only virtual address space and vmap_area bookkeeping, negligible against the
64-bit vmalloc window. get_vm_area_size() is adjusted by the same amount so
the usable size reported to callers is unchanged.

Open question for maintainers
=============================

On x86_64 this widens the guard for all guarded vmap areas, not only thread
stacks. ret2enter targets the stack specifically, so a narrower alternative
is to apply the wider guard only on the thread-stack allocation path via a
dedicated VM_ flag. We kept it in the common path as defense in depth for any
vmalloc-adjacent pivot target, but are happy to scope it to stacks if you
prefer.

We would like to hear your feedback and suggestions.

Thanks,
Xiang
 

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-26 17:46 ` Xiang Mei
@ 2026-06-26 17:48   ` Xiang Mei
  2026-06-30  7:12     ` Peter Zijlstra
  0 siblings, 1 reply; 13+ messages in thread
From: Xiang Mei @ 2026-06-26 17:48 UTC (permalink / raw)
  To: Kees Cook, Andrew Morton, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, linux-hardening
  Cc: Uladzislau Rezki, Gustavo A . R . Silva, H . Peter Anvin,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On Fri, Jun 26, 2026 at 10:46 AM Xiang Mei <xmei5@asu.edu> wrote:
>
> On Fri, Jun 26, 2026 at 10:34:44AM -0700, Xiang Mei wrote:
> > With CONFIG_VMAP_STACK, kernel stacks are allocated in the vmalloc area,
> > which an unprivileged user can surround with attacker-controlled data by
> > spraying vmap allocations adjacent to a target stack (for example via
> > XDP_UMEM_REG, though other vmalloc spray paths work too). Today each
> > guarded vmalloc allocation is followed by a single unmapped guard page.
> >
> > A single guard page is not enough to contain the x86_64 ENTER
> > instruction used as a one-instruction stack pivot. ENTER imm16, imm8
> > builds a stack frame and lowers RSP by:
> >
> >       imm16 + 8 * (L + 1),  L = imm8 & 0x1f
> >
> > imm16 is an unsigned 16-bit operand (ENTER never raises RSP), and L is
> > in [0, 31], so the maximum displacement of a single ENTER is:
> >
> >       0xffff + 8 * 0x20 = 0x100ff bytes
> >
> > That is more than enough to step off the current stack, across the
> > one-page guard, and into the adjacent sprayed pages. When those pages
> > contain a return sled feeding a ROP chain, reaching any ENTER gadget
> > (opcode 0xc8, abundant as both intended and unintended gadgets) turns a
> > control-flow hijack into full ROP execution without any register control
> > at the hijack site, making it a one-gadget-style primitive that
> > significantly eases exploitation. The pivot happens after the control
> > transfer, so it is not constrained by CFI (kCFI/FineIBT).
> >
> > Widen the guard region from one page to VMAP_GUARD_PAGES (0x11 pages,
> > 0x11000 bytes), which is the smallest whole-page span exceeding the
> > 0x100ff-byte maximum single-ENTER pivot. A pivot off the top of the
> > stack now lands in the unmapped guard and faults, instead of in mapped,
> > attacker-controlled memory. RANDOMIZE_KSTACK_OFFSET only perturbs RSP by
> > a sub-page amount, so it does not change the required width.
> >
> > Introduce a VMAP_GUARD_PAGES knob that defaults to a single page (no
> > change for current architectures) and can be overridden per arch via
> > asm/vmalloc.h, and set it to 0x11 on x86_64. This is deliberately scoped
> > to x86_64: the 0x100ff bound is a property of the ENTER opcode, and ENTER
> > is also a one-byte opcode (0xc8) that appears as abundant unintended
> > gadgets. Other architectures (e.g. arm64) have no equivalent
> > single-instruction, immediate-controlled pivot reachable as an unaligned
> > unintended gadget, so they keep the one-page guard and pay no cost.
> >
> > The override is gated on CONFIG_X86_64 rather than applying to all of x86:
> > VMAP_STACK is selected only on x86_64, so 32-bit kernel stacks are not in
> > the vmalloc area and the technique does not apply there. 32-bit x86 also
> > has a far smaller vmalloc window, where widening every guarded area by 16
> > pages would needlessly pressure the address space.
> >
> > The guard pages are never populated, so there is no extra physical
> > memory and no additional page-table population beyond the larger virtual
> > span; the cost is virtual address space and vmap_area bookkeeping, which
> > is negligible against the 64-bit vmalloc window. get_vm_area_size() is
> > adjusted by the same VMAP_GUARD_SIZE so the usable size reported to
> > callers is unchanged.
> >
> > On x86 this widens the guard for all guarded vmap areas, not only thread
> > stacks. ret2enter targets the stack specifically, so a narrower
> > alternative is to apply the wider guard only on the thread-stack
> > allocation path via a dedicated VM_ flag; we kept the change in the
> > common path as defense in depth for any vmalloc-adjacent pivot target,
> > but are happy to scope it to stacks if maintainers prefer.
> >
> > Signed-off-by: Xiang Mei <xmei5@asu.edu>
> > Signed-off-by: Jennifer Miller <jmill@asu.edu>
> > ---
> >  arch/x86/include/asm/vmalloc.h | 21 +++++++++++++++++++++
> >  include/linux/vmalloc.h        | 16 ++++++++++++++--
> >  mm/vmalloc.c                   |  2 +-
> >  3 files changed, 36 insertions(+), 3 deletions(-)
> >
> > diff --git a/arch/x86/include/asm/vmalloc.h b/arch/x86/include/asm/vmalloc.h
> > index 49ce331f3ac6..2c341f398227 100644
> > --- a/arch/x86/include/asm/vmalloc.h
> > +++ b/arch/x86/include/asm/vmalloc.h
> > @@ -5,6 +5,27 @@
> >  #include <asm/page.h>
> >  #include <asm/pgtable_areas.h>
> >
> > +/*
> > + * The x86 ENTER instruction can be used as a one-instruction stack pivot:
> > + * ENTER imm16, imm8 lowers RSP by imm16 + 8 * (L + 1), L = imm8 & 0x1f.
> > + * imm16 is an unsigned 16-bit operand (ENTER never raises RSP) and L is in
> > + * [0, 31], so a single ENTER can lower RSP by at most
> > + * 0xffff + 8 * 0x20 = 0x100ff bytes. With CONFIG_VMAP_STACK the kernel
> > + * stack lives in the vmalloc area, where an unprivileged user can spray
> > + * adjacent allocations; a single-page guard is too small to contain such a
> > + * pivot. Use 0x11 guard pages (0x11000 bytes), the smallest whole-page
> > + * span exceeding 0x100ff, so the pivot faults in the guard instead of
> > + * landing in attacker-controlled memory.
> > + *
> > + * Restrict this to 64-bit: VMAP_STACK is selected only on x86_64, so 32-bit
> > + * kernel stacks are not in the vmalloc area and the technique does not apply.
> > + * 32-bit also has a far smaller vmalloc window, where a 16-page-per-area
> > + * widening would needlessly pressure the address space.
> > + */
> > +#ifdef CONFIG_X86_64
> > +#define VMAP_GUARD_PAGES     0x11
> > +#endif
> > +
> >  #ifdef CONFIG_HAVE_ARCH_HUGE_VMAP
> >
> >  #ifdef CONFIG_X86_64
> > diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h
> > index 3b02c0c6b371..b8546e519deb 100644
> > --- a/include/linux/vmalloc.h
> > +++ b/include/linux/vmalloc.h
> > @@ -49,6 +49,18 @@ struct iov_iter;           /* in uio.h */
> >  #define IOREMAP_MAX_ORDER    (7 + PAGE_SHIFT)        /* 128 pages */
> >  #endif
> >
> > +/*
> > + * Number of unmapped guard pages appended to each guarded vmalloc
> > + * allocation. The default is a single page; an architecture may override
> > + * VMAP_GUARD_PAGES (via asm/vmalloc.h) when a wider guard is needed to
> > + * contain a worst-case single-instruction stack pivot into an adjacent,
> > + * attacker-controlled vmap allocation (see arch/x86 for the ENTER case).
> > + */
> > +#ifndef VMAP_GUARD_PAGES
> > +#define VMAP_GUARD_PAGES     1
> > +#endif
> > +#define VMAP_GUARD_SIZE              (VMAP_GUARD_PAGES * PAGE_SIZE)
> > +
> >  struct vm_struct {
> >       union {
> >               struct vm_struct *next;   /* Early registration of vm_areas. */
> > @@ -236,8 +248,8 @@ int vmap_pages_range(unsigned long addr, unsigned long end, pgprot_t prot,
> >  static inline size_t get_vm_area_size(const struct vm_struct *area)
> >  {
> >       if (!(area->flags & VM_NO_GUARD))
> > -             /* return actual size without guard page */
> > -             return area->size - PAGE_SIZE;
> > +             /* return actual size without guard region */
> > +             return area->size - VMAP_GUARD_SIZE;
> >       else
> >               return area->size;
> >
> > diff --git a/mm/vmalloc.c b/mm/vmalloc.c
> > index bb6ae08d18f5..8bb2b3ef40a8 100644
> > --- a/mm/vmalloc.c
> > +++ b/mm/vmalloc.c
> > @@ -3217,7 +3217,7 @@ struct vm_struct *__get_vm_area_node(unsigned long size,
> >               return NULL;
> >
> >       if (!(flags & VM_NO_GUARD))
> > -             size += PAGE_SIZE;
> > +             size += VMAP_GUARD_SIZE;
> >
> >       area->flags = flags;
> >       area->caller = caller;
> > --
> > 2.43.0
> >
>
>
> Hi hardening team and maintainers,
>
> This patch widens the vmalloc guard region on x86_64 to close a
> class of stack-pivot primitive against CONFIG_VMAP_STACK kernels. We would
> like the hardening and mm/x86 maintainers' view on whether this belongs in
> the common guard path or should be scoped to thread stacks.
>
> The description below is intentionally theoretical: it explains why a single
> guard page is insufficient, without operational exploitation detail.
>
> Background / threat model
> =========================
>
> With CONFIG_VMAP_STACK (the default on x86_64), kernel thread stacks live in
> the vmalloc area, which allocates roughly linearly. In principle an
> unprivileged user can place attacker-controlled vmalloc allocations near a
> target stack, the vmalloc area exposes several allocation paths reachable
> from userspace, so at the moment of a control-flow hijack the live stack
> can be adjacent to attacker-controlled mapped pages.
>
> Today each guarded vmalloc allocation is followed by a single unmapped guard
> page. The guard relies on the assumption that a corrupted RSP cannot jump
> far enough, in one step, to clear the guard and land in the next mapped
> allocation. On x86_64 that assumption does not hold.
>
> The primitive
> =============
>
> The x86_64 ENTER instruction (ENTER imm16, imm8) builds a stack frame in a
> single instruction and lowers RSP by:
>
>     imm16 + 8 * (L + 1),  L = imm8 & 0x1f
>
> imm16 is an unsigned 16-bit operand and ENTER never raises RSP, so a single
> ENTER lowers RSP by up to 0xffff + 8 * 0x20 = 0x100ff bytes, which is more than a
> page. A single instruction can therefore move RSP off the current stack,
> across the one-page guard, and into an adjacent vmalloc allocation.
>
> Theoretically this matters because:
>
>   - The displacement is attacker-chosen (via the immediates) up to 0x100ff,
>     so the pivot can clear any guard narrower than that in one step.
>   - ENTER is reachable as a gadget, so a pivot of this size is available
>     without depending on register state at the hijack site.
>   - The pivot happens after the control transfer, so it is not constrained
>     by forward-edge CFI (kCFI / FineIBT).
Please ignore this line; it is not related since we assume we already
have a CFH primitive. Sorry for the confusion.
>
> Taken together these make it a one-gadget-style pivot that generalizes
> across control-flow hijacks: because no register control is required at the
> hijack site, any control-flow hijack able to reach a single ENTER gadget can
> move RSP into an adjacent vmalloc allocation. The net effect is that a single
> guard page is not a reliable boundary between a pivoted RSP and the next
> mapped allocation on x86_64.
>
> Because this lifts a generic primitive across control-flow hijacks rather
> than easing one specific bug, we think the guard itself should be widened so
> the boundary holds regardless of the originating hijack.
>
> We have a working proof-of-concept and are happy to share it privately with
> maintainers; we are keeping the offensive details off the public list.
>
> The fix
> =======
>
> Widen the guard region from one page to the smallest whole-page span that
> exceeds the worst-case single-ENTER displacement: 0x11 pages (0x11000 bytes
> > 0x100ff). A pivot off the top of the stack then lands in unmapped guard
> memory and faults, instead of in mapped, attacker-controlled pages.
>
> This is scoped to x86_64 via a VMAP_GUARD_PAGES knob that defaults to one
> page (no change for any other architecture) and is overridden only under
> CONFIG_X86_64. The 0x100ff bound is a property of the ENTER opcode, which is
> also a one-byte unintended gadget; other architectures have no equivalent
> single-instruction, immediate-controlled pivot reachable as an unaligned
> gadget, so they keep the one-page guard and pay no cost. VMAP_STACK is
> selected only on x86_64, so 32-bit x86 is excluded as well.
>
> Cost: the guard pages are never populated, so there is no extra physical
> memory and no extra page-table population beyond the larger virtual span --
> only virtual address space and vmap_area bookkeeping, negligible against the
> 64-bit vmalloc window. get_vm_area_size() is adjusted by the same amount so
> the usable size reported to callers is unchanged.
>
> Open question for maintainers
> =============================
>
> On x86_64 this widens the guard for all guarded vmap areas, not only thread
> stacks. ret2enter targets the stack specifically, so a narrower alternative
> is to apply the wider guard only on the thread-stack allocation path via a
> dedicated VM_ flag. We kept it in the common path as defense in depth for any
> vmalloc-adjacent pivot target, but are happy to scope it to stacks if you
> prefer.
>
> We would like to hear your feedback and suggestions.
>
> Thanks,
> Xiang
>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-26 17:34 [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot Xiang Mei
  2026-06-26 17:46 ` Xiang Mei
@ 2026-06-26 19:39 ` Pedro Falcato
  2026-06-26 20:05   ` Xiang Mei
  1 sibling, 1 reply; 13+ messages in thread
From: Pedro Falcato @ 2026-06-26 19:39 UTC (permalink / raw)
  To: Xiang Mei
  Cc: Kees Cook, Andrew Morton, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, linux-hardening,
	Uladzislau Rezki, Gustavo A . R . Silva, H . Peter Anvin,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On Fri, Jun 26, 2026 at 10:34:44AM -0700, Xiang Mei wrote:
> With CONFIG_VMAP_STACK, kernel stacks are allocated in the vmalloc area,
> which an unprivileged user can surround with attacker-controlled data by
> spraying vmap allocations adjacent to a target stack (for example via
> XDP_UMEM_REG, though other vmalloc spray paths work too). Today each
> guarded vmalloc allocation is followed by a single unmapped guard page.
> 
> A single guard page is not enough to contain the x86_64 ENTER
> instruction used as a one-instruction stack pivot. ENTER imm16, imm8
> builds a stack frame and lowers RSP by:
> 
> 	imm16 + 8 * (L + 1),  L = imm8 & 0x1f
> 
> imm16 is an unsigned 16-bit operand (ENTER never raises RSP), and L is
> in [0, 31], so the maximum displacement of a single ENTER is:
> 
> 	0xffff + 8 * 0x20 = 0x100ff bytes
> 
> That is more than enough to step off the current stack, across the
> one-page guard, and into the adjacent sprayed pages. When those pages
> contain a return sled feeding a ROP chain, reaching any ENTER gadget
> (opcode 0xc8, abundant as both intended and unintended gadgets) turns a
> control-flow hijack into full ROP execution without any register control
> at the hijack site, making it a one-gadget-style primitive that
> significantly eases exploitation. The pivot happens after the control
> transfer, so it is not constrained by CFI (kCFI/FineIBT).
> 
> Widen the guard region from one page to VMAP_GUARD_PAGES (0x11 pages,
> 0x11000 bytes), which is the smallest whole-page span exceeding the
> 0x100ff-byte maximum single-ENTER pivot. A pivot off the top of the
> stack now lands in the unmapped guard and faults, instead of in mapped,
> attacker-controlled memory. RANDOMIZE_KSTACK_OFFSET only perturbs RSP by
> a sub-page amount, so it does not change the required width.

What's so special about enter? Why do we need to design our guard pages
around it? FWIW, I can't find enter instructions in any of my kernel builds,
nor can I convince gcc (on godbolt) to generate an enter instruction.

If it's just "this is a single instruction that adjusts RSP", why is e.g.
sub imm32, %rsp ok?

FTR, I think it's fine that you're proposing more guard pages; virtual address
space is virtually free on 64-bit architectures (apart from lower page table
density, which may take a little toll on memory usage and/or page table
caching). I'm just wondering why enter is being used as the concrete target
for this.

-- 
Pedro

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-26 19:39 ` Pedro Falcato
@ 2026-06-26 20:05   ` Xiang Mei
  2026-06-29  2:09     ` H. Peter Anvin
  0 siblings, 1 reply; 13+ messages in thread
From: Xiang Mei @ 2026-06-26 20:05 UTC (permalink / raw)
  To: Pedro Falcato
  Cc: Kees Cook, Andrew Morton, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, linux-hardening,
	Uladzislau Rezki, Gustavo A . R . Silva, H . Peter Anvin,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On Fri, Jun 26, 2026 at 12:39 PM Pedro Falcato <pfalcato@suse.de> wrote:
>
> On Fri, Jun 26, 2026 at 10:34:44AM -0700, Xiang Mei wrote:
> > With CONFIG_VMAP_STACK, kernel stacks are allocated in the vmalloc area,
> > which an unprivileged user can surround with attacker-controlled data by
> > spraying vmap allocations adjacent to a target stack (for example via
> > XDP_UMEM_REG, though other vmalloc spray paths work too). Today each
> > guarded vmalloc allocation is followed by a single unmapped guard page.
> >
> > A single guard page is not enough to contain the x86_64 ENTER
> > instruction used as a one-instruction stack pivot. ENTER imm16, imm8
> > builds a stack frame and lowers RSP by:
> >
> >       imm16 + 8 * (L + 1),  L = imm8 & 0x1f
> >
> > imm16 is an unsigned 16-bit operand (ENTER never raises RSP), and L is
> > in [0, 31], so the maximum displacement of a single ENTER is:
> >
> >       0xffff + 8 * 0x20 = 0x100ff bytes
> >
> > That is more than enough to step off the current stack, across the
> > one-page guard, and into the adjacent sprayed pages. When those pages
> > contain a return sled feeding a ROP chain, reaching any ENTER gadget
> > (opcode 0xc8, abundant as both intended and unintended gadgets) turns a
> > control-flow hijack into full ROP execution without any register control
> > at the hijack site, making it a one-gadget-style primitive that
> > significantly eases exploitation. The pivot happens after the control
> > transfer, so it is not constrained by CFI (kCFI/FineIBT).
> >
> > Widen the guard region from one page to VMAP_GUARD_PAGES (0x11 pages,
> > 0x11000 bytes), which is the smallest whole-page span exceeding the
> > 0x100ff-byte maximum single-ENTER pivot. A pivot off the top of the
> > stack now lands in the unmapped guard and faults, instead of in mapped,
> > attacker-controlled memory. RANDOMIZE_KSTACK_OFFSET only perturbs RSP by
> > a sub-page amount, so it does not change the required width.
>
> What's so special about enter? Why do we need to design our guard pages
> around it? FWIW, I can't find enter instructions in any of my kernel builds,
> nor can I convince gcc (on godbolt) to generate an enter instruction.
>

Thanks for your questions and attention.

1) `enter` can do big enough stack pivoting (where we can't find many
`add/sub/adc/sbb rsp, ` doing so)
2) `enter` is not rare since we can take part in the instruction.
Therefore, we are searching for `\xc8.*` (lose upper bound).

Take its subset as an example and perform the search on a default
config kernel, we search for gadgets and filter by `grep ": enter
0x5...,"`
```
0xffffffff853d9c44: enter 0x5157, 0xfc; mov eax, ebp; pop rbx; pop
rbp; pop r12; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff85514c34: enter 0x5e9f, 0xfc; mov eax, ebx; pop rbx; pop
rbp; pop r12; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff855d2034: enter 0x52cb, 0xfc; mov eax, ebx; pop rbx; pop
rbp; pop r12; pop r13; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff8589e155: enter 0x5c41, 0x41; pop rbp; pop r14; pop r15; jmp
0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff85d1f06a: enter 0x5d03, 0x41; pop rsp; pop r13; jmp
0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff85d51a2d: enter 0x5c41, 0x41; pop rbp; pop r14; pop r15; jmp
0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff862e8d3c: enter 0x5822, 5; jne short 0xffffffff862e8db0; add
rsp, 0xc8; pop rbx; pop r12; pop r13; pop r14; pop r15; pop rbp; jmp
0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff863a551e: enter 0x5c41, 0x41; pop rbp; pop r14; pop r15; pop
rbp; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff8658b134: enter 0x573a, 0xfb; mov eax, ebp; add rsp, 8; pop
rbx; pop rbp; pop r12; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff8658d434: enter 0x5717, 0xfb; mov eax, ebp; add rsp, 8; pop
rbx; pop rbp; pop r12; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff8679fcd6: enter 0x5d5b, 0x41; pop rsp; pop r13; pop r14; pop
r15; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
12:55 n132@p1 /home/n132/kCTF/fx0
% cat ./gadgets | grep ret | grep ": enter 0x5...," | wc
     40     672    5269
```
Each of them can be used to perform one-gadget stack pivoting for a
Control Flow Hijacking primitive.

> If it's just "this is a single instruction that adjusts RSP", why is e.g.
> sub imm32, %rsp ok?
>
These gadgets are common, but most of them can't skip the garden page,
and the operands are less than one page.
On the same kernel, we found 0 usable gadgets (pivot the RSP out of
the current stack frame) for such gadget family:
```
13:00 n132@p1 /home/n132/kCTF/fx0
% cat ./gadgets | grep ret | grep ": sub rsp, 0x....;"
13:01 n132@p1 /home/n132/kCTF/fx0
% cat ./gadgets | grep ret | grep ": add rsp, 0x....;"
13:01 n132@p1 /home/n132/kCTF/fx0
% cat ./gadgets | grep ret | grep ": adc rsp, 0x....;"
13:01 n132@p1 /home/n132/kCTF/fx0
% cat ./gadgets | grep ret | grep ": sbb rsp, 0x....;"
13:01 n132@p1 /home/n132/kCTF/fx0
% cat ./gadgets | grep ret | grep ": sbb rsp, 0x"  | less
13:01 n132@p1 /home/n132/kCTF/fx0
% cat ./gadgets | grep ret | grep ": add rsp, 0x"  | tail
0xffffffff86864d99: add rsp, 0x60; pop rbx; pop rbp; jmp
0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff86864e85: add rsp, 0x60; pop rbx; pop rbp; jmp
0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff86864f7b: add rsp, 0x60; pop rbx; pop rbp; jmp
0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff86865248: add rsp, 0x90; pop rbx; pop rbp; pop r12; pop r13;
pop r14; pop r15; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff86865fd2: add rsp, 0xd0; pop rbx; pop rbp; pop r12; pop r13;
pop r14; pop r15; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff86866fcc: add rsp, 0x68; pop rbx; pop rbp; pop r12; pop r13;
pop r14; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff86867463: add rsp, 0x90; pop rbx; pop rbp; pop r12; pop r13;
pop r14; pop r15; jmp 0xffffffff86869bf0 <__x86_return_thunk>;
0xffffffff86867944: add rsp, 0xa8; mov eax, ebp; pop rbx; pop rbp; pop
r12; pop r13; pop r14; pop r15; jmp 0xffffffff86869bf0
<__x86_return_thunk>;
0xffffffff86867b8f: add rsp, 0xb8; mov eax, r12d; pop rbx; pop rbp;
pop r12; pop r13; pop r14; pop r15; jmp 0xffffffff86869bf0
<__x86_return_thunk>;
0xffffffff86869b7d: add rsp, 0x80; mov qword ptr gs:[rip+0x5001490],
0xffffffffffffffff; ret;
```
> FTR, I think it's fine that you're proposing more guard pages; virtual address
> space is virtually free on 64-bit architectures (apart from lower page table
> density, which may take a little toll on memory usage and/or page table
> caching). I'm just wondering why enter is being used as the concrete target
> for this.

Thanks for your feedback. We are glad to provide more information and
its practical success for real CVEs.

Xiang
>
> --
> Pedro


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-26 20:05   ` Xiang Mei
@ 2026-06-29  2:09     ` H. Peter Anvin
  2026-06-29  4:43       ` Matthew Wilcox
  0 siblings, 1 reply; 13+ messages in thread
From: H. Peter Anvin @ 2026-06-29  2:09 UTC (permalink / raw)
  To: Xiang Mei, Pedro Falcato
  Cc: Kees Cook, Andrew Morton, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, linux-hardening,
	Uladzislau Rezki, Gustavo A . R . Silva, linux-mm, linux-kernel,
	Jennifer Miller, Tiffany Bao, Ruoyu Wang, Adam Doupe, Kyle Zeng,
	Yan Shoshitaishvili

On 2026-06-26 13:05, Xiang Mei wrote:
> 
> Thanks for your questions and attention.
> 
> 1) `enter` can do big enough stack pivoting (where we can't find many
> `add/sub/adc/sbb rsp, ` doing so)

Say what?

Using add/sub/lea on %rsp is probably more common than enter, because enter is
considered kind of slow.

gcc, I believe, will generate "leave" but not "enter" as a result.


> 2) `enter` is not rare since we can take part in the instruction.

This sentence doesn't parse.

	-hpa



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-29  2:09     ` H. Peter Anvin
@ 2026-06-29  4:43       ` Matthew Wilcox
  2026-06-29 12:50         ` H. Peter Anvin
  0 siblings, 1 reply; 13+ messages in thread
From: Matthew Wilcox @ 2026-06-29  4:43 UTC (permalink / raw)
  To: H. Peter Anvin
  Cc: Xiang Mei, Pedro Falcato, Kees Cook, Andrew Morton,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	linux-hardening, Uladzislau Rezki, Gustavo A . R . Silva,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On Sun, Jun 28, 2026 at 07:09:37PM -0700, H. Peter Anvin wrote:
> On 2026-06-26 13:05, Xiang Mei wrote:
> > 
> > Thanks for your questions and attention.
> > 
> > 1) `enter` can do big enough stack pivoting (where we can't find many
> > `add/sub/adc/sbb rsp, ` doing so)
> 
> Say what?
> 
> Using add/sub/lea on %rsp is probably more common than enter, because enter is
> considered kind of slow.
> 
> gcc, I believe, will generate "leave" but not "enter" as a result.
> 
> 
> > 2) `enter` is not rare since we can take part in the instruction.
> 
> This sentence doesn't parse.

They mean that we can jump into the middle of an instruction, and it is
not rare to see a 'c8' byte in the instruction stream.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-29  4:43       ` Matthew Wilcox
@ 2026-06-29 12:50         ` H. Peter Anvin
  2026-06-29 17:21           ` Xiang Mei
  0 siblings, 1 reply; 13+ messages in thread
From: H. Peter Anvin @ 2026-06-29 12:50 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: Xiang Mei, Pedro Falcato, Kees Cook, Andrew Morton,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	linux-hardening, Uladzislau Rezki, Gustavo A . R . Silva,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On 2026-06-28 21:43, Matthew Wilcox wrote:
> On Sun, Jun 28, 2026 at 07:09:37PM -0700, H. Peter Anvin wrote:
>>
>>> 2) `enter` is not rare since we can take part in the instruction.
>>
>> This sentence doesn't parse.
> 
> They mean that we can jump into the middle of an instruction, and it is
> not rare to see a 'c8' byte in the instruction stream.
>

OK, I see the point now. The main difference is that ENTER doesn't require a
REX prefix, whereas ADD/SUB/LEA do (otherwise it truncates RSP and everything
immediately dies as a result of SMAP violations [you are welcome].)

This means that an offending bit combination is far less common.

	-hpa



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-29 12:50         ` H. Peter Anvin
@ 2026-06-29 17:21           ` Xiang Mei
  2026-06-29 21:50             ` Xiang Mei
  0 siblings, 1 reply; 13+ messages in thread
From: Xiang Mei @ 2026-06-29 17:21 UTC (permalink / raw)
  To: H. Peter Anvin
  Cc: Matthew Wilcox, Pedro Falcato, Kees Cook, Andrew Morton,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	linux-hardening, Uladzislau Rezki, Gustavo A . R . Silva,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On Mon, Jun 29, 2026 at 5:50 AM H. Peter Anvin <hpa@zytor.com> wrote:
>
> On 2026-06-28 21:43, Matthew Wilcox wrote:
> > On Sun, Jun 28, 2026 at 07:09:37PM -0700, H. Peter Anvin wrote:
> >>
> >>> 2) `enter` is not rare since we can take part in the instruction.
> >>
> >> This sentence doesn't parse.
> >
> > They mean that we can jump into the middle of an instruction, and it is
> > not rare to see a 'c8' byte in the instruction stream.
> >
>
> OK, I see the point now. The main difference is that ENTER doesn't require a
> REX prefix, whereas ADD/SUB/LEA do (otherwise it truncates RSP and everything
> immediately dies as a result of SMAP violations [you are welcome].)
>

Yes, Matthew's explanation is correct. (Btw, thanks Matthew!)

Xiang

> This means that an offending bit combination is far less common.
>
>         -hpa
>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-29 17:21           ` Xiang Mei
@ 2026-06-29 21:50             ` Xiang Mei
  2026-06-29 21:51               ` Xiang Mei
  0 siblings, 1 reply; 13+ messages in thread
From: Xiang Mei @ 2026-06-29 21:50 UTC (permalink / raw)
  To: Andrew Morton
  Cc: H. Peter Anvin, Matthew Wilcox, Pedro Falcato, Kees Cook,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	linux-hardening, Uladzislau Rezki, Gustavo A . R . Silva,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

An AI reviewer found an issue:
https://sashiko.dev/#/patchset/20260626173444.2252041-1-xmei5@asu.edu
And that's correct; we added one more change in v2:

--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -3217,7 +3217,7 @@ struct vm_struct *__get_vm_area_node(unsigned long size,
return NULL;
if (!(flags & VM_NO_GUARD))
- size += PAGE_SIZE;
+ size += VMAP_GUARD_SIZE;
area->flags = flags;
area->caller = caller;

v2 was sent: https://lore.kernel.org/linux-mm/20260629214712.1198680-1-xmei5@asu.edu/T/#u

Thanks,
Xiang

On Mon, Jun 29, 2026 at 10:21 AM Xiang Mei <xmei5@asu.edu> wrote:
>
> On Mon, Jun 29, 2026 at 5:50 AM H. Peter Anvin <hpa@zytor.com> wrote:
> >
> > On 2026-06-28 21:43, Matthew Wilcox wrote:
> > > On Sun, Jun 28, 2026 at 07:09:37PM -0700, H. Peter Anvin wrote:
> > >>
> > >>> 2) `enter` is not rare since we can take part in the instruction.
> > >>
> > >> This sentence doesn't parse.
> > >
> > > They mean that we can jump into the middle of an instruction, and it is
> > > not rare to see a 'c8' byte in the instruction stream.
> > >
> >
> > OK, I see the point now. The main difference is that ENTER doesn't require a
> > REX prefix, whereas ADD/SUB/LEA do (otherwise it truncates RSP and everything
> > immediately dies as a result of SMAP violations [you are welcome].)
> >
>
> Yes, Matthew's explanation is correct. (Btw, thanks Matthew!)
>
> Xiang
>
> > This means that an offending bit combination is far less common.
> >
> >         -hpa
> >

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-29 21:50             ` Xiang Mei
@ 2026-06-29 21:51               ` Xiang Mei
  0 siblings, 0 replies; 13+ messages in thread
From: Xiang Mei @ 2026-06-29 21:51 UTC (permalink / raw)
  To: Andrew Morton
  Cc: H. Peter Anvin, Matthew Wilcox, Pedro Falcato, Kees Cook,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	linux-hardening, Uladzislau Rezki, Gustavo A . R . Silva,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On Mon, Jun 29, 2026 at 2:50 PM Xiang Mei <xmei5@asu.edu> wrote:
>
> An AI reviewer found an issue:
> https://sashiko.dev/#/patchset/20260626173444.2252041-1-xmei5@asu.edu
> And that's correct; we added one more change in v2:
>
> --- a/mm/vmalloc.c
> +++ b/mm/vmalloc.c
> @@ -3217,7 +3217,7 @@ struct vm_struct *__get_vm_area_node(unsigned long size,
> return NULL;
> if (!(flags & VM_NO_GUARD))
> - size += PAGE_SIZE;
> + size += VMAP_GUARD_SIZE;
> area->flags = flags;
> area->caller = caller;
>
Sorry for my mistake; the change I made was wrong. What we added in v2 is:

```
@@ -5027,7 +5027,7 @@ struct vm_struct **pcpu_get_vm_areas(const
unsigned long *offsets,
spin_lock(&vn->busy.lock);
insert_vmap_area(vas[area], &vn->busy.root, &vn->busy.head);
- setup_vmalloc_vm(vms[area], vas[area], VM_ALLOC,
+ setup_vmalloc_vm(vms[area], vas[area], VM_ALLOC | VM_NO_GUARD,
pcpu_get_vm_areas);
spin_unlock(&vn->busy.lock);
}
```

Xiang
> v2 was sent: https://lore.kernel.org/linux-mm/20260629214712.1198680-1-xmei5@asu.edu/T/#u
>
> Thanks,
> Xiang
>
> On Mon, Jun 29, 2026 at 10:21 AM Xiang Mei <xmei5@asu.edu> wrote:
> >
> > On Mon, Jun 29, 2026 at 5:50 AM H. Peter Anvin <hpa@zytor.com> wrote:
> > >
> > > On 2026-06-28 21:43, Matthew Wilcox wrote:
> > > > On Sun, Jun 28, 2026 at 07:09:37PM -0700, H. Peter Anvin wrote:
> > > >>
> > > >>> 2) `enter` is not rare since we can take part in the instruction.
> > > >>
> > > >> This sentence doesn't parse.
> > > >
> > > > They mean that we can jump into the middle of an instruction, and it is
> > > > not rare to see a 'c8' byte in the instruction stream.
> > > >
> > >
> > > OK, I see the point now. The main difference is that ENTER doesn't require a
> > > REX prefix, whereas ADD/SUB/LEA do (otherwise it truncates RSP and everything
> > > immediately dies as a result of SMAP violations [you are welcome].)
> > >
> >
> > Yes, Matthew's explanation is correct. (Btw, thanks Matthew!)
> >
> > Xiang
> >
> > > This means that an offending bit combination is far less common.
> > >
> > >         -hpa
> > >

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-26 17:48   ` Xiang Mei
@ 2026-06-30  7:12     ` Peter Zijlstra
  2026-06-30  8:09       ` Xiang Mei
  0 siblings, 1 reply; 13+ messages in thread
From: Peter Zijlstra @ 2026-06-30  7:12 UTC (permalink / raw)
  To: Xiang Mei
  Cc: Kees Cook, Andrew Morton, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, linux-hardening,
	Uladzislau Rezki, Gustavo A . R . Silva, H . Peter Anvin,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On Fri, Jun 26, 2026 at 10:48:46AM -0700, Xiang Mei wrote:

> >   - The displacement is attacker-chosen (via the immediates) up to 0x100ff,
> >     so the pivot can clear any guard narrower than that in one step.
> >   - ENTER is reachable as a gadget, so a pivot of this size is available
> >     without depending on register state at the hijack site.
> >   - The pivot happens after the control transfer, so it is not constrained
> >     by forward-edge CFI (kCFI / FineIBT).

> Please ignore this line; it is not related since we assume we already
> have a CFH primitive. Sorry for the confusion.

So I am still confused by all this. CFI does remove a ton of CFH
primitives. Until we have Shadow Stacks sorted, ROP will obviously be
the main alternative, but I'm really struggling to justify adding 16
guard pages rather than going after any actual control flow hijacking
primitives.

I mean, if you have a reliable CFH, we should be fixing that. But
somehow I'm thinking that if you do have one, ENTER isn't going to be
the worst of it.

Or am I missing something here?

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot
  2026-06-30  7:12     ` Peter Zijlstra
@ 2026-06-30  8:09       ` Xiang Mei
  0 siblings, 0 replies; 13+ messages in thread
From: Xiang Mei @ 2026-06-30  8:09 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Kees Cook, Andrew Morton, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, linux-hardening,
	Uladzislau Rezki, Gustavo A . R . Silva, H . Peter Anvin,
	linux-mm, linux-kernel, Jennifer Miller, Tiffany Bao, Ruoyu Wang,
	Adam Doupe, Kyle Zeng, Yan Shoshitaishvili

On Tue, Jun 30, 2026 at 12:13 AM Peter Zijlstra <peterz@infradead.org> wrote:
>
> On Fri, Jun 26, 2026 at 10:48:46AM -0700, Xiang Mei wrote:
>
> > >   - The displacement is attacker-chosen (via the immediates) up to 0x100ff,
> > >     so the pivot can clear any guard narrower than that in one step.
> > >   - ENTER is reachable as a gadget, so a pivot of this size is available
> > >     without depending on register state at the hijack site.
> > >   - The pivot happens after the control transfer, so it is not constrained
> > >     by forward-edge CFI (kCFI / FineIBT).
>
> > Please ignore this line; it is not related since we assume we already
> > have a CFH primitive. Sorry for the confusion.
>
> So I am still confused by all this. CFI does remove a ton of CFH
> primitives. Until we have Shadow Stacks sorted, ROP will obviously be
> the main alternative, but I'm really struggling to justify adding 16
> guard pages rather than going after any actual control flow hijacking
> primitives.

Sorry for my wrong information. This is not related to CFI. I'll remove it.

We assume we have a CFHP (control flow hijacking primitive), and ENTER
gadgets escalate the CFHP to ROP execution.

>
> I mean, if you have a reliable CFH, we should be fixing that. But
> somehow I'm thinking that if you do have one, ENTER isn't going to be
> the worst of it.
>
> Or am I missing something here?

You are totally correct. ENTER only works when we have CFH. It works
as a stable one-gadget primitive escalation (from CFH to Arbitrary
Code Execution), and we propose this patch as a hardening feature.
Sorry for the misunderstanding because of my mistake.

Xiang

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-06-30  8:09 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-26 17:34 [PATCH] mm/vmalloc: widen guard region to defeat ENTER-based stack pivot Xiang Mei
2026-06-26 17:46 ` Xiang Mei
2026-06-26 17:48   ` Xiang Mei
2026-06-30  7:12     ` Peter Zijlstra
2026-06-30  8:09       ` Xiang Mei
2026-06-26 19:39 ` Pedro Falcato
2026-06-26 20:05   ` Xiang Mei
2026-06-29  2:09     ` H. Peter Anvin
2026-06-29  4:43       ` Matthew Wilcox
2026-06-29 12:50         ` H. Peter Anvin
2026-06-29 17:21           ` Xiang Mei
2026-06-29 21:50             ` Xiang Mei
2026-06-29 21:51               ` Xiang Mei

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.