0

I've been inspecting the heap memory when executing the following code:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct data {
 char name[64];
};

struct fp {
 int (*fp)();
};

void winner()
{
 printf("level passed\n");
}

void nowinner()
{
  printf("level has not been passed\n");
}

int main(int argc, char **argv)
{
 struct data *d;
 struct fp *f;

 d = malloc(sizeof(struct data));
 f = malloc(sizeof(struct fp));
 f->fp = nowinner;

 printf("data is at %p, fp is at %p\n", d, f);

 strcpy(d->name, argv[1]);

 f->fp();

}

The code is compiled like this:

gcc winner.c -w -g -fno-stack-protector -z norelro -z execstack -o winner

(More code will be added later on so tags like fno-stack-protector are there)

I executed the project with argument "HELLO" and I set a break point on f->fp() and inspect the heap memory:

enter image description here

Everything after the first malloc() makes sense but I'm kinda puzzled about what happened after the second malloc(). The second chunk should only request 4 bytes of memory to store the function pointer but instead it took 12 bytes, which reflects on what is stored on the address 0x804a04c (4 bytes of metadata + 12 bytes of requested memory + status bit 1 = 17 => 0x00000011).

And as you can see, the function pointer did only take up four bytes on 0x804a050 with the address of nowinner (0x080484a1).

I read up on this SO post and this article but it seems it still can't explain why.

kevguy
  • 4,328
  • 1
  • 24
  • 45
  • 2
    Have you tried `printf("%zu\n", sizeof(struct fp));`? `malloc` can overallocate (and often does, for alignment reasons), but the compiler will tell you how big it thinks the struct is without the `malloc` overhead. I highly doubt the `struct` is more than 4-8 bytes in size. Also, if this is actual C++ code, not C, you should be using `new`, not `malloc`; by the C++ standard, it would be illegal to store the result of `malloc` without casting it to anything but a `void*` (`new` returns a typed pointer directly, so no casting needed). – ShadowRanger Jan 24 '18 at 04:49
  • @ShadowRanger Sorry, it's C code, I added the wrong tag. The struct of fp is 4. So it's actually because of alignment?? – kevguy Jan 24 '18 at 04:59
  • 1
    Alignment, and possibly allocator overhead (storing size associated with allocation or the like). Depends on implementation. Allowing excessive granularity can impose too much overhead (maintaining multiple special purpose heaps for very small allocations), and `malloc` is required to return memory "that is suitably aligned for any object type.", which means aligning to a stricter standard than the smallest types you might wish to allocate. – ShadowRanger Jan 24 '18 at 05:04
  • some gut guess: memory canary (buffer overflow protection added by compiler)? – WWW Jan 24 '18 at 05:09
  • @kevguy Is your machine really a 32-bit address model? – Jean-Baptiste Yunès Jan 24 '18 at 06:32
  • @Jean-BaptisteYunès Yes, and it's a VM. – kevguy Jan 24 '18 at 06:46
  • You don't allocate memory for `d->name` causing undefined behaviour when you do ` strcpy(d->name, argv[1]);` – Gerhardh Jan 24 '18 at 07:38
  • @Gerhardh This is part of some code trying to demo how to do a heap buffer overflow attack, so `strcpy(d->name, argv[1]);` is placed there on purpose. – kevguy Jan 24 '18 at 07:41

1 Answers1

3

Your initial question can be answered very easily by printing sizeof of your pointer. You will not see a 12 here.

The answer to your question "Why is function pointer 12 bytes long?" is simply: "It's not!"

But your question describes a different underlying question: "Why does allocating 4 bytes take 12 bytes on the heap?"

You are under the wrong impression that memory allocation only takes exactly what is needed to store the user data. This is wrong. Memory management also needs to store some management data for each allocation. When you call free the runtime library needs to know the size of the allocated block.

Therefore you can take it as granted that every allocation consumes more memory than the requested amount.

Depending on the implementation of the heap this can be within the heap itself or in a separate area. You can also not rely on taking the same amount of overhead for each allocation. There are weird implementation out there.

Some implementations take the requested amount and add fixed length of management data. Some implementations use a buddy system and follow a sequence of fibonacci numbers to determine smallest suitable block size.

Gerhardh
  • 11,688
  • 4
  • 17
  • 39