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.
5 #include "sandbox/win/src/win_utils.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/string_util.h"
11 #include "base/win/pe_image.h"
12 #include "sandbox/win/src/internal_types.h"
13 #include "sandbox/win/src/nt_internals.h"
14 #include "sandbox/win/src/sandbox_nt_util.h"
18 // Holds the information about a known registry key.
19 struct KnownReservedKey
{
24 // Contains all the known registry key by name and by handle.
25 const KnownReservedKey kKnownKey
[] = {
26 { L
"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT
},
27 { L
"HKEY_CURRENT_USER", HKEY_CURRENT_USER
},
28 { L
"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE
},
29 { L
"HKEY_USERS", HKEY_USERS
},
30 { L
"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA
},
31 { L
"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT
},
32 { L
"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT
},
33 { L
"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG
},
34 { L
"HKEY_DYN_DATA", HKEY_DYN_DATA
}
37 // These functions perform case independent path comparisons.
38 bool EqualPath(const base::string16
& first
, const base::string16
& second
) {
39 return _wcsicmp(first
.c_str(), second
.c_str()) == 0;
42 bool EqualPath(const base::string16
& first
, size_t first_offset
,
43 const base::string16
& second
, size_t second_offset
) {
44 return _wcsicmp(first
.c_str() + first_offset
,
45 second
.c_str() + second_offset
) == 0;
48 bool EqualPath(const base::string16
& first
,
49 const wchar_t* second
, size_t second_len
) {
50 return _wcsnicmp(first
.c_str(), second
, second_len
) == 0;
53 bool EqualPath(const base::string16
& first
, size_t first_offset
,
54 const wchar_t* second
, size_t second_len
) {
55 return _wcsnicmp(first
.c_str() + first_offset
, second
, second_len
) == 0;
58 // Returns true if |path| starts with "\??\" and returns a path without that
60 bool IsNTPath(const base::string16
& path
, base::string16
* trimmed_path
) {
61 if ((path
.size() < sandbox::kNTPrefixLen
) ||
62 (0 != path
.compare(0, sandbox::kNTPrefixLen
, sandbox::kNTPrefix
))) {
67 *trimmed_path
= path
.substr(sandbox::kNTPrefixLen
);
71 // Returns true if |path| starts with "\Device\" and returns a path without that
73 bool IsDevicePath(const base::string16
& path
, base::string16
* trimmed_path
) {
74 if ((path
.size() < sandbox::kNTDevicePrefixLen
) ||
75 (!EqualPath(path
, sandbox::kNTDevicePrefix
,
76 sandbox::kNTDevicePrefixLen
))) {
81 *trimmed_path
= path
.substr(sandbox::kNTDevicePrefixLen
);
85 bool StartsWithDriveLetter(const base::string16
& path
) {
89 if (path
[1] != L
':' || path
[2] != L
'\\')
92 return (path
[0] >= 'a' && path
[0] <= 'z') ||
93 (path
[0] >= 'A' && path
[0] <= 'Z');
96 const wchar_t kNTDotPrefix
[] = L
"\\\\.\\";
97 const size_t kNTDotPrefixLen
= arraysize(kNTDotPrefix
) - 1;
99 // Removes "\\\\.\\" from the path.
100 void RemoveImpliedDevice(base::string16
* path
) {
101 if (0 == path
->compare(0, kNTDotPrefixLen
, kNTDotPrefix
))
102 *path
= path
->substr(kNTDotPrefixLen
);
109 // Returns true if the provided path points to a pipe.
110 bool IsPipe(const base::string16
& path
) {
112 if (0 == path
.compare(0, sandbox::kNTPrefixLen
, sandbox::kNTPrefix
))
113 start
= sandbox::kNTPrefixLen
;
115 const wchar_t kPipe
[] = L
"pipe\\";
116 if (path
.size() < start
+ arraysize(kPipe
) - 1)
119 return EqualPath(path
, start
, kPipe
, arraysize(kPipe
) - 1);
122 HKEY
GetReservedKeyFromName(const base::string16
& name
) {
123 for (size_t i
= 0; i
< arraysize(kKnownKey
); ++i
) {
124 if (name
== kKnownKey
[i
].name
)
125 return kKnownKey
[i
].key
;
131 bool ResolveRegistryName(base::string16 name
, base::string16
* resolved_name
) {
132 for (size_t i
= 0; i
< arraysize(kKnownKey
); ++i
) {
133 if (name
.find(kKnownKey
[i
].name
) == 0) {
136 if (ERROR_SUCCESS
!= ::RegCreateKeyEx(kKnownKey
[i
].key
, L
"", 0, NULL
, 0,
137 MAXIMUM_ALLOWED
, NULL
, &key
,
141 bool result
= GetPathFromHandle(key
, resolved_name
);
147 *resolved_name
+= name
.substr(wcslen(kKnownKey
[i
].name
));
155 // |full_path| can have any of the following forms:
156 // \??\c:\some\foo\bar
157 // \Device\HarddiskVolume0\some\foo\bar
158 // \??\HarddiskVolume0\some\foo\bar
159 DWORD
IsReparsePoint(const base::string16
& full_path
) {
160 // Check if it's a pipe. We can't query the attributes of a pipe.
161 if (IsPipe(full_path
))
162 return ERROR_NOT_A_REPARSE_POINT
;
165 bool nt_path
= IsNTPath(full_path
, &path
);
166 bool has_drive
= StartsWithDriveLetter(path
);
167 bool is_device_path
= IsDevicePath(path
, &path
);
169 if (!has_drive
&& !is_device_path
&& !nt_path
)
170 return ERROR_INVALID_NAME
;
172 bool added_implied_device
= false;
174 path
= base::string16(kNTDotPrefix
) + path
;
175 added_implied_device
= true;
178 base::string16::size_type last_pos
= base::string16::npos
;
179 bool passed_once
= false;
182 path
= path
.substr(0, last_pos
);
184 DWORD attributes
= ::GetFileAttributes(path
.c_str());
185 if (INVALID_FILE_ATTRIBUTES
== attributes
) {
186 DWORD error
= ::GetLastError();
187 if (error
!= ERROR_FILE_NOT_FOUND
&&
188 error
!= ERROR_PATH_NOT_FOUND
&&
189 error
!= ERROR_INVALID_NAME
) {
191 if (passed_once
&& added_implied_device
&&
192 (path
.rfind(L
'\\') == kNTDotPrefixLen
- 1)) {
198 } else if (FILE_ATTRIBUTE_REPARSE_POINT
& attributes
) {
199 // This is a reparse point.
200 return ERROR_SUCCESS
;
204 last_pos
= path
.rfind(L
'\\');
205 } while (last_pos
> 2); // Skip root dir.
207 return ERROR_NOT_A_REPARSE_POINT
;
210 // We get a |full_path| of the forms accepted by IsReparsePoint(), and the name
211 // we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar.
212 bool SameObject(HANDLE handle
, const wchar_t* full_path
) {
213 // Check if it's a pipe.
214 if (IsPipe(full_path
))
217 base::string16 actual_path
;
218 if (!GetPathFromHandle(handle
, &actual_path
))
221 base::string16
path(full_path
);
222 DCHECK_NT(!path
.empty());
224 // This may end with a backslash.
225 const wchar_t kBackslash
= '\\';
226 if (path
[path
.length() - 1] == kBackslash
)
227 path
= path
.substr(0, path
.length() - 1);
229 // Perfect match (case-insesitive check).
230 if (EqualPath(actual_path
, path
))
233 bool nt_path
= IsNTPath(path
, &path
);
234 bool has_drive
= StartsWithDriveLetter(path
);
236 if (!has_drive
&& nt_path
) {
237 base::string16 simple_actual_path
;
238 if (!IsDevicePath(actual_path
, &simple_actual_path
))
241 // Perfect match (case-insesitive check).
242 return (EqualPath(simple_actual_path
, path
));
248 // We only need 3 chars, but let's alloc a buffer for four.
249 wchar_t drive
[4] = {0};
250 wchar_t vol_name
[MAX_PATH
];
251 memcpy(drive
, &path
[0], 2 * sizeof(*drive
));
253 // We'll get a double null terminated string.
254 DWORD vol_length
= ::QueryDosDeviceW(drive
, vol_name
, MAX_PATH
);
255 if (vol_length
< 2 || vol_length
== MAX_PATH
)
258 // Ignore the nulls at the end.
259 vol_length
= static_cast<DWORD
>(wcslen(vol_name
));
261 // The two paths should be the same length.
262 if (vol_length
+ path
.size() - 2 != actual_path
.size())
265 // Check up to the drive letter.
266 if (!EqualPath(actual_path
, vol_name
, vol_length
))
269 // Check the path after the drive letter.
270 if (!EqualPath(actual_path
, vol_length
, path
, 2))
276 // Paths like \Device\HarddiskVolume0\some\foo\bar are assumed to be already
278 bool ConvertToLongPath(base::string16
* path
) {
282 base::string16 temp_path
;
283 if (IsDevicePath(*path
, &temp_path
))
286 bool is_nt_path
= IsNTPath(temp_path
, &temp_path
);
287 bool added_implied_device
= false;
288 if (!StartsWithDriveLetter(temp_path
) && is_nt_path
) {
289 temp_path
= base::string16(kNTDotPrefix
) + temp_path
;
290 added_implied_device
= true;
293 DWORD size
= MAX_PATH
;
294 scoped_ptr
<wchar_t[]> long_path_buf(new wchar_t[size
]);
296 DWORD return_value
= ::GetLongPathName(temp_path
.c_str(), long_path_buf
.get(),
298 while (return_value
>= size
) {
300 long_path_buf
.reset(new wchar_t[size
]);
301 return_value
= ::GetLongPathName(temp_path
.c_str(), long_path_buf
.get(),
305 DWORD last_error
= ::GetLastError();
306 if (0 == return_value
&& (ERROR_FILE_NOT_FOUND
== last_error
||
307 ERROR_PATH_NOT_FOUND
== last_error
||
308 ERROR_INVALID_NAME
== last_error
)) {
309 // The file does not exist, but maybe a sub path needs to be expanded.
310 base::string16::size_type last_slash
= temp_path
.rfind(L
'\\');
311 if (base::string16::npos
== last_slash
)
314 base::string16 begin
= temp_path
.substr(0, last_slash
);
315 base::string16 end
= temp_path
.substr(last_slash
);
316 if (!ConvertToLongPath(&begin
))
319 // Ok, it worked. Let's reset the return value.
320 temp_path
= begin
+ end
;
322 } else if (0 != return_value
) {
323 temp_path
= long_path_buf
.get();
326 if (return_value
!= 0) {
327 if (added_implied_device
)
328 RemoveImpliedDevice(&temp_path
);
343 bool GetPathFromHandle(HANDLE handle
, base::string16
* path
) {
344 NtQueryObjectFunction NtQueryObject
= NULL
;
345 ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject
);
347 OBJECT_NAME_INFORMATION initial_buffer
;
348 OBJECT_NAME_INFORMATION
* name
= &initial_buffer
;
349 ULONG size
= sizeof(initial_buffer
);
350 // Query the name information a first time to get the size of the name.
351 // Windows XP requires that the size of the buffer passed in here be != 0.
352 NTSTATUS status
= NtQueryObject(handle
, ObjectNameInformation
, name
, size
,
355 scoped_ptr
<BYTE
[]> name_ptr
;
357 name_ptr
.reset(new BYTE
[size
]);
358 name
= reinterpret_cast<OBJECT_NAME_INFORMATION
*>(name_ptr
.get());
360 // Query the name information a second time to get the name of the
361 // object referenced by the handle.
362 status
= NtQueryObject(handle
, ObjectNameInformation
, name
, size
, &size
);
365 if (STATUS_SUCCESS
!= status
)
368 path
->assign(name
->ObjectName
.Buffer
, name
->ObjectName
.Length
/
369 sizeof(name
->ObjectName
.Buffer
[0]));
373 bool GetNtPathFromWin32Path(const base::string16
& path
,
374 base::string16
* nt_path
) {
375 HANDLE file
= ::CreateFileW(path
.c_str(), 0,
376 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, NULL
,
377 OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
378 if (file
== INVALID_HANDLE_VALUE
)
380 bool rv
= GetPathFromHandle(file
, nt_path
);
385 bool WriteProtectedChildMemory(HANDLE child_process
, void* address
,
386 const void* buffer
, size_t length
) {
387 // First, remove the protections.
388 DWORD old_protection
;
389 if (!::VirtualProtectEx(child_process
, address
, length
,
390 PAGE_WRITECOPY
, &old_protection
))
394 bool ok
= ::WriteProcessMemory(child_process
, address
, buffer
, length
,
395 &written
) && (length
== written
);
397 // Always attempt to restore the original protection.
398 if (!::VirtualProtectEx(child_process
, address
, length
,
399 old_protection
, &old_protection
))
405 }; // namespace sandbox
407 void ResolveNTFunctionPtr(const char* name
, void* ptr
) {
408 static volatile HMODULE ntdll
= NULL
;
411 HMODULE ntdll_local
= ::GetModuleHandle(sandbox::kNtdllName
);
412 // Use PEImage to sanity-check that we have a valid ntdll handle.
413 base::win::PEImage
ntdll_peimage(ntdll_local
);
414 CHECK_NT(ntdll_peimage
.VerifyMagic());
415 // Race-safe way to set static ntdll.
416 ::InterlockedCompareExchangePointer(
417 reinterpret_cast<PVOID
volatile*>(&ntdll
), ntdll_local
, NULL
);
422 FARPROC
* function_ptr
= reinterpret_cast<FARPROC
*>(ptr
);
423 *function_ptr
= ::GetProcAddress(ntdll
, name
);
424 CHECK_NT(*function_ptr
);