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 : extern "C" IMAGE_DOS_HEADER __ImageBase;
49 :
50 : namespace kasko {
51 :
52 : namespace {
53 :
54 : const char kPipeHandleSwitch[] = "pipe-handle";
55 :
56 : // Signals an event named by kReadyEventSwitch, then blocks indefinitely.
57 E : MULTIPROCESS_TEST_MAIN(MinidumpTestBlockingProcess) {
58 : // Read the caller-supplied parameters.
59 E : base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
60 : std::string pipe_handle_string =
61 E : cmd_line->GetSwitchValueASCII(kPipeHandleSwitch);
62 E : unsigned handle_value = 0;
63 E : CHECK(base::StringToUint(pipe_handle_string, &handle_value));
64 E : base::win::ScopedHandle pipe(reinterpret_cast<HANDLE>(handle_value));
65 E : DWORD written = 0;
66 E : uint32_t image_base = reinterpret_cast<uint32_t>(&__ImageBase);
67 : PCHECK(WriteFile(pipe.Get(), &image_base, sizeof(image_base), &written,
68 E : nullptr));
69 E : CHECK_EQ(sizeof(void*), written);
70 E : pipe.Close();
71 E : ::Sleep(INFINITE);
72 E : return 0;
73 E : }
74 :
75 : const char kGlobalString[] = "a global string";
76 :
77 : const char kCustomStreamContents[] = "hello world";
78 : uint32_t kCustomStreamType = LastReservedStream + 2468;
79 :
80 : void ValidateMinidump(IDebugClient4* debug_client,
81 : IDebugControl* debug_control,
82 E : IDebugSymbols* debug_symbols) {
83 : ASSERT_HRESULT_SUCCEEDED(
84 E : debug_symbols->GetModuleByModuleName("kasko_unittests", 0, NULL, NULL));
85 E : }
86 :
87 : } // namespace
88 :
89 : class MinidumpTest : public ::testing::Test {
90 : public:
91 E : MinidumpTest() {}
92 :
93 E : ~MinidumpTest() override {}
94 :
95 : // ::testing::Test implementation.
96 E : 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 : void CallGenerateMinidump(const base::FilePath& dump_file_path,
104 E : bool* result) {
105 E : testing::SafePipeReader pipe_reader;
106 : base::CommandLine child_command_line =
107 E : base::GetMultiProcessTestChildBaseCommandLine();
108 : child_command_line.AppendSwitchASCII(switches::kTestChildProcess,
109 E : "MinidumpTestBlockingProcess");
110 : child_command_line.AppendSwitchASCII(
111 : kPipeHandleSwitch, base::UintToString(reinterpret_cast<unsigned>(
112 E : pipe_reader.write_handle())));
113 E : base::LaunchOptions options;
114 E : options.inherit_handles = true;
115 : base::Process child_process =
116 E : base::LaunchProcess(child_command_line, options);
117 E : ASSERT_TRUE(child_process.IsValid());
118 E : uint32_t child_image_base = 0;
119 E : ASSERT_TRUE(pipe_reader.ReadData(base::TimeDelta::FromSeconds(15),
120 : sizeof(child_image_base),
121 : &child_image_base));
122 :
123 E : MinidumpRequest adjusted_request = request_;
124 E : for (auto& range : request_.user_selected_memory_ranges) {
125 : range = range.Offset(child_image_base -
126 E : reinterpret_cast<uint32_t>(&__ImageBase));
127 : }
128 : *result = kasko::GenerateMinidump(dump_file_path, child_process.Handle(), 0,
129 E : adjusted_request);
130 :
131 E : ASSERT_TRUE(child_process.Terminate(0, true));
132 E : }
133 :
134 : protected:
135 E : base::FilePath temp_dir() { return temp_dir_.path(); }
136 E : MinidumpRequest& request() { return request_; }
137 :
138 : private:
139 : MinidumpRequest request_;
140 : base::ScopedTempDir temp_dir_;
141 : DISALLOW_COPY_AND_ASSIGN(MinidumpTest);
142 : };
143 :
144 E : TEST_F(MinidumpTest, GenerateAndLoad) {
145 : // Generate a minidump for the current process.
146 E : base::FilePath dump_file_path = temp_dir().Append(L"test.dump");
147 E : bool result = false;
148 E : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(dump_file_path, &result));
149 E : ASSERT_TRUE(result);
150 :
151 : ASSERT_HRESULT_SUCCEEDED(
152 E : testing::VisitMinidump(dump_file_path, base::Bind(&ValidateMinidump)));
153 E : }
154 :
155 E : TEST_F(MinidumpTest, CustomStream) {
156 : // Generate a minidump for the current process.
157 E : base::FilePath dump_file_path = temp_dir().Append(L"test.dump");
158 : MinidumpRequest::CustomStream custom_stream = {
159 E : kCustomStreamType, kCustomStreamContents, sizeof(kCustomStreamContents)};
160 E : request().custom_streams.push_back(custom_stream);
161 E : bool result = false;
162 E : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(dump_file_path, &result));
163 E : ASSERT_TRUE(result);
164 :
165 : // Open the minidump file.
166 E : base::MemoryMappedFile memory_mapped_file;
167 E : ASSERT_TRUE(memory_mapped_file.Initialize(dump_file_path));
168 :
169 : // Access the custom stream.
170 E : MINIDUMP_DIRECTORY* dir = nullptr;
171 E : void* stream = nullptr;
172 E : ULONG stream_length = 0;
173 : ASSERT_TRUE(::MiniDumpReadDumpStream(
174 : const_cast<uint8*>(memory_mapped_file.data()), kCustomStreamType, &dir,
175 E : &stream, &stream_length));
176 :
177 : // Assert that the custom stream is what we expected.
178 E : ASSERT_EQ(sizeof(kCustomStreamContents), stream_length);
179 E : ASSERT_EQ(0, memcmp(stream, kCustomStreamContents, stream_length));
180 E : }
181 :
182 E : TEST_F(MinidumpTest, MinidumpType) {
183 : // Generate a minidump for the current process.
184 E : base::FilePath small_dump_file_path = temp_dir().Append(L"small.dump");
185 E : base::FilePath larger_dump_file_path = temp_dir().Append(L"larger.dump");
186 E : base::FilePath full_dump_file_path = temp_dir().Append(L"full.dump");
187 :
188 E : bool result = false;
189 E : request().type = MinidumpRequest::SMALL_DUMP_TYPE;
190 E : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(small_dump_file_path, &result));
191 E : ASSERT_TRUE(result);
192 E : request().type = MinidumpRequest::LARGER_DUMP_TYPE;
193 E : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(larger_dump_file_path, &result));
194 E : ASSERT_TRUE(result);
195 E : request().type = MinidumpRequest::FULL_DUMP_TYPE;
196 E : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(full_dump_file_path, &result));
197 E : 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 E : int64 small_dump_size = 0;
205 E : int64 larger_dump_size = 0;
206 E : int64 full_dump_size = 0;
207 :
208 E : ASSERT_TRUE(base::GetFileSize(small_dump_file_path, &small_dump_size));
209 E : ASSERT_TRUE(base::GetFileSize(larger_dump_file_path, &larger_dump_size));
210 E : ASSERT_TRUE(base::GetFileSize(full_dump_file_path, &full_dump_size));
211 :
212 E : EXPECT_GT(full_dump_size, larger_dump_size);
213 E : EXPECT_GT(larger_dump_size, small_dump_size);
214 E : }
215 :
216 E : TEST_F(MinidumpTest, MemoryRanges) {
217 : // Generate a minidump for the current process.
218 E : base::FilePath default_dump_file_path = temp_dir().Append(L"default.dump");
219 : base::FilePath dump_with_memory_range_file_path =
220 E : temp_dir().Append(L"with_range.dump");
221 :
222 E : bool result = false;
223 : ASSERT_NO_FATAL_FAILURE(
224 E : CallGenerateMinidump(default_dump_file_path, &result));
225 E : ASSERT_TRUE(result);
226 :
227 : MinidumpRequest::MemoryRange range = {
228 E : reinterpret_cast<uint32_t>(kGlobalString), sizeof(kGlobalString)};
229 E : request().user_selected_memory_ranges.push_back(range);
230 : ASSERT_NO_FATAL_FAILURE(
231 E : CallGenerateMinidump(dump_with_memory_range_file_path, &result));
232 E : ASSERT_TRUE(result);
233 :
234 E : std::string default_dump;
235 E : std::string dump_with_memory_range;
236 E : ASSERT_TRUE(base::ReadFileToString(default_dump_file_path, &default_dump));
237 : ASSERT_TRUE(base::ReadFileToString(dump_with_memory_range_file_path,
238 E : &dump_with_memory_range));
239 :
240 E : ASSERT_EQ(std::string::npos, default_dump.find(kGlobalString));
241 E : ASSERT_NE(std::string::npos, dump_with_memory_range.find(kGlobalString));
242 E : }
243 :
244 E : TEST_F(MinidumpTest, OverwriteExistingFile) {
245 E : base::ScopedTempDir temp_dir;
246 E : ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
247 E : base::FilePath dump_file_path;
248 E : ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.path(), &dump_file_path));
249 :
250 E : bool result = false;
251 E : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(dump_file_path, &result));
252 E : ASSERT_TRUE(result);
253 :
254 : ASSERT_HRESULT_SUCCEEDED(
255 E : testing::VisitMinidump(dump_file_path, base::Bind(&ValidateMinidump)));
256 E : }
257 :
258 E : TEST_F(MinidumpTest, NonexistantTargetDirectory) {
259 E : base::ScopedTempDir temp_dir;
260 E : ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
261 E : bool result = false;
262 : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(
263 E : temp_dir.path().Append(L"Foobar").Append(L"HelloWorld"), &result));
264 E : ASSERT_FALSE(result);
265 E : }
266 :
267 : // Tests that the ranges for loader lock and the loader lock debug info is
268 : // included in the minidump.
269 E : TEST_F(MinidumpTest, LoaderLock) {
270 : // Generate a minidump for the current process.
271 E : base::FilePath default_dump_file_path = temp_dir().Append(L"default.dump");
272 E : bool result = false;
273 : ASSERT_NO_FATAL_FAILURE(
274 E : CallGenerateMinidump(default_dump_file_path, &result));
275 E : ASSERT_TRUE(result);
276 :
277 E : minidump::Minidump minidump;
278 E : ASSERT_TRUE(minidump.Open(default_dump_file_path));
279 :
280 : minidump::Minidump::Stream stream =
281 E : minidump.FindNextStream(nullptr, MemoryListStream);
282 E : ASSERT_TRUE(stream.IsValid());
283 :
284 E : bool loader_lock_found = false;
285 E : bool debug_info_found = false;
286 : core::AddressRange<uint32_t, uint32_t> loader_lock_range(
287 E : reinterpret_cast<uint32_t>(GetLoaderLock()), sizeof(CRITICAL_SECTION));
288 : core::AddressRange<uint32_t, uint32_t> debug_info_range(
289 : reinterpret_cast<uint32_t>(GetLoaderLock()->DebugInfo),
290 E : sizeof(CRITICAL_SECTION_DEBUG));
291 :
292 E : ULONG32 num_memory_descriptors = 0;
293 E : stream.ReadElement(&num_memory_descriptors);
294 E : for (size_t i = 0;
295 E : i < num_memory_descriptors && !(loader_lock_found && debug_info_found);
296 E : ++i) {
297 E : MINIDUMP_MEMORY_DESCRIPTOR memory_descriptor = {};
298 E : stream.ReadElement(&memory_descriptor);
299 : core::AddressRange<uint32_t, uint32_t> descriptor_range(
300 : memory_descriptor.StartOfMemoryRange,
301 E : 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 E : if (!loader_lock_found && descriptor_range.Contains(loader_lock_range))
306 E : loader_lock_found = true;
307 E : if (!debug_info_found && descriptor_range.Contains(debug_info_range))
308 E : debug_info_found = true;
309 E : }
310 :
311 E : ASSERT_TRUE(loader_lock_found && debug_info_found);
312 E : }
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 E : TEST_F(MinidumpTest, NtdllLoadAddress) {
318 : // Generate a minidump for the current process.
319 E : base::FilePath dump_file_path = temp_dir().Append(L"default.dump");
320 E : bool result = false;
321 E : ASSERT_NO_FATAL_FAILURE(CallGenerateMinidump(dump_file_path, &result));
322 E : ASSERT_TRUE(result);
323 :
324 E : minidump::Minidump minidump;
325 E : ASSERT_TRUE(minidump.Open(dump_file_path));
326 :
327 : // Retrieve the unique module list stream.
328 : minidump::Minidump::Stream module_list =
329 E : minidump.FindNextStream(nullptr, ModuleListStream);
330 E : ASSERT_TRUE(module_list.IsValid());
331 :
332 E : ULONG32 num_modules = 0;
333 E : ASSERT_TRUE(module_list.ReadElement(&num_modules));
334 :
335 E : bool ntdll_found = false;
336 E : for (size_t i = 0; i < num_modules; ++i) {
337 E : MINIDUMP_MODULE module = {};
338 E : ASSERT_TRUE(module_list.ReadElement(&module));
339 :
340 : // Get the module name. The length of the name is included in the stream.
341 : MINIDUMP_LOCATION_DESCRIPTOR name_location = {static_cast<ULONG32>(-1),
342 E : module.ModuleNameRva};
343 : minidump::Minidump::Stream name_stream =
344 E : minidump.GetStreamFor(name_location);
345 E : ASSERT_TRUE(name_stream.IsValid());
346 :
347 E : std::wstring module_name;
348 E : ASSERT_TRUE(name_stream.ReadString(&module_name));
349 :
350 E : if (module_name.find(L"ntdll.dll") != -1) {
351 E : MODULEINFO ntdll_module_info = {};
352 : ASSERT_TRUE(::GetModuleInformation(
353 : ::GetCurrentProcess(), ::GetModuleHandle(L"ntdll.dll"),
354 E : &ntdll_module_info, sizeof(MODULEINFO)));
355 : ASSERT_EQ(reinterpret_cast<uintptr_t>(ntdll_module_info.lpBaseOfDll),
356 E : module.BaseOfImage);
357 E : ntdll_found = true;
358 E : break;
359 : }
360 E : }
361 : // Don't succeed if the address hasn't been checked.
362 E : ASSERT_TRUE(ntdll_found);
363 E : }
364 :
365 : } // namespace kasko
|