Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <aWilU81fLhCbVn/W@pi3.com.pl>
Date: Thu, 15 Jan 2026 09:29:07 +0100
From: Adam Zabrocki <pi3@....com.pl>
To: oss-security@...ts.openwall.com
Cc: Solar Designer <solar@...nwall.com>
Subject: Re: The Curious Case of Stack Pivot Detection

Hello,

Thanks for the summary of your experiments. Since LKRG was referenced, I wanted
to clarify what LKRG's pCFI is actually trying to protect and how it relates to
your observations.

It looks like that you are mostly talking about user-mode stack protection
rather than kernel mode. LKRG does not attempt to enforce a "valid user stack"
policy for user-mode processes/threads. As you observed, user-space stacks are
often runtime-managed and can legitimately move in ways that make a strict
policy noisy. Instead, LKRG's pCFI targets kernel-mode control-flow abuse and
validates kernel stacks only. I'm not aware of any user-mode like stack
variations in kernel mode stacks.

In LKRG terminology, pCFI ("poor man’s CFI") has two validation levels, exposed
via lkrg.pcfi_validate:
 - pcfi_validate=0: disables pCFI.
 - pcfi_validate=1: validate the kernel stack pointer - this is exactly what you
                    described as "a 4-year-old can come up with it".
 - pcfi_validate=2: LKRG performs a full stack walk and verifies
   that every return address on the stack points to a valid kernel instruction
   in a legitimate code section (.text) and not to the unexpected data area.

Note: We actually see other projects directly copying both ideas to their
projects (some of them literally copying 1:1 logic).

On x86 specifically, whether "validate all stack frames" is possible/robust
depends on the kernel's unwinder configuration. Kernels typically use either
frame pointers (CONFIG_FRAME_POINTER) or ORC unwinding (CONFIG_UNWINDER_ORC);
ORC provides reliable unwinding without requiring frame pointers. In practice,
some distros prefer to keep frame pointer instead of ORC (usually some LTS) but
ORC (then FP is usually disabled) become more popular.

Regarding your question about binaries compiled with -fomit-frame-pointer (and
the FP register being reused): when the kernel is built without frame pointers,
LKRG does not treat the FP register as a trustworthy frame chain. In that case,
frame-level validation relies on the kernel's unwinder support (e.g., ORC on
x86) rather than on FP.

Thanks again for the detailed analysis — I agree this whole problem looks very
different (and much harder) on the user-space side than it does for kernel
stacks.

Thanks,
Adam

On Sat, Jan 10, 2026 at 06:54:39PM +0000, Ali Polatel wrote:
> Dear kind people,
> 
> I hope you're fine and healthy. The reason I am writing this mail is to share
> a few of my experiments and research I've done to come up with a reasonable
> stack pivot detection for the Syd kernel. TL;DR I have failed and I have learned
> a lot. I have also learned everyone is doing various levels of wrong and I have
> yet to come up with a "correct" solution. Are we doing it wrong or are we attacking
> the wrong link? Please discuss.
> 
> The obvious idea is to check if stack pointer points to a valid stack region at
> various boundaries. This solution is so obvious you'd imagine even a 4-year-old
> can come up with it, yet G**gle has a patent[1] on it. Curious (mis)use of software
> patents where an entity patents the equation "foo < bar < baz". What do we do if
> they go one step ahead and patent "<"? Use emoji for maths? Move to Mars?
> 
> Anyhow, this is not what I am here to discuss. OpenBSD does something similar
> with MAP_STACK[2] and Windows 8 had a similar mitigation until someone demonstrated
> a trivial bypass[3]. Finding out this bypass was an important step forward for me so
> I went ahead and rewrote it[4] for UNIX and verified it bypasses what Syd had
> at the time and what OpenBSD has. This bypass is a simple improvement of what
> OpenBSD regression tests have and involves a quick jumpback to the stack. Funnily
> if you remove the printf in the intermediate stage of the bypass OpenBSD's SP check
> at write(2) boundary will catch and kill you so keep it quiet as you bypass this
> delicate mitigation ;).
> 
> Another roadblock for the SP points to a stack region detection is in userspace
> there's no clear definition of "stack". The stack of the main thread is handled by
> the kernel where everything is fine but thread stacks are typically your language
> runtime's business. As an example if you check the stack of a Go thread on Linux,
> you'll most probably find the VMA is named " Go: heap". It may be the stack for now,
> but maybe it was not a bit ago or won't be a bit later. Asking Go devs to use
> MAP_STACK would probably be rejected because it breaks the whole model of how they
> do multithreading...
> 
> I digged a bit deeper and found what I thought was an improvement at the time.
> Why not check the frame pointer instead of the stack pointer? Why not both?
> It was fairly easy to patch Syd for this so I got to testing. This way of detection
> is imho more reliable and less prone to bypassing however there's a big issue.
> Both gcc and clang imply -fomit-frame-pointer with -O2 so you'll have a hard time
> finding a binary in the wild these days that has frame pointers. If I were OpenBSD,
> I'd compile the world with -fno-omit-frame-pointer and move on with my life and I'd
> humbly and kindly recommend them to do that in short of any better ideas.
> 
> Now, my tests showed me another problem. Stack pivotting is not as unusual and as
> malicious as you think. In fact, various programs make use of it in arguably weird
> ways to achieve their goals. One example is Firefox's crashhelper, bash, gawk, ceph,
> ... I can easily come up with dozens more if I enable this mitigation and build Exherbo
> packages under Syd as we proudly enable package testing by default. So even if you'd come
> up with a reliable, efficient way to detect stack pivot, you're gonna have loads of false
> positives to manage. Good luck.
> 
> Finally, after I ended my experiments and reverted[5] Syd's stack pivot detection. I
> came across LKRG's README[6] incidentally which mentions validating the stack pointer
> with "pCFI", their version of coarse-grained CFI, which also seems to check the
> frame pointer[7] against the stack. I'd be curious to know whether there's any added
> mechanism to detect stack pivot when the binary is compiled without frame pointers when
> the frame pointer is reused by compiler for different purposes.
> 
> Best regards,
> Ali Polatel
> 
> [1]: https://patents.google.com/patent/US10853480B2/en
> [2]: https://isopenbsdsecu.re/mitigations/map_stack/
> [3]: https://archive.ph/xS2Fl#selection-13.0-243.52
> [4]: https://gitlab.exherbo.org/sydbox/sydbox/-/blob/main/dev/stackpivot-jumpback-bypass.c
> [5]: https://gitlab.exherbo.org/sydbox/sydbox/-/commit/f03db6c677ddf5dbf87adeb6bd5efb0677869104
> [6]: https://github.com/lkrg-org/lkrg/blob/b8b1418a6c1e7229cdf3dfa020fcc4945e108d83/README#L505
> [7]: https://github.com/lkrg-org/lkrg/blob/b8b1418a6c1e7229cdf3dfa020fcc4945e108d83/src/modules/exploit_detection/p_exploit_detection.c#L1585-L1591





-- 
pi3 (pi3ki31ny) - pi3 (at) itsec pl
http://pi3.com.pl

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.