![]() |
|
Message-ID: <20250710185131.186-3-mailto.luca.kellermann@gmail.com> Date: Thu, 10 Jul 2025 20:51:30 +0200 From: Luca Kellermann <mailto.luca.kellermann@...il.com> To: musl@...ts.openwall.com Subject: [PATCH 3/4] scandir: fix leaks caused by cancellation opendir(), sel(), closedir(), or cmp() might act upon a cancellation request. because scandir() did not 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 | 75 ++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/dirent/scandir.c b/src/dirent/scandir.c index cf668535..8883b9f8 100644 --- a/src/dirent/scandir.c +++ b/src/dirent/scandir.c @@ -1,37 +1,61 @@ #include <dirent.h> +#include <pthread.h> #include <string.h> #include <stdlib.h> #include <stdint.h> #include <errno.h> #include <stddef.h> +struct cleanup_params { + DIR *d; + struct dirent **names; + size_t cnt; +}; + +static void cleanup(void *p) +{ + struct cleanup_params *c = p; + /* Cancellation is disabled if c->d is not NULL, so closedir() + * won't act upon a cancellation request here. */ + if (c->d) closedir(c->d); + if (c->names) { + size_t cnt = c->cnt; + while (cnt-->0) free(c->names[cnt]); + free(c->names); + } +} + 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); - struct dirent *de, **names=0, **tmp; - size_t cnt=0, len=0; + /* opendir() might act upon a cancellation request. */ + struct cleanup_params c = {.d = opendir(path)}; + struct dirent *de, **tmp; + size_t len=0; int old_errno = errno, err; - if (!d) return -1; + if (!c.d) return -1; + + /* sel(), closedir(), or cmp() might act upon a cancellation request. */ + pthread_cleanup_push(cleanup, &c); - while ((errno=0), (de = readdir(d))) { + while ((errno=0), (de = readdir(c.d))) { if (sel) { /* sel() must not observe that errno was set to 0. */ errno = old_errno; if (!sel(de)) continue; } - if (cnt >= len) { + if (c.cnt >= len) { len = 2*len+1; - if (len > SIZE_MAX/sizeof *names) break; - tmp = realloc(names, len * sizeof *names); + if (len > SIZE_MAX/sizeof *c.names) break; + tmp = realloc(c.names, len * sizeof *c.names); if (!tmp) break; - names = tmp; + c.names = tmp; } - names[cnt] = malloc(de->d_reclen); - if (!names[cnt]) break; - memcpy(names[cnt++], de, de->d_reclen); + c.names[c.cnt] = malloc(de->d_reclen); + if (!c.names[c.cnt]) break; + memcpy(c.names[c.cnt++], de, de->d_reclen); } /* closedir() might set errno via __aio_close(). It might also "fail" * (return -1 and set errno). But even then, the file descriptor is @@ -39,18 +63,23 @@ int scandir(const char *path, struct dirent ***res, * "failure" of closedir() as a failure of scandir(). */ err = errno; - closedir(d); + /* If closedir() acts upon a cancellation request, it is retried by + * cleanup() with cancellation disabled. closedir() won't act upon a + * cancellation request in masked cancellation mode, meaning errno does + * not need to be inspected for ECANCELED. */ + closedir(c.d); + /* cleanup() shouldn't call closedir() from here on, because the + * directory stream is already closed. */ + c.d = 0; - if (err) { - if (names) while (cnt-->0) free(names[cnt]); - free(names); - errno = err; - return -1; + if (!err) { + /* cmp() and caller must not observe that errno was set to 0. */ + errno = old_errno; + if (cmp) qsort(c.names, c.cnt, sizeof *c.names, (int (*)(const void *, const void *))cmp); + *res = c.names; } - /* cmp() and caller must not observe that errno was set to 0. */ - errno = old_errno; - if (cmp) qsort(names, cnt, sizeof *names, (int (*)(const void *, const void *))cmp); - *res = names; - return cnt; + pthread_cleanup_pop(err); + + return err ? (errno = err), -1 : c.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.