From e8d226b22e8eef3501917cbb4513c79f1472511b Mon Sep 17 00:00:00 2001
From: Fred Wright <fw@fwright.net>
Date: Mon, 18 Nov 2024 13:45:20 -0800
Subject: [PATCH] test_fdopendir: Add fstatat() buffer overrun defense.

At the time of this writing, fstatat() may choose the wrong variant of
stat to return, possibly overflowing the provided buffer.  To defend
against this, we make the buffer the larger of the two possible sizes,
and also move it off the stack for less verbosity when debugging.  See
the comment in the code.

TESTED:
All versions pass, including previously crashing ino32 cases.
---
 test/test_fdopendir.c | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/test/test_fdopendir.c b/test/test_fdopendir.c
index 1f761ff..1712b30 100644
--- a/test/test_fdopendir.c
+++ b/test/test_fdopendir.c
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (c) 2019
  * Copyright (C) 2023 raf <raf@raf.org>
@@ -28,6 +27,20 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+/*
+ * Buffer for fstatat() result.  Due to possible confusion over which variant
+ * should apply, we make it large enough for either variant.  Since the
+ * use of fstatat is only incidental to this test, and the data isn't actually
+ * used, we don't bother checking for this confusion (and the related possible
+ * buffer overrun).  A separate test should handle that.
+ */
+static union stat_u {
+    struct stat st;
+#ifdef __DARWIN_STRUCT_STAT64
+    struct stat_64 __DARWIN_STRUCT_STAT64 st64;
+#endif
+} stbuf;
+
 /* Test expected failure case */
 
 int check_failure(int fd, const char *name, const char *exp_sym, int exp_val)
@@ -51,7 +64,6 @@ int
 main(int argc, char *argv[])
 {
     int verbose = 0;
-    struct stat st;
     struct dirent *entry;
     int dfd = -1;
     DIR *dir;
@@ -84,7 +96,7 @@ main(int argc, char *argv[])
             return 1;
         }
 
-        if (fstatat(dfd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+        if (fstatat(dfd, entry->d_name, &stbuf.st, AT_SYMLINK_NOFOLLOW) < 0) {
             perror("error: fstatat after fdopendir failed");
             fprintf(stderr, "dfd=%i d_name=%s\n", dfd, entry->d_name);
             free(first_entry);
@@ -193,7 +205,7 @@ main(int argc, char *argv[])
         if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
             continue;
 
-        if (fstatat(dfd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+        if (fstatat(dfd, entry->d_name, &stbuf.st, AT_SYMLINK_NOFOLLOW) < 0) {
             perror("error: fstatat after opendir failed");
             fprintf(stderr, "dfd=%i d_name=%s\n", dfd, entry->d_name);
             (void)closedir(dir);
-- 
GitLab