security/sandbox/win/src/win_utils.cc

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/security/sandbox/win/src/win_utils.cc	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,323 @@
     1.4 +// Copyright (c) 2011 The Chromium Authors. All rights reserved.
     1.5 +// Use of this source code is governed by a BSD-style license that can be
     1.6 +// found in the LICENSE file.
     1.7 +
     1.8 +#include "sandbox/win/src/win_utils.h"
     1.9 +
    1.10 +#include <map>
    1.11 +
    1.12 +#include "base/logging.h"
    1.13 +#include "base/memory/scoped_ptr.h"
    1.14 +#include "sandbox/win/src/internal_types.h"
    1.15 +#include "sandbox/win/src/nt_internals.h"
    1.16 +
    1.17 +namespace {
    1.18 +
    1.19 +// Holds the information about a known registry key.
    1.20 +struct KnownReservedKey {
    1.21 +  const wchar_t* name;
    1.22 +  HKEY key;
    1.23 +};
    1.24 +
    1.25 +// Contains all the known registry key by name and by handle.
    1.26 +const KnownReservedKey kKnownKey[] = {
    1.27 +    { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT },
    1.28 +    { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER },
    1.29 +    { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE},
    1.30 +    { L"HKEY_USERS", HKEY_USERS},
    1.31 +    { L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA},
    1.32 +    { L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT},
    1.33 +    { L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT},
    1.34 +    { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG},
    1.35 +    { L"HKEY_DYN_DATA", HKEY_DYN_DATA}
    1.36 +};
    1.37 +
    1.38 +// Returns true if the provided path points to a pipe.
    1.39 +bool IsPipe(const std::wstring& path) {
    1.40 +  size_t start = 0;
    1.41 +  if (0 == path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix))
    1.42 +    start = sandbox::kNTPrefixLen;
    1.43 +
    1.44 +  const wchar_t kPipe[] = L"pipe\\";
    1.45 +  return (0 == path.compare(start, arraysize(kPipe) - 1, kPipe));
    1.46 +}
    1.47 +
    1.48 +}  // namespace
    1.49 +
    1.50 +namespace sandbox {
    1.51 +
    1.52 +HKEY GetReservedKeyFromName(const std::wstring& name) {
    1.53 +  for (size_t i = 0; i < arraysize(kKnownKey); ++i) {
    1.54 +    if (name == kKnownKey[i].name)
    1.55 +      return kKnownKey[i].key;
    1.56 +  }
    1.57 +
    1.58 +  return NULL;
    1.59 +}
    1.60 +
    1.61 +bool ResolveRegistryName(std::wstring name, std::wstring* resolved_name) {
    1.62 +  for (size_t i = 0; i < arraysize(kKnownKey); ++i) {
    1.63 +    if (name.find(kKnownKey[i].name) == 0) {
    1.64 +      HKEY key;
    1.65 +      DWORD disposition;
    1.66 +      if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, NULL, 0,
    1.67 +                                            MAXIMUM_ALLOWED, NULL, &key,
    1.68 +                                            &disposition))
    1.69 +        return false;
    1.70 +
    1.71 +      bool result = GetPathFromHandle(key, resolved_name);
    1.72 +      ::RegCloseKey(key);
    1.73 +
    1.74 +      if (!result)
    1.75 +        return false;
    1.76 +
    1.77 +      *resolved_name += name.substr(wcslen(kKnownKey[i].name));
    1.78 +      return true;
    1.79 +    }
    1.80 +  }
    1.81 +
    1.82 +  return false;
    1.83 +}
    1.84 +
    1.85 +DWORD IsReparsePoint(const std::wstring& full_path, bool* result) {
    1.86 +  std::wstring path = full_path;
    1.87 +
    1.88 +  // Remove the nt prefix.
    1.89 +  if (0 == path.compare(0, kNTPrefixLen, kNTPrefix))
    1.90 +    path = path.substr(kNTPrefixLen);
    1.91 +
    1.92 +  // Check if it's a pipe. We can't query the attributes of a pipe.
    1.93 +  if (IsPipe(path)) {
    1.94 +    *result = FALSE;
    1.95 +    return ERROR_SUCCESS;
    1.96 +  }
    1.97 +
    1.98 +  std::wstring::size_type last_pos = std::wstring::npos;
    1.99 +
   1.100 +  do {
   1.101 +    path = path.substr(0, last_pos);
   1.102 +
   1.103 +    DWORD attributes = ::GetFileAttributes(path.c_str());
   1.104 +    if (INVALID_FILE_ATTRIBUTES == attributes) {
   1.105 +      DWORD error = ::GetLastError();
   1.106 +      if (error != ERROR_FILE_NOT_FOUND &&
   1.107 +          error != ERROR_PATH_NOT_FOUND &&
   1.108 +          error != ERROR_INVALID_NAME) {
   1.109 +        // Unexpected error.
   1.110 +        NOTREACHED();
   1.111 +        return error;
   1.112 +      }
   1.113 +    } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) {
   1.114 +      // This is a reparse point.
   1.115 +      *result = true;
   1.116 +      return ERROR_SUCCESS;
   1.117 +    }
   1.118 +
   1.119 +    last_pos = path.rfind(L'\\');
   1.120 +  } while (last_pos != std::wstring::npos);
   1.121 +
   1.122 +  *result = false;
   1.123 +  return ERROR_SUCCESS;
   1.124 +}
   1.125 +
   1.126 +// We get a |full_path| of the form \??\c:\some\foo\bar, and the name that
   1.127 +// we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar.
   1.128 +bool SameObject(HANDLE handle, const wchar_t* full_path) {
   1.129 +  std::wstring path(full_path);
   1.130 +  DCHECK(!path.empty());
   1.131 +
   1.132 +  // Check if it's a pipe.
   1.133 +  if (IsPipe(path))
   1.134 +    return true;
   1.135 +
   1.136 +  std::wstring actual_path;
   1.137 +  if (!GetPathFromHandle(handle, &actual_path))
   1.138 +    return false;
   1.139 +
   1.140 +  // This may end with a backslash.
   1.141 +  const wchar_t kBackslash = '\\';
   1.142 +  if (path[path.length() - 1] == kBackslash)
   1.143 +    path = path.substr(0, path.length() - 1);
   1.144 +
   1.145 +  // Perfect match (case-insesitive check).
   1.146 +  if (0 == _wcsicmp(actual_path.c_str(), path.c_str()))
   1.147 +    return true;
   1.148 +
   1.149 +  // Look for the drive letter.
   1.150 +  size_t colon_pos = path.find(L':');
   1.151 +  if (colon_pos == 0 || colon_pos == std::wstring::npos)
   1.152 +    return false;
   1.153 +
   1.154 +  // Only one character for the drive.
   1.155 +  if (colon_pos > 1 && path[colon_pos - 2] != kBackslash)
   1.156 +    return false;
   1.157 +
   1.158 +  // We only need 3 chars, but let's alloc a buffer for four.
   1.159 +  wchar_t drive[4] = {0};
   1.160 +  wchar_t vol_name[MAX_PATH];
   1.161 +  memcpy(drive, &path[colon_pos - 1], 2 * sizeof(*drive));
   1.162 +
   1.163 +  // We'll get a double null terminated string.
   1.164 +  DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH);
   1.165 +  if (vol_length < 2 || vol_length == MAX_PATH)
   1.166 +    return false;
   1.167 +
   1.168 +  // Ignore the nulls at the end.
   1.169 +  vol_length = static_cast<DWORD>(wcslen(vol_name));
   1.170 +
   1.171 +  // The two paths should be the same length.
   1.172 +  if (vol_length + path.size() - (colon_pos + 1) != actual_path.size())
   1.173 +    return false;
   1.174 +
   1.175 +  // Check up to the drive letter.
   1.176 +  if (0 != _wcsnicmp(actual_path.c_str(), vol_name, vol_length))
   1.177 +    return false;
   1.178 +
   1.179 +  // Check the path after the drive letter.
   1.180 +  if (0 != _wcsicmp(&actual_path[vol_length], &path[colon_pos + 1]))
   1.181 +    return false;
   1.182 +
   1.183 +  return true;
   1.184 +}
   1.185 +
   1.186 +bool ConvertToLongPath(const std::wstring& short_path,
   1.187 +                       std::wstring* long_path) {
   1.188 +  // Check if the path is a NT path.
   1.189 +  bool is_nt_path = false;
   1.190 +  std::wstring path = short_path;
   1.191 +  if (0 == path.compare(0, kNTPrefixLen, kNTPrefix)) {
   1.192 +    path = path.substr(kNTPrefixLen);
   1.193 +    is_nt_path = true;
   1.194 +  }
   1.195 +
   1.196 +  DWORD size = MAX_PATH;
   1.197 +  scoped_ptr<wchar_t[]> long_path_buf(new wchar_t[size]);
   1.198 +
   1.199 +  DWORD return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(),
   1.200 +                                         size);
   1.201 +  while (return_value >= size) {
   1.202 +    size *= 2;
   1.203 +    long_path_buf.reset(new wchar_t[size]);
   1.204 +    return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), size);
   1.205 +  }
   1.206 +
   1.207 +  DWORD last_error = ::GetLastError();
   1.208 +  if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error ||
   1.209 +                            ERROR_PATH_NOT_FOUND == last_error ||
   1.210 +                            ERROR_INVALID_NAME == last_error)) {
   1.211 +    // The file does not exist, but maybe a sub path needs to be expanded.
   1.212 +    std::wstring::size_type last_slash = path.rfind(L'\\');
   1.213 +    if (std::wstring::npos == last_slash)
   1.214 +      return false;
   1.215 +
   1.216 +    std::wstring begin = path.substr(0, last_slash);
   1.217 +    std::wstring end = path.substr(last_slash);
   1.218 +    if (!ConvertToLongPath(begin, &begin))
   1.219 +      return false;
   1.220 +
   1.221 +    // Ok, it worked. Let's reset the return value.
   1.222 +    path = begin + end;
   1.223 +    return_value = 1;
   1.224 +  } else if (0 != return_value) {
   1.225 +    path = long_path_buf.get();
   1.226 +  }
   1.227 +
   1.228 +  if (return_value != 0) {
   1.229 +    if (is_nt_path) {
   1.230 +      *long_path = kNTPrefix;
   1.231 +      *long_path += path;
   1.232 +    } else {
   1.233 +      *long_path = path;
   1.234 +    }
   1.235 +
   1.236 +    return true;
   1.237 +  }
   1.238 +
   1.239 +  return false;
   1.240 +}
   1.241 +
   1.242 +bool GetPathFromHandle(HANDLE handle, std::wstring* path) {
   1.243 +  NtQueryObjectFunction NtQueryObject = NULL;
   1.244 +  ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject);
   1.245 +
   1.246 +  OBJECT_NAME_INFORMATION initial_buffer;
   1.247 +  OBJECT_NAME_INFORMATION* name = &initial_buffer;
   1.248 +  ULONG size = sizeof(initial_buffer);
   1.249 +  // Query the name information a first time to get the size of the name.
   1.250 +  NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size,
   1.251 +                                  &size);
   1.252 +
   1.253 +  scoped_ptr<OBJECT_NAME_INFORMATION> name_ptr;
   1.254 +  if (size) {
   1.255 +    name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(new BYTE[size]);
   1.256 +    name_ptr.reset(name);
   1.257 +
   1.258 +    // Query the name information a second time to get the name of the
   1.259 +    // object referenced by the handle.
   1.260 +    status = NtQueryObject(handle, ObjectNameInformation, name, size, &size);
   1.261 +  }
   1.262 +
   1.263 +  if (STATUS_SUCCESS != status)
   1.264 +    return false;
   1.265 +
   1.266 +  path->assign(name->ObjectName.Buffer, name->ObjectName.Length /
   1.267 +                                        sizeof(name->ObjectName.Buffer[0]));
   1.268 +  return true;
   1.269 +}
   1.270 +
   1.271 +bool GetNtPathFromWin32Path(const std::wstring& path, std::wstring* nt_path) {
   1.272 +  HANDLE file = ::CreateFileW(path.c_str(), 0,
   1.273 +    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
   1.274 +    OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
   1.275 +  if (file == INVALID_HANDLE_VALUE)
   1.276 +    return false;
   1.277 +  bool rv = GetPathFromHandle(file, nt_path);
   1.278 +  ::CloseHandle(file);
   1.279 +  return rv;
   1.280 +}
   1.281 +
   1.282 +bool WriteProtectedChildMemory(HANDLE child_process, void* address,
   1.283 +                               const void* buffer, size_t length) {
   1.284 +  // First, remove the protections.
   1.285 +  DWORD old_protection;
   1.286 +  if (!::VirtualProtectEx(child_process, address, length,
   1.287 +                          PAGE_WRITECOPY, &old_protection))
   1.288 +    return false;
   1.289 +
   1.290 +  SIZE_T written;
   1.291 +  bool ok = ::WriteProcessMemory(child_process, address, buffer, length,
   1.292 +                                 &written) && (length == written);
   1.293 +
   1.294 +  // Always attempt to restore the original protection.
   1.295 +  if (!::VirtualProtectEx(child_process, address, length,
   1.296 +                          old_protection, &old_protection))
   1.297 +    return false;
   1.298 +
   1.299 +  return ok;
   1.300 +}
   1.301 +
   1.302 +};  // namespace sandbox
   1.303 +
   1.304 +// TODO(jschuh): http://crbug.com/11789
   1.305 +// I'm guessing we have a race where some "security" software is messing
   1.306 +// with ntdll/imports underneath us. So, we retry a few times, and in the
   1.307 +// worst case we sleep briefly before a few more attempts. (Normally sleeping
   1.308 +// would be very bad, but it's better than crashing in this case.)
   1.309 +void ResolveNTFunctionPtr(const char* name, void* ptr) {
   1.310 +  const int max_tries = 5;
   1.311 +  const int sleep_threshold = 2;
   1.312 +
   1.313 +  static HMODULE ntdll = ::GetModuleHandle(sandbox::kNtdllName);
   1.314 +
   1.315 +  FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr);
   1.316 +  *function_ptr = ::GetProcAddress(ntdll, name);
   1.317 +
   1.318 +  for (int tries = 1; !(*function_ptr) && tries < max_tries; ++tries) {
   1.319 +    if (tries >= sleep_threshold)
   1.320 +      ::Sleep(1);
   1.321 +    ntdll = ::GetModuleHandle(sandbox::kNtdllName);
   1.322 +    *function_ptr = ::GetProcAddress(ntdll, name);
   1.323 +  }
   1.324 +
   1.325 +  CHECK(*function_ptr);
   1.326 +}

mercurial