I'm wondering if anyone has previously proposed, evaluated, or deployed the following measure to harden systems against heap-based buffer overruns: basically, stack canaries, but applied before function pointers in objects stored in the heap rather than before return addresses stored in the stack.
Consider a struct like
struct whatever {
int blah;
char buf[256];
void (*fp)(); // a function pointer
}
Notice that if there is an overrun that writes past the end of the buf
field, it will be possible to overwrite the function pointer field fp
.
A compiler could plausibly defend against this by introducing a canary -- a secret random value -- stored between the buffer and the function pointer. Basically, the compiler would transform the layout of the structure to
struct whatever {
int blah;
char buf[256];
unsigned int canary; // inserted by compiler; not exposed to source code
void (*fp)(); // a function pointer
}
For instance, the compiler could arrange to write the canary
field with a global secret value any time the program writes to fp
, and could check that the value of the canary
remains unchanged any time the program reads from fp
.
This is basically the analog of stack canaries, but where now we focus on protecting function pointers in the heap instead of return addresses in the stack. It seems like a natural idea.
Has anyone proposed this before? Has anyone prototyped it or evaluated the performance cost of doing something like this? Are there any non-obvious barriers to deployment (beyond the fact that it requires changes to compilers, just like stack canaries do)?
Research I've done: I'm aware of the idea of inserting guard pages between objects in the heap, but that's different (it protects against heap overflows that go beyond the bounds of a single object, whereas I'm talking about something to protect against heap overflows that stay within the region of a single heap object). I'm familiar with Cruiser and ContraPolice, which places canaries between objects in the heap, but that too focuses on cross-object overflows rather than intra-object overflows. I'm also familiar with use of stack canaries or pointer encryption for protecting malloc's metadata, but again, that doesn't protect against intra-object overflows and is intended to protect malloc's metadata rather than function pointers.