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
This commit is contained in:
Dmitry Vyukov 2021-10-29 11:26:04 +02:00
parent 4acad5df33
commit e8861fa6c3
3 changed files with 46 additions and 13 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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)