5 ms·
If you don't use the standard library, and you don't need JIT, you can simply not use pointers to callbacks. You can still have something like qsort() but you n
by geocar 7y ago
If you don't use the standard library, and you don't need JIT, you can simply not use pointers to callbacks. You can still have something like qsort() but you need to have statically defined:
typedef void*(*callback)(...);extern const callback callbacks[256];
and qsort() takes an index instead of a raw pointer to a callback. "Validating" a callback is cheap: Just make sure it's <256 (how many do you need anyway?).
If you don't do an unchecked call*
or a jmp*
then you don't have anything an attacker can exploit, and I find it hard to believe a cached load is going to be slower than something like this.
- choeger 7y agoNeat. But how do you make this modular and still safe? When the code invoking qsort() does not know about how many callbacks there are, can you still deal with it?
- tedunangst 7y agoThe idea is you have some struct that is in peril. Perhaps it will be overflowed, or used after free, etc. It has a function pointer. struct danger { sort_fn sorter; char data[16]; }; danger->sorter(danger); /* what if it's a bad pointer? */ You replace that with this. int add_sort_fn(sort_fn sorter) { sort_fns[num_sorts++] = sorter; return num_sorts; } struct lessdanger { int sortidx; char data[16]; }; danger->sortidx = add_sort_fn(sorter); /* could clean this up a bit more */ sort_fns[danger->sortidx % num_sorts](danger); There's still the possibility of calling the wrong function, but only one from a finite set of possibilities. There's no direct control over the pointer value. You can make the sort_fns array resizeable, but in practice there's usually only so many targets.
- geocar 7y agoOur qsort() caller is an application with at most 256 different comparators. Let us call that an ABI limit. The addresses are in read-only memory so they cannot be changed- new addresses simply cannot be introduced at run-time, so there is no risk of qsort being tricked into “jumping into the middle of a function”. The implementation of my qsort() does not need to know if there are fewer- the entry will be null and the program will crash.
- zozbot234 7y agoThis is a whole-program transformation though, it doesn't seem to be possible to make it modular. Unless you manage to map some module-specific dispatch table whenever you're running code from that module - in a way that cannot easily be subverted by exploit code! Not sure how to do this however.
- yuubi 7y agoHave a section of callback pointers and check against the bounds of the section? G++ uses this mechanism for static constructors, but it's general purpose (Linux uses it to collect lists of drivers to initialize, for instance, with macros like IRQCHIP_DECLARE that populate a section for each type of entity, which then gets scanned at boot time). You would need a linker script to collect the callbacks into a section and provide a symbol for the end, and define variables something like int (my_sort_callback_ptr)(void , void *) __attribute__((section, "sort_callbacks"))=my_sort_comparator; You would, of course, use a macro for that.
- geocar 7y agogcc (ld actually) makes __start_sort_callbacks and __end_sort_callbacks for you, so you shouldn't need a custom linker script.
- Hello71 7y agohow does that fix ROP
- zozbot234 7y agoUse CPS and get rid of RET altogether.
- skunkpocalypse 7y agoCPS introduces more callbacks ("continuations") -- lots of them. One for every call site. Way more than 256.