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 <aclapi.h>
6#include <sddl.h>
7#include <vector>
8
9#include "sandbox/win/src/restricted_token_utils.h"
10
11#include "base/logging.h"
12#include "base/win/scoped_handle.h"
13#include "base/win/scoped_process_information.h"
14#include "base/win/windows_version.h"
15#include "sandbox/win/src/job.h"
16#include "sandbox/win/src/restricted_token.h"
17#include "sandbox/win/src/security_level.h"
18#include "sandbox/win/src/sid.h"
19
20namespace sandbox {
21
22DWORD CreateRestrictedToken(HANDLE *token_handle,
23                            TokenLevel security_level,
24                            IntegrityLevel integrity_level,
25                            TokenType token_type) {
26  if (!token_handle)
27    return ERROR_BAD_ARGUMENTS;
28
29  RestrictedToken restricted_token;
30  restricted_token.Init(NULL);  // Initialized with the current process token
31
32  std::vector<base::string16> privilege_exceptions;
33  std::vector<Sid> sid_exceptions;
34
35  bool deny_sids = true;
36  bool remove_privileges = true;
37
38  switch (security_level) {
39    case USER_UNPROTECTED: {
40      deny_sids = false;
41      remove_privileges = false;
42      break;
43    }
44    case USER_RESTRICTED_SAME_ACCESS: {
45      deny_sids = false;
46      remove_privileges = false;
47
48      unsigned err_code = restricted_token.AddRestrictingSidAllSids();
49      if (ERROR_SUCCESS != err_code)
50        return err_code;
51
52      break;
53    }
54    case USER_NON_ADMIN: {
55      sid_exceptions.push_back(WinBuiltinUsersSid);
56      sid_exceptions.push_back(WinWorldSid);
57      sid_exceptions.push_back(WinInteractiveSid);
58      sid_exceptions.push_back(WinAuthenticatedUserSid);
59      privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
60      break;
61    }
62    case USER_INTERACTIVE: {
63      sid_exceptions.push_back(WinBuiltinUsersSid);
64      sid_exceptions.push_back(WinWorldSid);
65      sid_exceptions.push_back(WinInteractiveSid);
66      sid_exceptions.push_back(WinAuthenticatedUserSid);
67      privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
68      restricted_token.AddRestrictingSid(WinBuiltinUsersSid);
69      restricted_token.AddRestrictingSid(WinWorldSid);
70      restricted_token.AddRestrictingSid(WinRestrictedCodeSid);
71      restricted_token.AddRestrictingSidCurrentUser();
72      restricted_token.AddRestrictingSidLogonSession();
73      break;
74    }
75    case USER_LIMITED: {
76      sid_exceptions.push_back(WinBuiltinUsersSid);
77      sid_exceptions.push_back(WinWorldSid);
78      sid_exceptions.push_back(WinInteractiveSid);
79      privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
80      restricted_token.AddRestrictingSid(WinBuiltinUsersSid);
81      restricted_token.AddRestrictingSid(WinWorldSid);
82      restricted_token.AddRestrictingSid(WinRestrictedCodeSid);
83
84      // This token has to be able to create objects in BNO.
85      // Unfortunately, on vista, it needs the current logon sid
86      // in the token to achieve this. You should also set the process to be
87      // low integrity level so it can't access object created by other
88      // processes.
89      if (base::win::GetVersion() >= base::win::VERSION_VISTA)
90        restricted_token.AddRestrictingSidLogonSession();
91      break;
92    }
93    case USER_RESTRICTED: {
94      privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
95      restricted_token.AddUserSidForDenyOnly();
96      restricted_token.AddRestrictingSid(WinRestrictedCodeSid);
97      break;
98    }
99    case USER_LOCKDOWN: {
100      restricted_token.AddUserSidForDenyOnly();
101      restricted_token.AddRestrictingSid(WinNullSid);
102      break;
103    }
104    default: {
105      return ERROR_BAD_ARGUMENTS;
106    }
107  }
108
109  DWORD err_code = ERROR_SUCCESS;
110  if (deny_sids) {
111    err_code = restricted_token.AddAllSidsForDenyOnly(&sid_exceptions);
112    if (ERROR_SUCCESS != err_code)
113      return err_code;
114  }
115
116  if (remove_privileges) {
117    err_code = restricted_token.DeleteAllPrivileges(&privilege_exceptions);
118    if (ERROR_SUCCESS != err_code)
119      return err_code;
120  }
121
122  restricted_token.SetIntegrityLevel(integrity_level);
123
124  switch (token_type) {
125    case PRIMARY: {
126      err_code = restricted_token.GetRestrictedTokenHandle(token_handle);
127      break;
128    }
129    case IMPERSONATION: {
130      err_code = restricted_token.GetRestrictedTokenHandleForImpersonation(
131          token_handle);
132      break;
133    }
134    default: {
135      err_code = ERROR_BAD_ARGUMENTS;
136      break;
137    }
138  }
139
140  return err_code;
141}
142
143DWORD StartRestrictedProcessInJob(wchar_t *command_line,
144                                  TokenLevel primary_level,
145                                  TokenLevel impersonation_level,
146                                  JobLevel job_level,
147                                  HANDLE *const job_handle_ret) {
148  Job job;
149  DWORD err_code = job.Init(job_level, NULL, 0, 0);
150  if (ERROR_SUCCESS != err_code)
151    return err_code;
152
153  if (JOB_UNPROTECTED != job_level) {
154    // Share the Desktop handle to be able to use MessageBox() in the sandboxed
155    // application.
156    err_code = job.UserHandleGrantAccess(GetDesktopWindow());
157    if (ERROR_SUCCESS != err_code)
158      return err_code;
159  }
160
161  // Create the primary (restricted) token for the process
162  HANDLE primary_token_handle = NULL;
163  err_code = CreateRestrictedToken(&primary_token_handle,
164                                   primary_level,
165                                   INTEGRITY_LEVEL_LAST,
166                                   PRIMARY);
167  if (ERROR_SUCCESS != err_code) {
168    return err_code;
169  }
170  base::win::ScopedHandle primary_token(primary_token_handle);
171
172  // Create the impersonation token (restricted) to be able to start the
173  // process.
174  HANDLE impersonation_token_handle;
175  err_code = CreateRestrictedToken(&impersonation_token_handle,
176                                   impersonation_level,
177                                   INTEGRITY_LEVEL_LAST,
178                                   IMPERSONATION);
179  if (ERROR_SUCCESS != err_code) {
180    return err_code;
181  }
182  base::win::ScopedHandle impersonation_token(impersonation_token_handle);
183
184  // Start the process
185  STARTUPINFO startup_info = {0};
186  PROCESS_INFORMATION temp_process_info = {};
187  DWORD flags = CREATE_SUSPENDED;
188
189  if (base::win::GetVersion() < base::win::VERSION_WIN8) {
190    // Windows 8 implements nested jobs, but for older systems we need to
191    // break out of any job we're in to enforce our restrictions.
192    flags |= CREATE_BREAKAWAY_FROM_JOB;
193  }
194
195  if (!::CreateProcessAsUser(primary_token.Get(),
196                             NULL,   // No application name.
197                             command_line,
198                             NULL,   // No security attribute.
199                             NULL,   // No thread attribute.
200                             FALSE,  // Do not inherit handles.
201                             flags,
202                             NULL,   // Use the environment of the caller.
203                             NULL,   // Use current directory of the caller.
204                             &startup_info,
205                             &temp_process_info)) {
206    return ::GetLastError();
207  }
208  base::win::ScopedProcessInformation process_info(temp_process_info);
209
210  // Change the token of the main thread of the new process for the
211  // impersonation token with more rights.
212  {
213    HANDLE temp_thread = process_info.thread_handle();
214    if (!::SetThreadToken(&temp_thread, impersonation_token.Get())) {
215      ::TerminateProcess(process_info.process_handle(),
216                         0);  // exit code
217      return ::GetLastError();
218    }
219  }
220
221  err_code = job.AssignProcessToJob(process_info.process_handle());
222  if (ERROR_SUCCESS != err_code) {
223    ::TerminateProcess(process_info.process_handle(),
224                       0);  // exit code
225    return ::GetLastError();
226  }
227
228  // Start the application
229  ::ResumeThread(process_info.thread_handle());
230
231  (*job_handle_ret) = job.Detach();
232
233  return ERROR_SUCCESS;
234}
235
236DWORD SetObjectIntegrityLabel(HANDLE handle, SE_OBJECT_TYPE type,
237                              const wchar_t* ace_access,
238                              const wchar_t* integrity_level_sid) {
239  // Build the SDDL string for the label.
240  base::string16 sddl = L"S:(";   // SDDL for a SACL.
241  sddl += SDDL_MANDATORY_LABEL;   // Ace Type is "Mandatory Label".
242  sddl += L";;";                  // No Ace Flags.
243  sddl += ace_access;             // Add the ACE access.
244  sddl += L";;;";                 // No ObjectType and Inherited Object Type.
245  sddl += integrity_level_sid;    // Trustee Sid.
246  sddl += L")";
247
248  DWORD error = ERROR_SUCCESS;
249  PSECURITY_DESCRIPTOR sec_desc = NULL;
250
251  PACL sacl = NULL;
252  BOOL sacl_present = FALSE;
253  BOOL sacl_defaulted = FALSE;
254
255  if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),
256                                                             SDDL_REVISION,
257                                                             &sec_desc, NULL)) {
258    if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
259                                    &sacl_defaulted)) {
260      error = ::SetSecurityInfo(handle, type,
261                                LABEL_SECURITY_INFORMATION, NULL, NULL, NULL,
262                                sacl);
263    } else {
264      error = ::GetLastError();
265    }
266
267    ::LocalFree(sec_desc);
268  } else {
269    return::GetLastError();
270  }
271
272  return error;
273}
274
275const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level) {
276  switch (integrity_level) {
277    case INTEGRITY_LEVEL_SYSTEM:
278      return L"S-1-16-16384";
279    case INTEGRITY_LEVEL_HIGH:
280      return L"S-1-16-12288";
281    case INTEGRITY_LEVEL_MEDIUM:
282      return L"S-1-16-8192";
283    case INTEGRITY_LEVEL_MEDIUM_LOW:
284      return L"S-1-16-6144";
285    case INTEGRITY_LEVEL_LOW:
286      return L"S-1-16-4096";
287    case INTEGRITY_LEVEL_BELOW_LOW:
288      return L"S-1-16-2048";
289    case INTEGRITY_LEVEL_UNTRUSTED:
290      return L"S-1-16-0";
291    case INTEGRITY_LEVEL_LAST:
292      return NULL;
293  }
294
295  NOTREACHED();
296  return NULL;
297}
298DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) {
299  if (base::win::GetVersion() < base::win::VERSION_VISTA)
300    return ERROR_SUCCESS;
301
302  const wchar_t* integrity_level_str = GetIntegrityLevelString(integrity_level);
303  if (!integrity_level_str) {
304    // No mandatory level specified, we don't change it.
305    return ERROR_SUCCESS;
306  }
307
308  PSID integrity_sid = NULL;
309  if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid))
310    return ::GetLastError();
311
312  TOKEN_MANDATORY_LABEL label = {0};
313  label.Label.Attributes = SE_GROUP_INTEGRITY;
314  label.Label.Sid = integrity_sid;
315
316  DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid);
317  BOOL result = ::SetTokenInformation(token, TokenIntegrityLevel, &label,
318                                      size);
319  ::LocalFree(integrity_sid);
320
321  return result ? ERROR_SUCCESS : ::GetLastError();
322}
323
324DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) {
325  if (base::win::GetVersion() < base::win::VERSION_VISTA)
326    return ERROR_SUCCESS;
327
328  // We don't check for an invalid level here because we'll just let it
329  // fail on the SetTokenIntegrityLevel call later on.
330  if (integrity_level == INTEGRITY_LEVEL_LAST) {
331    // No mandatory level specified, we don't change it.
332    return ERROR_SUCCESS;
333  }
334
335  HANDLE token_handle;
336  if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT,
337                          &token_handle))
338    return ::GetLastError();
339
340  base::win::ScopedHandle token(token_handle);
341
342  return SetTokenIntegrityLevel(token.Get(), integrity_level);
343}
344
345}  // namespace sandbox
346