1 // Copyright 2012 The Chromium Authors
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 "base/files/file_util.h"
6
7 #include <windows.h>
8
9 #include <io.h>
10 #include <psapi.h>
11 #include <shellapi.h>
12 #include <shlobj.h>
13 #include <stddef.h>
14 #include <stdint.h>
15 #include <time.h>
16 #include <winsock2.h>
17
18 #include <algorithm>
19 #include <limits>
20 #include <string>
21 #include <utility>
22 #include <vector>
23
24 #include "base/check.h"
25 #include "base/clang_profiling_buildflags.h"
26 #include "base/debug/alias.h"
27 #include "base/feature_list.h"
28 #include "base/features.h"
29 #include "base/files/file_enumerator.h"
30 #include "base/files/file_path.h"
31 #include "base/files/memory_mapped_file.h"
32 #include "base/functional/bind.h"
33 #include "base/functional/callback.h"
34 #include "base/guid.h"
35 #include "base/location.h"
36 #include "base/logging.h"
37 #include "base/numerics/safe_conversions.h"
38 #include "base/path_service.h"
39 #include "base/process/process_handle.h"
40 #include "base/rand_util.h"
41 #include "base/strings/strcat.h"
42 #include "base/strings/string_number_conversions.h"
43 #include "base/strings/string_piece.h"
44 #include "base/strings/string_util.h"
45 #include "base/strings/string_util_win.h"
46 #include "base/strings/utf_string_conversions.h"
47 #include "base/task/bind_post_task.h"
48 #include "base/task/sequenced_task_runner.h"
49 #include "base/task/thread_pool.h"
50 #include "base/threading/scoped_blocking_call.h"
51 #include "base/threading/scoped_thread_priority.h"
52 #include "base/time/time.h"
53 #include "base/win/scoped_handle.h"
54 #include "base/win/security_util.h"
55 #include "base/win/sid.h"
56 #include "base/win/windows_types.h"
57 #include "base/win/windows_version.h"
58
59 namespace base {
60
61 namespace {
62
63 int g_extra_allowed_path_for_no_execute = 0;
64
65 const DWORD kFileShareAll =
66 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
67 const wchar_t kDefaultTempDirPrefix[] = L"ChromiumTemp";
68
69 // Returns the Win32 last error code or ERROR_SUCCESS if the last error code is
70 // ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND. This is useful in cases where
71 // the absence of a file or path is a success condition (e.g., when attempting
72 // to delete an item in the filesystem).
ReturnLastErrorOrSuccessOnNotFound()73 DWORD ReturnLastErrorOrSuccessOnNotFound() {
74 const DWORD error_code = ::GetLastError();
75 return (error_code == ERROR_FILE_NOT_FOUND ||
76 error_code == ERROR_PATH_NOT_FOUND)
77 ? ERROR_SUCCESS
78 : error_code;
79 }
80
81 // Deletes all files and directories in a path.
82 // Returns ERROR_SUCCESS on success or the Windows error code corresponding to
83 // the first error encountered. ERROR_FILE_NOT_FOUND and ERROR_PATH_NOT_FOUND
84 // are considered success conditions, and are therefore never returned.
DeleteFileRecursive(const FilePath & path,const FilePath::StringType & pattern,bool recursive)85 DWORD DeleteFileRecursive(const FilePath& path,
86 const FilePath::StringType& pattern,
87 bool recursive) {
88 FileEnumerator traversal(path, false,
89 FileEnumerator::FILES | FileEnumerator::DIRECTORIES,
90 pattern);
91 DWORD result = ERROR_SUCCESS;
92 for (FilePath current = traversal.Next(); !current.empty();
93 current = traversal.Next()) {
94 // Try to clear the read-only bit if we find it.
95 FileEnumerator::FileInfo info = traversal.GetInfo();
96 if ((info.find_data().dwFileAttributes & FILE_ATTRIBUTE_READONLY) &&
97 (recursive || !info.IsDirectory())) {
98 ::SetFileAttributes(
99 current.value().c_str(),
100 info.find_data().dwFileAttributes & ~DWORD{FILE_ATTRIBUTE_READONLY});
101 }
102
103 DWORD this_result = ERROR_SUCCESS;
104 if (info.IsDirectory()) {
105 if (recursive) {
106 this_result = DeleteFileRecursive(current, pattern, true);
107 DCHECK_NE(static_cast<LONG>(this_result), ERROR_FILE_NOT_FOUND);
108 DCHECK_NE(static_cast<LONG>(this_result), ERROR_PATH_NOT_FOUND);
109 if (this_result == ERROR_SUCCESS &&
110 !::RemoveDirectory(current.value().c_str())) {
111 this_result = ReturnLastErrorOrSuccessOnNotFound();
112 }
113 }
114 } else if (!::DeleteFile(current.value().c_str())) {
115 this_result = ReturnLastErrorOrSuccessOnNotFound();
116 }
117 if (result == ERROR_SUCCESS)
118 result = this_result;
119 }
120 return result;
121 }
122
123 // Appends |mode_char| to |mode| before the optional character set encoding; see
124 // https://msdn.microsoft.com/library/yeby3zcb.aspx for details.
AppendModeCharacter(wchar_t mode_char,std::wstring * mode)125 void AppendModeCharacter(wchar_t mode_char, std::wstring* mode) {
126 size_t comma_pos = mode->find(L',');
127 mode->insert(comma_pos == std::wstring::npos ? mode->length() : comma_pos, 1,
128 mode_char);
129 }
130
DoCopyFile(const FilePath & from_path,const FilePath & to_path,bool fail_if_exists)131 bool DoCopyFile(const FilePath& from_path,
132 const FilePath& to_path,
133 bool fail_if_exists) {
134 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
135 if (from_path.ReferencesParent() || to_path.ReferencesParent())
136 return false;
137
138 // NOTE: I suspect we could support longer paths, but that would involve
139 // analyzing all our usage of files.
140 if (from_path.value().length() >= MAX_PATH ||
141 to_path.value().length() >= MAX_PATH) {
142 return false;
143 }
144
145 // Mitigate the issues caused by loading DLLs on a background thread
146 // (http://crbug/973868).
147 SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
148
149 // Unlike the posix implementation that copies the file manually and discards
150 // the ACL bits, CopyFile() copies the complete SECURITY_DESCRIPTOR and access
151 // bits, which is usually not what we want. We can't do much about the
152 // SECURITY_DESCRIPTOR but at least remove the read only bit.
153 const wchar_t* dest = to_path.value().c_str();
154 if (!::CopyFile(from_path.value().c_str(), dest, fail_if_exists)) {
155 // Copy failed.
156 return false;
157 }
158 DWORD attrs = GetFileAttributes(dest);
159 if (attrs == INVALID_FILE_ATTRIBUTES) {
160 return false;
161 }
162 if (attrs & FILE_ATTRIBUTE_READONLY) {
163 SetFileAttributes(dest, attrs & ~DWORD{FILE_ATTRIBUTE_READONLY});
164 }
165 return true;
166 }
167
DoCopyDirectory(const FilePath & from_path,const FilePath & to_path,bool recursive,bool fail_if_exists)168 bool DoCopyDirectory(const FilePath& from_path,
169 const FilePath& to_path,
170 bool recursive,
171 bool fail_if_exists) {
172 // NOTE(maruel): Previous version of this function used to call
173 // SHFileOperation(). This used to copy the file attributes and extended
174 // attributes, OLE structured storage, NTFS file system alternate data
175 // streams, SECURITY_DESCRIPTOR. In practice, this is not what we want, we
176 // want the containing directory to propagate its SECURITY_DESCRIPTOR.
177 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
178
179 // NOTE: I suspect we could support longer paths, but that would involve
180 // analyzing all our usage of files.
181 if (from_path.value().length() >= MAX_PATH ||
182 to_path.value().length() >= MAX_PATH) {
183 return false;
184 }
185
186 // This function does not properly handle destinations within the source.
187 FilePath real_to_path = to_path;
188 if (PathExists(real_to_path)) {
189 real_to_path = MakeAbsoluteFilePath(real_to_path);
190 if (real_to_path.empty())
191 return false;
192 } else {
193 real_to_path = MakeAbsoluteFilePath(real_to_path.DirName());
194 if (real_to_path.empty())
195 return false;
196 }
197 FilePath real_from_path = MakeAbsoluteFilePath(from_path);
198 if (real_from_path.empty())
199 return false;
200 if (real_to_path == real_from_path || real_from_path.IsParent(real_to_path))
201 return false;
202
203 int traverse_type = FileEnumerator::FILES;
204 if (recursive)
205 traverse_type |= FileEnumerator::DIRECTORIES;
206 FileEnumerator traversal(from_path, recursive, traverse_type);
207
208 if (!PathExists(from_path)) {
209 DLOG(ERROR) << "CopyDirectory() couldn't stat source directory: "
210 << from_path.value().c_str();
211 return false;
212 }
213 // TODO(maruel): This is not necessary anymore.
214 DCHECK(recursive || DirectoryExists(from_path));
215
216 FilePath current = from_path;
217 bool from_is_dir = DirectoryExists(from_path);
218 bool success = true;
219 FilePath from_path_base = from_path;
220 if (recursive && DirectoryExists(to_path)) {
221 // If the destination already exists and is a directory, then the
222 // top level of source needs to be copied.
223 from_path_base = from_path.DirName();
224 }
225
226 while (success && !current.empty()) {
227 // current is the source path, including from_path, so append
228 // the suffix after from_path to to_path to create the target_path.
229 FilePath target_path(to_path);
230 if (from_path_base != current) {
231 if (!from_path_base.AppendRelativePath(current, &target_path)) {
232 success = false;
233 break;
234 }
235 }
236
237 if (from_is_dir) {
238 if (!DirectoryExists(target_path) &&
239 !::CreateDirectory(target_path.value().c_str(), NULL)) {
240 DLOG(ERROR) << "CopyDirectory() couldn't create directory: "
241 << target_path.value().c_str();
242 success = false;
243 }
244 } else if (!DoCopyFile(current, target_path, fail_if_exists)) {
245 DLOG(ERROR) << "CopyDirectory() couldn't create file: "
246 << target_path.value().c_str();
247 success = false;
248 }
249
250 current = traversal.Next();
251 if (!current.empty())
252 from_is_dir = traversal.GetInfo().IsDirectory();
253 }
254
255 return success;
256 }
257
258 // Returns ERROR_SUCCESS on success, or a Windows error code on failure.
DoDeleteFile(const FilePath & path,bool recursive)259 DWORD DoDeleteFile(const FilePath& path, bool recursive) {
260 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
261
262 if (path.empty())
263 return ERROR_SUCCESS;
264
265 if (path.value().length() >= MAX_PATH)
266 return ERROR_BAD_PATHNAME;
267
268 // Handle any path with wildcards.
269 if (path.BaseName().value().find_first_of(FILE_PATH_LITERAL("*?")) !=
270 FilePath::StringType::npos) {
271 const DWORD error_code =
272 DeleteFileRecursive(path.DirName(), path.BaseName().value(), recursive);
273 DCHECK_NE(static_cast<LONG>(error_code), ERROR_FILE_NOT_FOUND);
274 DCHECK_NE(static_cast<LONG>(error_code), ERROR_PATH_NOT_FOUND);
275 return error_code;
276 }
277
278 // Report success if the file or path does not exist.
279 const DWORD attr = ::GetFileAttributes(path.value().c_str());
280 if (attr == INVALID_FILE_ATTRIBUTES)
281 return ReturnLastErrorOrSuccessOnNotFound();
282
283 // Clear the read-only bit if it is set.
284 if ((attr & FILE_ATTRIBUTE_READONLY) &&
285 !::SetFileAttributes(path.value().c_str(),
286 attr & ~DWORD{FILE_ATTRIBUTE_READONLY})) {
287 // It's possible for |path| to be gone now under a race with other deleters.
288 return ReturnLastErrorOrSuccessOnNotFound();
289 }
290
291 // Perform a simple delete on anything that isn't a directory.
292 if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) {
293 return ::DeleteFile(path.value().c_str())
294 ? ERROR_SUCCESS
295 : ReturnLastErrorOrSuccessOnNotFound();
296 }
297
298 if (recursive) {
299 const DWORD error_code =
300 DeleteFileRecursive(path, FILE_PATH_LITERAL("*"), true);
301 DCHECK_NE(static_cast<LONG>(error_code), ERROR_FILE_NOT_FOUND);
302 DCHECK_NE(static_cast<LONG>(error_code), ERROR_PATH_NOT_FOUND);
303 if (error_code != ERROR_SUCCESS)
304 return error_code;
305 }
306 return ::RemoveDirectory(path.value().c_str())
307 ? ERROR_SUCCESS
308 : ReturnLastErrorOrSuccessOnNotFound();
309 }
310
311 // Deletes the file/directory at |path| (recursively if |recursive| and |path|
312 // names a directory), returning true on success. Sets the Windows last-error
313 // code and returns false on failure.
DeleteFileOrSetLastError(const FilePath & path,bool recursive)314 bool DeleteFileOrSetLastError(const FilePath& path, bool recursive) {
315 const DWORD error = DoDeleteFile(path, recursive);
316 if (error == ERROR_SUCCESS)
317 return true;
318
319 ::SetLastError(error);
320 return false;
321 }
322
323 constexpr int kMaxDeleteAttempts = 9;
324
DeleteFileWithRetry(const FilePath & path,bool recursive,int attempt,OnceCallback<void (bool)> reply_callback)325 void DeleteFileWithRetry(const FilePath& path,
326 bool recursive,
327 int attempt,
328 OnceCallback<void(bool)> reply_callback) {
329 // Retry every 250ms for up to two seconds. These values were pulled out of
330 // thin air, and may be adjusted in the future based on the metrics collected.
331 static constexpr TimeDelta kDeleteFileRetryDelay = Milliseconds(250);
332
333 if (DeleteFileOrSetLastError(path, recursive)) {
334 // Consider introducing further retries until the item has been removed from
335 // the filesystem and its name is ready for reuse; see the comments in
336 // chrome/installer/mini_installer/delete_with_retry.cc for details.
337 if (!reply_callback.is_null())
338 std::move(reply_callback).Run(true);
339 return;
340 }
341
342 ++attempt;
343 DCHECK_LE(attempt, kMaxDeleteAttempts);
344 if (attempt == kMaxDeleteAttempts) {
345 if (!reply_callback.is_null())
346 std::move(reply_callback).Run(false);
347 return;
348 }
349
350 ThreadPool::PostDelayedTask(FROM_HERE,
351 {TaskPriority::BEST_EFFORT, MayBlock()},
352 BindOnce(&DeleteFileWithRetry, path, recursive,
353 attempt, std::move(reply_callback)),
354 kDeleteFileRetryDelay);
355 }
356
GetDeleteFileCallbackInternal(const FilePath & path,bool recursive,OnceCallback<void (bool)> reply_callback)357 OnceClosure GetDeleteFileCallbackInternal(
358 const FilePath& path,
359 bool recursive,
360 OnceCallback<void(bool)> reply_callback) {
361 OnceCallback<void(bool)> bound_callback;
362 if (!reply_callback.is_null()) {
363 bound_callback = BindPostTask(SequencedTaskRunner::GetCurrentDefault(),
364 std::move(reply_callback));
365 }
366 return BindOnce(&DeleteFileWithRetry, path, recursive, /*attempt=*/0,
367 std::move(bound_callback));
368 }
369
370 // This function verifies that no code is attempting to set an ACL on a file
371 // that is outside of 'safe' paths. A 'safe' path is defined as one that is
372 // within the user data dir, or the temporary directory. This is explicitly to
373 // prevent code from trying to pass a writeable handle to a file outside of
374 // these directories to an untrusted process. E.g. if some future code created a
375 // writeable handle to a file in c:\users\user\sensitive.dat, this DCHECK would
376 // hit. Setting an ACL on a file outside of these chrome-controlled directories
377 // might cause the browser or operating system to fail in unexpected ways.
IsPathSafeToSetAclOn(const FilePath & path)378 bool IsPathSafeToSetAclOn(const FilePath& path) {
379 #if BUILDFLAG(CLANG_PROFILING)
380 // Ignore .profraw profiling files, as they can occur anywhere, and only occur
381 // during testing.
382 if (path.Extension() == FILE_PATH_LITERAL(".profraw")) {
383 return true;
384 }
385 #endif // BUILDFLAG(CLANG_PROFILING)
386 std::vector<int> valid_path_keys({DIR_TEMP});
387 if (g_extra_allowed_path_for_no_execute) {
388 valid_path_keys.push_back(g_extra_allowed_path_for_no_execute);
389 }
390
391 // MakeLongFilePath is needed here because temp files can have an 8.3 path
392 // under certain conditions. See comments in base::MakeLongFilePath.
393 FilePath long_path = MakeLongFilePath(path);
394 DCHECK(!long_path.empty()) << "Cannot get long path for " << path;
395
396 std::vector<FilePath> valid_paths;
397 for (const auto path_key : valid_path_keys) {
398 FilePath valid_path;
399 if (!PathService::Get(path_key, &valid_path)) {
400 DLOG(FATAL) << "Cannot get path for pathservice key " << path_key;
401 continue;
402 }
403 valid_paths.push_back(valid_path);
404 }
405
406 // Admin users create temporary files in `GetSecureSystemTemp`, see
407 // `CreateNewTempDirectory` below.
408 FilePath secure_system_temp;
409 if (::IsUserAnAdmin() && GetSecureSystemTemp(&secure_system_temp)) {
410 valid_paths.push_back(secure_system_temp);
411 }
412
413 for (const auto& valid_path : valid_paths) {
414 // Temp files can sometimes have an 8.3 path. See comments in
415 // `MakeLongFilePath`.
416 FilePath full_path = MakeLongFilePath(valid_path);
417 DCHECK(!full_path.empty()) << "Cannot get long path for " << valid_path;
418 if (full_path.IsParent(long_path)) {
419 return true;
420 }
421 }
422
423 return false;
424 }
425
426 } // namespace
427
GetDeleteFileCallback(const FilePath & path,OnceCallback<void (bool)> reply_callback)428 OnceClosure GetDeleteFileCallback(const FilePath& path,
429 OnceCallback<void(bool)> reply_callback) {
430 return GetDeleteFileCallbackInternal(path, /*recursive=*/false,
431 std::move(reply_callback));
432 }
433
GetDeletePathRecursivelyCallback(const FilePath & path,OnceCallback<void (bool)> reply_callback)434 OnceClosure GetDeletePathRecursivelyCallback(
435 const FilePath& path,
436 OnceCallback<void(bool)> reply_callback) {
437 return GetDeleteFileCallbackInternal(path, /*recursive=*/true,
438 std::move(reply_callback));
439 }
440
MakeAbsoluteFilePath(const FilePath & input)441 FilePath MakeAbsoluteFilePath(const FilePath& input) {
442 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
443 wchar_t file_path[MAX_PATH];
444 if (!_wfullpath(file_path, input.value().c_str(), MAX_PATH))
445 return FilePath();
446 return FilePath(file_path);
447 }
448
DeleteFile(const FilePath & path)449 bool DeleteFile(const FilePath& path) {
450 return DeleteFileOrSetLastError(path, /*recursive=*/false);
451 }
452
DeletePathRecursively(const FilePath & path)453 bool DeletePathRecursively(const FilePath& path) {
454 return DeleteFileOrSetLastError(path, /*recursive=*/true);
455 }
456
DeleteFileAfterReboot(const FilePath & path)457 bool DeleteFileAfterReboot(const FilePath& path) {
458 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
459
460 if (path.value().length() >= MAX_PATH)
461 return false;
462
463 return ::MoveFileEx(path.value().c_str(), nullptr,
464 MOVEFILE_DELAY_UNTIL_REBOOT);
465 }
466
ReplaceFile(const FilePath & from_path,const FilePath & to_path,File::Error * error)467 bool ReplaceFile(const FilePath& from_path,
468 const FilePath& to_path,
469 File::Error* error) {
470 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
471
472 // Alias paths for investigation of shutdown hangs. crbug.com/1054164
473 FilePath::CharType from_path_str[MAX_PATH];
474 base::wcslcpy(from_path_str, from_path.value().c_str(),
475 std::size(from_path_str));
476 base::debug::Alias(from_path_str);
477 FilePath::CharType to_path_str[MAX_PATH];
478 base::wcslcpy(to_path_str, to_path.value().c_str(), std::size(to_path_str));
479 base::debug::Alias(to_path_str);
480
481 // Assume that |to_path| already exists and try the normal replace. This will
482 // fail with ERROR_FILE_NOT_FOUND if |to_path| does not exist. When writing to
483 // a network share, we may not be able to change the ACLs. Ignore ACL errors
484 // then (REPLACEFILE_IGNORE_MERGE_ERRORS).
485 if (::ReplaceFile(to_path.value().c_str(), from_path.value().c_str(), NULL,
486 REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL)) {
487 return true;
488 }
489
490 File::Error replace_error = File::OSErrorToFileError(GetLastError());
491
492 // Try a simple move next. It will only succeed when |to_path| doesn't already
493 // exist.
494 if (::MoveFile(from_path.value().c_str(), to_path.value().c_str()))
495 return true;
496
497 // In the case of FILE_ERROR_NOT_FOUND from ReplaceFile, it is likely that
498 // |to_path| does not exist. In this case, the more relevant error comes
499 // from the call to MoveFile.
500 if (error) {
501 *error = replace_error == File::FILE_ERROR_NOT_FOUND
502 ? File::GetLastFileError()
503 : replace_error;
504 }
505 return false;
506 }
507
CopyDirectory(const FilePath & from_path,const FilePath & to_path,bool recursive)508 bool CopyDirectory(const FilePath& from_path,
509 const FilePath& to_path,
510 bool recursive) {
511 return DoCopyDirectory(from_path, to_path, recursive, false);
512 }
513
CopyDirectoryExcl(const FilePath & from_path,const FilePath & to_path,bool recursive)514 bool CopyDirectoryExcl(const FilePath& from_path,
515 const FilePath& to_path,
516 bool recursive) {
517 return DoCopyDirectory(from_path, to_path, recursive, true);
518 }
519
PathExists(const FilePath & path)520 bool PathExists(const FilePath& path) {
521 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
522 return (GetFileAttributes(path.value().c_str()) != INVALID_FILE_ATTRIBUTES);
523 }
524
525 namespace {
526
PathHasAccess(const FilePath & path,DWORD dir_desired_access,DWORD file_desired_access)527 bool PathHasAccess(const FilePath& path,
528 DWORD dir_desired_access,
529 DWORD file_desired_access) {
530 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
531
532 const wchar_t* const path_str = path.value().c_str();
533 DWORD fileattr = GetFileAttributes(path_str);
534 if (fileattr == INVALID_FILE_ATTRIBUTES)
535 return false;
536
537 bool is_directory = fileattr & FILE_ATTRIBUTE_DIRECTORY;
538 DWORD desired_access =
539 is_directory ? dir_desired_access : file_desired_access;
540 DWORD flags_and_attrs =
541 is_directory ? FILE_FLAG_BACKUP_SEMANTICS : FILE_ATTRIBUTE_NORMAL;
542
543 win::ScopedHandle file(CreateFile(path_str, desired_access, kFileShareAll,
544 nullptr, OPEN_EXISTING, flags_and_attrs,
545 nullptr));
546
547 return file.is_valid();
548 }
549
550 } // namespace
551
PathIsReadable(const FilePath & path)552 bool PathIsReadable(const FilePath& path) {
553 return PathHasAccess(path, FILE_LIST_DIRECTORY, GENERIC_READ);
554 }
555
PathIsWritable(const FilePath & path)556 bool PathIsWritable(const FilePath& path) {
557 return PathHasAccess(path, FILE_ADD_FILE, GENERIC_WRITE);
558 }
559
DirectoryExists(const FilePath & path)560 bool DirectoryExists(const FilePath& path) {
561 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
562 DWORD fileattr = GetFileAttributes(path.value().c_str());
563 if (fileattr != INVALID_FILE_ATTRIBUTES)
564 return (fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0;
565 return false;
566 }
567
GetTempDir(FilePath * path)568 bool GetTempDir(FilePath* path) {
569 wchar_t temp_path[MAX_PATH + 1];
570 DWORD path_len = ::GetTempPath(MAX_PATH, temp_path);
571 if (path_len >= MAX_PATH || path_len <= 0)
572 return false;
573 // TODO(evanm): the old behavior of this function was to always strip the
574 // trailing slash. We duplicate this here, but it shouldn't be necessary
575 // when everyone is using the appropriate FilePath APIs.
576 *path = FilePath(temp_path).StripTrailingSeparators();
577 return true;
578 }
579
GetHomeDir()580 FilePath GetHomeDir() {
581 wchar_t result[MAX_PATH];
582 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROFILE, NULL, SHGFP_TYPE_CURRENT,
583 result)) &&
584 result[0]) {
585 return FilePath(result);
586 }
587
588 // Fall back to the temporary directory on failure.
589 FilePath temp;
590 if (GetTempDir(&temp))
591 return temp;
592
593 // Last resort.
594 return FilePath(FILE_PATH_LITERAL("C:\\"));
595 }
596
CreateAndOpenTemporaryFileInDir(const FilePath & dir,FilePath * temp_file)597 File CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) {
598 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
599
600 // Open the file with exclusive r/w/d access, and allow the caller to decide
601 // to mark it for deletion upon close after the fact.
602 constexpr uint32_t kFlags = File::FLAG_CREATE | File::FLAG_READ |
603 File::FLAG_WRITE | File::FLAG_WIN_EXCLUSIVE_READ |
604 File::FLAG_WIN_EXCLUSIVE_WRITE |
605 File::FLAG_CAN_DELETE_ON_CLOSE;
606
607 // Use GUID instead of ::GetTempFileName() to generate unique file names.
608 // "Due to the algorithm used to generate file names, GetTempFileName can
609 // perform poorly when creating a large number of files with the same prefix.
610 // In such cases, it is recommended that you construct unique file names based
611 // on GUIDs."
612 // https://msdn.microsoft.com/library/windows/desktop/aa364991.aspx
613
614 FilePath temp_name;
615 File file;
616
617 // Although it is nearly impossible to get a duplicate name with GUID, we
618 // still use a loop here in case it happens.
619 for (int i = 0; i < 100; ++i) {
620 temp_name = dir.Append(FormatTemporaryFileName(UTF8ToWide(GenerateGUID())));
621 file.Initialize(temp_name, kFlags);
622 if (file.IsValid())
623 break;
624 }
625
626 if (!file.IsValid()) {
627 DPLOG(WARNING) << "Failed to get temporary file name in " << dir.value();
628 return file;
629 }
630
631 wchar_t long_temp_name[MAX_PATH + 1];
632 const DWORD long_name_len =
633 GetLongPathName(temp_name.value().c_str(), long_temp_name, MAX_PATH);
634 if (long_name_len != 0 && long_name_len <= MAX_PATH) {
635 *temp_file =
636 FilePath(FilePath::StringPieceType(long_temp_name, long_name_len));
637 } else {
638 // GetLongPathName() failed, but we still have a temporary file.
639 *temp_file = std::move(temp_name);
640 }
641
642 return file;
643 }
644
CreateTemporaryFileInDir(const FilePath & dir,FilePath * temp_file)645 bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) {
646 return CreateAndOpenTemporaryFileInDir(dir, temp_file).IsValid();
647 }
648
FormatTemporaryFileName(FilePath::StringPieceType identifier)649 FilePath FormatTemporaryFileName(FilePath::StringPieceType identifier) {
650 return FilePath(StrCat({identifier, FILE_PATH_LITERAL(".tmp")}));
651 }
652
CreateAndOpenTemporaryStreamInDir(const FilePath & dir,FilePath * path)653 ScopedFILE CreateAndOpenTemporaryStreamInDir(const FilePath& dir,
654 FilePath* path) {
655 // Open file in binary mode, to avoid problems with fwrite. On Windows
656 // it replaces \n's with \r\n's, which may surprise you.
657 // Reference: http://msdn.microsoft.com/en-us/library/h9t88zwz(VS.71).aspx
658 return ScopedFILE(
659 FileToFILE(CreateAndOpenTemporaryFileInDir(dir, path), "wb+"));
660 }
661
CreateTemporaryDirInDir(const FilePath & base_dir,const FilePath::StringType & prefix,FilePath * new_dir)662 bool CreateTemporaryDirInDir(const FilePath& base_dir,
663 const FilePath::StringType& prefix,
664 FilePath* new_dir) {
665 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
666
667 FilePath path_to_create;
668
669 for (int count = 0; count < 50; ++count) {
670 // Try create a new temporary directory with random generated name. If
671 // the one exists, keep trying another path name until we reach some limit.
672 std::wstring new_dir_name;
673 new_dir_name.assign(prefix);
674 new_dir_name.append(AsWString(NumberToString16(GetCurrentProcId())));
675 new_dir_name.push_back('_');
676 new_dir_name.append(AsWString(
677 NumberToString16(RandInt(0, std::numeric_limits<int32_t>::max()))));
678
679 path_to_create = base_dir.Append(new_dir_name);
680 if (::CreateDirectory(path_to_create.value().c_str(), NULL)) {
681 *new_dir = path_to_create;
682 return true;
683 }
684 }
685
686 return false;
687 }
688
GetSecureSystemTemp(FilePath * temp)689 bool GetSecureSystemTemp(FilePath* temp) {
690 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
691
692 CHECK(temp);
693
694 for (const auto key : {DIR_WINDOWS, DIR_PROGRAM_FILES}) {
695 FilePath secure_system_temp;
696 if (!PathService::Get(key, &secure_system_temp)) {
697 continue;
698 }
699
700 if (key == DIR_WINDOWS) {
701 secure_system_temp = secure_system_temp.AppendASCII("SystemTemp");
702 }
703
704 if (PathExists(secure_system_temp) && PathIsWritable(secure_system_temp)) {
705 *temp = secure_system_temp;
706 return true;
707 }
708 }
709
710 return false;
711 }
712
713 // The directory is created under `GetSecureSystemTemp` for security reasons if
714 // the caller is admin to avoid attacks from lower privilege processes.
715 //
716 // If unable to create a dir under `GetSecureSystemTemp`, the dir is created
717 // under %TEMP%. The reasons for not being able to create a dir under
718 // `GetSecureSystemTemp` could be because `%systemroot%\SystemTemp` does not
719 // exist, or unable to resolve `DIR_WINDOWS` or `DIR_PROGRAM_FILES`, say due to
720 // registry redirection, or unable to create a directory due to
721 // `GetSecureSystemTemp` being read-only or having atypical ACLs.
CreateNewTempDirectory(const FilePath::StringType & prefix,FilePath * new_temp_path)722 bool CreateNewTempDirectory(const FilePath::StringType& prefix,
723 FilePath* new_temp_path) {
724 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
725
726 DCHECK(new_temp_path);
727
728 FilePath parent_dir;
729 if (::IsUserAnAdmin() && GetSecureSystemTemp(&parent_dir) &&
730 CreateTemporaryDirInDir(parent_dir,
731 prefix.empty() ? kDefaultTempDirPrefix : prefix,
732 new_temp_path)) {
733 return true;
734 }
735
736 if (!GetTempDir(&parent_dir))
737 return false;
738
739 return CreateTemporaryDirInDir(parent_dir, prefix, new_temp_path);
740 }
741
CreateDirectoryAndGetError(const FilePath & full_path,File::Error * error)742 bool CreateDirectoryAndGetError(const FilePath& full_path,
743 File::Error* error) {
744 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
745
746 // If the path exists, we've succeeded if it's a directory, failed otherwise.
747 const wchar_t* const full_path_str = full_path.value().c_str();
748 const DWORD fileattr = ::GetFileAttributes(full_path_str);
749 if (fileattr != INVALID_FILE_ATTRIBUTES) {
750 if ((fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
751 return true;
752 }
753 DLOG(WARNING) << "CreateDirectory(" << full_path_str << "), "
754 << "conflicts with existing file.";
755 if (error)
756 *error = File::FILE_ERROR_NOT_A_DIRECTORY;
757 ::SetLastError(ERROR_FILE_EXISTS);
758 return false;
759 }
760
761 // Invariant: Path does not exist as file or directory.
762
763 // Attempt to create the parent recursively. This will immediately return
764 // true if it already exists, otherwise will create all required parent
765 // directories starting with the highest-level missing parent.
766 FilePath parent_path(full_path.DirName());
767 if (parent_path.value() == full_path.value()) {
768 if (error)
769 *error = File::FILE_ERROR_NOT_FOUND;
770 ::SetLastError(ERROR_FILE_NOT_FOUND);
771 return false;
772 }
773 if (!CreateDirectoryAndGetError(parent_path, error)) {
774 DLOG(WARNING) << "Failed to create one of the parent directories.";
775 DCHECK(!error || *error != File::FILE_OK);
776 return false;
777 }
778
779 if (::CreateDirectory(full_path_str, NULL))
780 return true;
781
782 const DWORD error_code = ::GetLastError();
783 if (error_code == ERROR_ALREADY_EXISTS && DirectoryExists(full_path)) {
784 // This error code ERROR_ALREADY_EXISTS doesn't indicate whether we were
785 // racing with someone creating the same directory, or a file with the same
786 // path. If DirectoryExists() returns true, we lost the race to create the
787 // same directory.
788 return true;
789 }
790 if (error)
791 *error = File::OSErrorToFileError(error_code);
792 ::SetLastError(error_code);
793 DPLOG(WARNING) << "Failed to create directory " << full_path_str;
794 return false;
795 }
796
NormalizeFilePath(const FilePath & path,FilePath * real_path)797 bool NormalizeFilePath(const FilePath& path, FilePath* real_path) {
798 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
799 File file(path,
800 File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WIN_SHARE_DELETE);
801 if (!file.IsValid())
802 return false;
803
804 // The expansion of |path| into a full path may make it longer.
805 constexpr int kMaxPathLength = MAX_PATH + 10;
806 wchar_t native_file_path[kMaxPathLength];
807 // kMaxPathLength includes space for trailing '\0' so we subtract 1.
808 // Returned length, used_wchars, does not include trailing '\0'.
809 // Failure is indicated by returning 0 or >= kMaxPathLength.
810 DWORD used_wchars = ::GetFinalPathNameByHandle(
811 file.GetPlatformFile(), native_file_path, kMaxPathLength - 1,
812 FILE_NAME_NORMALIZED | VOLUME_NAME_NT);
813
814 if (used_wchars >= kMaxPathLength || used_wchars == 0)
815 return false;
816
817 // GetFinalPathNameByHandle() returns the \\?\ syntax for file names and
818 // existing code expects we return a path starting 'X:\' so we call
819 // DevicePathToDriveLetterPath rather than using VOLUME_NAME_DOS above.
820 return DevicePathToDriveLetterPath(
821 FilePath(FilePath::StringPieceType(native_file_path, used_wchars)),
822 real_path);
823 }
824
DevicePathToDriveLetterPath(const FilePath & nt_device_path,FilePath * out_drive_letter_path)825 bool DevicePathToDriveLetterPath(const FilePath& nt_device_path,
826 FilePath* out_drive_letter_path) {
827 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
828
829 // Get the mapping of drive letters to device paths.
830 const int kDriveMappingSize = 1024;
831 wchar_t drive_mapping[kDriveMappingSize] = {'\0'};
832 if (!::GetLogicalDriveStrings(kDriveMappingSize - 1, drive_mapping)) {
833 DLOG(ERROR) << "Failed to get drive mapping.";
834 return false;
835 }
836
837 // The drive mapping is a sequence of null terminated strings.
838 // The last string is empty.
839 wchar_t* drive_map_ptr = drive_mapping;
840 wchar_t device_path_as_string[MAX_PATH];
841 wchar_t drive[] = FILE_PATH_LITERAL(" :");
842
843 // For each string in the drive mapping, get the junction that links
844 // to it. If that junction is a prefix of |device_path|, then we
845 // know that |drive| is the real path prefix.
846 while (*drive_map_ptr) {
847 drive[0] = drive_map_ptr[0]; // Copy the drive letter.
848
849 if (QueryDosDevice(drive, device_path_as_string, MAX_PATH)) {
850 FilePath device_path(device_path_as_string);
851 if (device_path == nt_device_path ||
852 device_path.IsParent(nt_device_path)) {
853 *out_drive_letter_path =
854 FilePath(drive + nt_device_path.value().substr(
855 wcslen(device_path_as_string)));
856 return true;
857 }
858 }
859 // Move to the next drive letter string, which starts one
860 // increment after the '\0' that terminates the current string.
861 while (*drive_map_ptr++) {}
862 }
863
864 // No drive matched. The path does not start with a device junction
865 // that is mounted as a drive letter. This means there is no drive
866 // letter path to the volume that holds |device_path|, so fail.
867 return false;
868 }
869
MakeLongFilePath(const FilePath & input)870 FilePath MakeLongFilePath(const FilePath& input) {
871 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
872
873 DWORD path_long_len = ::GetLongPathName(input.value().c_str(), nullptr, 0);
874 if (path_long_len == 0UL)
875 return FilePath();
876
877 std::wstring path_long_str;
878 path_long_len = ::GetLongPathName(input.value().c_str(),
879 WriteInto(&path_long_str, path_long_len),
880 path_long_len);
881 if (path_long_len == 0UL)
882 return FilePath();
883
884 return FilePath(path_long_str);
885 }
886
CreateWinHardLink(const FilePath & to_file,const FilePath & from_file)887 bool CreateWinHardLink(const FilePath& to_file, const FilePath& from_file) {
888 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
889 return ::CreateHardLink(to_file.value().c_str(), from_file.value().c_str(),
890 nullptr);
891 }
892
893 // TODO(rkc): Work out if we want to handle NTFS junctions here or not, handle
894 // them if we do decide to.
IsLink(const FilePath & file_path)895 bool IsLink(const FilePath& file_path) {
896 return false;
897 }
898
GetFileInfo(const FilePath & file_path,File::Info * results)899 bool GetFileInfo(const FilePath& file_path, File::Info* results) {
900 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
901
902 WIN32_FILE_ATTRIBUTE_DATA attr;
903 if (!GetFileAttributesEx(file_path.value().c_str(), GetFileExInfoStandard,
904 &attr)) {
905 return false;
906 }
907
908 ULARGE_INTEGER size;
909 size.HighPart = attr.nFileSizeHigh;
910 size.LowPart = attr.nFileSizeLow;
911 // TODO(crbug.com/1333521): Change Info::size to uint64_t and eliminate this
912 // cast.
913 results->size = checked_cast<int64_t>(size.QuadPart);
914
915 results->is_directory =
916 (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
917 results->last_modified = Time::FromFileTime(attr.ftLastWriteTime);
918 results->last_accessed = Time::FromFileTime(attr.ftLastAccessTime);
919 results->creation_time = Time::FromFileTime(attr.ftCreationTime);
920
921 return true;
922 }
923
OpenFile(const FilePath & filename,const char * mode)924 FILE* OpenFile(const FilePath& filename, const char* mode) {
925 // 'N' is unconditionally added below, so be sure there is not one already
926 // present before a comma in |mode|.
927 DCHECK(
928 strchr(mode, 'N') == nullptr ||
929 (strchr(mode, ',') != nullptr && strchr(mode, 'N') > strchr(mode, ',')));
930 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
931 std::wstring w_mode = UTF8ToWide(mode);
932 AppendModeCharacter(L'N', &w_mode);
933 return _wfsopen(filename.value().c_str(), w_mode.c_str(), _SH_DENYNO);
934 }
935
FileToFILE(File file,const char * mode)936 FILE* FileToFILE(File file, const char* mode) {
937 DCHECK(!file.async());
938 if (!file.IsValid())
939 return NULL;
940 int fd =
941 _open_osfhandle(reinterpret_cast<intptr_t>(file.GetPlatformFile()), 0);
942 if (fd < 0)
943 return NULL;
944 file.TakePlatformFile();
945 FILE* stream = _fdopen(fd, mode);
946 if (!stream)
947 _close(fd);
948 return stream;
949 }
950
FILEToFile(FILE * file_stream)951 File FILEToFile(FILE* file_stream) {
952 if (!file_stream)
953 return File();
954
955 int fd = _fileno(file_stream);
956 DCHECK_GE(fd, 0);
957 intptr_t file_handle = _get_osfhandle(fd);
958 DCHECK_NE(file_handle, reinterpret_cast<intptr_t>(INVALID_HANDLE_VALUE));
959
960 HANDLE other_handle = nullptr;
961 if (!::DuplicateHandle(
962 /*hSourceProcessHandle=*/GetCurrentProcess(),
963 reinterpret_cast<HANDLE>(file_handle),
964 /*hTargetProcessHandle=*/GetCurrentProcess(), &other_handle,
965 /*dwDesiredAccess=*/0,
966 /*bInheritHandle=*/FALSE,
967 /*dwOptions=*/DUPLICATE_SAME_ACCESS)) {
968 return File(File::GetLastFileError());
969 }
970
971 return File(ScopedPlatformFile(other_handle));
972 }
973
ReadFile(const FilePath & filename,char * data,int max_size)974 int ReadFile(const FilePath& filename, char* data, int max_size) {
975 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
976 win::ScopedHandle file(CreateFile(filename.value().c_str(), GENERIC_READ,
977 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
978 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN,
979 NULL));
980 if (!file.is_valid() || max_size < 0)
981 return -1;
982
983 DWORD read;
984 if (::ReadFile(file.get(), data, static_cast<DWORD>(max_size), &read, NULL)) {
985 // TODO(crbug.com/1333521): Change to return some type with a uint64_t size
986 // and eliminate this cast.
987 return checked_cast<int>(read);
988 }
989
990 return -1;
991 }
992
WriteFile(const FilePath & filename,const char * data,int size)993 int WriteFile(const FilePath& filename, const char* data, int size) {
994 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
995 win::ScopedHandle file(CreateFile(filename.value().c_str(), GENERIC_WRITE, 0,
996 NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
997 NULL));
998 if (!file.is_valid() || size < 0) {
999 DPLOG(WARNING) << "WriteFile failed for path " << filename.value();
1000 return -1;
1001 }
1002
1003 DWORD written;
1004 BOOL result =
1005 ::WriteFile(file.get(), data, static_cast<DWORD>(size), &written, NULL);
1006 if (result && static_cast<int>(written) == size)
1007 return static_cast<int>(written);
1008
1009 if (!result) {
1010 // WriteFile failed.
1011 DPLOG(WARNING) << "writing file " << filename.value() << " failed";
1012 } else {
1013 // Didn't write all the bytes.
1014 DLOG(WARNING) << "wrote" << written << " bytes to " << filename.value()
1015 << " expected " << size;
1016 }
1017 return -1;
1018 }
1019
AppendToFile(const FilePath & filename,span<const uint8_t> data)1020 bool AppendToFile(const FilePath& filename, span<const uint8_t> data) {
1021 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
1022 win::ScopedHandle file(CreateFile(filename.value().c_str(), FILE_APPEND_DATA,
1023 0, nullptr, OPEN_EXISTING, 0, nullptr));
1024 if (!file.is_valid()) {
1025 VPLOG(1) << "CreateFile failed for path " << filename.value();
1026 return false;
1027 }
1028
1029 DWORD written;
1030 DWORD size = checked_cast<DWORD>(data.size());
1031 BOOL result = ::WriteFile(file.get(), data.data(), size, &written, nullptr);
1032 if (result && written == size)
1033 return true;
1034
1035 if (!result) {
1036 // WriteFile failed.
1037 VPLOG(1) << "Writing file " << filename.value() << " failed";
1038 } else {
1039 // Didn't write all the bytes.
1040 VPLOG(1) << "Only wrote " << written << " out of " << size << " byte(s) to "
1041 << filename.value();
1042 }
1043 return false;
1044 }
1045
AppendToFile(const FilePath & filename,StringPiece data)1046 bool AppendToFile(const FilePath& filename, StringPiece data) {
1047 return AppendToFile(filename, as_bytes(make_span(data)));
1048 }
1049
GetCurrentDirectory(FilePath * dir)1050 bool GetCurrentDirectory(FilePath* dir) {
1051 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
1052
1053 wchar_t system_buffer[MAX_PATH];
1054 system_buffer[0] = 0;
1055 DWORD len = ::GetCurrentDirectory(MAX_PATH, system_buffer);
1056 if (len == 0 || len > MAX_PATH)
1057 return false;
1058 // TODO(evanm): the old behavior of this function was to always strip the
1059 // trailing slash. We duplicate this here, but it shouldn't be necessary
1060 // when everyone is using the appropriate FilePath APIs.
1061 *dir = FilePath(FilePath::StringPieceType(system_buffer))
1062 .StripTrailingSeparators();
1063 return true;
1064 }
1065
SetCurrentDirectory(const FilePath & directory)1066 bool SetCurrentDirectory(const FilePath& directory) {
1067 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
1068 return ::SetCurrentDirectory(directory.value().c_str()) != 0;
1069 }
1070
GetMaximumPathComponentLength(const FilePath & path)1071 int GetMaximumPathComponentLength(const FilePath& path) {
1072 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
1073
1074 wchar_t volume_path[MAX_PATH];
1075 if (!GetVolumePathNameW(path.NormalizePathSeparators().value().c_str(),
1076 volume_path, std::size(volume_path))) {
1077 return -1;
1078 }
1079
1080 DWORD max_length = 0;
1081 if (!GetVolumeInformationW(volume_path, NULL, 0, NULL, &max_length, NULL,
1082 NULL, 0)) {
1083 return -1;
1084 }
1085
1086 // Length of |path| with path separator appended.
1087 size_t prefix = path.StripTrailingSeparators().value().size() + 1;
1088 // The whole path string must be shorter than MAX_PATH. That is, it must be
1089 // prefix + component_length < MAX_PATH (or equivalently, <= MAX_PATH - 1).
1090 int whole_path_limit = std::max(0, MAX_PATH - 1 - static_cast<int>(prefix));
1091 return std::min(whole_path_limit, static_cast<int>(max_length));
1092 }
1093
CopyFile(const FilePath & from_path,const FilePath & to_path)1094 bool CopyFile(const FilePath& from_path, const FilePath& to_path) {
1095 return DoCopyFile(from_path, to_path, false);
1096 }
1097
SetNonBlocking(int fd)1098 bool SetNonBlocking(int fd) {
1099 unsigned long nonblocking = 1;
1100 if (ioctlsocket(static_cast<SOCKET>(fd), static_cast<long>(FIONBIO),
1101 &nonblocking) == 0)
1102 return true;
1103 return false;
1104 }
1105
PreReadFile(const FilePath & file_path,bool is_executable,int64_t max_bytes)1106 bool PreReadFile(const FilePath& file_path,
1107 bool is_executable,
1108 int64_t max_bytes) {
1109 DCHECK_GE(max_bytes, 0);
1110
1111 if (max_bytes == 0) {
1112 // ::PrefetchVirtualMemory() fails when asked to read zero bytes.
1113 // base::MemoryMappedFile::Initialize() fails on an empty file.
1114 return true;
1115 }
1116
1117 // ::PrefetchVirtualMemory() fails if the file is opened with write access.
1118 MemoryMappedFile::Access access = is_executable
1119 ? MemoryMappedFile::READ_CODE_IMAGE
1120 : MemoryMappedFile::READ_ONLY;
1121 MemoryMappedFile mapped_file;
1122 if (!mapped_file.Initialize(file_path, access))
1123 return internal::PreReadFileSlow(file_path, max_bytes);
1124
1125 const ::SIZE_T length =
1126 std::min(base::saturated_cast<::SIZE_T>(max_bytes),
1127 base::saturated_cast<::SIZE_T>(mapped_file.length()));
1128 ::_WIN32_MEMORY_RANGE_ENTRY address_range = {mapped_file.data(), length};
1129 // Use ::PrefetchVirtualMemory(). This is better than a
1130 // simple data file read, more from a RAM perspective than CPU. This is
1131 // because reading the file as data results in double mapping to
1132 // Image/executable pages for all pages of code executed.
1133 if (!::PrefetchVirtualMemory(::GetCurrentProcess(),
1134 /*NumberOfEntries=*/1, &address_range,
1135 /*Flags=*/0)) {
1136 return internal::PreReadFileSlow(file_path, max_bytes);
1137 }
1138 return true;
1139 }
1140
PreventExecuteMapping(const FilePath & path)1141 bool PreventExecuteMapping(const FilePath& path) {
1142 if (!base::FeatureList::IsEnabled(
1143 features::kEnforceNoExecutableFileHandles)) {
1144 return true;
1145 }
1146
1147 bool is_path_safe = IsPathSafeToSetAclOn(path);
1148
1149 if (!is_path_safe) {
1150 // To mitigate the effect of past OS bugs where attackers are able to use
1151 // writeable handles to create malicious executable images which can be
1152 // later mapped into unsandboxed processes, file handles that permit writing
1153 // that are passed to untrusted processes, e.g. renderers, should be marked
1154 // with a deny execute ACE. This prevents re-opening the file for execute
1155 // later on.
1156 //
1157 // To accomplish this, code that needs to pass writable file handles to a
1158 // renderer should open the file with the flags added by
1159 // `AddFlagsForPassingToUntrustedProcess()` (explicitly
1160 // FLAG_WIN_NO_EXECUTE). This results in this PreventExecuteMapping being
1161 // called by base::File.
1162 //
1163 // However, simply using this universally on all files that are opened
1164 // writeable is also undesirable: things can and will randomly break if they
1165 // are marked no-exec (e.g. marking an exe that the user downloads as
1166 // no-exec will prevent the user from running it). There are also
1167 // performance implications of doing this for all files unnecessarily.
1168 //
1169 // Code that passes writable files to the renderer is also expected to
1170 // reference files in places like the user data dir (e.g. for the filesystem
1171 // API) or temp files. Any attempt to pass a writeable handle to a path
1172 // outside these areas is likely its own security issue as an untrusted
1173 // renderer process should never have write access to e.g. system files or
1174 // downloads.
1175 //
1176 // This check aims to catch misuse of
1177 // `AddFlagsForPassingToUntrustedProcess()` on paths outside these
1178 // locations. Any time it hits it is also likely that a handle to a
1179 // dangerous path is being passed to a renderer, which is inherently unsafe.
1180 //
1181 // If this check hits, please do not ignore it but consult security team.
1182 DLOG(FATAL) << "Unsafe to deny execute access to path : " << path;
1183
1184 return false;
1185 }
1186
1187 static constexpr wchar_t kEveryoneSid[] = L"WD";
1188 auto sids = win::Sid::FromSddlStringVector({kEveryoneSid});
1189
1190 // Remove executable access from the file. The API does not add a duplicate
1191 // ACE if it already exists.
1192 return win::DenyAccessToPath(path, *sids, FILE_EXECUTE, /*NO_INHERITANCE=*/0,
1193 /*recursive=*/false);
1194 }
1195
SetExtraNoExecuteAllowedPath(int path_key)1196 void SetExtraNoExecuteAllowedPath(int path_key) {
1197 DCHECK(!g_extra_allowed_path_for_no_execute ||
1198 g_extra_allowed_path_for_no_execute == path_key);
1199 g_extra_allowed_path_for_no_execute = path_key;
1200 base::FilePath valid_path;
1201 DCHECK(
1202 base::PathService::Get(g_extra_allowed_path_for_no_execute, &valid_path));
1203 }
1204
1205 // -----------------------------------------------------------------------------
1206
1207 namespace internal {
1208
MoveUnsafe(const FilePath & from_path,const FilePath & to_path)1209 bool MoveUnsafe(const FilePath& from_path, const FilePath& to_path) {
1210 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
1211
1212 // NOTE: I suspect we could support longer paths, but that would involve
1213 // analyzing all our usage of files.
1214 if (from_path.value().length() >= MAX_PATH ||
1215 to_path.value().length() >= MAX_PATH) {
1216 return false;
1217 }
1218 if (MoveFileEx(from_path.value().c_str(), to_path.value().c_str(),
1219 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0)
1220 return true;
1221
1222 // Keep the last error value from MoveFileEx around in case the below
1223 // fails.
1224 bool ret = false;
1225 DWORD last_error = ::GetLastError();
1226
1227 if (DirectoryExists(from_path)) {
1228 // MoveFileEx fails if moving directory across volumes. We will simulate
1229 // the move by using Copy and Delete. Ideally we could check whether
1230 // from_path and to_path are indeed in different volumes.
1231 ret = internal::CopyAndDeleteDirectory(from_path, to_path);
1232 }
1233
1234 if (!ret) {
1235 // Leave a clue about what went wrong so that it can be (at least) picked
1236 // up by a PLOG entry.
1237 ::SetLastError(last_error);
1238 }
1239
1240 return ret;
1241 }
1242
CopyAndDeleteDirectory(const FilePath & from_path,const FilePath & to_path)1243 bool CopyAndDeleteDirectory(const FilePath& from_path,
1244 const FilePath& to_path) {
1245 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
1246 if (CopyDirectory(from_path, to_path, true)) {
1247 if (DeletePathRecursively(from_path))
1248 return true;
1249
1250 // Like Move, this function is not transactional, so we just
1251 // leave the copied bits behind if deleting from_path fails.
1252 // If to_path exists previously then we have already overwritten
1253 // it by now, we don't get better off by deleting the new bits.
1254 }
1255 return false;
1256 }
1257
1258 } // namespace internal
1259 } // namespace base
1260