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#include "base/win/scoped_process_information.h"
6#include "base/win/windows_version.h"
7#include "sandbox/win/src/sandbox.h"
8#include "sandbox/win/src/sandbox_factory.h"
9#include "sandbox/win/src/sandbox_utils.h"
10#include "sandbox/win/src/target_services.h"
11#include "sandbox/win/tests/common/controller.h"
12#include "testing/gtest/include/gtest/gtest.h"
13
14namespace sandbox {
15
16#define BINDNTDLL(name) \
17    name ## Function name = reinterpret_cast<name ## Function>( \
18      ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name))
19
20// Reverts to self and verify that SetInformationToken was faked. Returns
21// SBOX_TEST_SUCCEEDED if faked and SBOX_TEST_FAILED if not faked.
22SBOX_TESTS_COMMAND int PolicyTargetTest_token(int argc, wchar_t **argv) {
23  HANDLE thread_token;
24  // Get the thread token, using impersonation.
25  if (!::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE |
26                             TOKEN_DUPLICATE, FALSE, &thread_token))
27    return ::GetLastError();
28
29  ::RevertToSelf();
30  ::CloseHandle(thread_token);
31
32  int ret = SBOX_TEST_FAILED;
33  if (::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE,
34                        FALSE, &thread_token)) {
35    ret = SBOX_TEST_SUCCEEDED;
36    ::CloseHandle(thread_token);
37  }
38  return ret;
39}
40
41// Stores the high privilege token on a static variable, change impersonation
42// again to that one and verify that we are not interfering anymore with
43// RevertToSelf.
44SBOX_TESTS_COMMAND int PolicyTargetTest_steal(int argc, wchar_t **argv) {
45  static HANDLE thread_token;
46  if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) {
47    if (!::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE |
48                               TOKEN_DUPLICATE, FALSE, &thread_token))
49      return ::GetLastError();
50  } else {
51    if (!::SetThreadToken(NULL, thread_token))
52      return ::GetLastError();
53
54    // See if we fake the call again.
55    int ret = PolicyTargetTest_token(argc, argv);
56    ::CloseHandle(thread_token);
57    return ret;
58  }
59  return 0;
60}
61
62// Opens the thread token with and without impersonation.
63SBOX_TESTS_COMMAND int PolicyTargetTest_token2(int argc, wchar_t **argv) {
64  HANDLE thread_token;
65  // Get the thread token, using impersonation.
66  if (!::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE |
67                             TOKEN_DUPLICATE, FALSE, &thread_token))
68    return ::GetLastError();
69  ::CloseHandle(thread_token);
70
71  // Get the thread token, without impersonation.
72  if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE,
73                       TRUE, &thread_token))
74    return ::GetLastError();
75  ::CloseHandle(thread_token);
76  return SBOX_TEST_SUCCEEDED;
77}
78
79// Opens the thread token with and without impersonation, using
80// NtOpenThreadTokenEX.
81SBOX_TESTS_COMMAND int PolicyTargetTest_token3(int argc, wchar_t **argv) {
82  BINDNTDLL(NtOpenThreadTokenEx);
83  if (!NtOpenThreadTokenEx)
84    return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND;
85
86  HANDLE thread_token;
87  // Get the thread token, using impersonation.
88  NTSTATUS status = NtOpenThreadTokenEx(GetCurrentThread(),
89                                        TOKEN_IMPERSONATE | TOKEN_DUPLICATE,
90                                        FALSE, 0, &thread_token);
91  if (status == STATUS_NO_TOKEN)
92    return ERROR_NO_TOKEN;
93  if (!NT_SUCCESS(status))
94    return SBOX_TEST_FAILED;
95
96  ::CloseHandle(thread_token);
97
98  // Get the thread token, without impersonation.
99  status = NtOpenThreadTokenEx(GetCurrentThread(),
100                               TOKEN_IMPERSONATE | TOKEN_DUPLICATE, TRUE, 0,
101                               &thread_token);
102  if (!NT_SUCCESS(status))
103    return SBOX_TEST_FAILED;
104
105  ::CloseHandle(thread_token);
106  return SBOX_TEST_SUCCEEDED;
107}
108
109// Tests that we can open the current thread.
110SBOX_TESTS_COMMAND int PolicyTargetTest_thread(int argc, wchar_t **argv) {
111  DWORD thread_id = ::GetCurrentThreadId();
112  HANDLE thread = ::OpenThread(SYNCHRONIZE, FALSE, thread_id);
113  if (!thread)
114    return ::GetLastError();
115  if (!::CloseHandle(thread))
116    return ::GetLastError();
117
118  return SBOX_TEST_SUCCEEDED;
119}
120
121// New thread entry point: do  nothing.
122DWORD WINAPI PolicyTargetTest_thread_main(void* param) {
123  ::Sleep(INFINITE);
124  return 0;
125}
126
127// Tests that we can create a new thread, and open it.
128SBOX_TESTS_COMMAND int PolicyTargetTest_thread2(int argc, wchar_t **argv) {
129  // Use default values to create a new thread.
130  DWORD thread_id;
131  HANDLE thread = ::CreateThread(NULL, 0, &PolicyTargetTest_thread_main, 0, 0,
132                                 &thread_id);
133  if (!thread)
134    return ::GetLastError();
135  if (!::CloseHandle(thread))
136    return ::GetLastError();
137
138  thread = ::OpenThread(SYNCHRONIZE, FALSE, thread_id);
139  if (!thread)
140    return ::GetLastError();
141
142  if (!::CloseHandle(thread))
143    return ::GetLastError();
144
145  return SBOX_TEST_SUCCEEDED;
146}
147
148// Tests that we can call CreateProcess.
149SBOX_TESTS_COMMAND int PolicyTargetTest_process(int argc, wchar_t **argv) {
150  // Use default values to create a new process.
151  STARTUPINFO startup_info = {0};
152  startup_info.cb = sizeof(startup_info);
153  PROCESS_INFORMATION temp_process_info = {};
154  // Note: CreateProcessW() can write to its lpCommandLine, don't pass a
155  // raw string literal.
156  base::string16 writable_cmdline_str(L"foo.exe");
157  if (!::CreateProcessW(L"foo.exe", &writable_cmdline_str[0], NULL, NULL, FALSE,
158                        0, NULL, NULL, &startup_info, &temp_process_info))
159    return SBOX_TEST_SUCCEEDED;
160  base::win::ScopedProcessInformation process_info(temp_process_info);
161  return SBOX_TEST_FAILED;
162}
163
164TEST(PolicyTargetTest, SetInformationThread) {
165  TestRunner runner;
166  if (base::win::GetVersion() >= base::win::VERSION_XP) {
167    runner.SetTestState(BEFORE_REVERT);
168    EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token"));
169  }
170
171  runner.SetTestState(AFTER_REVERT);
172  EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token"));
173
174  runner.SetTestState(EVERY_STATE);
175  if (base::win::GetVersion() >= base::win::VERSION_XP)
176    EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"PolicyTargetTest_steal"));
177}
178
179TEST(PolicyTargetTest, OpenThreadToken) {
180  TestRunner runner;
181  if (base::win::GetVersion() >= base::win::VERSION_XP) {
182    runner.SetTestState(BEFORE_REVERT);
183    EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token2"));
184  }
185
186  runner.SetTestState(AFTER_REVERT);
187  EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token2"));
188}
189
190TEST(PolicyTargetTest, OpenThreadTokenEx) {
191  TestRunner runner;
192  if (base::win::GetVersion() < base::win::VERSION_XP)
193    return;
194
195  runner.SetTestState(BEFORE_REVERT);
196  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token3"));
197
198  runner.SetTestState(AFTER_REVERT);
199  EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token3"));
200}
201
202TEST(PolicyTargetTest, OpenThread) {
203  TestRunner runner;
204  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread")) <<
205      "Opens the current thread";
206
207  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread2")) <<
208      "Creates a new thread and opens it";
209}
210
211TEST(PolicyTargetTest, OpenProcess) {
212  TestRunner runner;
213  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_process")) <<
214      "Opens a process";
215}
216
217// Launches the app in the sandbox and ask it to wait in an
218// infinite loop. Waits for 2 seconds and then check if the
219// desktop associated with the app thread is not the same as the
220// current desktop.
221TEST(PolicyTargetTest, DesktopPolicy) {
222  BrokerServices* broker = GetBroker();
223
224  // Precreate the desktop.
225  TargetPolicy* temp_policy = broker->CreatePolicy();
226  temp_policy->CreateAlternateDesktop(false);
227  temp_policy->Release();
228
229  ASSERT_TRUE(broker != NULL);
230
231  // Get the path to the sandboxed app.
232  wchar_t prog_name[MAX_PATH];
233  GetModuleFileNameW(NULL, prog_name, MAX_PATH);
234
235  base::string16 arguments(L"\"");
236  arguments += prog_name;
237  arguments += L"\" -child 0 wait";  // Don't care about the "state" argument.
238
239  // Launch the app.
240  ResultCode result = SBOX_ALL_OK;
241  base::win::ScopedProcessInformation target;
242
243  TargetPolicy* policy = broker->CreatePolicy();
244  policy->SetAlternateDesktop(false);
245  policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN);
246  PROCESS_INFORMATION temp_process_info = {};
247  result = broker->SpawnTarget(prog_name, arguments.c_str(), policy,
248                               &temp_process_info);
249  base::string16 desktop_name = policy->GetAlternateDesktop();
250  policy->Release();
251
252  EXPECT_EQ(SBOX_ALL_OK, result);
253  if (result == SBOX_ALL_OK)
254    target.Set(temp_process_info);
255
256  EXPECT_EQ(1, ::ResumeThread(target.thread_handle()));
257
258  EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(target.process_handle(), 2000));
259
260  EXPECT_NE(::GetThreadDesktop(target.thread_id()),
261            ::GetThreadDesktop(::GetCurrentThreadId()));
262
263  HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE);
264  EXPECT_TRUE(NULL != desk);
265  EXPECT_TRUE(::CloseDesktop(desk));
266  EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0));
267
268  ::WaitForSingleObject(target.process_handle(), INFINITE);
269
270  // Close the desktop handle.
271  temp_policy = broker->CreatePolicy();
272  temp_policy->DestroyAlternateDesktop();
273  temp_policy->Release();
274
275  // Make sure the desktop does not exist anymore.
276  desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE);
277  EXPECT_TRUE(NULL == desk);
278}
279
280// Launches the app in the sandbox and ask it to wait in an
281// infinite loop. Waits for 2 seconds and then check if the
282// winstation associated with the app thread is not the same as the
283// current desktop.
284TEST(PolicyTargetTest, WinstaPolicy) {
285  BrokerServices* broker = GetBroker();
286
287  // Precreate the desktop.
288  TargetPolicy* temp_policy = broker->CreatePolicy();
289  temp_policy->CreateAlternateDesktop(true);
290  temp_policy->Release();
291
292  ASSERT_TRUE(broker != NULL);
293
294  // Get the path to the sandboxed app.
295  wchar_t prog_name[MAX_PATH];
296  GetModuleFileNameW(NULL, prog_name, MAX_PATH);
297
298  base::string16 arguments(L"\"");
299  arguments += prog_name;
300  arguments += L"\" -child 0 wait";  // Don't care about the "state" argument.
301
302  // Launch the app.
303  ResultCode result = SBOX_ALL_OK;
304  base::win::ScopedProcessInformation target;
305
306  TargetPolicy* policy = broker->CreatePolicy();
307  policy->SetAlternateDesktop(true);
308  policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN);
309  PROCESS_INFORMATION temp_process_info = {};
310  result = broker->SpawnTarget(prog_name, arguments.c_str(), policy,
311                               &temp_process_info);
312  base::string16 desktop_name = policy->GetAlternateDesktop();
313  policy->Release();
314
315  EXPECT_EQ(SBOX_ALL_OK, result);
316  if (result == SBOX_ALL_OK)
317    target.Set(temp_process_info);
318
319  EXPECT_EQ(1, ::ResumeThread(target.thread_handle()));
320
321  EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(target.process_handle(), 2000));
322
323  EXPECT_NE(::GetThreadDesktop(target.thread_id()),
324            ::GetThreadDesktop(::GetCurrentThreadId()));
325
326  ASSERT_FALSE(desktop_name.empty());
327
328  // Make sure there is a backslash, for the window station name.
329  EXPECT_NE(desktop_name.find_first_of(L'\\'), base::string16::npos);
330
331  // Isolate the desktop name.
332  desktop_name = desktop_name.substr(desktop_name.find_first_of(L'\\') + 1);
333
334  HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE);
335  // This should fail if the desktop is really on another window station.
336  EXPECT_FALSE(NULL != desk);
337  EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0));
338
339  ::WaitForSingleObject(target.process_handle(), INFINITE);
340
341  // Close the desktop handle.
342  temp_policy = broker->CreatePolicy();
343  temp_policy->DestroyAlternateDesktop();
344  temp_policy->Release();
345}
346
347}  // namespace sandbox
348