Force Security
Force Security

Reputation: 1

How realloc works passed 0 size as argument

How realloc works passed 0 size as argument?

from man page: Unless ptr is NULL, it must have been returned by an earlier call to malloc(), calloc(), or realloc().

Why it needs to be?

compile this with gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 with no options(flags)

this sample

#include <stdlib.h>

int main () {
    int *p = malloc(0);
    p = realloc(p, 0);
    return 0;
}

this is working code, checking memory with valgrind show this:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./test
==185872== Memcheck, a memory error detector
==185872== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==185872== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==185872== Command: ./test
==185872== 
==185872== 
==185872== HEAP SUMMARY:
==185872==     in use at exit: 0 bytes in 0 blocks
==185872==   total heap usage: 1 allocs, 1 frees, 0 bytes allocated
==185872== 
==185872== All heap blocks were freed -- no leaks are possible
==185872== 
==185872== For lists of detected and suppressed errors, rerun with: -s
==185872== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 

but then compile this(added flag -g):

#include <stdlib.h>

int main () {
    int *p = NULL;
    p = realloc(p, 0);
    return 0;
}

valgrind output shows errors(memory leak):

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./test
==186749== Memcheck, a memory error detector
==186749== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==186749== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==186749== Command: ./test
==186749== 
==186749== 
==186749== HEAP SUMMARY:
==186749==     in use at exit: 0 bytes in 1 blocks
==186749==   total heap usage: 1 allocs, 0 frees, 0 bytes allocated
==186749== 
==186749== 0 bytes in 1 blocks are definitely lost in loss record 1 of 1
==186749==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==186749==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==186749==    by 0x10916D: main (test1.c:5)
==186749== 
==186749== LEAK SUMMARY:
==186749==    definitely lost: 0 bytes in 1 blocks
==186749==    indirectly lost: 0 bytes in 0 blocks
==186749==      possibly lost: 0 bytes in 0 blocks
==186749==    still reachable: 0 bytes in 0 blocks
==186749==         suppressed: 0 bytes in 0 blocks
==186749== 
==186749== For lists of detected and suppressed errors, rerun with: -s
==186749== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

why this is happening even i didnt allocate anything?

edits: compiled this with -std=c11 -g:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define ASSERT_ERROR_PREFIX "Assertion "
#define ASSERT_ERROR_SUFFIX " failed\n"

#define assert(x, num) { \
    if(!(x)) { \
        write(STDOUT_FILENO, ASSERT_ERROR_PREFIX, strlen(ASSERT_ERROR_PREFIX)); \
        write(STDOUT_FILENO, num, sizeof(char)); \
        write(STDOUT_FILENO, ASSERT_ERROR_SUFFIX, strlen(ASSERT_ERROR_SUFFIX)); \
    } \
}

int main () {
    int *p = NULL;
    p = realloc(p, 0);
    *p = '1';
    assert((*p == '1'), "1");
    assert((p == NULL), "2");
    return 0;
}

output: Assertion 2 failed

the valgrind output:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./test
==190041== Memcheck, a memory error detector
==190041== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==190041== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==190041== Command: ./test
==190041== 
==190041== Invalid write of size 4
==190041==    at 0x109196: main (test1.c:19)
==190041==  Address 0x4a5f040 is 0 bytes after a block of size 0 alloc'd
==190041==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x10918D: main (test1.c:18)
==190041== 
==190041== Invalid read of size 4
==190041==    at 0x1091A0: main (test1.c:20)
==190041==  Address 0x4a5f040 is 0 bytes after a block of size 0 alloc'd
==190041==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x10918D: main (test1.c:18)
==190041== 
Assertion 2 failed
==190041== 
==190041== HEAP SUMMARY:
==190041==     in use at exit: 0 bytes in 1 blocks
==190041==   total heap usage: 1 allocs, 0 frees, 0 bytes allocated
==190041== 
==190041== 0 bytes in 1 blocks are definitely lost in loss record 1 of 1
==190041==    at 0x483B723: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x483E017: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==190041==    by 0x10918D: main (test1.c:18)
==190041== 
==190041== LEAK SUMMARY:
==190041==    definitely lost: 0 bytes in 1 blocks
==190041==    indirectly lost: 0 bytes in 0 blocks
==190041==      possibly lost: 0 bytes in 0 blocks
==190041==    still reachable: 0 bytes in 0 blocks
==190041==         suppressed: 0 bytes in 0 blocks
==190041== 
==190041== For lists of detected and suppressed errors, rerun with: -s
==190041== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

Upvotes: 0

Views: 53

Answers (1)

Paul Floyd
Paul Floyd

Reputation: 6936

Update your Valgrind!

You should use a more recent version of Valgrind. Since version 3.21 it has included a check for realloc of size 0.

You will now get errors like

==3891215== realloc() with size 0
==3891215==    at 0x40440FB: realloc (vg_replace_malloc.c:1801)
==3891215==    by 0x4011AE: main (realloc_size_zero.c:11)
==3891215==  Address 0x4e08040 is 0 bytes inside a block of size 1,024 alloc'd
==3891215==    at 0x403C7B2: malloc (vg_replace_malloc.c:446)
==3891215==    by 0x401187: main (realloc_size_zero.c:8)

realloc size 0 is now UB

Since C23 realloc of size 0 has been made UB (undefined behaviour). Prior to that it was Implementation Defined (and still unsafe to use portably).

The problem with realloc size 0 is that you can't easily tell what it is doing. Some implementations will free the memory, others may do nothing or free the memory then allocate a minimum sized block. Since realloc is replaceable you might have a different behaviour between your development environment and the deployment environment. For instance, if you develop on Linux with GNU libc your users could use LD_PRELOAD with the snmalloc library.

You can use the --show-realloc-size-zero=yes option to turn off this check. If you do that then you may also need to use --realloc-zero-bytes-frees=yes (or no) to try to get Valgrind to match the behaviour of the libc or allocator library that you are using.

What does a 0 size alloc do?

The next thing that you are missing is what the behaviour of malloc of size 0 is (and also realloc when ptr is NULL). That's implementation defined. It will either return a NULL pointer or a pointer to allocate some memory that you are not supposed to access.

So, your first example

  • allocates a small block
  • frees it with realloc

and your second example

  • allocates a small block which leaks

and your third example

  • allocates a small block which is inaccessible
  • writes to it causing an error
  • reads from it causing an error
  • leaks the allocated block

Advice

Don't allow realloc of size 0 in your code.

I know, 'cos I was there -- Max Boyce

Upvotes: 2

Related Questions