clinuxfunctiongdbmemcpy

Why am I unable to copy and execute a function in C?


I tried to write a C program that would copy a function to other memory location, and then execute it as a function pointer. But I'm facing issues. This is my code:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

extern uint64_t __start_myfnsection[];
extern uint64_t __stop_myfnsection[];

// I create a simple function, which will be placed in a separate binary section
__attribute__((noinline, section("myfnsection"))) void myfn() {
    static int i;
    i++;
    printf("I love lemons!^^.\n");
    printf("i=%d\n", i);
    return;
}

int main () {
    // Test the function.
    myfn();

    // Find out and print the size of my function.
    uint64_t myfn_length = (__stop_myfnsection - __start_myfnsection);
    printf("Length of myfn() function is: %lu\n", myfn_length);

    // Allocate on-stack memory, and copy my function to it
    void* memory = __builtin_alloca(myfn_length);
    memcpy(memory, &myfn, myfn_length);

    // Create a pointer to the copied function.
    void (*myfn_copy)() = memory;

    // Attempt to execute it.
    myfn_copy();

    return 0;
}

I compile with executable stack:

$ gcc -pie -z execstack -fno-stack-protector -g test.c

But the program crashes on start:

$ ./a.out
I love lemons!^^.
i=1
Length of myfn() function is: 9
Illegal instruction

With gdb:

# gdb ./a.out
GNU gdb (GDB) 14.2
Reading symbols from ./a.out...
(gdb) run
Starting program: /home/mika/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so".
I love lemons!^^.
i=1
Length of myfn() function is: 9

Program received signal SIGILL, Illegal instruction.
0x0000007ffffffb10 in ?? ()
(gdb)
(gdb) bt
#0  0x0000007ffffffb10 in ?? ()
#1  0x0000005555556748 in _start_main ()
#2  0x0000000000000000 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb)

This system is arm64, if that's relevant.

What am I missing, or am trying to do something impossible? Thanks.


Solution

  • First of all you need some manner of guarantee from the system that you can execute code from stack memory space, which is not something I'd assume.

    If you know that part is OK, then illegal instruction means that the memory contains gibberish. Which is likely caused by using the wrong types all over the code.

    You need apply various bug fixes and get rid of all dangerous gcc extensions... You didn't include string.h. You invoke UB with the pointer subtractions. You assign between void* and function pointers which is invalid C. And so on.

    Less bad code would be something like this:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <inttypes.h>  // to print uint64_t
    #include <string.h>    // to give memcpy a chance to succeed at all
    
    // This is raw binary, not chunks of uint64_t:
    extern uint8_t __start_myfnsection[];
    extern uint8_t __stop_myfnsection[];
    
    __attribute__((noinline, section("myfnsection"))) void myfn() {
        static int i;
        i++;
        printf("I love lemons!^^.\n");
        printf("i=%d\n", i);
        return;
    }
    
    // dirty type punning but better than wild & crazy pointer casts/gcc extensions:
    typedef union
    {
      void* vptr;
      void (*fptr)();
    } ptr_t;
    
    // some bare minimum of assumptions (we still know nothing about alignment though):
    static_assert(sizeof(void*) == sizeof(void(*)(void)), "Pointers are too exotic...");
    
    int main () {
        // Test the function.
        myfn();
    
        // Find out and print the size of my function.
        // use uintptr_t since pointer arithmetic on two unrelated sections is UB
        uint64_t myfn_length = (uintptr_t)__stop_myfnsection - (uintptr_t)__start_myfnsection;
        printf("Length of myfn() function is: %" PRIu64 "\n", myfn_length);
    
        ptr_t memory;
        ptr_t old_func = { .fptr = myfn };
    
        // Allocate on-stack memory, and copy my function to it
        memory.vptr = __builtin_alloca(myfn_length);
        memcpy(memory.vptr, old_func.vptr, myfn_length);
    
        // Create a pointer to the copied function.
        void (*myfn_copy)() = memory.fptr;
    
        // Attempt to execute it.
        myfn_copy(); // SIGSEGV here if you can't execute code from the stack
    
        return 0;
    }
    

    This would probably work on various low-end von Neumann microcontrollers but probably not on x86 Linux etc.