Hello guys. Today I come to you with (what I think are) unusual C function pointer behaviors. I’ve tried looking for answers online but it seems I’ve crossed a threshold and entered “voodoo programming” territory. And it feels a bit like this :
Some context first. I’m working on a tiny library to create simple menu-based user interfaces for Linux console programs. I just needed this for a larger job and since it’s kind of a recurring need I decided to spend a few days hacking a generic menu library together.
As you might guess, this is a curses-based project. The gist of it is, it lets you build menus dynamically at runtime because it stores menus as linked lists of individual entries; each entry being a structure that contains (mainly) a label string, a pointer to the next menu entry in the linked list, and a pointer to the function that will be called when the entry is selected. I’m not trying to innovate, here.
I was kind of in a hurry and I didn’t remember the exact syntax for function pointers, so I ended-up using void pointers instead (when passing a function’s address to my library) and then calling those void pointers like functions. It works, gcc doesn’t even throw a warning. Eventually, as I added more code, I toyed with passing arguments to those functions.
That is where the weird questions come.
Normally, when you want a function to take a function pointer as argument, you write something like this :
int menuAddEntry (menuItem* m, char* l, void (*f)());
The last argument specifies argument “f” must be a function that doesn’t return a value (void) and takes no argument. Instead I wrote this :
int menuAddEntry (menuItem* m, char* l, void* f);
You can call this function just like the first one, however it will not cause the compiler to check for the type of the return value and the argument list on the function(s) you pass as argument. It will take any function.
On its own, that’s kinda cool, but then I thought : “wait a sec, how do I call this function ? The compiler won’t know if I’m providing the right arguments to it.”
Turns out, that’s not a problem. You can call it without argument, or with ten arguments, and it works : gcc just lets it slide, no warning. Daym !
Better yet, it runs, apparently without a problem.
So that made me think harder : what happens if I call a pointer to a function that takes some arguments, but call it without any ? For example, suppose this function :
int func (int a, int b, int c, int d);
I assign “func” to a void pointer, and then call it like so :
void *fptr = func;
fptr();
This compiles without warning and runs without problem.
Now you’re probably be wondering, what are the values of a, b, c, d ?
Well, it appears that “a” is consistently zero but any argument past the first one is some “random” number. On occasion, it’s the value of a local variable in the scope of the function that called the pointer.
I don’t have a K&R close-by, it’s probably already packed with my compiler design books as I’m moving soon. Do you guys know exactly what’s happening here ? Specifically, how unsafe is it to use void pointers as function pointers ? Because it does seem rather flexible and useful… if it’s deterministic.
A few things I’m wondering :
1/ When I call a void pointer like a function, what does the compiler really do ? It’s obviously going to create a new stack frame for that call and transfer execution to the function. Somewhere along the way, it’s going to push the arguments from my function call onto the stack, so the function can get them.
If I declared my function pointer normally, the compiler would be able to check my calls and see if I’m passing too few or too many arguments, and if the types don’t match. But with a call to a void pointer, it appears gcc just pushes everything onto the stack without doing any checks.
That sounds suspiciously like a vulnerability.
2/ When my function executes, if its argument list is bigger than what I provided when calling it through a void pointer, the compiler still creates them as local variables and pops their values from the stack. Except now it’s popping more values than I actually gave, which means some of them are actually data from a different stack frame.
This also sounds suspiciously like a vulnerability.
Now, I’m been around the block quite a few times. My first use of C dates back to the mid-90’s and it’s the language I use the most. Yet I don’t consider myself a guru or a hacker. Just an above-average programmer who even manages to make a living out of it. I’m surprised I’ve never noticed those behaviors before, but at the same time, I feel pretty sure this is something actual gurus have to be well aware of already.
Or is it ?
One thing I haven’t tried yet is get a return value from a function that doesn’t return one, by calling it through a void pointer. I’ll keep you posted. Also, if this is something I should have been aware of since school, feel free to pelt me with virtual rotten tomatoes, but right now I’m feeling like Neo :