diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.h b/compiler-rt/lib/sanitizer_common/sanitizer_common.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_common.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.h @@ -197,7 +197,7 @@ // 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 --- a/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_common.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_procmaps_common.cpp @@ -155,18 +155,29 @@ 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 --- 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, ParseUnixMemoryProfile) { - struct entry { - uptr p; - uptr rss; - bool file; - }; - typedef std::vector 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 @@ Size: 12 kB Rss: 12 kB )"; + +TEST(MemoryMapping, ParseUnixMemoryProfile) { + struct entry { + uptr p; + uptr rss; + bool file; + }; + typedef std::vector entries_t; entries_t entries; + std::vector input(parse_unix_input, + parse_unix_input + strlen(parse_unix_input)); ParseUnixMemoryProfile( [](uptr p, uptr rss, bool file, uptr *mem) { reinterpret_cast(mem)->push_back({p, rss, file}); }, - reinterpret_cast(&entries), input, strlen(input)); + reinterpret_cast(&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 @@ 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( + MmapOrDie(2 * page, "ParseUnixMemoryProfileTruncated")); + EXPECT_TRUE(MprotectNoAccess(reinterpret_cast(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)