Thursday, August 8, 2019

free()ing an invalid pointer

One of my friend asked this question. What happens if you free() an invalid pointer location? i.e. the pointer which was not returned by malloc(). I thought for a while. Few years back when I skimmed through glibc malloc code, I remember it was some kind of lookup based on pointer. After a while, I replied saying it would be a memory leak.

But wait a minute, the free() call returns nothing. While the programmer is happy about his bad code, internally there is something baking bad without his notice. When customer reports a long run memory leak issue, then the furnace erupts and developer is under intense pressure. So there should be something else free() doing. So let's try a sample program

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char ptr = (char)(malloc(100));

    ptr++;

    free(ptr);
}

And the output is...

nanda@nanda-MS-7640:/media/nanda/PERSONAL/c_code/const$ ./a.out 
free(): invalid pointer
Aborted (core dumped)

Yes the ABORT is called by glibc and the actions are performed as per the SIGABRT behaviour of the calling process. This was a fantastic learning. Now let's look at the glibc code.

if you try to free invalid pointer, this is result

if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
      || __builtin_expect (misaligned_chunk (p), 0))
    malloc_printerr ("free(): invalid pointer");

this guy calls abort

malloc_printerr (const char *str)
{
  __libc_message (do_abort, "%s\n", str);
  __builtin_unreachable ();
}

So what's the point here?

Look at the start of free() function : https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#3092

In line 3109 it tries to get the metadata associated with the allocated memory

p = mem2chunk (mem);

In line 3131

ar_ptr = arena_for_chunk (p);
  _int_free (ar_ptr, p, 0);

The _int_free has this comment

/* Little security check which won't hurt performance: the
     allocator never wrapps around at the end of the address space.
     Therefore we can exclude some size values which might appear
     here by accident or by "design" from some intruder.  */

  if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
      || __builtin_expect (misaligned_chunk (p), 0))
    malloc_printerr ("free(): invalid pointer");

Probably in our case, it was misaligned chunk. There is no way ptr in actual code would have wrapped around.


Ending Note:

This is specific to glibc implementation. The man pages do not mandate specific behaviour in such a case. It just says the pointer to free() should be the one returned by malloc.

More deep dive on malloc/free code in subsequent posts.