OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "sandbox/win/src/win_utils.h" | |
6 | |
7 #include <stddef.h> | |
8 | |
9 #include <map> | |
10 | |
11 #include "base/macros.h" | |
12 #include "base/memory/scoped_ptr.h" | |
13 #include "base/strings/string_util.h" | |
14 #include "base/win/pe_image.h" | |
15 #include "sandbox/win/src/internal_types.h" | |
16 #include "sandbox/win/src/nt_internals.h" | |
17 #include "sandbox/win/src/sandbox_nt_util.h" | |
18 | |
19 namespace { | |
20 | |
21 // Holds the information about a known registry key. | |
22 struct KnownReservedKey { | |
23 const wchar_t* name; | |
24 HKEY key; | |
25 }; | |
26 | |
27 // Contains all the known registry key by name and by handle. | |
28 const KnownReservedKey kKnownKey[] = { | |
29 { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT }, | |
30 { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER }, | |
31 { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, | |
32 { L"HKEY_USERS", HKEY_USERS}, | |
33 { L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA}, | |
34 { L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT}, | |
35 { L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT}, | |
36 { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, | |
37 { L"HKEY_DYN_DATA", HKEY_DYN_DATA} | |
38 }; | |
39 | |
40 // These functions perform case independent path comparisons. | |
41 bool EqualPath(const base::string16& first, const base::string16& second) { | |
42 return _wcsicmp(first.c_str(), second.c_str()) == 0; | |
43 } | |
44 | |
45 bool EqualPath(const base::string16& first, size_t first_offset, | |
46 const base::string16& second, size_t second_offset) { | |
47 return _wcsicmp(first.c_str() + first_offset, | |
48 second.c_str() + second_offset) == 0; | |
49 } | |
50 | |
51 bool EqualPath(const base::string16& first, | |
52 const wchar_t* second, size_t second_len) { | |
53 return _wcsnicmp(first.c_str(), second, second_len) == 0; | |
54 } | |
55 | |
56 bool EqualPath(const base::string16& first, size_t first_offset, | |
57 const wchar_t* second, size_t second_len) { | |
58 return _wcsnicmp(first.c_str() + first_offset, second, second_len) == 0; | |
59 } | |
60 | |
61 // Returns true if |path| starts with "\??\" and returns a path without that | |
62 // component. | |
63 bool IsNTPath(const base::string16& path, base::string16* trimmed_path ) { | |
64 if ((path.size() < sandbox::kNTPrefixLen) || | |
65 (0 != path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix))) { | |
66 *trimmed_path = path; | |
67 return false; | |
68 } | |
69 | |
70 *trimmed_path = path.substr(sandbox::kNTPrefixLen); | |
71 return true; | |
72 } | |
73 | |
74 // Returns true if |path| starts with "\Device\" and returns a path without that | |
75 // component. | |
76 bool IsDevicePath(const base::string16& path, base::string16* trimmed_path ) { | |
77 if ((path.size() < sandbox::kNTDevicePrefixLen) || | |
78 (!EqualPath(path, sandbox::kNTDevicePrefix, | |
79 sandbox::kNTDevicePrefixLen))) { | |
80 *trimmed_path = path; | |
81 return false; | |
82 } | |
83 | |
84 *trimmed_path = path.substr(sandbox::kNTDevicePrefixLen); | |
85 return true; | |
86 } | |
87 | |
88 bool StartsWithDriveLetter(const base::string16& path) { | |
89 if (path.size() < 3) | |
90 return false; | |
91 | |
92 if (path[1] != L':' || path[2] != L'\\') | |
93 return false; | |
94 | |
95 return (path[0] >= 'a' && path[0] <= 'z') || | |
96 (path[0] >= 'A' && path[0] <= 'Z'); | |
97 } | |
98 | |
99 const wchar_t kNTDotPrefix[] = L"\\\\.\\"; | |
100 const size_t kNTDotPrefixLen = arraysize(kNTDotPrefix) - 1; | |
101 | |
102 // Removes "\\\\.\\" from the path. | |
103 void RemoveImpliedDevice(base::string16* path) { | |
104 if (0 == path->compare(0, kNTDotPrefixLen, kNTDotPrefix)) | |
105 *path = path->substr(kNTDotPrefixLen); | |
106 } | |
107 | |
108 } // namespace | |
109 | |
110 namespace sandbox { | |
111 | |
112 // Returns true if the provided path points to a pipe. | |
113 bool IsPipe(const base::string16& path) { | |
114 size_t start = 0; | |
115 if (0 == path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix)) | |
116 start = sandbox::kNTPrefixLen; | |
117 | |
118 const wchar_t kPipe[] = L"pipe\\"; | |
119 if (path.size() < start + arraysize(kPipe) - 1) | |
120 return false; | |
121 | |
122 return EqualPath(path, start, kPipe, arraysize(kPipe) - 1); | |
123 } | |
124 | |
125 HKEY GetReservedKeyFromName(const base::string16& name) { | |
126 for (size_t i = 0; i < arraysize(kKnownKey); ++i) { | |
127 if (name == kKnownKey[i].name) | |
128 return kKnownKey[i].key; | |
129 } | |
130 | |
131 return NULL; | |
132 } | |
133 | |
134 bool ResolveRegistryName(base::string16 name, base::string16* resolved_name) { | |
135 for (size_t i = 0; i < arraysize(kKnownKey); ++i) { | |
136 if (name.find(kKnownKey[i].name) == 0) { | |
137 HKEY key; | |
138 DWORD disposition; | |
139 if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, NULL, 0, | |
140 MAXIMUM_ALLOWED, NULL, &key, | |
141 &disposition)) | |
142 return false; | |
143 | |
144 bool result = GetPathFromHandle(key, resolved_name); | |
145 ::RegCloseKey(key); | |
146 | |
147 if (!result) | |
148 return false; | |
149 | |
150 *resolved_name += name.substr(wcslen(kKnownKey[i].name)); | |
151 return true; | |
152 } | |
153 } | |
154 | |
155 return false; | |
156 } | |
157 | |
158 // |full_path| can have any of the following forms: | |
159 // \??\c:\some\foo\bar | |
160 // \Device\HarddiskVolume0\some\foo\bar | |
161 // \??\HarddiskVolume0\some\foo\bar | |
162 DWORD IsReparsePoint(const base::string16& full_path) { | |
163 // Check if it's a pipe. We can't query the attributes of a pipe. | |
164 if (IsPipe(full_path)) | |
165 return ERROR_NOT_A_REPARSE_POINT; | |
166 | |
167 base::string16 path; | |
168 bool nt_path = IsNTPath(full_path, &path); | |
169 bool has_drive = StartsWithDriveLetter(path); | |
170 bool is_device_path = IsDevicePath(path, &path); | |
171 | |
172 if (!has_drive && !is_device_path && !nt_path) | |
173 return ERROR_INVALID_NAME; | |
174 | |
175 bool added_implied_device = false; | |
176 if (!has_drive) { | |
177 path = base::string16(kNTDotPrefix) + path; | |
178 added_implied_device = true; | |
179 } | |
180 | |
181 base::string16::size_type last_pos = base::string16::npos; | |
182 bool passed_once = false; | |
183 | |
184 do { | |
185 path = path.substr(0, last_pos); | |
186 | |
187 DWORD attributes = ::GetFileAttributes(path.c_str()); | |
188 if (INVALID_FILE_ATTRIBUTES == attributes) { | |
189 DWORD error = ::GetLastError(); | |
190 if (error != ERROR_FILE_NOT_FOUND && | |
191 error != ERROR_PATH_NOT_FOUND && | |
192 error != ERROR_INVALID_NAME) { | |
193 // Unexpected error. | |
194 if (passed_once && added_implied_device && | |
195 (path.rfind(L'\\') == kNTDotPrefixLen - 1)) { | |
196 break; | |
197 } | |
198 NOTREACHED_NT(); | |
199 return error; | |
200 } | |
201 } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) { | |
202 // This is a reparse point. | |
203 return ERROR_SUCCESS; | |
204 } | |
205 | |
206 passed_once = true; | |
207 last_pos = path.rfind(L'\\'); | |
208 } while (last_pos > 2); // Skip root dir. | |
209 | |
210 return ERROR_NOT_A_REPARSE_POINT; | |
211 } | |
212 | |
213 // We get a |full_path| of the forms accepted by IsReparsePoint(), and the name | |
214 // we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar. | |
215 bool SameObject(HANDLE handle, const wchar_t* full_path) { | |
216 // Check if it's a pipe. | |
217 if (IsPipe(full_path)) | |
218 return true; | |
219 | |
220 base::string16 actual_path; | |
221 if (!GetPathFromHandle(handle, &actual_path)) | |
222 return false; | |
223 | |
224 base::string16 path(full_path); | |
225 DCHECK_NT(!path.empty()); | |
226 | |
227 // This may end with a backslash. | |
228 const wchar_t kBackslash = '\\'; | |
229 if (path.back() == kBackslash) | |
230 path = path.substr(0, path.length() - 1); | |
231 | |
232 // Perfect match (case-insesitive check). | |
233 if (EqualPath(actual_path, path)) | |
234 return true; | |
235 | |
236 bool nt_path = IsNTPath(path, &path); | |
237 bool has_drive = StartsWithDriveLetter(path); | |
238 | |
239 if (!has_drive && nt_path) { | |
240 base::string16 simple_actual_path; | |
241 if (!IsDevicePath(actual_path, &simple_actual_path)) | |
242 return false; | |
243 | |
244 // Perfect match (case-insesitive check). | |
245 return (EqualPath(simple_actual_path, path)); | |
246 } | |
247 | |
248 if (!has_drive) | |
249 return false; | |
250 | |
251 // We only need 3 chars, but let's alloc a buffer for four. | |
252 wchar_t drive[4] = {0}; | |
253 wchar_t vol_name[MAX_PATH]; | |
254 memcpy(drive, &path[0], 2 * sizeof(*drive)); | |
255 | |
256 // We'll get a double null terminated string. | |
257 DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH); | |
258 if (vol_length < 2 || vol_length == MAX_PATH) | |
259 return false; | |
260 | |
261 // Ignore the nulls at the end. | |
262 vol_length = static_cast<DWORD>(wcslen(vol_name)); | |
263 | |
264 // The two paths should be the same length. | |
265 if (vol_length + path.size() - 2 != actual_path.size()) | |
266 return false; | |
267 | |
268 // Check up to the drive letter. | |
269 if (!EqualPath(actual_path, vol_name, vol_length)) | |
270 return false; | |
271 | |
272 // Check the path after the drive letter. | |
273 if (!EqualPath(actual_path, vol_length, path, 2)) | |
274 return false; | |
275 | |
276 return true; | |
277 } | |
278 | |
279 // Paths like \Device\HarddiskVolume0\some\foo\bar are assumed to be already | |
280 // expanded. | |
281 bool ConvertToLongPath(base::string16* path) { | |
282 if (IsPipe(*path)) | |
283 return true; | |
284 | |
285 base::string16 temp_path; | |
286 if (IsDevicePath(*path, &temp_path)) | |
287 return false; | |
288 | |
289 bool is_nt_path = IsNTPath(temp_path, &temp_path); | |
290 bool added_implied_device = false; | |
291 if (!StartsWithDriveLetter(temp_path) && is_nt_path) { | |
292 temp_path = base::string16(kNTDotPrefix) + temp_path; | |
293 added_implied_device = true; | |
294 } | |
295 | |
296 DWORD size = MAX_PATH; | |
297 scoped_ptr<wchar_t[]> long_path_buf(new wchar_t[size]); | |
298 | |
299 DWORD return_value = ::GetLongPathName(temp_path.c_str(), long_path_buf.get(), | |
300 size); | |
301 while (return_value >= size) { | |
302 size *= 2; | |
303 long_path_buf.reset(new wchar_t[size]); | |
304 return_value = ::GetLongPathName(temp_path.c_str(), long_path_buf.get(), | |
305 size); | |
306 } | |
307 | |
308 DWORD last_error = ::GetLastError(); | |
309 if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error || | |
310 ERROR_PATH_NOT_FOUND == last_error || | |
311 ERROR_INVALID_NAME == last_error)) { | |
312 // The file does not exist, but maybe a sub path needs to be expanded. | |
313 base::string16::size_type last_slash = temp_path.rfind(L'\\'); | |
314 if (base::string16::npos == last_slash) | |
315 return false; | |
316 | |
317 base::string16 begin = temp_path.substr(0, last_slash); | |
318 base::string16 end = temp_path.substr(last_slash); | |
319 if (!ConvertToLongPath(&begin)) | |
320 return false; | |
321 | |
322 // Ok, it worked. Let's reset the return value. | |
323 temp_path = begin + end; | |
324 return_value = 1; | |
325 } else if (0 != return_value) { | |
326 temp_path = long_path_buf.get(); | |
327 } | |
328 | |
329 if (return_value != 0) { | |
330 if (added_implied_device) | |
331 RemoveImpliedDevice(&temp_path); | |
332 | |
333 if (is_nt_path) { | |
334 *path = kNTPrefix; | |
335 *path += temp_path; | |
336 } else { | |
337 *path = temp_path; | |
338 } | |
339 | |
340 return true; | |
341 } | |
342 | |
343 return false; | |
344 } | |
345 | |
346 bool GetPathFromHandle(HANDLE handle, base::string16* path) { | |
347 NtQueryObjectFunction NtQueryObject = NULL; | |
348 ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); | |
349 | |
350 OBJECT_NAME_INFORMATION initial_buffer; | |
351 OBJECT_NAME_INFORMATION* name = &initial_buffer; | |
352 ULONG size = sizeof(initial_buffer); | |
353 // Query the name information a first time to get the size of the name. | |
354 // Windows XP requires that the size of the buffer passed in here be != 0. | |
355 NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size, | |
356 &size); | |
357 | |
358 scoped_ptr<BYTE[]> name_ptr; | |
359 if (size) { | |
360 name_ptr.reset(new BYTE[size]); | |
361 name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(name_ptr.get()); | |
362 | |
363 // Query the name information a second time to get the name of the | |
364 // object referenced by the handle. | |
365 status = NtQueryObject(handle, ObjectNameInformation, name, size, &size); | |
366 } | |
367 | |
368 if (STATUS_SUCCESS != status) | |
369 return false; | |
370 | |
371 path->assign(name->ObjectName.Buffer, name->ObjectName.Length / | |
372 sizeof(name->ObjectName.Buffer[0])); | |
373 return true; | |
374 } | |
375 | |
376 bool GetNtPathFromWin32Path(const base::string16& path, | |
377 base::string16* nt_path) { | |
378 HANDLE file = ::CreateFileW(path.c_str(), 0, | |
379 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, | |
380 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); | |
381 if (file == INVALID_HANDLE_VALUE) | |
382 return false; | |
383 bool rv = GetPathFromHandle(file, nt_path); | |
384 ::CloseHandle(file); | |
385 return rv; | |
386 } | |
387 | |
388 bool WriteProtectedChildMemory(HANDLE child_process, void* address, | |
389 const void* buffer, size_t length) { | |
390 // First, remove the protections. | |
391 DWORD old_protection; | |
392 if (!::VirtualProtectEx(child_process, address, length, | |
393 PAGE_WRITECOPY, &old_protection)) | |
394 return false; | |
395 | |
396 SIZE_T written; | |
397 bool ok = ::WriteProcessMemory(child_process, address, buffer, length, | |
398 &written) && (length == written); | |
399 | |
400 // Always attempt to restore the original protection. | |
401 if (!::VirtualProtectEx(child_process, address, length, | |
402 old_protection, &old_protection)) | |
403 return false; | |
404 | |
405 return ok; | |
406 } | |
407 | |
408 }; // namespace sandbox | |
409 | |
410 void ResolveNTFunctionPtr(const char* name, void* ptr) { | |
411 static volatile HMODULE ntdll = NULL; | |
412 | |
413 if (!ntdll) { | |
414 HMODULE ntdll_local = ::GetModuleHandle(sandbox::kNtdllName); | |
415 // Use PEImage to sanity-check that we have a valid ntdll handle. | |
416 base::win::PEImage ntdll_peimage(ntdll_local); | |
417 CHECK_NT(ntdll_peimage.VerifyMagic()); | |
418 // Race-safe way to set static ntdll. | |
419 ::InterlockedCompareExchangePointer( | |
420 reinterpret_cast<PVOID volatile*>(&ntdll), ntdll_local, NULL); | |
421 | |
422 } | |
423 | |
424 CHECK_NT(ntdll); | |
425 FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); | |
426 *function_ptr = ::GetProcAddress(ntdll, name); | |
427 CHECK_NT(*function_ptr); | |
428 } | |
OLD | NEW |