[compiler-rt] e8861fa - sanitizer_common: fix crashes in parsing of memory profiles

Dmitry Vyukov via llvm-commits llvm-commits at lists.llvm.org
Fri Oct 29 05:03:15 PDT 2021


Author: Dmitry Vyukov
Date: 2021-10-29T14:03:12+02:00
New Revision: e8861fa6c3fd785eae6f6bb7f51a3f79cd430b65

URL: https://github.com/llvm/llvm-project/commit/e8861fa6c3fd785eae6f6bb7f51a3f79cd430b65
DIFF: https://github.com/llvm/llvm-project/commit/e8861fa6c3fd785eae6f6bb7f51a3f79cd430b65.diff

LOG: sanitizer_common: fix crashes in parsing of memory profiles

ParseUnixMemoryProfile assumes well-formed input with \n at the end, etc.
It can over-read the input and crash on basically every line
in the case of malformed input.
ReadFileToBuffer has cap the max file size (64MB) and returns
truncated contents if the file is larger. Thus even if kernel behaves,
ParseUnixMemoryProfile crashes on too large /proc/self/smaps.
Fix input over-reading in ParseUnixMemoryProfile.

Depends on D112792.

Reviewed By: melver

Differential Revision: https://reviews.llvm.org/D112793

Added: 
    

Modified: 
    compiler-rt/lib/sanitizer_common/sanitizer_common.h
    compiler-rt/lib/sanitizer_common/sanitizer_procmaps_common.cpp
    compiler-rt/lib/sanitizer_common/tests/sanitizer_procmaps_test.cpp

Removed: 
    


################################################################################
diff  --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.h b/compiler-rt/lib/sanitizer_common/sanitizer_common.h
index 9a1cd9b96629..8b4d2e8b93bd 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.h
@@ -197,7 +197,7 @@ typedef void (*fill_profile_f)(uptr start, uptr rss, bool file,
 // Parse the contents of /proc/self/smaps and generate a memory profile.
 // |cb| is a tool-specific callback that fills the |stats| array.
 void GetMemoryProfile(fill_profile_f cb, uptr *stats);
-void ParseUnixMemoryProfile(fill_profile_f cb, uptr *stats, const char *smaps,
+void ParseUnixMemoryProfile(fill_profile_f cb, uptr *stats, char *smaps,
                             uptr smaps_len);
 
 // Simple low-level (mmap-based) allocator for internal use. Doesn't have

diff  --git a/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_common.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_common.cpp
index 689b217c9c01..eb351b0f06fd 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_common.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_common.cpp
@@ -155,18 +155,29 @@ void GetMemoryProfile(fill_profile_f cb, uptr *stats) {
   UnmapOrDie(smaps, smaps_cap);
 }
 
-void ParseUnixMemoryProfile(fill_profile_f cb, uptr *stats, const char *smaps,
+void ParseUnixMemoryProfile(fill_profile_f cb, uptr *stats, char *smaps,
                             uptr smaps_len) {
   uptr start = 0;
   bool file = false;
   const char *pos = smaps;
-  while (pos < smaps + smaps_len) {
+  char *end = smaps + smaps_len;
+  if (smaps_len < 2)
+    return;
+  // The following parsing can crash on almost every line
+  // in the case of malformed/truncated input.
+  // Fixing that is hard b/c e.g. ParseDecimal does not
+  // even accept end of the buffer and assumes well-formed input.
+  // So instead we patch end of the input a bit,
+  // it does not affect well-formed complete inputs.
+  *--end = 0;
+  *--end = '\n';
+  while (pos < end) {
     if (IsHex(pos[0])) {
       start = ParseHex(&pos);
       for (; *pos != '/' && *pos > '\n'; pos++) {}
       file = *pos == '/';
     } else if (internal_strncmp(pos, "Rss:", 4) == 0) {
-      while (!IsDecimal(*pos)) pos++;
+      while (pos < end && !IsDecimal(*pos)) pos++;
       uptr rss = ParseDecimal(&pos) * 1024;
       cb(start, rss, file, stats);
     }

diff  --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_procmaps_test.cpp b/compiler-rt/lib/sanitizer_common/tests/sanitizer_procmaps_test.cpp
index ea435fcf7b8f..2157d4a03549 100644
--- a/compiler-rt/lib/sanitizer_common/tests/sanitizer_procmaps_test.cpp
+++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_procmaps_test.cpp
@@ -78,14 +78,7 @@ TEST(MemoryMapping, LoadedModuleArchAndUUID) {
   }
 }
 
-TEST(MemoryMapping, ParseUnixMemoryProfile) {
-  struct entry {
-    uptr p;
-    uptr rss;
-    bool file;
-  };
-  typedef std::vector<entry> entries_t;
-  const char *input = R"(
+const char *const parse_unix_input = R"(
 7fb9862f1000-7fb9862f3000 rw-p 00000000 00:00 0 
 Size:                  8 kB
 Rss:                   4 kB
@@ -93,12 +86,22 @@ Rss:                   4 kB
 Size:                 12 kB
 Rss:                  12 kB
 )";
+
+TEST(MemoryMapping, ParseUnixMemoryProfile) {
+  struct entry {
+    uptr p;
+    uptr rss;
+    bool file;
+  };
+  typedef std::vector<entry> entries_t;
   entries_t entries;
+  std::vector<char> input(parse_unix_input,
+                          parse_unix_input + strlen(parse_unix_input));
   ParseUnixMemoryProfile(
       [](uptr p, uptr rss, bool file, uptr *mem) {
         reinterpret_cast<entries_t *>(mem)->push_back({p, rss, file});
       },
-      reinterpret_cast<uptr *>(&entries), input, strlen(input));
+      reinterpret_cast<uptr *>(&entries), &input[0], input.size());
   EXPECT_EQ(entries.size(), 2ul);
   EXPECT_EQ(entries[0].p, 0x7fb9862f1000ul);
   EXPECT_EQ(entries[0].rss, 4ul << 10);
@@ -108,5 +111,24 @@ Rss:                  12 kB
   EXPECT_EQ(entries[1].file, true);
 }
 
+TEST(MemoryMapping, ParseUnixMemoryProfileTruncated) {
+  // ParseUnixMemoryProfile used to crash on truncated inputs.
+  // This test allocates 2 pages, protects the second one
+  // and places the input at the very end of the first page
+  // to test for over-reads.
+  uptr page = GetPageSizeCached();
+  char *mem = static_cast<char *>(
+      MmapOrDie(2 * page, "ParseUnixMemoryProfileTruncated"));
+  EXPECT_TRUE(MprotectNoAccess(reinterpret_cast<uptr>(mem + page), page));
+  const uptr len = strlen(parse_unix_input);
+  for (uptr i = 0; i < len; i++) {
+    char *smaps = mem + page - len + i;
+    memcpy(smaps, parse_unix_input, len - i);
+    ParseUnixMemoryProfile([](uptr p, uptr rss, bool file, uptr *mem) {},
+                           nullptr, smaps, len - i);
+  }
+  UnmapOrDie(mem, 2 * page);
+}
+
 }  // namespace __sanitizer
 #endif  // !defined(_WIN32)


        


More information about the llvm-commits mailing list