C Function That Returns Twice. (not even nearly similar to fork())

Viewed 146

So I was working on a project and was a little bored and thought about how to break C really hard: Is it be possible, to trick the compiler in using jumps (goto) for a function call? - Maybe, I answered to myself. So after a bit of working and doing I realised, that some pointer stuff wasn't working correctly, but in an (at least for me) unexpected way: the goto wouldn't work as intended. After a little bit of experimenting, I came up with this stuff (comments removed, since I sometimes keep unused code in them, when testing):

//author: me, The Array :)

#include <stdio.h>

void * func_return();
void (*break_ptr)(void) = (void *)func_return;

void * func_return(){
    printf("ok2\n");
    break_ptr = &&test2;
    return NULL;
    if(0 == 1){
      test2:
      printf("sh*t\n");
    }
}

void scoping(){
    printf("beginning of scoping\n");
    break_ptr();
    printf("after func call #1\n");
    break_ptr();
    printf("!!!YOU WILL NOT SEE THIS!!!!\n");
}

int main(){
    printf("beginning of programm\n");
    scoping();
    printf("ending programm\n");
}

I used gcc to compile this as I don't know any other compiler, that supports the use of that &&! My platform is windows 64 bit and I used that most basic way to compile this:

gcc.exe "however_you_want_to_call_it.c" -o "however_you_want_to_call_it.exe"

When looking over that code I expected and wanted it to print "sh*t\n" to the console window (of course the \n will be invisible). But it turns out gcc is somewhat too smart for me? I guess this comes, when trying to break something.. Infact, as the title says, it returns twice:

beginning of programm
beginning of scoping
ok2
after func call #1
ok2
ending programm

It does not return twice, like the fork function and propably prints the following stuff twice or sth., no it returns out of the function AND the function that called it. So after the second call it does not print "!!!YOU WILL NOT SEE THIS!!!!\n" to the console, but rather "ending programm", as it returned twice. (I am trying to amplify the fact, that the "ending programm" is printed, as the programm does not crash)

So the reason, why I posted that here, is the following: my questions..

  1. Why does it not go to/ jump to/ call to the actual test2 label and instead goes to the beginning of that function?
  2. How would I achieve the thing of my first question?
  3. Why does it return twice? I figured it is propably a compiler thing instead of a runtime thing, but I guess I'll wait for someones answer
  4. Can the same thing (the returning twice) be achieved the first time the function "break_ptr" is called, instead of the second time?

I do not know and do not care if this also works in c++.

Now I can see many ways this can be usefull, some malicious and some actually good. For example could you code an enterprise function, which returns your function. Enterprise solutions to problems tend to be weird, so why not make a function which returns your code, idk.. Yet it can be malicious, for example, when some code is returning unexpectatly or even without return values.. I can imagine this existing in a dll file and a header file which simply reads "extern void *break_ptr();" or sth.. did not test it. (Yet there are way crueler ways to mess with someone..)

I could not find this documented anywhere on the internet. Please send me some links or references about this, if you find some, I want to learn more about it.

If this is "just" a bug and someone of the gnu/gcc guys is reading this: Please do NOT remove it, as it is too much fun working with these things.

Thank you in advance for your answers and your time and I am sorry for making this so long. I wanted to make sure everything collected about this is in one place. (Yet still I am sorry if I missed something..)

2 Answers

From gcc documentation on labels of values:

You may not use this mechanism to jump to code in a different function. If you do that, totally unpredictable things happen.

The behavior you are seeing is properly documented. Inspect the generated assembly to really know what code does the compiler generate.

The assembly from godbolt on gcc10.2 with no optimizations:

break_ptr:
        .quad   func_return
.LC0:
        .string "ok2"
func_return:
        push    rbp
        mov     rbp, rsp
.L2:
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        mov     eax, OFFSET FLAT:.L2
        mov     QWORD PTR break_ptr[rip], rax
        mov     eax, 0
        pop     rbp
        ret
.LC1:
        .string "beginning of scoping"
.LC2:
        .string "after func call #1"
.LC3:
        .string "!!!YOU WILL NOT SEE THIS!!!!"
scoping:
        push    rbp
        mov     rbp, rsp
        mov     edi, OFFSET FLAT:.LC1
        call    puts
        mov     rax, QWORD PTR break_ptr[rip]
        call    rax
        mov     edi, OFFSET FLAT:.LC2
        call    puts
        mov     rax, QWORD PTR break_ptr[rip]
        call    rax
        mov     edi, OFFSET FLAT:.LC3
        call    puts
        nop
        pop     rbp
        ret
.LC4:
        .string "beginning of programm"
.LC5:
        .string "ending programm"
main:
        push    rbp
        mov     rbp, rsp
        mov     edi, OFFSET FLAT:.LC4
        call    puts
        mov     eax, 0
        call    scoping
        mov     edi, OFFSET FLAT:.LC5
        call    puts
        mov     eax, 0
        pop     rbp
        ret

shows that .L2 label was placed on top of function and the if (0 == 1) { /* this */ } was optimized out by the compiler. When you jump on .L2 you jump to beginning of the function, except that stack is incorrectly setup, because push rbp is omitted.

Why does it not go to/ jump to/ call to the actual test2 label and instead goes to the beginning of that function?

Because the documentation says that if you jump to another function "totally unpredictable things happen"

How would I achieve the thing of my first question?

Hard to say, since "jumping into a function" is not really something you should do.

Why does it return twice? I figured it is propably a compiler thing instead of a runtime thing, but I guess I'll wait for someones answer

Because returning twice is an element of the set of "unpredictable things"

Can the same thing (the returning twice) be achieved the first time the function "break_ptr" is called, instead of the second time?

See above. What you're doing will cause unpredictable things.

And just to point it out, your code has other flaws that may or may not be a part of this. func_return is a function taking an unspecified number of arguments returning a void pointer. break_ptr is a function taking NO arguments and returning void. The proper pointer would be

void * func_return();
void *(*break_ptr)() = func_return; 

Notice three things. Apart from removing the cast, I removed void from the parenthesis and added an asterisk. But a better alternative would be

void * func_return(void);
void *(*break_ptr)(void) = func_return; 

The main thing here is, do NOT cast to silence the compiler. Fix the problem instead. Read more about casting here

Your cast invokes undefined behavior, which essentially is the same thing as "unpredictable things happen".

Also, you're missing a return statement in that function.

void * func_return(){
    printf("ok2\n");
    break_ptr = &&test2;
    return NULL;
    if(0 == 1){
      test2:
      printf("sh*t\n");
    }
    // What happens here?
}

Omitting the return statement can only safely be done in a function returning void but this function returns void*. Omitting it will cause undefined behavior which, again, means that unpredictable things happen.

Related