1 : // Copyright 2014 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/kasko/minidump.h"
16 :
17 : #include <Windows.h> // NOLINT
18 : #include <Dbgeng.h>
19 : #include <DbgHelp.h>
20 : #include <Psapi.h>
21 :
22 : #include <cstring>
23 : #include <vector>
24 :
25 : #include "base/base_switches.h"
26 : #include "base/bind.h"
27 : #include "base/command_line.h"
28 : #include "base/macros.h"
29 : #include "base/files/file_path.h"
30 : #include "base/files/file_util.h"
31 : #include "base/files/memory_mapped_file.h"
32 : #include "base/files/scoped_temp_dir.h"
33 : #include "base/process/kill.h"
34 : #include "base/process/launch.h"
35 : #include "base/strings/string16.h"
36 : #include "base/strings/string_number_conversions.h"
37 : #include "base/strings/utf_string_conversions.h"
38 : #include "base/test/multiprocess_test.h"
39 : #include "gtest/gtest.h"
40 : #include "syzygy/kasko/loader_lock.h"
41 : #include "syzygy/kasko/minidump_request.h"
42 : #include "syzygy/kasko/testing/minidump_unittest_helpers.h"
43 : #include "syzygy/kasko/testing/safe_pipe_reader.h"
44 : #include "syzygy/minidump/minidump.h"
45 : #include "testing/multiprocess_func_list.h"
46 :
47 : // http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx
48 m : extern "C" IMAGE_DOS_HEADER __ImageBase;
49 :
50 m : namespace kasko {
51 :
52 m : namespace {
53 :
54 m : const char kPipeHandleSwitch[] = "pipe-handle";
55 :
56 : // Signals an event named by kReadyEventSwitch, then blocks indefinitely.
57 m : MULTIPROCESS_TEST_MAIN(MinidumpTestBlockingProcess) {
58 : // Read the caller-supplied parameters.
59 m : base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
60 m : std::string pipe_handle_string =
61 m : cmd_line->GetSwitchValueASCII(kPipeHandleSwitch);
62 m : unsigned handle_value = 0;
63 m : CHECK(base::StringToUint(pipe_handle_string, &handle_value));
64 m : base::win::ScopedHandle pipe(reinterpret_cast<HANDLE>(handle_value));
65 m : DWORD written = 0;
66 m : uint32_t image_base = reinterpret_cast<uint32_t>(&__ImageBase);
67 m : PCHECK(WriteFile(pipe.Get(), &image_base, sizeof(image_base), &written,
68 m : nullptr));
69 m : CHECK_EQ(sizeof(void*), written);
70 m : pipe.Close();
71 m : ::Sleep(INFINITE);
72 m : return 0;
73 m : }
74 :
75 m : const char kGlobalString[] = "a global string";
76 :
77 m : const char kCustomStreamContents[] = "hello world";
78 m : uint32_t kCustomStreamType = LastReservedStream + 2468;
79 :
80 m : void ValidateMinidump(IDebugClient4* debug_client,
81 m : IDebugControl* debug_control,
82 m : IDebugSymbols* debug_symbols) {
83 m : ASSERT_HRESULT_SUCCEEDED(
84 m : debug_symbols->GetModuleByModuleName("kasko_unittests", 0, NULL, NULL));
85 m : }
86 :
87 m : } // namespace
88 :
89 m : class MinidumpTest : public ::testing::Test {
90 m : public:
91 m : MinidumpTest() {}
92 :
93 m : ~MinidumpTest() override {}
94 :
95 : // ::testing::Test implementation.
96 m : void SetUp() override { temp_dir_.CreateUniqueTempDir(); }
97 :
98 : // Launches a child process, waits until it has loaded, and then invokes
99 : // GenerateMinidump for the child.
100 : // The contents of |request().memory_ranges| must be within the current image
101 : // (kasko_unittests.exe). They will be adjusted so as to read the same offset
102 : // (from the image base) in the child process.
103 m : void CallGenerateMinidump(const base::FilePath& dump_file_path,
104 m : bool* result) {
105 m : testing::SafePipeReader pipe_reader;
106 m : base::CommandLine child_command_line =
107 m : base::GetMultiProcessTestChildBaseCommandLine();
108 m : child_command_line.AppendSwitchASCII(switches::kTestChildProcess,
109 m : "MinidumpTestBlockingProcess");
110 m : child_command_line.AppendSwitchASCII(
111 m : kPipeHandleSwitch, base::UintToString(reinterpret_cast<unsigned>(
112 m : pipe_reader.write_handle())));
113 m : base::LaunchOptions options;
114 m : options.inherit_handles = true;
115 m : base::Process child_process =
116 m : base::LaunchProcess(child_command_line, options);
117 m : ASSERT_TRUE(child_process.IsValid());
118 m : uint32_t child_image_base = 0;
119 m : ASSERT_TRUE(pipe_reader.ReadData(base::TimeDelta::FromSeconds(15),
120 m : sizeof(child_image_base),
121 m : &child_image_base));
122 :
123 m : MinidumpRequest adjusted_request = request_;
124 m : for (auto& range : request_.user_selected_memory_ranges) {
125 m : range = range.Offset(child_image_base -
126 m : reinterpret_cast<uint32_t>(&__ImageBase));
127 m : }
128 m : *result = kasko::GenerateMinidump(dump_file_path, child_process.Handle(), 0,
129 m : adjusted_request);
130 :
131 m : ASSERT_TRUE(child_process.Terminate(0, true));
132 m : }
133 :
134 m : protected:
135 m : base::FilePath temp_dir() { return temp_dir_.path(); }
136 m : MinidumpRequest& request() { return request_; }
137 :
138 m : private:
139 m : MinidumpRequest request_;
140 m : base::ScopedTempDir temp_dir_;
141 m : DISALLOW_COPY_AND_ASSIGN(MinidumpTest);
142 m : };
143 :
144 m : TEST_F(MinidumpTest, GenerateAndLoad) {
145 : // Generate a minidump for the current process.
146 m : base::FilePath dump_file_path = temp_dir().Append(L"test.dump");
147 m : bool result = false;
148 m : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(dump_file_path, &result));
149 m : ASSERT_TRUE(result);
150 :
151 m : ASSERT_HRESULT_SUCCEEDED(
152 m : testing::VisitMinidump(dump_file_path, base::Bind(&ValidateMinidump)));
153 m : }
154 :
155 m : TEST_F(MinidumpTest, CustomStream) {
156 : // Generate a minidump for the current process.
157 m : base::FilePath dump_file_path = temp_dir().Append(L"test.dump");
158 m : MinidumpRequest::CustomStream custom_stream = {
159 m : kCustomStreamType, kCustomStreamContents, sizeof(kCustomStreamContents)};
160 m : request().custom_streams.push_back(custom_stream);
161 m : bool result = false;
162 m : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(dump_file_path, &result));
163 m : ASSERT_TRUE(result);
164 :
165 : // Open the minidump file.
166 m : base::MemoryMappedFile memory_mapped_file;
167 m : ASSERT_TRUE(memory_mapped_file.Initialize(dump_file_path));
168 :
169 : // Access the custom stream.
170 m : MINIDUMP_DIRECTORY* dir = nullptr;
171 m : void* stream = nullptr;
172 m : ULONG stream_length = 0;
173 m : ASSERT_TRUE(::MiniDumpReadDumpStream(
174 m : const_cast<uint8_t*>(memory_mapped_file.data()), kCustomStreamType, &dir,
175 m : &stream, &stream_length));
176 :
177 : // Assert that the custom stream is what we expected.
178 m : ASSERT_EQ(sizeof(kCustomStreamContents), stream_length);
179 m : ASSERT_EQ(0, memcmp(stream, kCustomStreamContents, stream_length));
180 m : }
181 :
182 m : TEST_F(MinidumpTest, MinidumpType) {
183 : // Generate a minidump for the current process.
184 m : base::FilePath small_dump_file_path = temp_dir().Append(L"small.dump");
185 m : base::FilePath larger_dump_file_path = temp_dir().Append(L"larger.dump");
186 m : base::FilePath full_dump_file_path = temp_dir().Append(L"full.dump");
187 :
188 m : bool result = false;
189 m : request().type = MinidumpRequest::SMALL_DUMP_TYPE;
190 m : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(small_dump_file_path, &result));
191 m : ASSERT_TRUE(result);
192 m : request().type = MinidumpRequest::LARGER_DUMP_TYPE;
193 m : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(larger_dump_file_path, &result));
194 m : ASSERT_TRUE(result);
195 m : request().type = MinidumpRequest::FULL_DUMP_TYPE;
196 m : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(full_dump_file_path, &result));
197 m : ASSERT_TRUE(result);
198 :
199 : // Use the relative file sizes to infer that the correct minidump type was
200 : // respected.
201 : // Other approaches (testing the memory ranges included in the dump) were
202 : // rejected due to the difficulty of deterministically knowing what should and
203 : // shouldn't be included in the various dump types.
204 m : int64_t small_dump_size = 0;
205 m : int64_t larger_dump_size = 0;
206 m : int64_t full_dump_size = 0;
207 :
208 m : ASSERT_TRUE(base::GetFileSize(small_dump_file_path, &small_dump_size));
209 m : ASSERT_TRUE(base::GetFileSize(larger_dump_file_path, &larger_dump_size));
210 m : ASSERT_TRUE(base::GetFileSize(full_dump_file_path, &full_dump_size));
211 :
212 m : EXPECT_GT(full_dump_size, larger_dump_size);
213 m : EXPECT_GT(larger_dump_size, small_dump_size);
214 m : }
215 :
216 m : TEST_F(MinidumpTest, MemoryRanges) {
217 : // Generate a minidump for the current process.
218 m : base::FilePath default_dump_file_path = temp_dir().Append(L"default.dump");
219 m : base::FilePath dump_with_memory_range_file_path =
220 m : temp_dir().Append(L"with_range.dump");
221 :
222 m : bool result = false;
223 m : ASSERT_NO_FATAL_FAILURE(
224 m : CallGenerateMinidump(default_dump_file_path, &result));
225 m : ASSERT_TRUE(result);
226 :
227 m : MinidumpRequest::MemoryRange range = {
228 m : reinterpret_cast<uint32_t>(kGlobalString), sizeof(kGlobalString)};
229 m : request().user_selected_memory_ranges.push_back(range);
230 m : ASSERT_NO_FATAL_FAILURE(
231 m : CallGenerateMinidump(dump_with_memory_range_file_path, &result));
232 m : ASSERT_TRUE(result);
233 :
234 m : std::string default_dump;
235 m : std::string dump_with_memory_range;
236 m : ASSERT_TRUE(base::ReadFileToString(default_dump_file_path, &default_dump));
237 m : ASSERT_TRUE(base::ReadFileToString(dump_with_memory_range_file_path,
238 m : &dump_with_memory_range));
239 :
240 m : ASSERT_EQ(std::string::npos, default_dump.find(kGlobalString));
241 m : ASSERT_NE(std::string::npos, dump_with_memory_range.find(kGlobalString));
242 m : }
243 :
244 m : TEST_F(MinidumpTest, OverwriteExistingFile) {
245 m : base::ScopedTempDir temp_dir;
246 m : ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
247 m : base::FilePath dump_file_path;
248 m : ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.path(), &dump_file_path));
249 :
250 m : bool result = false;
251 m : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(dump_file_path, &result));
252 m : ASSERT_TRUE(result);
253 :
254 m : ASSERT_HRESULT_SUCCEEDED(
255 m : testing::VisitMinidump(dump_file_path, base::Bind(&ValidateMinidump)));
256 m : }
257 :
258 m : TEST_F(MinidumpTest, NonexistantTargetDirectory) {
259 m : base::ScopedTempDir temp_dir;
260 m : ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
261 m : bool result = false;
262 m : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(
263 m : temp_dir.path().Append(L"Foobar").Append(L"HelloWorld"), &result));
264 m : ASSERT_FALSE(result);
265 m : }
266 :
267 : // Tests that the ranges for loader lock and the loader lock debug info is
268 : // included in the minidump.
269 m : TEST_F(MinidumpTest, LoaderLock) {
270 : // Generate a minidump for the current process.
271 m : base::FilePath default_dump_file_path = temp_dir().Append(L"default.dump");
272 m : bool result = false;
273 m : ASSERT_NO_FATAL_FAILURE(
274 m : CallGenerateMinidump(default_dump_file_path, &result));
275 m : ASSERT_TRUE(result);
276 :
277 m : minidump::FileMinidump minidump;
278 m : ASSERT_TRUE(minidump.Open(default_dump_file_path));
279 :
280 m : minidump::Minidump::Stream stream =
281 m : minidump.FindNextStream(nullptr, MemoryListStream);
282 m : ASSERT_TRUE(stream.IsValid());
283 :
284 m : bool loader_lock_found = false;
285 m : bool debug_info_found = false;
286 m : core::AddressRange<uint32_t, uint32_t> loader_lock_range(
287 m : reinterpret_cast<uint32_t>(GetLoaderLock()), sizeof(CRITICAL_SECTION));
288 m : core::AddressRange<uint32_t, uint32_t> debug_info_range(
289 m : reinterpret_cast<uint32_t>(GetLoaderLock()->DebugInfo),
290 m : sizeof(CRITICAL_SECTION_DEBUG));
291 :
292 m : ULONG32 num_memory_descriptors = 0;
293 m : stream.ReadAndAdvanceElement(&num_memory_descriptors);
294 m : for (size_t i = 0;
295 m : i < num_memory_descriptors && !(loader_lock_found && debug_info_found);
296 m : ++i) {
297 m : MINIDUMP_MEMORY_DESCRIPTOR memory_descriptor = {};
298 m : stream.ReadAndAdvanceElement(&memory_descriptor);
299 m : core::AddressRange<uint32_t, uint32_t> descriptor_range(
300 m : memory_descriptor.StartOfMemoryRange,
301 m : memory_descriptor.Memory.DataSize);
302 :
303 : // It is possible that adjacent ranges have been merged in the minidump so
304 : // comparing start address and size might not work.
305 m : if (!loader_lock_found && descriptor_range.Contains(loader_lock_range))
306 m : loader_lock_found = true;
307 m : if (!debug_info_found && descriptor_range.Contains(debug_info_range))
308 m : debug_info_found = true;
309 m : }
310 :
311 m : ASSERT_TRUE(loader_lock_found && debug_info_found);
312 m : }
313 :
314 : // When generating the minidump, it is assumed that ntdll is always loaded at
315 : // the same address in all processes on the system. This test is to make sure
316 : // the assertion never changes in the future.
317 m : TEST_F(MinidumpTest, NtdllLoadAddress) {
318 : // Generate a minidump for the current process.
319 m : base::FilePath dump_file_path = temp_dir().Append(L"default.dump");
320 m : bool result = false;
321 m : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(dump_file_path, &result));
322 m : ASSERT_TRUE(result);
323 :
324 m : minidump::FileMinidump minidump;
325 m : ASSERT_TRUE(minidump.Open(dump_file_path));
326 :
327 : // Retrieve the unique module list stream.
328 m : minidump::Minidump::Stream module_list =
329 m : minidump.FindNextStream(nullptr, ModuleListStream);
330 m : ASSERT_TRUE(module_list.IsValid());
331 :
332 m : ULONG32 num_modules = 0;
333 m : ASSERT_TRUE(module_list.ReadAndAdvanceElement(&num_modules));
334 :
335 m : bool ntdll_found = false;
336 m : for (size_t i = 0; i < num_modules; ++i) {
337 m : MINIDUMP_MODULE module = {};
338 m : ASSERT_TRUE(module_list.ReadAndAdvanceElement(&module));
339 :
340 : // Get the module name. The length of the name is included in the stream.
341 m : MINIDUMP_LOCATION_DESCRIPTOR name_location = {static_cast<ULONG32>(-1),
342 m : module.ModuleNameRva};
343 m : minidump::Minidump::Stream name_stream =
344 m : minidump.GetStreamFor(name_location);
345 m : ASSERT_TRUE(name_stream.IsValid());
346 :
347 m : std::wstring module_name;
348 m : ASSERT_TRUE(name_stream.ReadAndAdvanceString(&module_name));
349 :
350 m : if (module_name.find(L"ntdll.dll") != -1) {
351 m : MODULEINFO ntdll_module_info = {};
352 m : ASSERT_TRUE(::GetModuleInformation(
353 m : ::GetCurrentProcess(), ::GetModuleHandle(L"ntdll.dll"),
354 m : &ntdll_module_info, sizeof(MODULEINFO)));
355 m : ASSERT_EQ(reinterpret_cast<uintptr_t>(ntdll_module_info.lpBaseOfDll),
356 m : module.BaseOfImage);
357 m : ntdll_found = true;
358 m : break;
359 m : }
360 m : }
361 : // Don't succeed if the address hasn't been checked.
362 m : ASSERT_TRUE(ntdll_found);
363 m : }
364 :
365 m : } // namespace kasko
|