1// Copyright (c) 2012 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// This file contains unit tests for ServiceResolverThunk.
6
7#include "base/basictypes.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/win/windows_version.h"
10#include "sandbox/win/src/resolver.h"
11#include "sandbox/win/src/sandbox_utils.h"
12#include "sandbox/win/src/service_resolver.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15namespace {
16
17// This is the concrete resolver used to perform service-call type functions
18// inside ntdll.dll.
19template<typename T>
20class ResolverThunkTest : public T {
21 public:
22  // The service resolver needs a child process to write to.
23  explicit ResolverThunkTest(bool relaxed)
24      : T(::GetCurrentProcess(), relaxed) {}
25
26  // Sets the interception target to the desired address.
27  void set_target(void* target) {
28    fake_target_ = target;
29  }
30
31 protected:
32  // Overrides Resolver::Init
33  virtual NTSTATUS Init(const void* target_module,
34                        const void* interceptor_module,
35                        const char* target_name,
36                        const char* interceptor_name,
37                        const void* interceptor_entry_point,
38                        void* thunk_storage,
39                        size_t storage_bytes) {
40    NTSTATUS ret = STATUS_SUCCESS;
41    ret = ResolverThunk::Init(target_module, interceptor_module, target_name,
42                              interceptor_name, interceptor_entry_point,
43                              thunk_storage, storage_bytes);
44    EXPECT_EQ(STATUS_SUCCESS, ret);
45
46    target_ = fake_target_;
47
48    return ret;
49  };
50
51 private:
52  // Holds the address of the fake target.
53  void* fake_target_;
54
55  DISALLOW_COPY_AND_ASSIGN(ResolverThunkTest);
56};
57
58typedef ResolverThunkTest<sandbox::ServiceResolverThunk> WinXpResolverTest;
59
60#if !defined(_WIN64)
61typedef ResolverThunkTest<sandbox::Win2kResolverThunk> Win2kResolverTest;
62typedef ResolverThunkTest<sandbox::Win8ResolverThunk> Win8ResolverTest;
63typedef ResolverThunkTest<sandbox::Wow64ResolverThunk> Wow64ResolverTest;
64typedef ResolverThunkTest<sandbox::Wow64W8ResolverThunk> Wow64W8ResolverTest;
65#endif
66
67const BYTE kJump32 = 0xE9;
68
69void CheckJump(void* source, void* target) {
70#pragma pack(push)
71#pragma pack(1)
72  struct Code {
73    BYTE jump;
74    ULONG delta;
75  };
76#pragma pack(pop)
77
78#if defined(_WIN64)
79  FAIL() << "Running 32-bit codepath";
80#else
81  Code* patched = reinterpret_cast<Code*>(source);
82  EXPECT_EQ(kJump32, patched->jump);
83
84  ULONG source_addr = bit_cast<ULONG>(source);
85  ULONG target_addr = bit_cast<ULONG>(target);
86  EXPECT_EQ(target_addr + 19 - source_addr, patched->delta);
87#endif
88}
89
90NTSTATUS PatchNtdllWithResolver(const char* function, bool relaxed,
91                                sandbox::ServiceResolverThunk* resolver) {
92  HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll");
93  EXPECT_TRUE(NULL != ntdll_base);
94
95  void* target = ::GetProcAddress(ntdll_base, function);
96  EXPECT_TRUE(NULL != target);
97  if (NULL == target)
98    return STATUS_UNSUCCESSFUL;
99
100  BYTE service[50];
101  memcpy(service, target, sizeof(service));
102
103  static_cast<WinXpResolverTest*>(resolver)->set_target(service);
104
105  // Any pointer will do as an interception_entry_point
106  void* function_entry = resolver;
107  size_t thunk_size = resolver->GetThunkSize();
108  scoped_ptr<char[]> thunk(new char[thunk_size]);
109  size_t used;
110
111  resolver->AllowLocalPatches();
112
113  NTSTATUS ret = resolver->Setup(ntdll_base, NULL, function, NULL,
114                                 function_entry, thunk.get(), thunk_size,
115                                 &used);
116  if (NT_SUCCESS(ret)) {
117    EXPECT_EQ(thunk_size, used);
118    EXPECT_NE(0, memcmp(service, target, sizeof(service)));
119    EXPECT_NE(kJump32, service[0]);
120
121    if (relaxed) {
122      // It's already patched, let's patch again, and simulate a direct patch.
123      service[0] = kJump32;
124      ret = resolver->Setup(ntdll_base, NULL, function, NULL, function_entry,
125                            thunk.get(), thunk_size, &used);
126      CheckJump(service, thunk.get());
127    }
128  }
129
130  return ret;
131}
132
133sandbox::ServiceResolverThunk* GetTestResolver(bool relaxed) {
134#if defined(_WIN64)
135  return new WinXpResolverTest(relaxed);
136#else
137  base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
138  if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) {
139    if (os_info->version() >= base::win::VERSION_WIN8)
140      return new Wow64W8ResolverTest(relaxed);
141    return new Wow64ResolverTest(relaxed);
142  }
143
144  if (!sandbox::IsXPSP2OrLater())
145    return new Win2kResolverTest(relaxed);
146
147  if (os_info->version() >= base::win::VERSION_WIN8)
148    return new Win8ResolverTest(relaxed);
149
150  return new WinXpResolverTest(relaxed);
151#endif
152}
153
154NTSTATUS PatchNtdll(const char* function, bool relaxed) {
155  sandbox::ServiceResolverThunk* resolver = GetTestResolver(relaxed);
156
157  NTSTATUS ret = PatchNtdllWithResolver(function, relaxed, resolver);
158  delete resolver;
159  return ret;
160}
161
162TEST(ServiceResolverTest, PatchesServices) {
163  NTSTATUS ret = PatchNtdll("NtClose", false);
164  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError();
165
166  ret = PatchNtdll("NtCreateFile", false);
167  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " <<
168    ::GetLastError();
169
170  ret = PatchNtdll("NtCreateMutant", false);
171  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " <<
172    ::GetLastError();
173
174  ret = PatchNtdll("NtMapViewOfSection", false);
175  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " <<
176    ::GetLastError();
177}
178
179TEST(ServiceResolverTest, FailsIfNotService) {
180#if !defined(_WIN64)
181  EXPECT_NE(STATUS_SUCCESS, PatchNtdll("RtlUlongByteSwap", false));
182#endif
183
184  EXPECT_NE(STATUS_SUCCESS, PatchNtdll("LdrLoadDll", false));
185}
186
187TEST(ServiceResolverTest, PatchesPatchedServices) {
188// We don't support "relaxed mode" for Win64 apps.
189#if !defined(_WIN64)
190  NTSTATUS ret = PatchNtdll("NtClose", true);
191  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError();
192
193  ret = PatchNtdll("NtCreateFile", true);
194  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " <<
195    ::GetLastError();
196
197  ret = PatchNtdll("NtCreateMutant", true);
198  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " <<
199    ::GetLastError();
200
201  ret = PatchNtdll("NtMapViewOfSection", true);
202  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " <<
203    ::GetLastError();
204#endif
205}
206
207TEST(ServiceResolverTest, MultiplePatchedServices) {
208// We don't support "relaxed mode" for Win64 apps.
209#if !defined(_WIN64)
210  sandbox::ServiceResolverThunk* resolver = GetTestResolver(true);
211  NTSTATUS ret = PatchNtdllWithResolver("NtClose", true, resolver);
212  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError();
213
214  ret = PatchNtdllWithResolver("NtCreateFile", true, resolver);
215  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " <<
216    ::GetLastError();
217
218  ret = PatchNtdllWithResolver("NtCreateMutant", true, resolver);
219  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " <<
220    ::GetLastError();
221
222  ret = PatchNtdllWithResolver("NtMapViewOfSection", true, resolver);
223  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " <<
224    ::GetLastError();
225  delete resolver;
226#endif
227}
228
229TEST(ServiceResolverTest, LocalPatchesAllowed) {
230  sandbox::ServiceResolverThunk* resolver = GetTestResolver(true);
231
232  HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll");
233  ASSERT_TRUE(NULL != ntdll_base);
234
235  const char kFunctionName[] = "NtClose";
236
237  void* target = ::GetProcAddress(ntdll_base, kFunctionName);
238  ASSERT_TRUE(NULL != target);
239
240  BYTE service[50];
241  memcpy(service, target, sizeof(service));
242  static_cast<WinXpResolverTest*>(resolver)->set_target(service);
243
244  // Any pointer will do as an interception_entry_point
245  void* function_entry = resolver;
246  size_t thunk_size = resolver->GetThunkSize();
247  scoped_ptr<char[]> thunk(new char[thunk_size]);
248  size_t used;
249
250  NTSTATUS ret = STATUS_UNSUCCESSFUL;
251
252  // First try patching without having allowed local patches.
253  ret = resolver->Setup(ntdll_base, NULL, kFunctionName, NULL,
254                        function_entry, thunk.get(), thunk_size,
255                        &used);
256  EXPECT_FALSE(NT_SUCCESS(ret));
257
258  // Now allow local patches and check that things work.
259  resolver->AllowLocalPatches();
260  ret = resolver->Setup(ntdll_base, NULL, kFunctionName, NULL,
261                        function_entry, thunk.get(), thunk_size,
262                        &used);
263  EXPECT_EQ(STATUS_SUCCESS, ret);
264}
265
266}  // namespace
267