-
Notifications
You must be signed in to change notification settings - Fork 15.4k
Open
Labels
Description
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]$