sandbox::TouchMemory now doesn't destroy input buffer

Windows 10 RS4 (maybe older as well) x64 implementation of CopyFile expects
that the HANDLE passed to NtCreateFile is not modified upon error. Old
implementation of TouchMemory 'destroys' the buffer before passing it to
the broker. This can be fixed by first reading the original value and then
restoring back at the original place. This has to be done carefully so
optimizer won't remove the code.

R=wfh@chromium.org

Bug: 839775
Change-Id: Iff80f3a5f145b85da2f0ba500e74387fce4e0e4b
Reviewed-on: https://chromium-review.googlesource.com/1061460
Reviewed-by: Will Harris <wfh@chromium.org>
Commit-Queue: Will Harris <wfh@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#559279}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 9466f2db2d1d17d12ba472a57a3382bb20dc196e
diff --git a/win/src/file_policy_test.cc b/win/src/file_policy_test.cc
index 3eb1bb6..74ba62f 100644
--- a/win/src/file_policy_test.cc
+++ b/win/src/file_policy_test.cc
@@ -259,6 +259,21 @@
   return SBOX_TEST_FAILED;
 }
 
+// Tries to create a backup of calc.exe in system32 folder. This should fail
+// with ERROR_ACCESS_DENIED if everything is working as expected.
+SBOX_TESTS_COMMAND int File_CopyFile(int argc, wchar_t** argv) {
+  base::string16 calc_path = MakePathToSys(L"calc.exe", false);
+  base::string16 calc_backup_path = MakePathToSys(L"calc.exe.bak", false);
+
+  if (::CopyFile(calc_path.c_str(), calc_backup_path.c_str(), FALSE))
+    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
+
+  if (::GetLastError() != ERROR_ACCESS_DENIED)
+    return SBOX_TEST_FAILED;
+
+  return SBOX_TEST_SUCCEEDED;
+}
+
 TEST(FilePolicyTest, DenyNtCreateCalc) {
   TestRunner runner;
   EXPECT_TRUE(
@@ -664,4 +679,27 @@
   EXPECT_STREQ(result.c_str(), L"\\/?/?\\C:\\NAME");
 }
 
+TEST(FilePolicyTest, TestCopyFile) {
+  // Check if the test is running Win8 or newer since
+  // MITIGATION_STRICT_HANDLE_CHECKS is not supported on older systems.
+  if (base::win::GetVersion() < base::win::VERSION_WIN8)
+    return;
+
+  TestRunner runner;
+  runner.SetTimeout(2000);
+
+  // Allow read access to calc.exe, this should be on all Windows versions.
+  ASSERT_TRUE(
+      runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_READONLY, L"calc.exe"));
+
+  sandbox::TargetPolicy* policy = runner.GetPolicy();
+
+  // Set proper mitigation.
+  EXPECT_EQ(
+      policy->SetDelayedProcessMitigations(MITIGATION_STRICT_HANDLE_CHECKS),
+      SBOX_ALL_OK);
+
+  ASSERT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CopyFile"));
+}
+
 }  // namespace sandbox
diff --git a/win/src/sandbox_nt_util.cc b/win/src/sandbox_nt_util.cc
index 873fb73..171887f 100644
--- a/win/src/sandbox_nt_util.cc
+++ b/win/src/sandbox_nt_util.cc
@@ -190,14 +190,14 @@
 int TouchMemory(void* buffer, size_t size_bytes, RequiredAccess intent) {
   const int kPageSize = 4096;
   int dummy = 0;
-  char* start = reinterpret_cast<char*>(buffer);
-  char* end = start + size_bytes - 1;
+  volatile char* start = reinterpret_cast<char*>(buffer);
+  volatile char* end = start + size_bytes - 1;
 
   if (WRITE == intent) {
     for (; start < end; start += kPageSize) {
-      *start = 0;
+      *start = *start;
     }
-    *end = 0;
+    *end = *end;
   } else {
     for (; start < end; start += kPageSize) {
       dummy += *start;
diff --git a/win/src/sandbox_nt_util.h b/win/src/sandbox_nt_util.h
index 66e63f3..f90c635 100644
--- a/win/src/sandbox_nt_util.h
+++ b/win/src/sandbox_nt_util.h
@@ -100,8 +100,6 @@
 // Performs basic user mode buffer validation. In any case, buffers access must
 // be protected by SEH. intent specifies if the buffer should be tested for read
 // or write.
-// Note that write intent implies destruction of the buffer content (we actually
-// write)
 bool ValidParameter(void* buffer, size_t size, RequiredAccess intent);
 
 // Copies data from a user buffer to our buffer. Returns the operation status.