On Fri, Jun 26, 2026 at 12:39 PM Pedro Falcato <[email protected]> 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