[Bug c/106119] New: Bogus use-after-free warning triggered by optimizer

tom.cosgrove at arm dot com gcc-bugzilla@gcc.gnu.org
Tue Jun 28 14:28:08 GMT 2022


https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106119

            Bug ID: 106119
           Summary: Bogus use-after-free warning triggered by optimizer
           Product: gcc
           Version: 12.1.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c
          Assignee: unassigned at gcc dot gnu.org
          Reporter: tom.cosgrove at arm dot com
  Target Milestone: ---

There are a number of bug reports related to -Wuse-after-free - it's not clear
to me if our particular issue has already been captured, so please do close as
duplicate if it has (it seems that the optimiser is moving code around, so
could potentially be a duplicate of
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104215).

Assuming not a duplicate, this should probably be linked to
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104075.


Our self-test code checks to see if two consecutive calloc() calls return the
same value, by storing the pointer value in a uintptr_t integer and comparing
against this value later. When we compile with optimisation (we don't see the
problem with -O0) and with an if-statement later in the code, we get a spurious
use-after-free warning.

We don't get this warning at -O0, or if we remove the if-statement.

We have also confirmed that if we reorder the assignment to the uintptr_t and
the call to free() we consistently DO get the use-after-free warning (as
expected) regardless of optimisation level.


gcc-12 $ uname -a
Linux e120653 5.17.15-1-MANJARO #1 SMP PREEMPT Wed Jun 15 07:09:31 UTC 2022
x86_64 GNU/Linux

gcc-12 $ gcc --version
gcc (GCC) 12.1.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

gcc-12 $ cat foo.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

void calloc_self_test( __attribute__ ((unused)) int verbose )
{
    void *buffer1 = calloc( 1, 1 );
    uintptr_t old_buffer1 = (uintptr_t) buffer1;
    free( buffer1 );
    buffer1 = calloc( 1, 1 );
    int same = ( old_buffer1 == (uintptr_t) buffer1 );
    if( verbose )
        printf( "  CALLOC(1 again): passed (%s address)\n",
                same ? "same" : "different" );
    free( buffer1 );
}

No warning at -O0:

gcc-12 $ gcc -O0 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o foo -c
foo.c
gcc-12 $

But unexpected warning at -O2:

gcc-12 $ gcc -O2 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o foo -c
foo.c
foo.c: In function ‘calloc_self_test’:
foo.c:8:15: warning: pointer ‘buffer1’ may be used after ‘free’
[-Wuse-after-free]
    8 |     uintptr_t old_buffer1 = (uintptr_t) buffer1;
      |               ^~~~~~~~~~~
foo.c:9:5: note: call to ‘free’ here
    9 |     free( buffer1 );
      |     ^~~~~~~~~~~~~~~

Removing the if-statement quashes the warning:

gcc-12 $ perl -ni -e 'print unless /if.*verbose/' foo.c
gcc-12 $ cat foo.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

void calloc_self_test( __attribute__ ((unused)) int verbose )
{
    void *buffer1 = calloc( 1, 1 );
    uintptr_t old_buffer1 = (uintptr_t) buffer1;
    free( buffer1 );
    buffer1 = calloc( 1, 1 );
    int same = ( old_buffer1 == (uintptr_t) buffer1 );
        printf( "  CALLOC(1 again): passed (%s address)\n",
                same ? "same" : "different" );
    free( buffer1 );
}

gcc-12 $ gcc -O0 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o foo -c
foo.c
gcc-12 $ gcc -O2 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o foo -c
foo.c
gcc-12 $


Checking that we always get the error when we swap the assignment and free()
statements:

gcc-12 $ cat bar.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

void calloc_self_test( __attribute__ ((unused)) int verbose )
{
    void *buffer1 = calloc( 1, 1 );
    free( buffer1 );
    uintptr_t old_buffer1 = (uintptr_t) buffer1;
    buffer1 = calloc( 1, 1 );
    int same = ( old_buffer1 == (uintptr_t) buffer1 );
    if( verbose )
        printf( "  CALLOC(1 again): passed (%s address)\n",
                same ? "same" : "different" );
    free( buffer1 );
}

gcc-12 $ gcc -O0 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o bar -c
bar.c
bar.c: In function ‘calloc_self_test’:
bar.c:9:15: warning: pointer ‘buffer1’ used after ‘free’ [-Wuse-after-free]
    9 |     uintptr_t old_buffer1 = (uintptr_t) buffer1;
      |               ^~~~~~~~~~~
bar.c:8:5: note: call to ‘free’ here
    8 |     free( buffer1 );
      |     ^~~~~~~~~~~~~~~

gcc-12 $ gcc -O2 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o bar -c
bar.c
bar.c: In function ‘calloc_self_test’:
bar.c:9:15: warning: pointer ‘buffer1’ used after ‘free’ [-Wuse-after-free]
    9 |     uintptr_t old_buffer1 = (uintptr_t) buffer1;
      |               ^~~~~~~~~~~
bar.c:8:5: note: call to ‘free’ here
    8 |     free( buffer1 );

and we still get the warning (as we would hope) without the if-statement:

gcc-12 $ perl -ni -e 'print unless /if.*verbose/' bar.c
gcc-12 $ cat bar.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

void calloc_self_test( __attribute__ ((unused)) int verbose )
{
    void *buffer1 = calloc( 1, 1 );
    free( buffer1 );
    uintptr_t old_buffer1 = (uintptr_t) buffer1;
    buffer1 = calloc( 1, 1 );
    int same = ( old_buffer1 == (uintptr_t) buffer1 );
        printf( "  CALLOC(1 again): passed (%s address)\n",
                same ? "same" : "different" );
    free( buffer1 );
}

gcc-12 $ gcc -O0 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o bar -c
bar.c
bar.c: In function ‘calloc_self_test’:
bar.c:9:15: warning: pointer ‘buffer1’ used after ‘free’ [-Wuse-after-free]
    9 |     uintptr_t old_buffer1 = (uintptr_t) buffer1;
      |               ^~~~~~~~~~~
bar.c:8:5: note: call to ‘free’ here
    8 |     free( buffer1 );
      |     ^~~~~~~~~~~~~~~
gcc-12 $ gcc -O2 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o bar -c
bar.c
bar.c: In function ‘calloc_self_test’:
bar.c:9:15: warning: pointer ‘buffer1’ used after ‘free’ [-Wuse-after-free]
    9 |     uintptr_t old_buffer1 = (uintptr_t) buffer1;
      |               ^~~~~~~~~~~
bar.c:8:5: note: call to ‘free’ here
    8 |     free( buffer1 );
      |     ^~~~~~~~~~~~~~~


More information about the Gcc-bugs mailing list