1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
|
// Copyright 2010 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file defines helper methods used to schedule files for deletion
// on next reboot. The code here is heavily borrowed and simplified from
// http://code.google.com/p/omaha/source/browse/trunk/common/file.cc and
// http://code.google.com/p/omaha/source/browse/trunk/common/utils.cc
//
// This implementation really is not fast, so do not use it where that will
// matter.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/installer/util/delete_after_reboot_helper.h"
#include <string>
#include <string_view>
#include <vector>
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/win/registry.h"
// The moves-pending-reboot is a MULTISZ registry key in the HKLM part of the
// registry.
const wchar_t kSessionManagerKey[] =
L"SYSTEM\\CurrentControlSet\\Control\\Session Manager";
const wchar_t kPendingFileRenameOps[] = L"PendingFileRenameOperations";
namespace {
// Returns true if this directory name is 'safe' for deletion (doesn't contain
// "..", doesn't specify a drive root)
bool IsSafeDirectoryNameForDeletion(const base::FilePath& dir_name) {
// empty name isn't allowed
if (dir_name.empty())
return false;
// require a character other than \/:. after the last :
// disallow anything with ".."
bool ok = false;
const wchar_t* dir_name_str = dir_name.value().c_str();
for (const wchar_t* s = dir_name_str; *s; ++s) {
if (*s != L'\\' && *s != L'/' && *s != L':' && *s != L'.')
ok = true;
if (*s == L'.' && s > dir_name_str && *(s - 1) == L'.')
return false;
if (*s == L':')
ok = false;
}
return ok;
}
} // end namespace
// Must only be called for regular files or directories that will be empty.
bool ScheduleFileSystemEntityForDeletion(const base::FilePath& path) {
// Check if the file exists, return false if not.
WIN32_FILE_ATTRIBUTE_DATA attrs = {0};
if (!::GetFileAttributesEx(path.value().c_str(), ::GetFileExInfoStandard,
&attrs)) {
PLOG(WARNING) << path.value() << " does not exist.";
return false;
}
DWORD flags = MOVEFILE_DELAY_UNTIL_REBOOT;
if (!base::DirectoryExists(path)) {
// This flag valid only for files
flags |= MOVEFILE_REPLACE_EXISTING;
}
if (!::MoveFileEx(path.value().c_str(), nullptr, flags)) {
PLOG(ERROR) << "Could not schedule " << path.value() << " for deletion.";
return false;
}
#ifndef NDEBUG
// Useful debugging code to track down what files are in use.
if (flags & MOVEFILE_REPLACE_EXISTING) {
// Attempt to open the file exclusively.
HANDLE file =
::CreateFileW(path.value().c_str(), GENERIC_READ | GENERIC_WRITE, 0,
nullptr, OPEN_EXISTING, 0, nullptr);
if (file != INVALID_HANDLE_VALUE) {
VLOG(1) << " file not in use: " << path.value();
::CloseHandle(file);
} else {
PLOG(WARNING) << " file in use (or not found?): " << path.value();
}
}
#endif
VLOG(1) << "Scheduled for deletion: " << path.value();
return true;
}
bool ScheduleDirectoryForDeletion(const base::FilePath& dir_name) {
if (!IsSafeDirectoryNameForDeletion(dir_name)) {
LOG(ERROR) << "Unsafe directory name for deletion: " << dir_name.value();
return false;
}
// Make sure the directory exists (it is ok if it doesn't)
DWORD dir_attributes = ::GetFileAttributes(dir_name.value().c_str());
if (dir_attributes == INVALID_FILE_ATTRIBUTES) {
if (::GetLastError() == ERROR_FILE_NOT_FOUND) {
return true; // Ok if directory is missing
} else {
PLOG(ERROR) << "Could not GetFileAttributes for " << dir_name.value();
return false;
}
}
// Confirm it is a directory
if (!(dir_attributes & FILE_ATTRIBUTE_DIRECTORY)) {
LOG(ERROR) << "Scheduled directory is not a directory: "
<< dir_name.value();
return false;
}
// First schedule all the normal files for deletion.
{
bool success = true;
base::FileEnumerator file_enum(dir_name, false,
base::FileEnumerator::FILES);
for (base::FilePath file = file_enum.Next(); !file.empty();
file = file_enum.Next()) {
success = ScheduleFileSystemEntityForDeletion(file);
if (!success) {
LOG(ERROR) << "Failed to schedule file for deletion: " << file.value();
return false;
}
}
}
// Then recurse to all the subdirectories.
{
bool success = true;
base::FileEnumerator dir_enum(dir_name, false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath sub_dir = dir_enum.Next(); !sub_dir.empty();
sub_dir = dir_enum.Next()) {
success = ScheduleDirectoryForDeletion(sub_dir);
if (!success) {
LOG(ERROR) << "Failed to schedule subdirectory for deletion: "
<< sub_dir.value();
return false;
}
}
}
// Now schedule the empty directory itself
if (!ScheduleFileSystemEntityForDeletion(dir_name)) {
LOG(ERROR) << "Failed to schedule directory for deletion: "
<< dir_name.value();
}
return true;
}
// Converts the strings found in |buffer| to a list of wstrings that is returned
// in |value|.
// |buffer| points to a series of pairs of null-terminated wchar_t strings
// followed by a terminating null character.
// |byte_count| is the length of |buffer| in bytes.
// |value| is a pointer to an empty vector of wstrings. On success, this vector
// contains all of the strings extracted from |buffer|.
// Returns S_OK on success, E_INVALIDARG if buffer does not meet tha above
// specification.
HRESULT MultiSZBytesToStringArray(const char* buffer,
size_t byte_count,
std::vector<PendingMove>* value) {
DCHECK(buffer);
DCHECK(value);
DCHECK(value->empty());
DWORD data_len = byte_count / sizeof(wchar_t);
const wchar_t* data = reinterpret_cast<const wchar_t*>(buffer);
const wchar_t* data_end = data + data_len;
if (data_len > 1) {
// must be terminated by two null characters
if (data[data_len - 1] != 0 || data[data_len - 2] != 0) {
DLOG(ERROR) << "Invalid MULTI_SZ found.";
return E_INVALIDARG;
}
// put null-terminated strings into arrays
while (data < data_end) {
std::wstring str_from(data);
data += str_from.length() + 1;
if (data < data_end) {
std::wstring str_to(data);
data += str_to.length() + 1;
value->push_back(std::make_pair(str_from, str_to));
}
}
}
return S_OK;
}
void StringArrayToMultiSZBytes(const std::vector<PendingMove>& strings,
std::vector<char>* buffer) {
DCHECK(buffer);
buffer->clear();
if (strings.empty()) {
// Leave buffer empty if we have no strings.
return;
}
size_t total_wchars = 0;
{
std::vector<PendingMove>::const_iterator iter(strings.begin());
for (; iter != strings.end(); ++iter) {
total_wchars += iter->first.length();
total_wchars++; // Space for the null char.
total_wchars += iter->second.length();
total_wchars++; // Space for the null char.
}
total_wchars++; // Space for the extra terminating null char.
}
size_t total_length = total_wchars * sizeof(wchar_t);
buffer->resize(total_length);
wchar_t* write_pointer = reinterpret_cast<wchar_t*>(&((*buffer)[0]));
// Keep an end pointer around for sanity checking.
wchar_t* end_pointer = write_pointer + total_wchars;
std::vector<PendingMove>::const_iterator copy_iter(strings.begin());
for (; copy_iter != strings.end() && write_pointer < end_pointer;
copy_iter++) {
// First copy the source string.
size_t string_length = copy_iter->first.length() + 1;
memcpy(write_pointer, copy_iter->first.c_str(),
string_length * sizeof(wchar_t));
write_pointer += string_length;
// Now copy the destination string.
string_length = copy_iter->second.length() + 1;
memcpy(write_pointer, copy_iter->second.c_str(),
string_length * sizeof(wchar_t));
write_pointer += string_length;
// We should never run off the end while in this loop.
DCHECK(write_pointer < end_pointer);
}
*write_pointer = L'\0'; // Explicitly set the final null char.
DCHECK(++write_pointer == end_pointer);
}
base::FilePath GetShortPathName(const base::FilePath& path) {
std::wstring short_path;
DWORD length = GetShortPathName(
path.value().c_str(), base::WriteInto(&short_path, MAX_PATH), MAX_PATH);
DPLOG_IF(WARNING, length == 0 && GetLastError() != ERROR_PATH_NOT_FOUND)
<< __func__;
if ((length == 0) || (length > MAX_PATH)) {
// GetShortPathName fails if the path is no longer present or cannot be
// put in the size buffer provided. Instead of returning an empty string,
// just return the original string. This will serve our purposes.
return path;
}
short_path.resize(length);
return base::FilePath(short_path);
}
HRESULT GetPendingMovesValue(std::vector<PendingMove>* pending_moves) {
DCHECK(pending_moves);
pending_moves->clear();
// Get the current value of the key
// If the Key is missing, that's totally acceptable.
base::win::RegKey session_manager_key(HKEY_LOCAL_MACHINE, kSessionManagerKey,
KEY_QUERY_VALUE);
HKEY session_manager_handle = session_manager_key.Handle();
if (!session_manager_handle)
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
// The base::RegKey Read code squashes the return code from
// ReqQueryValueEx, we have to do things ourselves:
DWORD buffer_size = 0;
std::vector<char> buffer;
buffer.resize(1);
DWORD type;
DWORD result =
RegQueryValueEx(session_manager_handle, kPendingFileRenameOps, 0, &type,
reinterpret_cast<BYTE*>(&buffer[0]), &buffer_size);
if (result == ERROR_FILE_NOT_FOUND) {
// No pending moves were found.
return HRESULT_FROM_WIN32(result);
}
if (result != ERROR_MORE_DATA) {
// That was unexpected.
DLOG(ERROR) << "Unexpected result from RegQueryValueEx: " << result;
return HRESULT_FROM_WIN32(result);
}
if (type != REG_MULTI_SZ) {
DLOG(ERROR) << "Found PendingRename value of unexpected type.";
return E_UNEXPECTED;
}
if (buffer_size % 2) {
// The buffer size should be an even number (since we expect wchar_ts).
// If this is not the case, fail here.
DLOG(ERROR) << "Corrupt PendingRename value.";
return E_UNEXPECTED;
}
// There are pending file renames. Read them in.
buffer.resize(buffer_size);
result =
RegQueryValueEx(session_manager_handle, kPendingFileRenameOps, 0, &type,
reinterpret_cast<LPBYTE>(&buffer[0]), &buffer_size);
if (result != ERROR_SUCCESS) {
DLOG(ERROR) << "Failed to read from " << kPendingFileRenameOps;
return HRESULT_FROM_WIN32(result);
}
// We now have a buffer of bytes that is actually a sequence of
// null-terminated wchar_t strings terminated by an additional null character.
// Stick this into a vector of strings for clarity.
HRESULT hr =
MultiSZBytesToStringArray(&buffer[0], buffer.size(), pending_moves);
return hr;
}
bool MatchPendingDeletePath(const base::FilePath& short_form_needle,
const base::FilePath& reg_path) {
// Stores the path stored in each entry.
std::wstring_view match_path(reg_path.value());
// Skip past the "*1" prefix, if present (allowing any number).
if (match_path.size() >= 2 && match_path[0] == L'*' &&
(match_path[1] >= L'0' && match_path[1] <= L'9')) {
match_path = match_path.substr(2);
}
// First chomp the prefix since that will mess up GetShortPathName.
static constexpr wchar_t kNtPrefix[] = L"\\??\\";
if (base::StartsWith(match_path, kNtPrefix, base::CompareCase::SENSITIVE)) {
match_path = match_path.substr(sizeof(kNtPrefix) / sizeof(wchar_t) - 1);
}
// Get the short path name of the entry.
base::FilePath short_match_path(GetShortPathName(base::FilePath(match_path)));
// Now compare the paths. It's a match if short_form_needle is a
// case-insensitive prefix of short_match_path.
if (short_match_path.value().size() < short_form_needle.value().size())
return false;
DWORD prefix_len =
base::saturated_cast<DWORD>(short_form_needle.value().size());
return ::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
short_match_path.value().data(), prefix_len,
short_form_needle.value().data(),
prefix_len) == CSTR_EQUAL;
}
// Removes all pending moves for the given |directory| and any contained
// files or subdirectories. Returns true on success
bool RemoveFromMovesPendingReboot(const base::FilePath& directory) {
std::vector<PendingMove> pending_moves;
HRESULT hr = GetPendingMovesValue(&pending_moves);
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
// No pending moves, nothing to do.
return true;
}
if (FAILED(hr)) {
// Couldn't read the key or the key was corrupt.
return false;
}
// Get the short form of |directory| and use that to match.
base::FilePath short_directory(GetShortPathName(directory));
std::vector<PendingMove> strings_to_keep;
for (std::vector<PendingMove>::const_iterator iter(pending_moves.begin());
iter != pending_moves.end(); ++iter) {
base::FilePath move_path(iter->first);
if (!MatchPendingDeletePath(short_directory, move_path)) {
// This doesn't match the deletions we are looking for. Preserve
// this string pair, making sure that it is in fact a pair.
strings_to_keep.push_back(*iter);
}
}
if (strings_to_keep.size() == pending_moves.size()) {
// Nothing to remove, return true.
return true;
}
// Write the key back into a buffer.
base::win::RegKey session_manager_key(HKEY_LOCAL_MACHINE, kSessionManagerKey,
KEY_CREATE_SUB_KEY | KEY_SET_VALUE);
if (!session_manager_key.Handle()) {
// Couldn't open / create the key.
LOG(ERROR) << "Failed to open session manager key for writing.";
return false;
}
if (strings_to_keep.size() <= 1) {
// We have only the trailing empty string. Don't bother writing that.
return (session_manager_key.DeleteValue(kPendingFileRenameOps) ==
ERROR_SUCCESS);
}
std::vector<char> buffer;
StringArrayToMultiSZBytes(strings_to_keep, &buffer);
DCHECK_GT(buffer.size(), 0U);
if (buffer.empty())
return false;
return (session_manager_key.WriteValue(kPendingFileRenameOps, &buffer[0],
buffer.size(),
REG_MULTI_SZ) == ERROR_SUCCESS);
}
|