Search code examples
cassemblyx86-64calling-convention

Do indirect `call` instructions always point to a function prologue?


Suppose we have some C code that calls upon a function though a function pointer, whether it be through a function pointer table or a function pointer passed as parameter or other, like so:

/* ... some other code .. */
void (*f)(void) = something; // f function pointer to some function
(*f)();

This should be compiled to (or something equivalent)

mov  %rcx, [something] ; here f=ecx
callq *%rcx

Question: does %ecx always point to a function prologue or can it point to a small peice of code at the end on a function?

Example:

void big_func(){
    /* lots of code here */
    printf("bar");
    printf("foo");
}
void small_func(){
    printf("foo");

With big_func C compiled to

; some more code up here
1: 48 8d 3d c4 0e 00 00    lea    %rdi,[0xec4+%rip]  ;ptr to "bar"
2: b8 00 00 00 00          mov    %eax,$0x0
3: e8 e6 fe ff ff          callq  1030 <printf@plt>
4: 48 8d 3d b7 0e 00 00    lea    %rdi,[0xeb7+%rip]  ;ptr to "foo"
5: b8 00 00 00 00          mov    %eax,$0x0
6: e8 d5 fe ff ff          callq  1030 <printf@plt>
7: b8 00 00 00 00          mov    %eax,$0x0
8: 5d                      pop    %rbp
9: c3                      ret

Is it possible for a call to small_func to point to 4: as it's entry point? Does this happen ever (with a generic compiler like gcc) or only with some human modifying the assembly code behind the scenes?

Question limits:

  • Consider all assembly code code compiled by a non-specialised compiler (like gcc, clang etc., without fancy asm quirks for performance behind the scenes)
  • Only consider normal behaviour. Let's imagine the developer is a nice person and doesn't implement any pesky undefined behaviour

Additional mini-question: What happens would happen of one intentionally modifies the function pointer to skip some bytes from a function prologue? Is this considered undefined behaviour?
Example:

void (*f)(void) = something; // f function pointer to some function
f=(void (*func_ptr)(void)) ((*char)f+2)
(*f)(); //skips `push ebp`

EDIT: I should have been more clear as of why this question is asked. It is in the context of a research master, seeking a new way to mitigate ROP based attacks at a very low software or hardware level. If it were possible for indirect calls to point somewhere else than a function prologue it could break one of our tag-based implementation (missing the tag and terminating the program after incorrectly detecting an attack)


Solution

  • In C, like most languages, functions have a very specific meaning, purpose and implementation.  In invocation, a function pointer has to work just like a function.  We cannot invoke code that isn't a function, whether direct call or function pointer.  The function generally doesn't know whether it was invoked directly or by pointer — it must accept the arguments passed, perform its function and return to the caller (usually), no matter how it was called (directly or by function pointer).  In C, functions have a single entry point.

    There is no concept in C of a snippet of code that isn't a function, that can be transferred to (or invoked) by function pointer.  (C has labels and goto's, but there are no label pointers, or label variables or label variable goto's, for example.)

    Not all functions require prologue, they only have to be able to accept their arguments (do something) and return to the caller — that doesn't necessarily require prologue or epilogue, but they are still functions (with a single entry point).