forked from OSchip/llvm-project
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:
parent
4acad5df33
commit
e8861fa6c3
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue