|
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 <map> |
|
8 |
|
9 #include "base/logging.h" |
|
10 #include "base/memory/scoped_ptr.h" |
|
11 #include "sandbox/win/src/internal_types.h" |
|
12 #include "sandbox/win/src/nt_internals.h" |
|
13 |
|
14 namespace { |
|
15 |
|
16 // Holds the information about a known registry key. |
|
17 struct KnownReservedKey { |
|
18 const wchar_t* name; |
|
19 HKEY key; |
|
20 }; |
|
21 |
|
22 // Contains all the known registry key by name and by handle. |
|
23 const KnownReservedKey kKnownKey[] = { |
|
24 { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT }, |
|
25 { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER }, |
|
26 { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, |
|
27 { L"HKEY_USERS", HKEY_USERS}, |
|
28 { L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA}, |
|
29 { L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT}, |
|
30 { L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT}, |
|
31 { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, |
|
32 { L"HKEY_DYN_DATA", HKEY_DYN_DATA} |
|
33 }; |
|
34 |
|
35 // Returns true if the provided path points to a pipe. |
|
36 bool IsPipe(const std::wstring& path) { |
|
37 size_t start = 0; |
|
38 if (0 == path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix)) |
|
39 start = sandbox::kNTPrefixLen; |
|
40 |
|
41 const wchar_t kPipe[] = L"pipe\\"; |
|
42 return (0 == path.compare(start, arraysize(kPipe) - 1, kPipe)); |
|
43 } |
|
44 |
|
45 } // namespace |
|
46 |
|
47 namespace sandbox { |
|
48 |
|
49 HKEY GetReservedKeyFromName(const std::wstring& name) { |
|
50 for (size_t i = 0; i < arraysize(kKnownKey); ++i) { |
|
51 if (name == kKnownKey[i].name) |
|
52 return kKnownKey[i].key; |
|
53 } |
|
54 |
|
55 return NULL; |
|
56 } |
|
57 |
|
58 bool ResolveRegistryName(std::wstring name, std::wstring* resolved_name) { |
|
59 for (size_t i = 0; i < arraysize(kKnownKey); ++i) { |
|
60 if (name.find(kKnownKey[i].name) == 0) { |
|
61 HKEY key; |
|
62 DWORD disposition; |
|
63 if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, NULL, 0, |
|
64 MAXIMUM_ALLOWED, NULL, &key, |
|
65 &disposition)) |
|
66 return false; |
|
67 |
|
68 bool result = GetPathFromHandle(key, resolved_name); |
|
69 ::RegCloseKey(key); |
|
70 |
|
71 if (!result) |
|
72 return false; |
|
73 |
|
74 *resolved_name += name.substr(wcslen(kKnownKey[i].name)); |
|
75 return true; |
|
76 } |
|
77 } |
|
78 |
|
79 return false; |
|
80 } |
|
81 |
|
82 DWORD IsReparsePoint(const std::wstring& full_path, bool* result) { |
|
83 std::wstring path = full_path; |
|
84 |
|
85 // Remove the nt prefix. |
|
86 if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) |
|
87 path = path.substr(kNTPrefixLen); |
|
88 |
|
89 // Check if it's a pipe. We can't query the attributes of a pipe. |
|
90 if (IsPipe(path)) { |
|
91 *result = FALSE; |
|
92 return ERROR_SUCCESS; |
|
93 } |
|
94 |
|
95 std::wstring::size_type last_pos = std::wstring::npos; |
|
96 |
|
97 do { |
|
98 path = path.substr(0, last_pos); |
|
99 |
|
100 DWORD attributes = ::GetFileAttributes(path.c_str()); |
|
101 if (INVALID_FILE_ATTRIBUTES == attributes) { |
|
102 DWORD error = ::GetLastError(); |
|
103 if (error != ERROR_FILE_NOT_FOUND && |
|
104 error != ERROR_PATH_NOT_FOUND && |
|
105 error != ERROR_INVALID_NAME) { |
|
106 // Unexpected error. |
|
107 NOTREACHED(); |
|
108 return error; |
|
109 } |
|
110 } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) { |
|
111 // This is a reparse point. |
|
112 *result = true; |
|
113 return ERROR_SUCCESS; |
|
114 } |
|
115 |
|
116 last_pos = path.rfind(L'\\'); |
|
117 } while (last_pos != std::wstring::npos); |
|
118 |
|
119 *result = false; |
|
120 return ERROR_SUCCESS; |
|
121 } |
|
122 |
|
123 // We get a |full_path| of the form \??\c:\some\foo\bar, and the name that |
|
124 // we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar. |
|
125 bool SameObject(HANDLE handle, const wchar_t* full_path) { |
|
126 std::wstring path(full_path); |
|
127 DCHECK(!path.empty()); |
|
128 |
|
129 // Check if it's a pipe. |
|
130 if (IsPipe(path)) |
|
131 return true; |
|
132 |
|
133 std::wstring actual_path; |
|
134 if (!GetPathFromHandle(handle, &actual_path)) |
|
135 return false; |
|
136 |
|
137 // This may end with a backslash. |
|
138 const wchar_t kBackslash = '\\'; |
|
139 if (path[path.length() - 1] == kBackslash) |
|
140 path = path.substr(0, path.length() - 1); |
|
141 |
|
142 // Perfect match (case-insesitive check). |
|
143 if (0 == _wcsicmp(actual_path.c_str(), path.c_str())) |
|
144 return true; |
|
145 |
|
146 // Look for the drive letter. |
|
147 size_t colon_pos = path.find(L':'); |
|
148 if (colon_pos == 0 || colon_pos == std::wstring::npos) |
|
149 return false; |
|
150 |
|
151 // Only one character for the drive. |
|
152 if (colon_pos > 1 && path[colon_pos - 2] != kBackslash) |
|
153 return false; |
|
154 |
|
155 // We only need 3 chars, but let's alloc a buffer for four. |
|
156 wchar_t drive[4] = {0}; |
|
157 wchar_t vol_name[MAX_PATH]; |
|
158 memcpy(drive, &path[colon_pos - 1], 2 * sizeof(*drive)); |
|
159 |
|
160 // We'll get a double null terminated string. |
|
161 DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH); |
|
162 if (vol_length < 2 || vol_length == MAX_PATH) |
|
163 return false; |
|
164 |
|
165 // Ignore the nulls at the end. |
|
166 vol_length = static_cast<DWORD>(wcslen(vol_name)); |
|
167 |
|
168 // The two paths should be the same length. |
|
169 if (vol_length + path.size() - (colon_pos + 1) != actual_path.size()) |
|
170 return false; |
|
171 |
|
172 // Check up to the drive letter. |
|
173 if (0 != _wcsnicmp(actual_path.c_str(), vol_name, vol_length)) |
|
174 return false; |
|
175 |
|
176 // Check the path after the drive letter. |
|
177 if (0 != _wcsicmp(&actual_path[vol_length], &path[colon_pos + 1])) |
|
178 return false; |
|
179 |
|
180 return true; |
|
181 } |
|
182 |
|
183 bool ConvertToLongPath(const std::wstring& short_path, |
|
184 std::wstring* long_path) { |
|
185 // Check if the path is a NT path. |
|
186 bool is_nt_path = false; |
|
187 std::wstring path = short_path; |
|
188 if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) { |
|
189 path = path.substr(kNTPrefixLen); |
|
190 is_nt_path = true; |
|
191 } |
|
192 |
|
193 DWORD size = MAX_PATH; |
|
194 scoped_ptr<wchar_t[]> long_path_buf(new wchar_t[size]); |
|
195 |
|
196 DWORD return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), |
|
197 size); |
|
198 while (return_value >= size) { |
|
199 size *= 2; |
|
200 long_path_buf.reset(new wchar_t[size]); |
|
201 return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), size); |
|
202 } |
|
203 |
|
204 DWORD last_error = ::GetLastError(); |
|
205 if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error || |
|
206 ERROR_PATH_NOT_FOUND == last_error || |
|
207 ERROR_INVALID_NAME == last_error)) { |
|
208 // The file does not exist, but maybe a sub path needs to be expanded. |
|
209 std::wstring::size_type last_slash = path.rfind(L'\\'); |
|
210 if (std::wstring::npos == last_slash) |
|
211 return false; |
|
212 |
|
213 std::wstring begin = path.substr(0, last_slash); |
|
214 std::wstring end = path.substr(last_slash); |
|
215 if (!ConvertToLongPath(begin, &begin)) |
|
216 return false; |
|
217 |
|
218 // Ok, it worked. Let's reset the return value. |
|
219 path = begin + end; |
|
220 return_value = 1; |
|
221 } else if (0 != return_value) { |
|
222 path = long_path_buf.get(); |
|
223 } |
|
224 |
|
225 if (return_value != 0) { |
|
226 if (is_nt_path) { |
|
227 *long_path = kNTPrefix; |
|
228 *long_path += path; |
|
229 } else { |
|
230 *long_path = path; |
|
231 } |
|
232 |
|
233 return true; |
|
234 } |
|
235 |
|
236 return false; |
|
237 } |
|
238 |
|
239 bool GetPathFromHandle(HANDLE handle, std::wstring* path) { |
|
240 NtQueryObjectFunction NtQueryObject = NULL; |
|
241 ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); |
|
242 |
|
243 OBJECT_NAME_INFORMATION initial_buffer; |
|
244 OBJECT_NAME_INFORMATION* name = &initial_buffer; |
|
245 ULONG size = sizeof(initial_buffer); |
|
246 // Query the name information a first time to get the size of the name. |
|
247 NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size, |
|
248 &size); |
|
249 |
|
250 scoped_ptr<OBJECT_NAME_INFORMATION> name_ptr; |
|
251 if (size) { |
|
252 name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(new BYTE[size]); |
|
253 name_ptr.reset(name); |
|
254 |
|
255 // Query the name information a second time to get the name of the |
|
256 // object referenced by the handle. |
|
257 status = NtQueryObject(handle, ObjectNameInformation, name, size, &size); |
|
258 } |
|
259 |
|
260 if (STATUS_SUCCESS != status) |
|
261 return false; |
|
262 |
|
263 path->assign(name->ObjectName.Buffer, name->ObjectName.Length / |
|
264 sizeof(name->ObjectName.Buffer[0])); |
|
265 return true; |
|
266 } |
|
267 |
|
268 bool GetNtPathFromWin32Path(const std::wstring& path, std::wstring* nt_path) { |
|
269 HANDLE file = ::CreateFileW(path.c_str(), 0, |
|
270 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, |
|
271 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); |
|
272 if (file == INVALID_HANDLE_VALUE) |
|
273 return false; |
|
274 bool rv = GetPathFromHandle(file, nt_path); |
|
275 ::CloseHandle(file); |
|
276 return rv; |
|
277 } |
|
278 |
|
279 bool WriteProtectedChildMemory(HANDLE child_process, void* address, |
|
280 const void* buffer, size_t length) { |
|
281 // First, remove the protections. |
|
282 DWORD old_protection; |
|
283 if (!::VirtualProtectEx(child_process, address, length, |
|
284 PAGE_WRITECOPY, &old_protection)) |
|
285 return false; |
|
286 |
|
287 SIZE_T written; |
|
288 bool ok = ::WriteProcessMemory(child_process, address, buffer, length, |
|
289 &written) && (length == written); |
|
290 |
|
291 // Always attempt to restore the original protection. |
|
292 if (!::VirtualProtectEx(child_process, address, length, |
|
293 old_protection, &old_protection)) |
|
294 return false; |
|
295 |
|
296 return ok; |
|
297 } |
|
298 |
|
299 }; // namespace sandbox |
|
300 |
|
301 // TODO(jschuh): http://crbug.com/11789 |
|
302 // I'm guessing we have a race where some "security" software is messing |
|
303 // with ntdll/imports underneath us. So, we retry a few times, and in the |
|
304 // worst case we sleep briefly before a few more attempts. (Normally sleeping |
|
305 // would be very bad, but it's better than crashing in this case.) |
|
306 void ResolveNTFunctionPtr(const char* name, void* ptr) { |
|
307 const int max_tries = 5; |
|
308 const int sleep_threshold = 2; |
|
309 |
|
310 static HMODULE ntdll = ::GetModuleHandle(sandbox::kNtdllName); |
|
311 |
|
312 FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); |
|
313 *function_ptr = ::GetProcAddress(ntdll, name); |
|
314 |
|
315 for (int tries = 1; !(*function_ptr) && tries < max_tries; ++tries) { |
|
316 if (tries >= sleep_threshold) |
|
317 ::Sleep(1); |
|
318 ntdll = ::GetModuleHandle(sandbox::kNtdllName); |
|
319 *function_ptr = ::GetProcAddress(ntdll, name); |
|
320 } |
|
321 |
|
322 CHECK(*function_ptr); |
|
323 } |