On Thu, 2023-02-16 at 15:35 +0100, Alejandro Colomar via Gcc wrote:
> Hi!
>
> I was preparing an example program of a use-after-realloc bug,
> when I found that GCC doesn't warn in a case where it should.
>
>
> alx@debian:~/tmp$ cat realloc.c
> #include <stdint.h>
> #include <stdlib.h>
> #include <stdio.h>
> #include <string.h>
> #include <unistd.h>
>
> static inline char *
> xstrdup(const char *s)
> {
> char *p;
>
> p = strdup(s);
> if (p == NULL)
> exit(EXIT_FAILURE);
> return p;
> }
>
> static inline char *
> strnul(const char *s)
> {
> return (char *) s + strlen(s);
> }
>
> int
> main(void)
> {
> char *p, *q;
>
> p = xstrdup("");
> q = strnul(p);
>
> if (p == q)
> puts("equal before");
> else
> exit(EXIT_FAILURE); // It's an empty string; this
> won't happen
>
> printf("p = %p; q = %p\n", p, q);
>
> p = realloc(p, UINT16_MAX);
> if (p == NULL)
> exit(EXIT_FAILURE);
> puts("realloc()");
>
> if (p == q) { // Use after realloc. I'd expect a warning
> here.
> puts("equal after");
> } else {
> /* Can we get here?
> Let's see the options:
>
> - realloc(3) fails:
> We exit immediately. We don't arrive
> here.
>
> - realloc(3) doesn't move the memory:
> p == q, as before
>
> - realloc(3) moved the memory:
> p is guaranteed to be a unique
> pointer,
> and q is now an invalid pointer. It
> is
> Undefined Behavior to read `q`, so `p
> == q`
> is UB.
>
> As we see, there's no _defined_ path where this
> can happen
> */
> printf("PID = %i\n", (int) getpid());
> }
>
> printf("p = %p; q = %p\n", p, q);
> }
> alx@debian:~/tmp$ cc -Wall -Wextra realloc.c -O3 -fanalyzer
> realloc.c: In function ‘main’:
> realloc.c:67:9: warning: pointer ‘p’ may be used after ‘realloc’ [-
> Wuse-after-free]
> 67 | printf("p = %p; q = %p\n", p, q);
> | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> realloc.c:39:13: note: call to ‘realloc’ here
> 39 | p = realloc(p, UINT16_MAX);
> | ^~~~~~~~~~~~~~~~~~~~~~
> alx@debian:~/tmp$ ./a.out
> equal before
> p = 0x55bff80802a0; q = 0x55bff80802a0
> realloc()
> PID = 25222
> p = 0x55bff80806d0; q = 0x55bff80802a0
>
>
> Did I miss anything?
GCC's -fanalyzer will warn if you dereference q, so e.g. adding:
printf("*q = %i\n", *q);
gives a warning:
https://godbolt.org/z/6qx4afb3E
<source>: In function 'main':
<source>:65:29: warning: use after 'free' of 'q' [CWE-416]
[-Wanalyzer-use-after-free]
65 | printf("*q = %i\n", *q);
| ^~
'main': events 1-2
|
| 25 | main(void)
| | ^~~~
| | |
| | (1) entry to 'main'
|......
| 29 | p = xstrdup("");
| | ~~~~~~~~~~~
| | |
| | (2) calling 'xstrdup' from 'main'
|
+--> 'xstrdup': events 3-5
|
| 8 | xstrdup(const char *s)
| | ^~~~~~~
| | |
| | (3) entry to 'xstrdup'
|......
| 13 | if (p == NULL)
| | ~
| | |
| | (4) following 'false' branch (when 'p' is
non-NULL)...
| 14 | exit(EXIT_FAILURE);
| 15 | return p;
| | ~
| | |
| | (5) ...to here
|
<------+
|
'main': events 6-15
|
| 29 | p = xstrdup("");
| | ^~~~~~~~~~~
| | |
| | (6) returning to 'main' from 'xstrdup'
|......
| 32 | if (p == q)
| | ~
| | |
| | (7) following 'true' branch (when 'p == q')...
| 33 | puts("equal before");
| | ~~~~~~~~~~~~~~~~~~~~
| | |
| | (8) ...to here
|......
| 39 | p = realloc(p, UINT16_MAX);
| | ~~~~~~~~~~~~~~~~~~~~~~
| | |
| | (9) freed here
| | (10) when 'realloc' succeeds, moving buffer
| 40 | if (p == NULL)
| | ~
| | |
| | (11) following 'false' branch (when 'p' is non-NULL)...
| 41 | exit(EXIT_FAILURE);
| 42 | puts("realloc()");
| | ~~~~~~~~~~~~~~~~~
| | |
| | (12) ...to here
| 43 |
| 44 | if (p == q) { // Use after realloc. I'd expect a warning
here.
| | ~
| | |
| | (13) following 'false' branch (when 'p != q')...
|......
| 64 | printf("PID = %i\n", (int) getpid());
| | ~~~~~~~~
| | |
| | (14) ...to here
| 65 | printf("*q = %i\n", *q);
| | ~~
| | |
| | (15) use after 'free' of 'q'; freed at
(9)
|
I'm not convinced that it's useful to the end-user to warn about the
"use of q itself" case.
Dave