Fix a veritable conucopia of bugs in the readdir_r interceptors.
authorChandler Carruth <chandlerc@gmail.com>
Thu, 11 Jul 2013 18:51:40 +0000 (18:51 +0000)
committerChandler Carruth <chandlerc@gmail.com>
Thu, 11 Jul 2013 18:51:40 +0000 (18:51 +0000)
First, the reason I came here: I forgot to look at readdir64_r which had
the exact same bug as readdir_r. However, upon applying the same
quick-fix and testing it I discovered that it still didn't work at all.
As a consequence, I spent some time studying the code and thinking about
it and fixed several other problems.

Second, the code was checking for a null entry and result pointer, but
there is no indication that null pointers are viable here. Certainly,
the spec makes it extremely clear that there is no non-error case where
the implementation of readdir_r fails to dereference the 'result'
pointer and store NULL to it. Thus, our checking for a non-null 'result'
pointer before reflecting that write in the instrumentation was
trivially dead. Remove it.

Third, the interceptor was marking the write to the actual dirent struct
by looking at the entry pointer, but nothing in the spec requires that
the dirent struct written is actually written into the entry structure
provided. A threadlocal buffer would be just as conforming, and the spec
goes out of its way to say the pointer to the *actual* result dirent
struct is stored into *result, so *that* is where the interceptor should
reflect a write occuring. This also obviates the need to even consider
whether the 'entry' parameter is null.

Fourth, I got to the bottom of why nothing at all worked in readdir64_r
-- the interceptor structure for dirent64 was completely wrong in that
it was the same as dirent. I fixed this struct to be correct (64-bit
inode and 64-bit offset! just a 64-bit offset isn't enough!) and added
several missing tests for the size and layout of this struct.

llvm-svn: 186109

compiler-rt/lib/asan/lit_tests/TestCases/Linux/interception_readdir_r_test.cc
compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.cc
compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.h

index 8adc064..28c7df4 100644 (file)
@@ -2,29 +2,56 @@
 // RUN: %clangxx_asan -O1 %s -DTEMP_DIR='"'"%T"'"' -o %t && %t 2>&1 | FileCheck %s
 // RUN: %clangxx_asan -O2 %s -DTEMP_DIR='"'"%T"'"' -o %t && %t 2>&1 | FileCheck %s
 // RUN: %clangxx_asan -O3 %s -DTEMP_DIR='"'"%T"'"' -o %t && %t 2>&1 | FileCheck %s
+//
+// RUN: %clangxx_asan -O0 %s -D_FILE_OFFSET_BITS=64 -DTEMP_DIR='"'"%T"'"' -o %t && %t 2>&1 | FileCheck %s
+// RUN: %clangxx_asan -O1 %s -D_FILE_OFFSET_BITS=64 -DTEMP_DIR='"'"%T"'"' -o %t && %t 2>&1 | FileCheck %s
+// RUN: %clangxx_asan -O2 %s -D_FILE_OFFSET_BITS=64 -DTEMP_DIR='"'"%T"'"' -o %t && %t 2>&1 | FileCheck %s
+// RUN: %clangxx_asan -O3 %s -D_FILE_OFFSET_BITS=64 -DTEMP_DIR='"'"%T"'"' -o %t && %t 2>&1 | FileCheck %s
 
-#include <stdlib.h>
+#include <dirent.h>
+#include <memory.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <unistd.h>
-#include <dirent.h>
 
 
 int main() {
   // Ensure the readdir_r interceptor doesn't erroneously mark the entire dirent
   // as written when the end of the directory pointer is reached.
-  fputs("reading the " TEMP_DIR " directory...\n", stderr);
+  fputs("test1: reading the " TEMP_DIR " directory...\n", stderr);
   DIR *d = opendir(TEMP_DIR);
-  struct dirent entry, *result;
+  struct dirent *result = (struct dirent *)(0xfeedbeef);
+  // We assume the temp dir for this test doesn't have crazy long file names.
+  char entry_buffer[4096];
+  memset(entry_buffer, 0xab, sizeof(entry_buffer));
   unsigned count = 0;
   do {
     // Stamp the entry struct to try to trick the interceptor.
-    entry.d_reclen = 9999;
-    if (readdir_r(d, &entry, &result) != 0)
+    ((struct dirent *)entry_buffer)->d_reclen = 9999;
+    if (readdir_r(d, (struct dirent *)entry_buffer, &result) != 0)
       abort();
     ++count;
   } while (result != NULL);
   fprintf(stderr, "read %d entries\n", count);
-  // CHECK: reading the {{.*}} directory...
+  // CHECK: test1: reading the {{.*}} directory...
+  // CHECK-NOT: stack-buffer-overflow
+  // CHECK: read {{.*}} entries
+
+  // Ensure the readdir64_r interceptor doesn't have the bug either.
+  fputs("test2: reading the " TEMP_DIR " directory...\n", stderr);
+  d = opendir(TEMP_DIR);
+  struct dirent64 *result64;
+  memset(entry_buffer, 0xab, sizeof(entry_buffer));
+  count = 0;
+  do {
+    // Stamp the entry struct to try to trick the interceptor.
+    ((struct dirent64 *)entry_buffer)->d_reclen = 9999;
+    if (readdir64_r(d, (struct dirent64 *)entry_buffer, &result64) != 0)
+      abort();
+    ++count;
+  } while (result64 != NULL);
+  fprintf(stderr, "read %d entries\n", count);
+  // CHECK: test2: reading the {{.*}} directory...
   // CHECK-NOT: stack-buffer-overflow
   // CHECK: read {{.*}} entries
 }
index 033f816..329ff2c 100644 (file)
@@ -1372,10 +1372,9 @@ INTERCEPTOR(int, readdir_r, void *dirp, __sanitizer_dirent *entry,
   COMMON_INTERCEPTOR_ENTER(ctx, readdir_r, dirp, entry, result);
   int res = REAL(readdir_r)(dirp, entry, result);
   if (!res) {
-    if (result)
-      COMMON_INTERCEPTOR_WRITE_RANGE(ctx, result, sizeof(*result));
-    if (entry && *result)
-      COMMON_INTERCEPTOR_WRITE_RANGE(ctx, entry, entry->d_reclen);
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, result, sizeof(*result));
+    if (*result)
+      COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *result, (*result)->d_reclen);
   }
   return res;
 }
