|
|
Message-ID: <20250721002322.703-3-mailto.luca.kellermann@gmail.com>
Date: Mon, 21 Jul 2025 02:23:21 +0200
From: Luca Kellermann <mailto.luca.kellermann@...il.com>
To: Rich Felker <dalias@...c.org>
Cc: Markus Wichmann <nullplan@....net>,
musl@...ts.openwall.com
Subject: [PATCH v2 3/4] scandir: disable cancellation around cancellation points
opendir() or closedir() might act upon a cancellation request. because
scandir() did not disable cancellation or install a cancellation
cleanup handler, this could lead to memory and file descriptor leaks.
the following program demonstrates the leaks:
#include <dirent.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
static void die(const char *msg)
{
int cs, e = errno;
if (!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs)) {
errno = e;
perror(msg);
}
_Exit(EXIT_FAILURE);
}
static int posted;
static sem_t sem;
static int sel(const struct dirent *e)
{
if (!posted && sem_post(&sem)) die("sem_post");
posted = 1;
return 1;
}
static void *run(void *arg)
{
posted = 0;
struct dirent **es = 0;
int n = scandir(".", &es, sel, alphasort);
if (n == -1) die("scandir");
while (n > 0) free(es[--n]);
free(es);
return 0;
}
int main(void)
{
if (sem_init(&sem, 0, 0)) die("sem_init");
void *res = 0;
do {
pthread_t tid;
if (errno = pthread_create(&tid, 0, run, 0))
die("pthread_create");
if (sem_wait(&sem)) die("sem_wait");
if (errno = pthread_cancel(tid))
die("pthread_cancel");
if (errno = pthread_join(tid, &res))
die("pthread_join");
} while (res != PTHREAD_CANCELED);
if (sem_destroy(&sem)) die("sem_destroy");
}
$ valgrind -q --leak-check=full --show-leak-kinds=all ./scandir-leak
==31293== 296 bytes in 9 blocks are indirectly lost in loss record 1 of 3
==31293== at 0x48AF828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==31293== by 0x401C500: scandir (scandir.c:32)
==31293== by 0x10933F: run (scandir-leak.c:28)
==31293== by 0x405E653: start (pthread_create.c:207)
==31293== by 0x4060DEA: ??? (clone.s:22)
==31293==
==31293== 416 (120 direct, 296 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 3
==31293== at 0x48B6B80: realloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==31293== by 0x401C4EA: scandir (scandir.c:28)
==31293== by 0x10933F: run (scandir-leak.c:28)
==31293== by 0x405E653: start (pthread_create.c:207)
==31293== by 0x4060DEA: ??? (clone.s:22)
==31293==
==31293== 2,072 bytes in 1 blocks are definitely lost in loss record 3 of 3
==31293== at 0x48B6953: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==31293== by 0x401C2E0: opendir (opendir.c:15)
==31293== by 0x401C491: scandir (scandir.c:12)
==31293== by 0x10933F: run (scandir-leak.c:28)
==31293== by 0x405E653: start (pthread_create.c:207)
==31293== by 0x4060DEA: ??? (clone.s:22)
==31293==
$ strace -fe t=open,close ./scandir-leak
strace: Process 27136 attached
[pid 27136] open(".", O_RDONLY|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 3
[pid 27136] --- SIGRT_1 {si_signo=SIGRT_1, si_code=SI_TKILL, si_pid=27135, si_uid=1000} ---
[pid 27136] +++ exited with 0 +++
+++ exited with 0 +++
---
src/dirent/scandir.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/src/dirent/scandir.c b/src/dirent/scandir.c
index cf668535..4f91af9c 100644
--- a/src/dirent/scandir.c
+++ b/src/dirent/scandir.c
@@ -1,4 +1,5 @@
#include <dirent.h>
+#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
@@ -9,11 +10,20 @@ int scandir(const char *path, struct dirent ***res,
int (*sel)(const struct dirent *),
int (*cmp)(const struct dirent **, const struct dirent **))
{
- DIR *d = opendir(path);
+ DIR *d;
struct dirent *de, **names=0, **tmp;
size_t cnt=0, len=0;
- int old_errno = errno, err;
+ int old_errno = errno, err, cs;
+ /* opendir() and closedir() are cancellation points. scandir() is also
+ * allowed to be a cancellation point but we choose not to make it one.
+ * To avoid calling sel() and cmp() with altered thread state,
+ * cancellation is not explicitly disabled for those calls. This means
+ * if either of the callbacks acts upon a cancellation request, there
+ * can be memory and file descriptor leaks. */
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
+ d = opendir(path);
+ pthread_setcancelstate(cs, 0);
if (!d) return -1;
while ((errno=0), (de = readdir(d))) {
@@ -39,7 +49,9 @@ int scandir(const char *path, struct dirent ***res,
* "failure" of closedir() as a failure of scandir(). */
err = errno;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
closedir(d);
+ pthread_setcancelstate(cs, 0);
if (err) {
if (names) while (cnt-->0) free(names[cnt]);
--
2.43.0
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.