A glibc Pointer Mangle Bypass

Stefan Filipek
6 min readJun 25, 2020

The setjmp/longjmp mechanism is kind of an dark corner of C programming. It’s a powerful technique that is basically goto on steroids with utterly horrifying security implications. Contentious and exaggerated statements aside, it’s a useful tool to add in some sort of exception handling or a pseudo-stack-unwinding mechanism that is common among modern languages. Or, it’s a way to make unintelligible code. The power is yours!

The GNU standard C library attempts to protect this monstrosity from attack by mangling special registers. However, it’s not perfect…

A Brief Background

If you are fortunate enough to have never encountered these partners in crime before, let me briefly explain. If you have, I’m sorry. Feel free to skip to the next section as a consolation prize.

The state of a computer is, at its core, controlled by registers: The program counter, stack pointer, and all other data registers or other special function registers combined. Memory contents are also part of this, but let’s ignore that for now.

The setjmp() call stores the program state (registers) in a jmp_buf structure. This essentially bookmarks the program location and state for future use. The longjmp() call restores the program state to whatever is stored in jmp_buf. Once called, the program resumes execution from the next line after setjmp() as if nothing happened (well, almost).

It’s a powerful tool, but comes with lots of caveats and undefined behavior to worry about. The setjmp.h Wikipedia page does a good job at capturing uses and shortcomings if you’re so interested.

Security Implications

This technique isn’t used very often, and standards like MISRA C even go so far as to banning it. However, when used, it can be a decent target for exploitation. An attacker that can overwrite the tiny bit of memory belonging to the jmp_buf structure has just gained full write access to the entire processor state whenever it gets used next. Not good.

So, some smart people figured out a way to make this a bit harder: Mangle (XOR) the special pointers in memory with an unguessable value. Doing so raises the security bar a bit and makes the attacker have to know or guess the special value. Nothing is perfect, but it definitely helps.

However…

The current implementation of setjmp in glibc (for most architectures) masks just two special registers: The stack pointer (SP) and program counter (PC). That sounds reasonable, since they are the gatekeeper and the keymaster to the system’s operating state, but there is a sneaky little back door: the frame pointer (FP).

The frame pointer, if (ab)used, holds essentially the same power as the stack pointer. The stack pointer points to the end of the stack, while the frame pointer points somewhere near the beginning of the stack for the current function (exact location depends on the architecture and compiler, for ARM at least). It acts as a bookmark that can speed up stack access, aid in frame unwinding, and simplify function epilogues.

Example stack layout (Public Domain image from Wikipedia)

Since this is not always one of the protected registers… let’s see if we can abuse it!

The Technique

Note: I discovered this while working on a security assessment tool for 32-bit ARM, so this focuses on said architecture.

If we gain control over the unprotected frame pointer, we can utilize a technique called a stack pivot. To perform a stack pivot, we create our own stack in an arbitrary (acceptable and available) memory location, change the stack pointer to this location (pivot), and then let the program continue execution under the new stack environment.

So, how do we pull this off with the frame pointer? Looking at ARM assembly, we can find the following super-useful function epilogue:

sub   sp, fp, #N     <-- modified fp used to set sp (pivot)
pop {..., fp, pc} <-- modified sp used to set pc (control)

…which turns out to be more common than you might think.

Using this epilogue is simple as N is of no consequence — it just compensates for the number of registers restored during the subsequent pop operation. For the ARM instruction set, the frame pointer points to a predictable and consistent location in the frame. (For THUMB2 with GCC… that’s a different story.)

The Proof of Concept

The POC performs a stack pivot by abusing the FP stored in a jump buffer. The “attack” portion is simulated by having the program itself carry it out a few attack steps:

  1. Create a forged stack with the target address (PC)
  2. “Overflow” (modify) an initialized jmp_buf structure to make the saved FP point to the forged stack (with an appropriate offset)
  3. Wait for (or somehow trigger) a setjmp() call
static void* pivot_frame[FRAME_SIZE]; // Stack spacestatic void target_function(void) {
fputs("Target function reached\n", stderr);
}
int main(int argc, char ** argv) {
...
// Prime a jump buffer
if (setjmp(jmp_buffer) != 0) {
return 0;
}

// Assume a vuln exists that allows the following:

// Set the target in the pivot stack frame (last register)
pivot_frame[FRAME_SIZE - 1] = &target_function;

// Overwrite the stored frame pointer in the jump buffer
*JMP_BUF_FP_OFFSET_OF(jmp_buffer) = &pivot_frame[
FRAME_SIZE - FP_FRAME_COMPILER_OFFSET];

// Trigger!
longjmp(jmp_buffer, 1);
...
}

And that’s basically it… minus a few minor implementation details. The full POC can be found in my GitLab project.

The Impact

An attacker that can selectively overwrite a jmp_buf structure can bypass PC and SP register protection mechanisms and control the system state.

This proof-of-concept focused on a 32-bit ARM processor, but what about others? Below is a quick summary for glibc 2.31:

Some Discussion

Shouldn’t you notify glibc?

Absolutely! A bug report was filed in April of 2019. Still no resolution as of June 2020.

Should this be a CVE?

Probably not.

On the other hand, back in 2013, a similar bug resulted in a CVE being issued and was given a CVSS Version 2.0 score of 5.1 (medium).

However, this is a weakness in a defense mechanism, and not a first-class vulnerability. The severity is debatable, but a large project has to come up with a reasonable standard operating procedure for triaging bugs.

Note that musl-libc has zero protections in place for this, so there’s that… And data integrity can be trivially attacked in all cases. Nothing is perfect.

How often is the frame pointer used?

With ARM, the frame pointer is completely optional. The compiler generally makes a decision whether or not the function’s stack access is crazy enough to make it worth the trouble. You can generally force the use of the frame pointer with the compiler flag -fno-omit-frame-pointer. With the ARM instruction set, the frame pointer is r11. With THUMB, it’s r7. There is no architecture-defined register for ARM, it’s just a convention.

However, at the time of discovery, the Raspberry Pi’s libc.so contained over 190 instances of functions utilizing the frame pointer and containing vulnerable epilogues like shown above.

What could mitigate this issue?

Various attack mitigations may work, such as safe stacks, return address verification (retguard), general backward edge CFI, etc. Many of these are quite common now in modern architectures and systems, but unfortunately not quite as common on embedded 32-bit ARM systems.

Wouldn’t an attacker still have to know the mangled PC and SP?

Depends on the memory layout and/or the attacker’s write controls.

If the attacker has to overwrite the jmp_buf struct from the beginning, AND the protected registers are stored before the frame pointer, then yes. A memory disclosure attack would need to be used on conjunction.

On x86 and aarch64, the frame pointer is stored at a lower offset than the protected stack pointer and program counter.

Your POC isn’t convincing

It’s just a proof-of-concept, not a full attack. Let’s just say that I leave it as an exercise to the reader to come up with your own contrived indirect data write exploit.

--

--