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
, bool* result
) {
160 // Check if it's a pipe. We can't query the attributes of a pipe.
161 if (IsPipe(full_path
)) {
163 return ERROR_SUCCESS
;
167 bool nt_path
= IsNTPath(full_path
, &path
);
168 bool has_drive
= StartsWithDriveLetter(path
);
169 bool is_device_path
= IsDevicePath(path
, &path
);
171 if (!has_drive
&& !is_device_path
&& !nt_path
)
172 return ERROR_INVALID_NAME
;
174 bool added_implied_device
= false;
176 path
= base::string16(kNTDotPrefix
) + path
;
177 added_implied_device
= true;
180 base::string16::size_type last_pos
= base::string16::npos
;
181 bool passed_once
= false;
184 path
= path
.substr(0, last_pos
);
186 DWORD attributes
= ::GetFileAttributes(path
.c_str());
187 if (INVALID_FILE_ATTRIBUTES
== attributes
) {
188 DWORD error
= ::GetLastError();
189 if (error
!= ERROR_FILE_NOT_FOUND
&&
190 error
!= ERROR_PATH_NOT_FOUND
&&
191 error
!= ERROR_INVALID_NAME
) {
193 if (passed_once
&& added_implied_device
&&
194 (path
.rfind(L
'\\') == kNTDotPrefixLen
- 1)) {
200 } else if (FILE_ATTRIBUTE_REPARSE_POINT
& attributes
) {
201 // This is a reparse point.
203 return ERROR_SUCCESS
;
207 last_pos
= path
.rfind(L
'\\');
208 } while (last_pos
> 2); // Skip root dir.
211 return ERROR_SUCCESS
;
214 // We get a |full_path| of the forms accepted by IsReparsePoint(), and the name
215 // we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar.
216 bool SameObject(HANDLE handle
, const wchar_t* full_path
) {
217 // Check if it's a pipe.
218 if (IsPipe(full_path
))
221 base::string16 actual_path
;
222 if (!GetPathFromHandle(handle
, &actual_path
))
225 base::string16
path(full_path
);
226 DCHECK_NT(!path
.empty());
228 // This may end with a backslash.
229 const wchar_t kBackslash
= '\\';
230 if (path
[path
.length() - 1] == kBackslash
)
231 path
= path
.substr(0, path
.length() - 1);
233 // Perfect match (case-insesitive check).
234 if (EqualPath(actual_path
, path
))
237 bool nt_path
= IsNTPath(path
, &path
);
238 bool has_drive
= StartsWithDriveLetter(path
);
240 if (!has_drive
&& nt_path
) {
241 base::string16 simple_actual_path
;
242 if (!IsDevicePath(actual_path
, &simple_actual_path
))
245 // Perfect match (case-insesitive check).
246 return (EqualPath(simple_actual_path
, path
));
252 // We only need 3 chars, but let's alloc a buffer for four.
253 wchar_t drive
[4] = {0};
254 wchar_t vol_name
[MAX_PATH
];
255 memcpy(drive
, &path
[0], 2 * sizeof(*drive
));
257 // We'll get a double null terminated string.
258 DWORD vol_length
= ::QueryDosDeviceW(drive
, vol_name
, MAX_PATH
);
259 if (vol_length
< 2 || vol_length
== MAX_PATH
)
262 // Ignore the nulls at the end.
263 vol_length
= static_cast<DWORD
>(wcslen(vol_name
));
265 // The two paths should be the same length.
266 if (vol_length
+ path
.size() - 2 != actual_path
.size())
269 // Check up to the drive letter.
270 if (!EqualPath(actual_path
, vol_name
, vol_length
))
273 // Check the path after the drive letter.
274 if (!EqualPath(actual_path
, vol_length
, path
, 2))
280 // Paths like \Device\HarddiskVolume0\some\foo\bar are assumed to be already
282 bool ConvertToLongPath(const base::string16
& short_path
,
283 base::string16
* long_path
) {
284 if (IsPipe(short_path
)) {
285 // TODO(rvargas): Change the signature to use a single argument.
286 long_path
->assign(short_path
);
291 if (IsDevicePath(short_path
, &path
))
294 bool is_nt_path
= IsNTPath(path
, &path
);
295 bool added_implied_device
= false;
296 if (!StartsWithDriveLetter(path
) && is_nt_path
) {
297 path
= base::string16(kNTDotPrefix
) + path
;
298 added_implied_device
= true;
301 DWORD size
= MAX_PATH
;
302 scoped_ptr
<wchar_t[]> long_path_buf(new wchar_t[size
]);
304 DWORD return_value
= ::GetLongPathName(path
.c_str(), long_path_buf
.get(),
306 while (return_value
>= size
) {
308 long_path_buf
.reset(new wchar_t[size
]);
309 return_value
= ::GetLongPathName(path
.c_str(), long_path_buf
.get(), size
);
312 DWORD last_error
= ::GetLastError();
313 if (0 == return_value
&& (ERROR_FILE_NOT_FOUND
== last_error
||
314 ERROR_PATH_NOT_FOUND
== last_error
||
315 ERROR_INVALID_NAME
== last_error
)) {
316 // The file does not exist, but maybe a sub path needs to be expanded.
317 base::string16::size_type last_slash
= path
.rfind(L
'\\');
318 if (base::string16::npos
== last_slash
)
321 base::string16 begin
= path
.substr(0, last_slash
);
322 base::string16 end
= path
.substr(last_slash
);
323 if (!ConvertToLongPath(begin
, &begin
))
326 // Ok, it worked. Let's reset the return value.
329 } else if (0 != return_value
) {
330 path
= long_path_buf
.get();
333 if (return_value
!= 0) {
334 if (added_implied_device
)
335 RemoveImpliedDevice(&path
);
338 *long_path
= kNTPrefix
;
350 bool GetPathFromHandle(HANDLE handle
, base::string16
* path
) {
351 NtQueryObjectFunction NtQueryObject
= NULL
;
352 ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject
);
354 OBJECT_NAME_INFORMATION initial_buffer
;
355 OBJECT_NAME_INFORMATION
* name
= &initial_buffer
;
356 ULONG size
= sizeof(initial_buffer
);
357 // Query the name information a first time to get the size of the name.
358 NTSTATUS status
= NtQueryObject(handle
, ObjectNameInformation
, name
, size
,
361 scoped_ptr
<OBJECT_NAME_INFORMATION
> name_ptr
;
363 name
= reinterpret_cast<OBJECT_NAME_INFORMATION
*>(new BYTE
[size
]);
364 name_ptr
.reset(name
);
366 // Query the name information a second time to get the name of the
367 // object referenced by the handle.
368 status
= NtQueryObject(handle
, ObjectNameInformation
, name
, size
, &size
);
371 if (STATUS_SUCCESS
!= status
)
374 path
->assign(name
->ObjectName
.Buffer
, name
->ObjectName
.Length
/
375 sizeof(name
->ObjectName
.Buffer
[0]));
379 bool GetNtPathFromWin32Path(const base::string16
& path
,
380 base::string16
* nt_path
) {
381 HANDLE file
= ::CreateFileW(path
.c_str(), 0,
382 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
, NULL
,
383 OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
384 if (file
== INVALID_HANDLE_VALUE
)
386 bool rv
= GetPathFromHandle(file
, nt_path
);
391 bool WriteProtectedChildMemory(HANDLE child_process
, void* address
,
392 const void* buffer
, size_t length
) {
393 // First, remove the protections.
394 DWORD old_protection
;
395 if (!::VirtualProtectEx(child_process
, address
, length
,
396 PAGE_WRITECOPY
, &old_protection
))
400 bool ok
= ::WriteProcessMemory(child_process
, address
, buffer
, length
,
401 &written
) && (length
== written
);
403 // Always attempt to restore the original protection.
404 if (!::VirtualProtectEx(child_process
, address
, length
,
405 old_protection
, &old_protection
))
411 }; // namespace sandbox
413 void ResolveNTFunctionPtr(const char* name
, void* ptr
) {
414 static volatile HMODULE ntdll
= NULL
;
417 HMODULE ntdll_local
= ::GetModuleHandle(sandbox::kNtdllName
);
418 // Use PEImage to sanity-check that we have a valid ntdll handle.
419 base::win::PEImage
ntdll_peimage(ntdll_local
);
420 CHECK_NT(ntdll_peimage
.VerifyMagic());
421 // Race-safe way to set static ntdll.
422 ::InterlockedCompareExchangePointer(
423 reinterpret_cast<PVOID
volatile*>(&ntdll
), ntdll_local
, NULL
);
428 FARPROC
* function_ptr
= reinterpret_cast<FARPROC
*>(ptr
);
429 *function_ptr
= ::GetProcAddress(ntdll
, name
);
430 CHECK_NT(*function_ptr
);