chrome_create_file_unittest.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1// Copyright 2014 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.
4
5#include "chrome_elf/create_file/chrome_create_file.h"
6
7#include <windows.h>
8
9#include <bitset>
10#include <string>
11
12#include "base/base_paths_win.h"
13#include "base/file_util.h"
14#include "base/files/file_path.h"
15#include "base/files/scoped_temp_dir.h"
16#include "base/path_service.h"
17#include "base/threading/platform_thread.h"
18#include "base/win/iat_patch_function.h"
19#include "base/win/scoped_handle.h"
20#include "base/win/windows_version.h"
21#include "chrome_elf/chrome_elf_constants.h"
22#include "chrome_elf/ntdll_cache.h"
23#include "sandbox/win/src/nt_internals.h"
24#include "testing/gtest/include/gtest/gtest.h"
25#include "testing/platform_test.h"
26
27
28namespace {
29
30// Test fixtures -------------------------------------------------------------
31
32class ChromeCreateFileTest : public PlatformTest {
33 protected:
34  struct NtCreateFileParams {
35    ACCESS_MASK desired_access;
36    OBJECT_ATTRIBUTES object_attributes;
37    PLARGE_INTEGER allocation_size;
38    ULONG file_attributes;
39    ULONG share_access;
40    ULONG create_disposition;
41    ULONG create_options;
42    PVOID ea_buffer;
43    ULONG ea_length;
44  };
45
46  enum CallPath {
47    ELF,
48    KERNEL
49  };
50
51  template<CallPath path>
52  static NTSTATUS WINAPI FakeNtCreateFile(
53      PHANDLE file_handle,
54      ACCESS_MASK desired_access,
55      POBJECT_ATTRIBUTES object_attributes,
56      PIO_STATUS_BLOCK io_status_block,
57      PLARGE_INTEGER allocation_size,
58      ULONG file_attributes,
59      ULONG share_access,
60      ULONG create_disposition,
61      ULONG create_options,
62      PVOID ea_buffer,
63      ULONG ea_length) {
64    return self_->HandleCreateFileCall(file_handle,
65        desired_access,
66        object_attributes,
67        io_status_block,
68        allocation_size,
69        file_attributes,
70        share_access,
71        create_disposition,
72        create_options,
73        ea_buffer,
74        ea_length,
75        path);
76  }
77
78  virtual void SetUp() OVERRIDE {
79    original_thread_ = base::PlatformThread::CurrentId();
80    InitCache();
81    PlatformTest::SetUp();
82
83    base::FilePath user_data_dir;
84    PathService::Get(base::DIR_LOCAL_APP_DATA, &user_data_dir);
85    ASSERT_TRUE(temp_dir_.CreateUniqueTempDirUnderPath(user_data_dir));
86    ASSERT_TRUE(temp_dir2_.CreateUniqueTempDir());
87    self_ = this;
88  }
89
90  void RedirectNtCreateFileCalls() {
91    old_func_ptr_ =
92        reinterpret_cast<NtCreateFileFunction>(g_ntdll_lookup["NtCreateFile"]);
93
94    // KernelBase.dll only exists for Win7 and later, prior to that, kernel32
95    // imports from ntdll directly.
96    if (base::win::GetVersion() < base::win::VERSION_WIN7) {
97      patcher_.Patch(L"kernel32.dll", "ntdll.dll", "NtCreateFile",
98          reinterpret_cast<void(*)()>(&FakeNtCreateFile<KERNEL>));
99    } else {
100      patcher_.Patch(L"kernelbase.dll", "ntdll.dll", "NtCreateFile",
101          reinterpret_cast<void(*)()>(&FakeNtCreateFile<KERNEL>));
102    }
103
104    g_ntdll_lookup["NtCreateFile"] = reinterpret_cast<void(*)()>(
105        &ChromeCreateFileTest::FakeNtCreateFile<ELF>);
106  }
107
108  void ResetNtCreateFileCalls() {
109    g_ntdll_lookup["NtCreateFile"] = reinterpret_cast<void*>(old_func_ptr_);
110    patcher_.Unpatch();
111  }
112
113  NTSTATUS HandleCreateFileCall(PHANDLE file_handle,
114      ACCESS_MASK desired_access,
115      POBJECT_ATTRIBUTES object_attributes,
116      PIO_STATUS_BLOCK io_status_block,
117      PLARGE_INTEGER allocation_size,
118      ULONG file_attributes,
119      ULONG share_access,
120      ULONG create_disposition,
121      ULONG create_options,
122      PVOID ea_buffer,
123      ULONG ea_length,
124      CallPath call_path) {
125    if (original_thread_ == base::PlatformThread::CurrentId()) {
126      SetParams(desired_access,
127          object_attributes,
128          allocation_size,
129          file_attributes,
130          share_access,
131          create_disposition,
132          create_options,
133          ea_buffer,
134          ea_length,
135          call_path == ELF ? &elf_params_ : &kernel_params_);
136    }
137
138    // Forward the call to the real NTCreateFile.
139    return old_func_ptr_(file_handle,
140        desired_access,
141        object_attributes,
142        io_status_block,
143        allocation_size,
144        file_attributes,
145        share_access,
146        create_disposition,
147        create_options,
148        ea_buffer,
149        ea_length);
150  }
151
152  void SetParams(ACCESS_MASK desired_access,
153      POBJECT_ATTRIBUTES object_attributes,
154      PLARGE_INTEGER allocation_size,
155      ULONG file_attributes,
156      ULONG share_access,
157      ULONG create_disposition,
158      ULONG create_options,
159      PVOID ea_buffer,
160      ULONG ea_length,
161      NtCreateFileParams* params) {
162    params->desired_access = desired_access;
163    params->object_attributes.Length = object_attributes->Length;
164    params->object_attributes.ObjectName = object_attributes->ObjectName;
165    params->object_attributes.RootDirectory = object_attributes->RootDirectory;
166    params->object_attributes.Attributes = object_attributes->Attributes;
167    params->object_attributes.SecurityDescriptor =
168        object_attributes->SecurityDescriptor;
169    params->object_attributes.SecurityQualityOfService =
170        object_attributes->SecurityQualityOfService;
171    params->allocation_size = allocation_size;
172    params->file_attributes = file_attributes;
173    params->share_access = share_access;
174    params->create_disposition = create_disposition;
175    params->create_options = create_options;
176    params->ea_buffer = ea_buffer;
177    params->ea_length = ea_length;
178  }
179
180  void CheckParams() {
181    std::bitset<32> elf((int) elf_params_.desired_access);
182    std::bitset<32> ker((int) kernel_params_.desired_access);
183
184    EXPECT_EQ(kernel_params_.desired_access, elf_params_.desired_access)
185        << elf << "\n" << ker;
186    EXPECT_EQ(kernel_params_.object_attributes.Length,
187              elf_params_.object_attributes.Length);
188    EXPECT_EQ(kernel_params_.object_attributes.RootDirectory,
189              elf_params_.object_attributes.RootDirectory);
190    EXPECT_EQ(kernel_params_.object_attributes.Attributes,
191              elf_params_.object_attributes.Attributes);
192    EXPECT_EQ(kernel_params_.object_attributes.SecurityDescriptor,
193              elf_params_.object_attributes.SecurityDescriptor);
194    EXPECT_EQ(kernel_params_.allocation_size, elf_params_.allocation_size);
195    EXPECT_EQ(kernel_params_.file_attributes, elf_params_.file_attributes);
196    EXPECT_EQ(kernel_params_.share_access, elf_params_.share_access);
197    EXPECT_EQ(kernel_params_.create_disposition,
198              elf_params_.create_disposition);
199    EXPECT_EQ(kernel_params_.create_options, elf_params_.create_options);
200    EXPECT_EQ(kernel_params_.ea_buffer, elf_params_.ea_buffer);
201    EXPECT_EQ(kernel_params_.ea_length, elf_params_.ea_length);
202  }
203
204  void DoWriteCheck(const base::FilePath& path, DWORD flag, bool is_system) {
205    base::win::ScopedHandle file_handle;
206    const char kTestData[] = "0123456789";
207    int buffer_size = sizeof(kTestData) - 1;
208    DWORD bytes_written;
209
210    if (is_system) {
211      file_handle.Set(::CreateFileW(path.value().c_str(),
212                                    GENERIC_WRITE,
213                                    FILE_SHARE_READ,
214                                    NULL,
215                                    CREATE_ALWAYS,
216                                    FILE_ATTRIBUTE_NORMAL | flag,
217                                    NULL));
218    } else {
219      file_handle.Set(CreateFileNTDLL(path.value().c_str(),
220                                      GENERIC_WRITE,
221                                      FILE_SHARE_READ,
222                                      NULL,
223                                      CREATE_ALWAYS,
224                                      FILE_ATTRIBUTE_NORMAL | flag,
225                                      NULL));
226    }
227
228
229    EXPECT_FALSE(file_handle == INVALID_HANDLE_VALUE);
230    ::WriteFile(file_handle, kTestData, buffer_size, &bytes_written, NULL);
231    EXPECT_EQ(buffer_size, bytes_written);
232  }
233
234  void DoReadCheck(const base::FilePath& path, DWORD flag, bool is_system) {
235    base::win::ScopedHandle file_handle;
236    const char kTestData[] = "0123456789";
237    int buffer_size = sizeof(kTestData) - 1;
238    DWORD bytes_read;
239    char read_buffer[10];
240
241    if (is_system) {
242      file_handle.Set(::CreateFileW(path.value().c_str(),
243                                    GENERIC_READ,
244                                    0,
245                                    NULL,
246                                    OPEN_ALWAYS,
247                                    FILE_ATTRIBUTE_NORMAL | flag,
248                                    NULL));
249    } else {
250      file_handle.Set(CreateFileNTDLL(path.value().c_str(),
251                                      GENERIC_READ,
252                                      0,
253                                      NULL,
254                                      OPEN_ALWAYS,
255                                      FILE_ATTRIBUTE_NORMAL | flag,
256                                      NULL));
257    }
258
259    EXPECT_FALSE(file_handle == INVALID_HANDLE_VALUE);
260    ::ReadFile(file_handle, read_buffer, buffer_size, &bytes_read, NULL);
261    EXPECT_EQ(buffer_size, bytes_read);
262    EXPECT_EQ(0, memcmp(kTestData, read_buffer, bytes_read));
263  }
264
265  void RunChecks(DWORD flag, bool check_reads) {
266    // Make sure we can write to this file handle when called via the system.
267    base::FilePath junk_path_1 = temp_dir_.path().Append(L"junk_1.txt");
268    base::FilePath junk_path_2 = temp_dir_.path().Append(L"junk_2.txt");
269    DoWriteCheck(junk_path_1, flag, true);
270    DoWriteCheck(junk_path_2, flag, false);
271    CheckParams();
272
273    if (check_reads) {
274      // Make sure we can read from this file handle when called via the system.
275      DoReadCheck(junk_path_1, flag, true);
276      DoReadCheck(junk_path_2, flag, false);
277      CheckParams();
278    }
279    base::DeleteFile(junk_path_1, false);
280    base::DeleteFile(junk_path_2, false);
281
282  }
283
284  static ChromeCreateFileTest* self_;
285
286  NtCreateFileFunction old_func_ptr_;
287  base::ScopedTempDir temp_dir_;
288  base::ScopedTempDir temp_dir2_;
289  base::win::IATPatchFunction patcher_;
290  NtCreateFileParams kernel_params_;
291  NtCreateFileParams elf_params_;
292  base::PlatformThreadId original_thread_;
293};
294
295ChromeCreateFileTest* ChromeCreateFileTest::self_ = NULL;
296
297// Tests ---------------------------------------------------------------------
298TEST_F(ChromeCreateFileTest, CheckParams_FILE_ATTRIBUTE_NORMAL) {
299  RedirectNtCreateFileCalls();
300  RunChecks(FILE_ATTRIBUTE_NORMAL, true);
301  ResetNtCreateFileCalls();
302}
303
304TEST_F(ChromeCreateFileTest, CheckParams_FILE_FLAG_WRITE_THROUGH) {
305  RedirectNtCreateFileCalls();
306  RunChecks(FILE_FLAG_WRITE_THROUGH, true);
307  ResetNtCreateFileCalls();
308}
309
310TEST_F(ChromeCreateFileTest, CheckParams_FILE_FLAG_RANDOM_ACCESS) {
311  RedirectNtCreateFileCalls();
312  RunChecks(FILE_FLAG_RANDOM_ACCESS, true);
313  ResetNtCreateFileCalls();
314}
315
316TEST_F(ChromeCreateFileTest, CheckParams_FILE_FLAG_SEQUENTIAL_SCAN) {
317  RedirectNtCreateFileCalls();
318  RunChecks(FILE_FLAG_SEQUENTIAL_SCAN, true);
319  ResetNtCreateFileCalls();
320}
321
322TEST_F(ChromeCreateFileTest, CheckParams_FILE_FLAG_DELETE_ON_CLOSE) {
323  RedirectNtCreateFileCalls();
324  RunChecks(FILE_FLAG_DELETE_ON_CLOSE, false);
325  ResetNtCreateFileCalls();
326}
327
328TEST_F(ChromeCreateFileTest, CheckParams_FILE_FLAG_BACKUP_SEMANTICS) {
329  RedirectNtCreateFileCalls();
330  RunChecks(FILE_FLAG_BACKUP_SEMANTICS, true);
331  ResetNtCreateFileCalls();
332}
333
334TEST_F(ChromeCreateFileTest, CheckParams_FILE_FLAG_OPEN_REPARSE_POINT) {
335  RedirectNtCreateFileCalls();
336  RunChecks(FILE_FLAG_OPEN_REPARSE_POINT, true);
337  ResetNtCreateFileCalls();
338}
339
340TEST_F(ChromeCreateFileTest, CheckParams_FILE_FLAG_OPEN_NO_RECALL) {
341  RedirectNtCreateFileCalls();
342  RunChecks(FILE_FLAG_OPEN_NO_RECALL, true);
343  ResetNtCreateFileCalls();
344}
345
346TEST_F(ChromeCreateFileTest, CanaryTest) {
347  EXPECT_TRUE(IsCanary(L"C:\\Users\\user\\AppData\\Local\\Google\\Chrome SxS"));
348  EXPECT_FALSE(IsCanary(L"C:\\Users\\user\\AppData\\Local\\Google\\Chrome"));
349  EXPECT_FALSE(IsCanary(L"C:\\Users\\user\\AppData\\Local\\Chromium"));
350}
351
352TEST_F(ChromeCreateFileTest, BypassTest) {
353  std::wstring UNC_filepath_file(L"\\\\.\\some_file.txt");
354
355  base::FilePath local_path;
356  PathService::Get(base::DIR_LOCAL_APP_DATA, &local_path);
357
358  base::FilePath local_prefs_path = local_path.Append(kAppDataDirName).Append(
359      kUserDataDirName).Append(L"default\\Preferences");
360  base::FilePath local_state_path = local_path.Append(kAppDataDirName).Append(
361      kUserDataDirName).Append(L"ninja\\Local State");
362  base::FilePath local_junk_path = local_path.Append(kAppDataDirName).Append(
363      kUserDataDirName).Append(L"default\\Junk");
364
365  base::FilePath desktop_path;
366  PathService::Get(base::DIR_USER_DESKTOP, &desktop_path);
367  base::FilePath desktop_junk_path =
368      desktop_path.Append(L"Downloads\\junk.txt");
369  base::FilePath desktop_prefs_path =
370      desktop_path.Append(L"Downloads\\Preferences");
371
372  // Don't redirect UNC files.
373  EXPECT_FALSE(ShouldBypass(UNC_filepath_file.c_str()));
374
375  // Don't redirect if file is not in UserData directory.
376  EXPECT_FALSE(ShouldBypass(desktop_junk_path.value().c_str()));
377  EXPECT_FALSE(ShouldBypass(desktop_prefs_path.value().c_str()));
378
379  // Only redirect "Preferences" and "Local State" files.
380  EXPECT_TRUE(ShouldBypass(local_prefs_path.value().c_str()));
381  EXPECT_TRUE(ShouldBypass(local_state_path.value().c_str()));
382  EXPECT_FALSE(ShouldBypass(local_junk_path.value().c_str()));
383}
384
385TEST_F(ChromeCreateFileTest, NtCreateFileAddressCheck) {
386  HMODULE ntdll_handle = ::GetModuleHandle(L"ntdll.dll");
387  EXPECT_EQ(::GetProcAddress(ntdll_handle, "NtCreateFile"),
388            g_ntdll_lookup["NtCreateFile"]);
389}
390
391TEST_F(ChromeCreateFileTest, ReadWriteFromNtDll) {
392  base::FilePath file_name = temp_dir_.path().Append(L"some_file.txt");
393  DoWriteCheck(file_name, FILE_ATTRIBUTE_NORMAL, false);
394  DoReadCheck(file_name, FILE_ATTRIBUTE_NORMAL, false);
395}
396
397}  // namespace
398