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::Win8ResolverThunk> Win8ResolverTest;
62typedef ResolverThunkTest<sandbox::Wow64ResolverThunk> Wow64ResolverTest;
63typedef ResolverThunkTest<sandbox::Wow64W8ResolverThunk> Wow64W8ResolverTest;
64#endif
65
66const BYTE kJump32 = 0xE9;
67
68void CheckJump(void* source, void* target) {
69#pragma pack(push)
70#pragma pack(1)
71  struct Code {
72    BYTE jump;
73    ULONG delta;
74  };
75#pragma pack(pop)
76
77#if defined(_WIN64)
78  FAIL() << "Running 32-bit codepath";
79#else
80  Code* patched = reinterpret_cast<Code*>(source);
81  EXPECT_EQ(kJump32, patched->jump);
82
83  ULONG source_addr = bit_cast<ULONG>(source);
84  ULONG target_addr = bit_cast<ULONG>(target);
85  EXPECT_EQ(target_addr + 19 - source_addr, patched->delta);
86#endif
87}
88
89NTSTATUS PatchNtdllWithResolver(const char* function, bool relaxed,
90                                sandbox::ServiceResolverThunk* resolver) {
91  HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll");
92  EXPECT_TRUE(NULL != ntdll_base);
93
94  void* target = ::GetProcAddress(ntdll_base, function);
95  EXPECT_TRUE(NULL != target);
96  if (NULL == target)
97    return STATUS_UNSUCCESSFUL;
98
99  BYTE service[50];
100  memcpy(service, target, sizeof(service));
101
102  static_cast<WinXpResolverTest*>(resolver)->set_target(service);
103
104  // Any pointer will do as an interception_entry_point
105  void* function_entry = resolver;
106  size_t thunk_size = resolver->GetThunkSize();
107  scoped_ptr<char[]> thunk(new char[thunk_size]);
108  size_t used;
109
110  resolver->AllowLocalPatches();
111
112  NTSTATUS ret = resolver->Setup(ntdll_base, NULL, function, NULL,
113                                 function_entry, thunk.get(), thunk_size,
114                                 &used);
115  if (NT_SUCCESS(ret)) {
116    EXPECT_EQ(thunk_size, used);
117    EXPECT_NE(0, memcmp(service, target, sizeof(service)));
118    EXPECT_NE(kJump32, service[0]);
119
120    if (relaxed) {
121      // It's already patched, let's patch again, and simulate a direct patch.
122      service[0] = kJump32;
123      ret = resolver->Setup(ntdll_base, NULL, function, NULL, function_entry,
124                            thunk.get(), thunk_size, &used);
125      CheckJump(service, thunk.get());
126    }
127  }
128
129  return ret;
130}
131
132sandbox::ServiceResolverThunk* GetTestResolver(bool relaxed) {
133#if defined(_WIN64)
134  return new WinXpResolverTest(relaxed);
135#else
136  base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
137  if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) {
138    if (os_info->version() >= base::win::VERSION_WIN8)
139      return new Wow64W8ResolverTest(relaxed);
140    return new Wow64ResolverTest(relaxed);
141  }
142
143  if (os_info->version() >= base::win::VERSION_WIN8)
144    return new Win8ResolverTest(relaxed);
145
146  return new WinXpResolverTest(relaxed);
147#endif
148}
149
150NTSTATUS PatchNtdll(const char* function, bool relaxed) {
151  sandbox::ServiceResolverThunk* resolver = GetTestResolver(relaxed);
152
153  NTSTATUS ret = PatchNtdllWithResolver(function, relaxed, resolver);
154  delete resolver;
155  return ret;
156}
157
158TEST(ServiceResolverTest, PatchesServices) {
159  NTSTATUS ret = PatchNtdll("NtClose", false);
160  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError();
161
162  ret = PatchNtdll("NtCreateFile", false);
163  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " <<
164    ::GetLastError();
165
166  ret = PatchNtdll("NtCreateMutant", false);
167  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " <<
168    ::GetLastError();
169
170  ret = PatchNtdll("NtMapViewOfSection", false);
171  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " <<
172    ::GetLastError();
173}
174
175TEST(ServiceResolverTest, FailsIfNotService) {
176#if !defined(_WIN64)
177  EXPECT_NE(STATUS_SUCCESS, PatchNtdll("RtlUlongByteSwap", false));
178#endif
179
180  EXPECT_NE(STATUS_SUCCESS, PatchNtdll("LdrLoadDll", false));
181}
182
183TEST(ServiceResolverTest, PatchesPatchedServices) {
184// We don't support "relaxed mode" for Win64 apps.
185#if !defined(_WIN64)
186  NTSTATUS ret = PatchNtdll("NtClose", true);
187  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError();
188
189  ret = PatchNtdll("NtCreateFile", true);
190  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " <<
191    ::GetLastError();
192
193  ret = PatchNtdll("NtCreateMutant", true);
194  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " <<
195    ::GetLastError();
196
197  ret = PatchNtdll("NtMapViewOfSection", true);
198  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " <<
199    ::GetLastError();
200#endif
201}
202
203TEST(ServiceResolverTest, MultiplePatchedServices) {
204// We don't support "relaxed mode" for Win64 apps.
205#if !defined(_WIN64)
206  sandbox::ServiceResolverThunk* resolver = GetTestResolver(true);
207  NTSTATUS ret = PatchNtdllWithResolver("NtClose", true, resolver);
208  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError();
209
210  ret = PatchNtdllWithResolver("NtCreateFile", true, resolver);
211  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " <<
212    ::GetLastError();
213
214  ret = PatchNtdllWithResolver("NtCreateMutant", true, resolver);
215  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " <<
216    ::GetLastError();
217
218  ret = PatchNtdllWithResolver("NtMapViewOfSection", true, resolver);
219  EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " <<
220    ::GetLastError();
221  delete resolver;
222#endif
223}
224
225TEST(ServiceResolverTest, LocalPatchesAllowed) {
226  sandbox::ServiceResolverThunk* resolver = GetTestResolver(true);
227
228  HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll");
229  ASSERT_TRUE(NULL != ntdll_base);
230
231  const char kFunctionName[] = "NtClose";
232
233  void* target = ::GetProcAddress(ntdll_base, kFunctionName);
234  ASSERT_TRUE(NULL != target);
235
236  BYTE service[50];
237  memcpy(service, target, sizeof(service));
238  static_cast<WinXpResolverTest*>(resolver)->set_target(service);
239
240  // Any pointer will do as an interception_entry_point
241  void* function_entry = resolver;
242  size_t thunk_size = resolver->GetThunkSize();
243  scoped_ptr<char[]> thunk(new char[thunk_size]);
244  size_t used;
245
246  NTSTATUS ret = STATUS_UNSUCCESSFUL;
247
248  // First try patching without having allowed local patches.
249  ret = resolver->Setup(ntdll_base, NULL, kFunctionName, NULL,
250                        function_entry, thunk.get(), thunk_size,
251                        &used);
252  EXPECT_FALSE(NT_SUCCESS(ret));
253
254  // Now allow local patches and check that things work.
255  resolver->AllowLocalPatches();
256  ret = resolver->Setup(ntdll_base, NULL, kFunctionName, NULL,
257                        function_entry, thunk.get(), thunk_size,
258                        &used);
259  EXPECT_EQ(STATUS_SUCCESS, ret);
260}
261
262}  // namespace
263