We discussedlast timehow one can use
nested functions on GCC 17 and Clang for callbacks with requiring an
executable stack.
But what if ones needs to support older versions of GCC? Of course,
one can simply accept an executable stack (it is not quite as terrible as
some people claim), but it is also possible to avoid this with a hack.
Let’s discuss first how GCC supports taking the address of a nested
function. Our toy example without the use of the new macros is shown below
(Godbolt Example).typedef int cb_f(int y);
int baz(cb_f p, int x)
{
return p(x);
}
int foo(int k)
{
int bar(int x) { return k + x; }
return baz(bar, 2 * k);
}
bar.0:
movl %edi, %eax
addl (%r10), %eax
ret
foo:
subq $56, %rsp
leaq 64(%rsp), %rax
movq %rax, 32(%rsp)
movl %edi, (%rsp)
leaq 4(%rsp), %rax
movw $-17591, 4(%rsp)
movabsq $bar.0, %rcx
movq %rcx, 6(%rsp)
movw $-17847, 14(%rsp)
movq %rsp, 16(%rsp)
movl $-1864106167, 24(%rsp)
addl %edi, %edi
call *%rax
addq $56, %rsp
retThis code places a trampoline on the stack and immediately
invokes it via the inlinedbazfunction. The trampoline
is a short code sequence that loads the static frame register to a
structure on the stack that contains the captured variables from the
parent function and then jumps to the local function. If one
translates the constants -17591, -17847, and -1864106167 back to
assembly instructions one obtains the following x86_64 code.movq $bar.0, r11
movq $frame, r10
jump *r11Both, the static chain and the code address are immediate constants
used by move instructions in the code of the trampoline.
Instead of using the address of the trampoline to call the function,
we can extract the code address and the static chain from the trampoline
and use them with the__builtin_call_with_static_chainbuilt-in function to call the local function directly. For example,
using noplate’speekandarray_slicemacros,
this could be done in the following way for x64_64 (and a large memory
model).unsigned char (*tramp)[24] = (void*)bar;
void *code = peek(uint64_t, &array_slice(tramp, 2, 10));
void *chain = peek(uint64_t, &array_slice(tramp, 12, 20));These pointers are then exactly the same information that can be obtained
on the yet to-be-released GCC 17 with the new built-ins__builtin_call_static_chainand__builtin_call_code_adressand can be used to call the local function with as discussed previously.__builtin_call_with_static_chain(((typeof(bar)*)code)(arg), chain);Thus, we can use reading of these
two pointer values from a trampoline as a fallback mechanism in older versions
of GCC. The downsides are that a trampoline is still created, the compiler can
still not devirtualize the indirect call, and the stack will still be marked executable.
So what was gained? Since we never actually invoke the trampoline, we can
make the stack non-executable again with the following command, which
at least addresses the security concerns of this feature.patchelf –clear-execstack programThis idea is implemented in my experimental library,noplate,
where a wide pointer is constructed from the code address and the static chain.Trampolines as Function DescriptorsThere is another idea I find worth exploring: One could also use the trampoline
itself as a function descriptor. Instead of extracting the static chain
and code pointer where the trampoline is created we just pass on the address of
the trampoline as usually. But everywhere where we might call the trampoline,
we first check whether the pointer points to a trampoline, and then extract code
address and static chain to call the nested function directly using__builtin_call_with_static_chain. In some sense we could say that
instead of invoking the trampoline, we areinterpretingthe code of the
trampoline at the call site using a super simple interpreter that only can
interpret this specific code sequence and that is so simple that it can be inlined
(Godbolt Example).Literature
This code places a trampoline on the stack and immediately
invokes it via the inlinedbazfunction. The trampoline
is a short code sequence that loads the static frame register to a
structure on the stack that contains the captured variables from the
parent function and then jumps to the local function. If one
translates the constants -17591, -17847, and -1864106167 back to
assembly instructions one obtains the following x86_64 code.
Both, the static chain and the code address are immediate constants
used by move instructions in the code of the trampoline.
Instead of using the address of the trampoline to call the function,
we can extract the code address and the static chain from the trampoline
and use them with the__builtin_call_with_static_chainbuilt-in function to call the local function directly. For example,
using noplate’speekandarray_slicemacros,
this could be done in the following way for x64_64 (and a large memory
model).
These pointers are then exactly the same information that can be obtained
on the yet to-be-released GCC 17 with the new built-ins__builtin_call_static_chainand__builtin_call_code_adressand can be used to call the local function with as discussed previously.
Thus, we can use reading of these
two pointer values from a trampoline as a fallback mechanism in older versions
of GCC. The downsides are that a trampoline is still created, the compiler can
still not devirtualize the indirect call, and the stack will still be marked executable.
So what was gained? Since we never actually invoke the trampoline, we can
make the stack non-executable again with the following command, which
at least addresses the security concerns of this feature.
This idea is implemented in my experimental library,noplate,
where a wide pointer is constructed from the code address and the static chain.
There is another idea I find worth exploring: One could also use the trampoline
itself as a function descriptor. Instead of extracting the static chain
and code pointer where the trampoline is created we just pass on the address of
the trampoline as usually. But everywhere where we might call the trampoline,
we first check whether the pointer points to a trampoline, and then extract code
address and static chain to call the nested function directly using__builtin_call_with_static_chain. In some sense we could say that
instead of invoking the trampoline, we areinterpretingthe code of the
trampoline at the call site using a super simple interpreter that only can
interpret this specific code sequence and that is so simple that it can be inlined
(Godbolt Example).