Sanitize dumped stacks to remove data that may be identifiable.

In order to sanitize the stack contents we erase any pointer-aligned
word that could not be interpreted as a pointer into one of the
processes' memory mappings, or a small integer (+/-4096).

This still retains enough information to unwind stack frames, and also
to recover some register values.

BUG=682278

Change-Id: I541a13b2e92a9d1aea2c06a50bd769a9e25601d3
Reviewed-on: https://chromium-review.googlesource.com/430050
Reviewed-by: Robert Sesek <rsesek@chromium.org>
This commit is contained in:
Tobias Sargeant 2017-01-31 13:42:52 +00:00 committed by Tobias Sargeant
parent cb94b71d28
commit 7c2799f3ba
9 changed files with 362 additions and 62 deletions

View File

@ -594,6 +594,7 @@ bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
mapping_list_, mapping_list_,
minidump_descriptor_.skip_dump_if_principal_mapping_not_referenced(), minidump_descriptor_.skip_dump_if_principal_mapping_not_referenced(),
minidump_descriptor_.address_within_principal_mapping(), minidump_descriptor_.address_within_principal_mapping(),
minidump_descriptor_.sanitize_stacks(),
*minidump_descriptor_.microdump_extra_info()); *minidump_descriptor_.microdump_extra_info());
} }
if (minidump_descriptor_.IsFD()) { if (minidump_descriptor_.IsFD()) {

View File

@ -49,6 +49,7 @@ MinidumpDescriptor::MinidumpDescriptor(const MinidumpDescriptor& descriptor)
descriptor.address_within_principal_mapping_), descriptor.address_within_principal_mapping_),
skip_dump_if_principal_mapping_not_referenced_( skip_dump_if_principal_mapping_not_referenced_(
descriptor.skip_dump_if_principal_mapping_not_referenced_), descriptor.skip_dump_if_principal_mapping_not_referenced_),
sanitize_stacks_(descriptor.sanitize_stacks_),
microdump_extra_info_(descriptor.microdump_extra_info_) { microdump_extra_info_(descriptor.microdump_extra_info_) {
// The copy constructor is not allowed to be called on a MinidumpDescriptor // The copy constructor is not allowed to be called on a MinidumpDescriptor
// with a valid path_, as getting its c_path_ would require the heap which // with a valid path_, as getting its c_path_ would require the heap which
@ -74,6 +75,7 @@ MinidumpDescriptor& MinidumpDescriptor::operator=(
descriptor.address_within_principal_mapping_; descriptor.address_within_principal_mapping_;
skip_dump_if_principal_mapping_not_referenced_ = skip_dump_if_principal_mapping_not_referenced_ =
descriptor.skip_dump_if_principal_mapping_not_referenced_; descriptor.skip_dump_if_principal_mapping_not_referenced_;
sanitize_stacks_ = descriptor.sanitize_stacks_;
microdump_extra_info_ = descriptor.microdump_extra_info_; microdump_extra_info_ = descriptor.microdump_extra_info_;
return *this; return *this;
} }

View File

@ -64,7 +64,8 @@ class MinidumpDescriptor {
c_path_(NULL), c_path_(NULL),
size_limit_(-1), size_limit_(-1),
address_within_principal_mapping_(0), address_within_principal_mapping_(0),
skip_dump_if_principal_mapping_not_referenced_(false) { skip_dump_if_principal_mapping_not_referenced_(false),
sanitize_stacks_(false) {
assert(!directory.empty()); assert(!directory.empty());
} }
@ -74,7 +75,8 @@ class MinidumpDescriptor {
c_path_(NULL), c_path_(NULL),
size_limit_(-1), size_limit_(-1),
address_within_principal_mapping_(0), address_within_principal_mapping_(0),
skip_dump_if_principal_mapping_not_referenced_(false) { skip_dump_if_principal_mapping_not_referenced_(false),
sanitize_stacks_(false) {
assert(fd != -1); assert(fd != -1);
} }
@ -83,7 +85,8 @@ class MinidumpDescriptor {
fd_(-1), fd_(-1),
size_limit_(-1), size_limit_(-1),
address_within_principal_mapping_(0), address_within_principal_mapping_(0),
skip_dump_if_principal_mapping_not_referenced_(false) {} skip_dump_if_principal_mapping_not_referenced_(false),
sanitize_stacks_(false) {}
explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor); explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor);
MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor); MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor);
@ -126,6 +129,11 @@ class MinidumpDescriptor {
skip_dump_if_principal_mapping_not_referenced; skip_dump_if_principal_mapping_not_referenced;
} }
bool sanitize_stacks() const { return sanitize_stacks_; }
void set_sanitize_stacks(bool sanitize_stacks) {
sanitize_stacks_ = sanitize_stacks;
}
MicrodumpExtraInfo* microdump_extra_info() { MicrodumpExtraInfo* microdump_extra_info() {
assert(IsMicrodumpOnConsole()); assert(IsMicrodumpOnConsole());
return &microdump_extra_info_; return &microdump_extra_info_;
@ -167,6 +175,13 @@ class MinidumpDescriptor {
// stacks logged. // stacks logged.
bool skip_dump_if_principal_mapping_not_referenced_; bool skip_dump_if_principal_mapping_not_referenced_;
// If set, stacks are sanitized to remove PII. This involves
// overwriting any pointer-aligned words that are not either
// pointers into a process mapping or small integers (+/-4096). This
// leaves enough information to unwind stacks, and preserve some
// register values, but elides strings and other program data.
bool sanitize_stacks_;
// The extra microdump data (e.g. product name/version, build // The extra microdump data (e.g. product name/version, build
// fingerprint, gpu fingerprint) that should be appended to the dump // fingerprint, gpu fingerprint) that should be appended to the dump
// (microdump only). Microdumps don't have the ability of appending // (microdump only). Microdumps don't have the ability of appending

View File

@ -134,6 +134,7 @@ class MicrodumpWriter {
const MappingList& mappings, const MappingList& mappings,
bool skip_dump_if_principal_mapping_not_referenced, bool skip_dump_if_principal_mapping_not_referenced,
uintptr_t address_within_principal_mapping, uintptr_t address_within_principal_mapping,
bool sanitize_stack,
const MicrodumpExtraInfo& microdump_extra_info, const MicrodumpExtraInfo& microdump_extra_info,
LinuxDumper* dumper) LinuxDumper* dumper)
: ucontext_(context ? &context->context : NULL), : ucontext_(context ? &context->context : NULL),
@ -145,6 +146,7 @@ class MicrodumpWriter {
skip_dump_if_principal_mapping_not_referenced_( skip_dump_if_principal_mapping_not_referenced_(
skip_dump_if_principal_mapping_not_referenced), skip_dump_if_principal_mapping_not_referenced),
address_within_principal_mapping_(address_within_principal_mapping), address_within_principal_mapping_(address_within_principal_mapping),
sanitize_stack_(sanitize_stack),
microdump_extra_info_(microdump_extra_info), microdump_extra_info_(microdump_extra_info),
log_line_(NULL), log_line_(NULL),
stack_copy_(NULL), stack_copy_(NULL),
@ -368,6 +370,11 @@ class MicrodumpWriter {
} }
void DumpThreadStack() { void DumpThreadStack() {
if (sanitize_stack_) {
dumper_->SanitizeStackCopy(stack_copy_, stack_len_, stack_pointer_,
stack_pointer_ - stack_lower_bound_);
}
LogAppend("S 0 "); LogAppend("S 0 ");
LogAppend(stack_pointer_); LogAppend(stack_pointer_);
LogAppend(" "); LogAppend(" ");
@ -580,6 +587,7 @@ class MicrodumpWriter {
const MappingList& mapping_list_; const MappingList& mapping_list_;
bool skip_dump_if_principal_mapping_not_referenced_; bool skip_dump_if_principal_mapping_not_referenced_;
uintptr_t address_within_principal_mapping_; uintptr_t address_within_principal_mapping_;
bool sanitize_stack_;
const MicrodumpExtraInfo microdump_extra_info_; const MicrodumpExtraInfo microdump_extra_info_;
char* log_line_; char* log_line_;
@ -607,6 +615,7 @@ bool WriteMicrodump(pid_t crashing_process,
const MappingList& mappings, const MappingList& mappings,
bool skip_dump_if_principal_mapping_not_referenced, bool skip_dump_if_principal_mapping_not_referenced,
uintptr_t address_within_principal_mapping, uintptr_t address_within_principal_mapping,
bool sanitize_stack,
const MicrodumpExtraInfo& microdump_extra_info) { const MicrodumpExtraInfo& microdump_extra_info) {
LinuxPtraceDumper dumper(crashing_process); LinuxPtraceDumper dumper(crashing_process);
const ExceptionHandler::CrashContext* context = NULL; const ExceptionHandler::CrashContext* context = NULL;
@ -619,9 +628,10 @@ bool WriteMicrodump(pid_t crashing_process,
dumper.set_crash_signal(context->siginfo.si_signo); dumper.set_crash_signal(context->siginfo.si_signo);
dumper.set_crash_thread(context->tid); dumper.set_crash_thread(context->tid);
} }
MicrodumpWriter writer( MicrodumpWriter writer(context, mappings,
context, mappings, skip_dump_if_principal_mapping_not_referenced, skip_dump_if_principal_mapping_not_referenced,
address_within_principal_mapping, microdump_extra_info, &dumper); address_within_principal_mapping, sanitize_stack,
microdump_extra_info, &dumper);
if (!writer.Init()) if (!writer.Init())
return false; return false;
writer.Dump(); writer.Dump();

View File

@ -60,6 +60,7 @@ bool WriteMicrodump(pid_t crashing_process,
const MappingList& mappings, const MappingList& mappings,
bool skip_dump_if_main_module_not_referenced, bool skip_dump_if_main_module_not_referenced,
uintptr_t address_within_main_module, uintptr_t address_within_main_module,
bool sanitize_stack,
const MicrodumpExtraInfo& microdump_extra_info); const MicrodumpExtraInfo& microdump_extra_info);
} // namespace google_breakpad } // namespace google_breakpad

View File

@ -73,11 +73,14 @@ bool ContainsMicrodump(const std::string& buf) {
std::string::npos != buf.find("-----END BREAKPAD MICRODUMP-----"); std::string::npos != buf.find("-----END BREAKPAD MICRODUMP-----");
} }
const char kIdentifiableString[] = "_IDENTIFIABLE_";
void CrashAndGetMicrodump(const MappingList& mappings, void CrashAndGetMicrodump(const MappingList& mappings,
const MicrodumpExtraInfo& microdump_extra_info, const MicrodumpExtraInfo& microdump_extra_info,
std::string* microdump, std::string* microdump,
bool skip_dump_if_principal_mapping_not_referenced = false, bool skip_dump_if_principal_mapping_not_referenced = false,
uintptr_t address_within_principal_mapping = 0) { uintptr_t address_within_principal_mapping = 0,
bool sanitize_stack = false) {
int fds[2]; int fds[2];
ASSERT_NE(-1, pipe(fds)); ASSERT_NE(-1, pipe(fds));
@ -86,6 +89,14 @@ void CrashAndGetMicrodump(const MappingList& mappings,
int err_fd = open(stderr_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); int err_fd = open(stderr_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ASSERT_NE(-1, err_fd); ASSERT_NE(-1, err_fd);
char identifiable_string[sizeof(kIdentifiableString)];
// This string should not appear in the resulting microdump if it
// has been sanitized.
strcpy(identifiable_string, kIdentifiableString);
// Force the strcpy to not be optimized away.
write(STDOUT_FILENO, identifiable_string, 0);
const pid_t child = fork(); const pid_t child = fork();
if (child == 0) { if (child == 0) {
close(fds[1]); close(fds[1]);
@ -112,7 +123,8 @@ void CrashAndGetMicrodump(const MappingList& mappings,
ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings, ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings,
skip_dump_if_principal_mapping_not_referenced, skip_dump_if_principal_mapping_not_referenced,
address_within_principal_mapping, microdump_extra_info)); address_within_principal_mapping, sanitize_stack,
microdump_extra_info));
// Revert stderr back to the console. // Revert stderr back to the console.
dup2(save_err, STDERR_FILENO); dup2(save_err, STDERR_FILENO);
@ -134,6 +146,27 @@ void CrashAndGetMicrodump(const MappingList& mappings,
close(fds[1]); close(fds[1]);
} }
void ExtractMicrodumpStackContents(const string& microdump_content,
string* result) {
std::istringstream iss(microdump_content);
result->clear();
for (string line; std::getline(iss, line);) {
if (line.find("S ") == 0) {
std::istringstream stack_data(line);
std::string key;
std::string addr;
std::string data;
stack_data >> key >> addr >> data;
EXPECT_TRUE((data.size() & 1u) == 0u);
result->reserve(result->size() + data.size() / 2);
for (size_t i = 0; i < data.size(); i += 2) {
std::string byte = data.substr(i, 2);
result->push_back(static_cast<char>(strtoul(byte.c_str(), NULL, 16)));
}
}
}
}
void CheckMicrodumpContents(const string& microdump_content, void CheckMicrodumpContents(const string& microdump_content,
const MicrodumpExtraInfo& expected_info) { const MicrodumpExtraInfo& expected_info) {
std::istringstream iss(microdump_content); std::istringstream iss(microdump_content);
@ -175,6 +208,13 @@ void CheckMicrodumpContents(const string& microdump_content,
ASSERT_TRUE(did_find_gpu_info); ASSERT_TRUE(did_find_gpu_info);
} }
bool MicrodumpStackContains(const string& microdump_content,
const string& expected_content) {
string result;
ExtractMicrodumpStackContents(microdump_content, &result);
return result.find(kIdentifiableString) != string::npos;
}
void CheckMicrodumpContents(const string& microdump_content, void CheckMicrodumpContents(const string& microdump_content,
const string& expected_fingerprint, const string& expected_fingerprint,
const string& expected_product_info, const string& expected_product_info,
@ -244,6 +284,46 @@ TEST(MicrodumpWriterTest, NoOutputIfUninteresting) {
ASSERT_FALSE(ContainsMicrodump(buf)); ASSERT_FALSE(ContainsMicrodump(buf));
} }
// Ensure that stack content does not contain an identifiable string if the
// stack is sanitized.
TEST(MicrodumpWriterTest, StringRemovedBySanitization) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, true);
ASSERT_TRUE(ContainsMicrodump(buf));
ASSERT_FALSE(MicrodumpStackContains(buf, kIdentifiableString));
}
// Ensure that stack content does contain an identifiable string if the
// stack is not sanitized.
TEST(MicrodumpWriterTest, StringPresentIfNotSanitized) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, false);
ASSERT_TRUE(ContainsMicrodump(buf));
ASSERT_TRUE(MicrodumpStackContains(buf, kIdentifiableString));
}
// Ensure that output occurs if the interest region is set, and // Ensure that output occurs if the interest region is set, and
// does overlap something on the stack. // does overlap something on the stack.
TEST(MicrodumpWriterTest, OutputIfInteresting) { TEST(MicrodumpWriterTest, OutputIfInteresting) {

View File

@ -84,10 +84,15 @@ inline static bool IsMappedFileOpenUnsafe(
namespace google_breakpad { namespace google_breakpad {
#if defined(__CHROMEOS__)
namespace { namespace {
bool MappingContainsAddress(const MappingInfo& mapping, uintptr_t address) {
return mapping.system_mapping_info.start_addr <= address &&
address < mapping.system_mapping_info.end_addr;
}
#if defined(__CHROMEOS__)
// Recover memory mappings before writing dump on ChromeOS // Recover memory mappings before writing dump on ChromeOS
// //
// On Linux, breakpad relies on /proc/[pid]/maps to associate symbols from // On Linux, breakpad relies on /proc/[pid]/maps to associate symbols from
@ -248,9 +253,10 @@ void CrOSPostProcessMappings(wasteful_vector<MappingInfo*>& mappings) {
mappings.resize(f); mappings.resize(f);
} }
} // namespace
#endif // __CHROMEOS__ #endif // __CHROMEOS__
} // namespace
// All interesting auvx entry types are below AT_SYSINFO_EHDR // All interesting auvx entry types are below AT_SYSINFO_EHDR
#define AT_MAX AT_SYSINFO_EHDR #define AT_MAX AT_SYSINFO_EHDR
@ -705,6 +711,99 @@ bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
return true; return true;
} }
void LinuxDumper::SanitizeStackCopy(uint8_t* stack_copy, size_t stack_len,
uintptr_t stack_pointer,
uintptr_t sp_offset) {
// We optimize the search for containing mappings in three ways:
// 1) We expect that pointers into the stack mapping will be common, so
// we cache that address range.
// 2) The last referenced mapping is a reasonable predictor for the next
// referenced mapping, so we test that first.
// 3) We precompute a bitfield based upon bits 32:32-n of the start and
// stop addresses, and use that to short circuit any values that can
// not be pointers. (n=11)
const uintptr_t defaced =
#if defined(__LP64__)
0x0defaced0defaced;
#else
0x0defaced;
#endif
// the bitfield length is 2^test_bits long.
const unsigned int test_bits = 11;
// byte length of the corresponding array.
const unsigned int array_size = 1 << (test_bits - 3);
const unsigned int array_mask = array_size - 1;
// The amount to right shift pointers by. This captures the top bits
// on 32 bit architectures. On 64 bit architectures this would be
// uninformative so we take the same range of bits.
const unsigned int shift = 32 - 11;
const MappingInfo* last_hit_mapping = nullptr;
const MappingInfo* hit_mapping = nullptr;
const MappingInfo* stack_mapping = FindMappingNoBias(stack_pointer);
// The magnitude below which integers are considered to be to be
// 'small', and not constitute a PII risk. These are included to
// avoid eliding useful register values.
const ssize_t small_int_magnitude = 4096;
char could_hit_mapping[array_size];
my_memset(could_hit_mapping, 0, array_size);
// Initialize the bitfield such that if the (pointer >> shift)'th
// bit, modulo the bitfield size, is not set then there does not
// exist a mapping in mappings_ that would contain that pointer.
for (size_t i = 0; i < mappings_.size(); ++i) {
// For each mapping, work out the (unmodulo'ed) range of bits to
// set.
uintptr_t start = mappings_[i]->start_addr;
uintptr_t end = start + mappings_[i]->size;
start >>= shift;
end >>= shift;
for (size_t bit = start; bit <= end; ++bit) {
// Set each bit in the range, applying the modulus.
could_hit_mapping[(bit >> 3) & array_mask] |= 1 << (bit & 7);
}
}
// Zero memory that is below the current stack pointer.
const uintptr_t offset =
(sp_offset + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);
if (offset) {
my_memset(stack_copy, 0, offset);
}
// Apply sanitization to each complete pointer-aligned word in the
// stack.
uint8_t* sp;
for (sp = stack_copy + offset;
sp <= stack_copy + stack_len - sizeof(uintptr_t);
sp += sizeof(uintptr_t)) {
uintptr_t addr;
my_memcpy(&addr, sp, sizeof(uintptr_t));
if (static_cast<intptr_t>(addr) <= small_int_magnitude &&
static_cast<intptr_t>(addr) >= -small_int_magnitude) {
continue;
}
if (stack_mapping && MappingContainsAddress(*stack_mapping, addr)) {
continue;
}
if (last_hit_mapping && MappingContainsAddress(*last_hit_mapping, addr)) {
continue;
}
uintptr_t test = addr >> shift;
if (could_hit_mapping[(test >> 3) & array_mask] & (1 << (test & 7)) &&
(hit_mapping = FindMappingNoBias(addr)) != nullptr) {
last_hit_mapping = hit_mapping;
continue;
}
my_memcpy(sp, &defaced, sizeof(uintptr_t));
}
// Zero any partial word at the top of the stack, if alignment is
// such that that is required.
if (sp < stack_copy + stack_len) {
my_memset(sp, 0, stack_copy + stack_len - sp);
}
}
bool LinuxDumper::StackHasPointerToMapping(const uint8_t* stack_copy, bool LinuxDumper::StackHasPointerToMapping(const uint8_t* stack_copy,
size_t stack_len, size_t stack_len,
uintptr_t sp_offset, uintptr_t sp_offset,

View File

@ -116,6 +116,19 @@ class LinuxDumper {
// stack_top: the current top of the stack // stack_top: the current top of the stack
bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top); bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top);
// Sanitize a copy of the stack by overwriting words that are not
// pointers with a sentinel (0x0defaced).
// stack_copy: a copy of the stack to sanitize. |stack_copy| might
// not be word aligned, but it represents word aligned
// data copied from another location.
// stack_len: the length of the allocation pointed to by |stack_copy|.
// stack_pointer: the address of the stack pointer (used to locate
// the stack mapping, as an optimization).
// sp_offset: the offset relative to stack_copy that reflects the
// current value of the stack pointer.
void SanitizeStackCopy(uint8_t* stack_copy, size_t stack_len,
uintptr_t stack_pointer, uintptr_t sp_offset);
// Test whether |stack_copy| contains a pointer-aligned word that // Test whether |stack_copy| contains a pointer-aligned word that
// could be an address within a given mapping. // could be an address within a given mapping.
// stack_copy: a copy of the stack to check. |stack_copy| might // stack_copy: a copy of the stack to check. |stack_copy| might

View File

@ -66,6 +66,62 @@ using namespace google_breakpad;
namespace { namespace {
pid_t SetupChildProcess(int number_of_threads) {
char kNumberOfThreadsArgument[2];
sprintf(kNumberOfThreadsArgument, "%d", number_of_threads);
int fds[2];
EXPECT_NE(-1, pipe(fds));
pid_t child_pid = fork();
if (child_pid == 0) {
// In child process.
close(fds[0]);
string helper_path(GetHelperBinary());
if (helper_path.empty()) {
ADD_FAILURE() << "Couldn't find helper binary";
return -1;
}
// Pass the pipe fd and the number of threads as arguments.
char pipe_fd_string[8];
sprintf(pipe_fd_string, "%d", fds[1]);
execl(helper_path.c_str(),
"linux_dumper_unittest_helper",
pipe_fd_string,
kNumberOfThreadsArgument,
NULL);
// Kill if we get here.
printf("Errno from exec: %d", errno);
ADD_FAILURE() << "Exec of " << helper_path << " failed: " << strerror(errno);
return -1;
}
close(fds[1]);
// Wait for all child threads to indicate that they have started
for (int threads = 0; threads < number_of_threads; threads++) {
struct pollfd pfd;
memset(&pfd, 0, sizeof(pfd));
pfd.fd = fds[0];
pfd.events = POLLIN | POLLERR;
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
EXPECT_EQ(1, r);
EXPECT_TRUE(pfd.revents & POLLIN);
uint8_t junk;
EXPECT_EQ(read(fds[0], &junk, sizeof(junk)),
static_cast<ssize_t>(sizeof(junk)));
}
close(fds[0]);
// There is a race here because we may stop a child thread before
// it is actually running the busy loop. Empirically this sleep
// is sufficient to avoid the race.
usleep(100000);
return child_pid;
}
typedef wasteful_vector<uint8_t> id_vector; typedef wasteful_vector<uint8_t> id_vector;
typedef testing::Test LinuxPtraceDumperTest; typedef testing::Test LinuxPtraceDumperTest;
@ -370,58 +426,9 @@ TEST_F(LinuxPtraceDumperChildTest, FileIDsMatch) {
TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) { TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
static const int kNumberOfThreadsInHelperProgram = 5; static const int kNumberOfThreadsInHelperProgram = 5;
char kNumberOfThreadsArgument[2];
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
int fds[2]; pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
ASSERT_NE(-1, pipe(fds)); ASSERT_NE(child_pid, -1);
pid_t child_pid = fork();
if (child_pid == 0) {
// In child process.
close(fds[0]);
string helper_path(GetHelperBinary());
if (helper_path.empty()) {
FAIL() << "Couldn't find helper binary";
exit(1);
}
// Pass the pipe fd and the number of threads as arguments.
char pipe_fd_string[8];
sprintf(pipe_fd_string, "%d", fds[1]);
execl(helper_path.c_str(),
"linux_dumper_unittest_helper",
pipe_fd_string,
kNumberOfThreadsArgument,
NULL);
// Kill if we get here.
printf("Errno from exec: %d", errno);
FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
exit(0);
}
close(fds[1]);
// Wait for all child threads to indicate that they have started
for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) {
struct pollfd pfd;
memset(&pfd, 0, sizeof(pfd));
pfd.fd = fds[0];
pfd.events = POLLIN | POLLERR;
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
ASSERT_EQ(1, r);
ASSERT_TRUE(pfd.revents & POLLIN);
uint8_t junk;
ASSERT_EQ(read(fds[0], &junk, sizeof(junk)),
static_cast<ssize_t>(sizeof(junk)));
}
close(fds[0]);
// There is a race here because we may stop a child thread before
// it is actually running the busy loop. Empirically this sleep
// is sufficient to avoid the race.
usleep(100000);
// Children are ready now. // Children are ready now.
LinuxPtraceDumper dumper(child_pid); LinuxPtraceDumper dumper(child_pid);
@ -468,3 +475,75 @@ TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
ASSERT_TRUE(WIFSIGNALED(status)); ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_EQ(SIGKILL, WTERMSIG(status)); ASSERT_EQ(SIGKILL, WTERMSIG(status));
} }
TEST_F(LinuxPtraceDumperTest, SanitizeStackCopy) {
static const int kNumberOfThreadsInHelperProgram = 1;
pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
ASSERT_NE(child_pid, -1);
LinuxPtraceDumper dumper(child_pid);
ASSERT_TRUE(dumper.Init());
EXPECT_TRUE(dumper.ThreadsSuspend());
ThreadInfo thread_info;
EXPECT_TRUE(dumper.GetThreadInfoByIndex(0, &thread_info));
const void* stack;
size_t stack_len;
EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len, thread_info.stack_pointer));
uint8_t* stack_copy = new uint8_t[stack_len];
dumper.CopyFromProcess(stack_copy, child_pid, stack, stack_len);
size_t stack_offset =
thread_info.stack_pointer - reinterpret_cast<uintptr_t>(stack);
const size_t word_count = (stack_len - stack_offset) / sizeof(uintptr_t);
uintptr_t* stack_words = new uintptr_t[word_count];
memcpy(stack_words, stack_copy + stack_offset, word_count * sizeof(uintptr_t));
std::map<uintptr_t, int> pre_sanitization_words;
for (size_t i = 0; i < word_count; ++i)
++pre_sanitization_words[stack_words[i]];
fprintf(stderr, "stack_offset=%lu stack_len=%lu stack=%p\n", stack_offset, stack_len, stack);
dumper.SanitizeStackCopy(stack_copy, stack_len, thread_info.stack_pointer,
stack_offset);
// Memory below the stack pointer should be zeroed.
for (size_t i = 0; i < stack_offset; ++i) {
ASSERT_EQ(0, stack_copy[i]);
}
memcpy(stack_words, stack_copy + stack_offset, word_count * sizeof(uintptr_t));
std::map<uintptr_t, int> post_sanitization_words;
for (size_t i = 0; i < word_count; ++i)
++post_sanitization_words[stack_words[i]];
std::set<uintptr_t> words;
for (auto &word : pre_sanitization_words) words.insert(word.first);
for (auto &word : post_sanitization_words) words.insert(word.first);
for (auto word : words) {
if (word == static_cast<uintptr_t>(0X0DEFACED0DEFACEDull)) {
continue;
}
bool should_be_sanitized = true;
if (static_cast<intptr_t>(word) <= 4096 &&
static_cast<intptr_t>(word) >= -4096) should_be_sanitized = false;
if (dumper.FindMappingNoBias(word)) should_be_sanitized = false;
ASSERT_EQ(should_be_sanitized, post_sanitization_words[word] == 0);
}
EXPECT_TRUE(dumper.ThreadsResume());
kill(child_pid, SIGKILL);
// Reap child
int status;
ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_EQ(SIGKILL, WTERMSIG(status));
}