@@ -1403,10 +1402,9 @@ INTERCEPTOR(int, readdir64_r, void *dirp, __sanitizer_dirent64 *entry,
   COMMON_INTERCEPTOR_ENTER(ctx, readdir64_r, dirp, entry, result);
   int res = REAL(readdir64_r)(dirp, entry, result);
   if (!res) {
-    if (result)
-      COMMON_INTERCEPTOR_WRITE_RANGE(ctx, result, sizeof(*result));
-    if (entry)
-      COMMON_INTERCEPTOR_WRITE_RANGE(ctx, entry, entry->d_reclen);
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, result, sizeof(*result));
+    if (*result)
+      COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *result, (*result)->d_reclen);
   }
   return res;
 }
@@ -1542,10 +1540,8 @@ INTERCEPTOR(SIZE_T, mbstowcs, wchar_t *dest, const char *src, SIZE_T len) {
   void *ctx;
   COMMON_INTERCEPTOR_ENTER(ctx, mbstowcs, dest, src, len);
   SIZE_T res = REAL(mbstowcs)(dest, src, len);
-  if (res != (SIZE_T) - 1 && dest) {
-    SIZE_T write_cnt = res + (res < len);
-    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, write_cnt * sizeof(wchar_t));
-  }
+  if (res != (SIZE_T) - 1 && dest)
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, (res + 1) * sizeof(wchar_t));
   return res;
 }
 
@@ -1553,15 +1549,12 @@ INTERCEPTOR(SIZE_T, mbsrtowcs, wchar_t *dest, const char **src, SIZE_T len,
             void *ps) {
   void *ctx;
   COMMON_INTERCEPTOR_ENTER(ctx, mbsrtowcs, dest, src, len, ps);
-  if (src) COMMON_INTERCEPTOR_READ_RANGE(ctx, src, sizeof(*src));
-  if (ps) COMMON_INTERCEPTOR_READ_RANGE(ctx, ps, mbstate_t_sz);
-  SIZE_T res = REAL(mbsrtowcs)(dest, src, len, ps);
-  if (res != (SIZE_T)(-1) && dest && src) {
-    // This function, and several others, may or may not write the terminating
-    // \0 character. They write it iff they clear *src.
-    SIZE_T write_cnt = res + !*src;
-    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, write_cnt * sizeof(wchar_t));
+  if (src) {
+    COMMON_INTERCEPTOR_READ_RANGE(ctx, src, sizeof(*src));
   }
+  SIZE_T res = REAL(mbsrtowcs)(dest, src, len, ps);
+  if (res != (SIZE_T) - 1 && dest)
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, (res + 1) * sizeof(wchar_t));
   return res;
 }
 
@@ -1581,12 +1574,9 @@ INTERCEPTOR(SIZE_T, mbsnrtowcs, wchar_t *dest, const char **src, SIZE_T nms,
     COMMON_INTERCEPTOR_READ_RANGE(ctx, src, sizeof(*src));
     if (nms) COMMON_INTERCEPTOR_READ_RANGE(ctx, *src, nms);
   }
-  if (ps) COMMON_INTERCEPTOR_READ_RANGE(ctx, ps, mbstate_t_sz);
   SIZE_T res = REAL(mbsnrtowcs)(dest, src, nms, len, ps);
-  if (res != (SIZE_T)(-1) && dest && src) {
-    SIZE_T write_cnt = res + !*src;
-    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, write_cnt * sizeof(wchar_t));
-  }
+  if (res != (SIZE_T) - 1 && dest)
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, (res + 1) * sizeof(wchar_t));
   return res;
 }
 
@@ -1600,10 +1590,8 @@ INTERCEPTOR(SIZE_T, wcstombs, char *dest, const wchar_t *src, SIZE_T len) {
   void *ctx;
   COMMON_INTERCEPTOR_ENTER(ctx, wcstombs, dest, src, len);
   SIZE_T res = REAL(wcstombs)(dest, src, len);
-  if (res != (SIZE_T) - 1 && dest) {
-    SIZE_T write_cnt = res + (res < len);
-    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, write_cnt);
-  }
+  if (res != (SIZE_T) - 1 && dest)
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, res + 1);
   return res;
 }
 
@@ -1611,13 +1599,12 @@ INTERCEPTOR(SIZE_T, wcsrtombs, char *dest, const wchar_t **src, SIZE_T len,
             void *ps) {
   void *ctx;
   COMMON_INTERCEPTOR_ENTER(ctx, wcsrtombs, dest, src, len, ps);
-  if (src) COMMON_INTERCEPTOR_READ_RANGE(ctx, src, sizeof(*src));
-  if (ps) COMMON_INTERCEPTOR_READ_RANGE(ctx, ps, mbstate_t_sz);
-  SIZE_T res = REAL(wcsrtombs)(dest, src, len, ps);
-  if (res != (SIZE_T) - 1 && dest && src) {
-    SIZE_T write_cnt = res + !*src;
-    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, write_cnt);
+  if (src) {
+    COMMON_INTERCEPTOR_READ_RANGE(ctx, src, sizeof(*src));
   }
+  SIZE_T res = REAL(wcsrtombs)(dest, src, len, ps);
+  if (res != (SIZE_T) - 1 && dest)
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, res + 1);
   return res;
 }
 
@@ -1637,12 +1624,9 @@ INTERCEPTOR(SIZE_T, wcsnrtombs, char *dest, const wchar_t **src, SIZE_T nms,
     COMMON_INTERCEPTOR_READ_RANGE(ctx, src, sizeof(*src));
     if (nms) COMMON_INTERCEPTOR_READ_RANGE(ctx, *src, nms);
   }
-  if (ps) COMMON_INTERCEPTOR_READ_RANGE(ctx, ps, mbstate_t_sz);
   SIZE_T res = REAL(wcsnrtombs)(dest, src, nms, len, ps);
-  if (res != (SIZE_T) - 1 && dest && src) {
-    SIZE_T write_cnt = res + !*src;
-    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, write_cnt);
-  }
+  if (res != (SIZE_T) - 1 && dest)
+    COMMON_INTERCEPTOR_WRITE_RANGE(ctx, dest, res + 1);
   return res;
 }
 
index 93d0d7f..16f12ed 100644 (file)
@@ -795,12 +795,20 @@ CHECK_SIZE_AND_OFFSET(cmsghdr, cmsg_len);
 CHECK_SIZE_AND_OFFSET(cmsghdr, cmsg_level);
 CHECK_SIZE_AND_OFFSET(cmsghdr, cmsg_type);
 
+COMPILER_CHECK(sizeof(__sanitizer_dirent) <= sizeof(dirent));
 CHECK_SIZE_AND_OFFSET(dirent, d_ino);
 #ifndef SANITIZER_MAC
 CHECK_SIZE_AND_OFFSET(dirent, d_off);
 #endif
 CHECK_SIZE_AND_OFFSET(dirent, d_reclen);
 
+#if SANITIZER_LINUX && !SANITIZER_ANDROID
+COMPILER_CHECK(sizeof(__sanitizer_dirent64) <= sizeof(dirent64));
+CHECK_SIZE_AND_OFFSET(dirent64, d_ino);
+CHECK_SIZE_AND_OFFSET(dirent64, d_off);
+CHECK_SIZE_AND_OFFSET(dirent64, d_reclen);
+#endif
+
 CHECK_TYPE_SIZE(ifconf);
 CHECK_SIZE_AND_OFFSET(ifconf, ifc_len);
 CHECK_SIZE_AND_OFFSET(ifconf, ifc_ifcu);
index 6b91fca..781a655 100644 (file)
@@ -118,8 +118,8 @@ namespace __sanitizer {
 
 #if SANITIZER_LINUX && !SANITIZER_ANDROID
   struct __sanitizer_dirent64 {
-    uptr d_ino;
-    uptr d_off;
+    unsigned long long d_ino;
+    unsigned long long d_off;
     unsigned short d_reclen;
     // more fields that we don't care about
   };