Quote:
Originally Posted by sk8land
I've found an epic exploit: When you enter -1 as the amount of values, calloc is invoked with 0 as first argument. Instead of returning NULL, calloc apparently returns a non-NULL pointer. I have no idea how glibc's memory allocation work, the memory the pointer points to probably contains metadata only. Then we dereference the pointer and write -1 to which I guess is unallocated memory.
|
tl;dr
"epic exploit" or in glibc's terms: intended behaviour. :D
long version:
malloc has a "minimum allocated size". Due to the metadata and due to more metadata which is being stored once a junk is free'd (next/bck ptr's) a chunk has to have a minimum size to store all that additional data. Here a useful comment within malloc.c:
Quote:
Minimum allocated size: 4-byte ptrs: 16 bytes (including 4 overhead)
8-byte ptrs: 24/32 bytes (including, 4/8 overhead)
When a chunk is freed, 12 (for 4byte ptrs) or 20 (for 8 byte
ptrs but 4 byte size) or 24 (for 8/8) additional bytes are
needed; 4 (8) for a trailing size field and 8 (16) bytes for
free list pointers. Thus, the minimum allocatable size is
16/24/32 bytes.
Even a request for zero bytes (i.e., malloc(0)) returns a
pointer to something of the minimum allocatable size.
|
and the corresponding code which is the very first thing _int_malloc (the internal malloc implementation, called by malloc, calloc, et al) does:
Code:
/*
Convert request size to internal form by adding SIZE_SZ bytes
overhead plus possibly more to obtain necessary alignment and/or
to obtain a size of at least MINSIZE, the smallest allocatable
size. Also, checked_request2size returns false for request sizes
that are so large that they wrap around zero when padded and
aligned.
*/
if (!checked_request2size (bytes, &nb))
{
__set_errno (ENOMEM);
return NULL;
}
Further, reading the code in the challenge:
Code:
// This is no problem, as we just learned. Access within MINSIZE alloc'd junk.
data[0] = cnt;
// This loop will then terminate immediately, due to the fact that `1 < 0` is false.
// (signed-ness does not play any role here (since 0), so it does not matter if its a jg or ja.)
for(int i=1; i < cnt + 1; i++){
[..]
}
thus leaving no room for exploitation, such as OOB writes. (Which might be given, if cnt + 1 results to -1 and the compare is unsigned.)
Anyone else with bug-suggestions? :P