Skip to main content
1 of 4
Harith
  • 9.8k
  • 1
  • 14
  • 50

Use-after-free bug:

In stack_pop():

    --s->size;
    void *const top = (char *) s->data + (s->size * s->memb_size);
        
    if (s->size && (s->size <= s->cap / 4)) {
        void *const tmp = realloc(s->data, s->cap / 2 * s->memb_size);
        
        if (tmp) {
            s->data = tmp;
            s->cap /= 2;
        } 
        /* Else do nothing. The original memory is left intact. */
    }

The lifetime of s->data ends with the call to realloc(), assuming it succeeded, so top is pointing to a block of memory that might have already been freed whilst shrinking.

GCC 12.3 caught the bug with -O1 -Wall -Werror -Wpedantic.

In file included from <source>:269:
<source>: In function 'main':
<source>:291:16: error: pointer used after 'realloc' [-Werror=use-after-free]
  291 |         assert(*(size_t *) gstack_pop(stack) == i);
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In function 'stack_pop',
    inlined from 'main' at <source>:291:9:
<source>:238:27: note: call to 'realloc' here
  238 |         void *const tmp = realloc(s->data, s->memb_size * new_cap);
      |                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors

If you remove -O1 it doesn't detect anything. GCC 13.1 and 13.2 did not detect the bug, of if they did, they did not output anything.

Valgrind also reports a read error for this in stack_pop().

The solution is to move the statement to the end of the function:

return (char *) s->data + (s->size * s->memb_size);

This also eliminated the variable top.

Harith
  • 9.8k
  • 1
  • 14
  • 50