Skip to content

clang-static-analyzer: False positive UAF when removing STAILQ entries #167586

@rojer

Description

@rojer

Static analyzer reports a false positive UAF (clang-analyzer-cplusplus.NewDelete) when an element of a TAILQ (singly-linked list with head/tail pointers from <sys/queue.h>) is removed and deleted (in a safe manner) inside a FOREACH loop.

Minimal(ish) repro:

#include <stdio.h>
#include <sys/queue.h>

struct Foo {
  Foo(int _n) : n(_n) {}

  int n = 0;
  STAILQ_ENTRY(Foo) next = {};
};

STAILQ_HEAD(foos, Foo)
foos = STAILQ_HEAD_INITIALIZER(foos);

int main() {
  Foo *fi;

  for (int i = 1; i <= 3; i++) {
    fi = new Foo(i);
    STAILQ_INSERT_TAIL(&foos, fi, next);
  }

  STAILQ_FOREACH(fi, &foos, next) {
    printf("%d\n", fi->n);
  }
  printf("\n");

  bool removed = false;
  do {
    removed = false;
    STAILQ_FOREACH(fi, &foos, next) {
      printf("%p\n", fi);  // False UAF reported here
      if (fi->n == 1) {
        STAILQ_REMOVE(&foos, fi, Foo, next);
        printf(" delete %p\n", fi);
        delete fi;
        removed = true;
        break;
      }
    }
  } while (removed);
  printf("\n");

  STAILQ_FOREACH(fi, &foos, next) {
    printf("%d\n", fi->n);
  }

  return 0;
}

clang-tidy 21.1.5 warning:

[rojer@nbf /tmp]$ ~/Downloads/LLVM-21.1.5-Linux-X64/bin/clang-tidy --version
LLVM (http://llvm.org/):
  LLVM version 21.1.5
  Optimized build.
[rojer@nbf /tmp]$ ~/Downloads/LLVM-21.1.5-Linux-X64/bin/clang-tidy qq.cpp 
Error while trying to load a compilation database:
Could not auto-detect compilation database for file "qq.cpp"
No compilation database found in /tmp or any parent directory
fixed-compilation-database: Error while opening fixed database: No such file or directory
json-compilation-database: Error while opening JSON database: No such file or directory
Running without flags.
1 warning generated.
/tmp/qq.cpp:31:7: warning: Use of memory after it is freed [clang-analyzer-cplusplus.NewDelete]
   31 |       printf("%p\n", fi);  // False UAF reported here
      |       ^              ~~
/tmp/qq.cpp:17:3: note: Loop condition is true.  Entering loop body
   17 |   for (int i = 1; i <= 3; i++) {
      |   ^
/tmp/qq.cpp:19:5: note: Loop condition is false.  Exiting loop
   19 |     STAILQ_INSERT_TAIL(&foos, fi, next);
      |     ^
/usr/include/x86_64-linux-gnu/sys/queue.h:239:46: note: expanded from macro 'STAILQ_INSERT_TAIL'
  239 | #define STAILQ_INSERT_TAIL(head, elm, field) do {                       \
      |                                              ^
/tmp/qq.cpp:17:3: note: Loop condition is true.  Entering loop body
   17 |   for (int i = 1; i <= 3; i++) {
      |   ^
/tmp/qq.cpp:19:5: note: Loop condition is false.  Exiting loop
   19 |     STAILQ_INSERT_TAIL(&foos, fi, next);
      |     ^
/usr/include/x86_64-linux-gnu/sys/queue.h:239:46: note: expanded from macro 'STAILQ_INSERT_TAIL'
  239 | #define STAILQ_INSERT_TAIL(head, elm, field) do {                       \
      |                                              ^
/tmp/qq.cpp:17:3: note: Loop condition is true.  Entering loop body
   17 |   for (int i = 1; i <= 3; i++) {
      |   ^
/tmp/qq.cpp:19:5: note: Loop condition is false.  Exiting loop
   19 |     STAILQ_INSERT_TAIL(&foos, fi, next);
      |     ^
/usr/include/x86_64-linux-gnu/sys/queue.h:239:46: note: expanded from macro 'STAILQ_INSERT_TAIL'
  239 | #define STAILQ_INSERT_TAIL(head, elm, field) do {                       \
      |                                              ^
/tmp/qq.cpp:17:3: note: Loop condition is false. Execution continues on line 22
   17 |   for (int i = 1; i <= 3; i++) {
      |   ^
/tmp/qq.cpp:22:3: note: Loop condition is true.  Entering loop body
   22 |   STAILQ_FOREACH(fi, &foos, next) {
      |   ^
/usr/include/x86_64-linux-gnu/sys/queue.h:270:2: note: expanded from macro 'STAILQ_FOREACH'
  270 |         for ((var) = ((head)->stqh_first);                              \
      |         ^
/tmp/qq.cpp:22:3: note: Loop condition is false. Execution continues on line 25
   22 |   STAILQ_FOREACH(fi, &foos, next) {
      |   ^
/usr/include/x86_64-linux-gnu/sys/queue.h:270:2: note: expanded from macro 'STAILQ_FOREACH'
  270 |         for ((var) = ((head)->stqh_first);                              \
      |         ^
/tmp/qq.cpp:30:5: note: Loop condition is true.  Entering loop body
   30 |     STAILQ_FOREACH(fi, &foos, next) {
      |     ^
/usr/include/x86_64-linux-gnu/sys/queue.h:270:2: note: expanded from macro 'STAILQ_FOREACH'
  270 |         for ((var) = ((head)->stqh_first);                              \
      |         ^
/tmp/qq.cpp:32:11: note: Assuming field 'n' is not equal to 1
   32 |       if (fi->n == 1) {
      |           ^~~~~~~~~~
/tmp/qq.cpp:32:7: note: Taking false branch
   32 |       if (fi->n == 1) {
      |       ^
/tmp/qq.cpp:30:5: note: Loop condition is true.  Entering loop body
   30 |     STAILQ_FOREACH(fi, &foos, next) {
      |     ^
/usr/include/x86_64-linux-gnu/sys/queue.h:270:2: note: expanded from macro 'STAILQ_FOREACH'
  270 |         for ((var) = ((head)->stqh_first);                              \
      |         ^
/tmp/qq.cpp:32:11: note: Assuming field 'n' is equal to 1
   32 |       if (fi->n == 1) {
      |           ^~~~~~~~~~
/tmp/qq.cpp:32:7: note: Taking true branch
   32 |       if (fi->n == 1) {
      |       ^
/tmp/qq.cpp:33:9: note: Assuming 'fi' is equal to field 'stqh_first'
   33 |         STAILQ_REMOVE(&foos, fi, Foo, next);
      |         ^
/usr/include/x86_64-linux-gnu/sys/queue.h:257:6: note: expanded from macro 'STAILQ_REMOVE'
  257 |         if ((head)->stqh_first == (elm)) {                              \
      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/qq.cpp:33:9: note: Taking true branch
   33 |         STAILQ_REMOVE(&foos, fi, Foo, next);
      |         ^
/usr/include/x86_64-linux-gnu/sys/queue.h:257:2: note: expanded from macro 'STAILQ_REMOVE'
  257 |         if ((head)->stqh_first == (elm)) {                              \
      |         ^
/tmp/qq.cpp:33:9: note: Taking false branch
   33 |         STAILQ_REMOVE(&foos, fi, Foo, next);
      |         ^
/usr/include/x86_64-linux-gnu/sys/queue.h:258:3: note: expanded from macro 'STAILQ_REMOVE'
  258 |                 STAILQ_REMOVE_HEAD((head), field);                      \
      |                 ^
/usr/include/x86_64-linux-gnu/sys/queue.h:252:2: note: expanded from macro 'STAILQ_REMOVE_HEAD'
  252 |         if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \
      |         ^
/tmp/qq.cpp:33:9: note: Loop condition is false.  Exiting loop
   33 |         STAILQ_REMOVE(&foos, fi, Foo, next);
      |         ^
/usr/include/x86_64-linux-gnu/sys/queue.h:258:3: note: expanded from macro 'STAILQ_REMOVE'
  258 |                 STAILQ_REMOVE_HEAD((head), field);                      \
      |                 ^
/usr/include/x86_64-linux-gnu/sys/queue.h:251:41: note: expanded from macro 'STAILQ_REMOVE_HEAD'
  251 | #define STAILQ_REMOVE_HEAD(head, field) do {                            \
      |                                         ^
/tmp/qq.cpp:33:9: note: Loop condition is false.  Exiting loop
   33 |         STAILQ_REMOVE(&foos, fi, Foo, next);
      |         ^
/usr/include/x86_64-linux-gnu/sys/queue.h:256:47: note: expanded from macro 'STAILQ_REMOVE'
  256 | #define STAILQ_REMOVE(head, elm, type, field) do {                      \
      |                                               ^
/tmp/qq.cpp:35:9: note: Memory is released
   35 |         delete fi;
      |         ^~~~~~~~~
/tmp/qq.cpp:37:9: note:  Execution continues on line 40
   37 |         break;
      |         ^
/tmp/qq.cpp:28:3: note: Loop condition is true. Execution continues on line 29
   28 |   do {
      |   ^
/tmp/qq.cpp:30:5: note: Loop condition is true.  Entering loop body
   30 |     STAILQ_FOREACH(fi, &foos, next) {
      |     ^
/usr/include/x86_64-linux-gnu/sys/queue.h:270:2: note: expanded from macro 'STAILQ_FOREACH'
  270 |         for ((var) = ((head)->stqh_first);                              \
      |         ^
/tmp/qq.cpp:31:7: note: Use of memory after it is freed
   31 |       printf("%p\n", fi);  // False UAF reported here
      |       ^              ~~
[rojer@nbf /tmp]$

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions