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.
9 #include "sandbox/win/src/restricted_token_utils.h"
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"
22 DWORD CreateRestrictedToken(HANDLE *token_handle,
23 TokenLevel security_level,
24 IntegrityLevel integrity_level,
25 TokenType token_type) {
27 return ERROR_BAD_ARGUMENTS;
29 RestrictedToken restricted_token;
30 restricted_token.Init(NULL); // Initialized with the current process token
32 std::vector<std::wstring> privilege_exceptions;
33 std::vector<Sid> sid_exceptions;
35 bool deny_sids = true;
36 bool remove_privileges = true;
38 switch (security_level) {
39 case USER_UNPROTECTED: {
41 remove_privileges = false;
44 case USER_RESTRICTED_SAME_ACCESS: {
46 remove_privileges = false;
48 unsigned err_code = restricted_token.AddRestrictingSidAllSids();
49 if (ERROR_SUCCESS != err_code)
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);
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();
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);
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
89 if (base::win::GetVersion() >= base::win::VERSION_VISTA)
90 restricted_token.AddRestrictingSidLogonSession();
93 case USER_RESTRICTED: {
94 privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
95 restricted_token.AddUserSidForDenyOnly();
96 restricted_token.AddRestrictingSid(WinRestrictedCodeSid);
100 restricted_token.AddUserSidForDenyOnly();
101 restricted_token.AddRestrictingSid(WinNullSid);
105 return ERROR_BAD_ARGUMENTS;
109 DWORD err_code = ERROR_SUCCESS;
111 err_code = restricted_token.AddAllSidsForDenyOnly(&sid_exceptions);
112 if (ERROR_SUCCESS != err_code)
116 if (remove_privileges) {
117 err_code = restricted_token.DeleteAllPrivileges(&privilege_exceptions);
118 if (ERROR_SUCCESS != err_code)
122 restricted_token.SetIntegrityLevel(integrity_level);
124 switch (token_type) {
126 err_code = restricted_token.GetRestrictedTokenHandle(token_handle);
129 case IMPERSONATION: {
130 err_code = restricted_token.GetRestrictedTokenHandleForImpersonation(
135 err_code = ERROR_BAD_ARGUMENTS;
143 DWORD StartRestrictedProcessInJob(wchar_t *command_line,
144 TokenLevel primary_level,
145 TokenLevel impersonation_level,
147 HANDLE *const job_handle_ret) {
149 DWORD err_code = job.Init(job_level, NULL, 0);
150 if (ERROR_SUCCESS != err_code)
153 if (JOB_UNPROTECTED != job_level) {
154 // Share the Desktop handle to be able to use MessageBox() in the sandboxed
156 err_code = job.UserHandleGrantAccess(GetDesktopWindow());
157 if (ERROR_SUCCESS != err_code)
161 // Create the primary (restricted) token for the process
162 HANDLE primary_token_handle = NULL;
163 err_code = CreateRestrictedToken(&primary_token_handle,
165 INTEGRITY_LEVEL_LAST,
167 if (ERROR_SUCCESS != err_code) {
170 base::win::ScopedHandle primary_token(primary_token_handle);
172 // Create the impersonation token (restricted) to be able to start the
174 HANDLE impersonation_token_handle;
175 err_code = CreateRestrictedToken(&impersonation_token_handle,
177 INTEGRITY_LEVEL_LAST,
179 if (ERROR_SUCCESS != err_code) {
182 base::win::ScopedHandle impersonation_token(impersonation_token_handle);
185 STARTUPINFO startup_info = {0};
186 base::win::ScopedProcessInformation process_info;
187 DWORD flags = CREATE_SUSPENDED;
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;
195 if (!::CreateProcessAsUser(primary_token.Get(),
196 NULL, // No application name.
198 NULL, // No security attribute.
199 NULL, // No thread attribute.
200 FALSE, // Do not inherit handles.
202 NULL, // Use the environment of the caller.
203 NULL, // Use current directory of the caller.
205 process_info.Receive())) {
206 return ::GetLastError();
209 // Change the token of the main thread of the new process for the
210 // impersonation token with more rights.
212 HANDLE temp_thread = process_info.thread_handle();
213 if (!::SetThreadToken(&temp_thread, impersonation_token.Get())) {
214 ::TerminateProcess(process_info.process_handle(),
216 return ::GetLastError();
220 err_code = job.AssignProcessToJob(process_info.process_handle());
221 if (ERROR_SUCCESS != err_code) {
222 ::TerminateProcess(process_info.process_handle(),
224 return ::GetLastError();
227 // Start the application
228 ::ResumeThread(process_info.thread_handle());
230 (*job_handle_ret) = job.Detach();
232 return ERROR_SUCCESS;
235 DWORD SetObjectIntegrityLabel(HANDLE handle, SE_OBJECT_TYPE type,
236 const wchar_t* ace_access,
237 const wchar_t* integrity_level_sid) {
238 // Build the SDDL string for the label.
239 std::wstring sddl = L"S:("; // SDDL for a SACL.
240 sddl += SDDL_MANDATORY_LABEL; // Ace Type is "Mandatory Label".
241 sddl += L";;"; // No Ace Flags.
242 sddl += ace_access; // Add the ACE access.
243 sddl += L";;;"; // No ObjectType and Inherited Object Type.
244 sddl += integrity_level_sid; // Trustee Sid.
247 DWORD error = ERROR_SUCCESS;
248 PSECURITY_DESCRIPTOR sec_desc = NULL;
251 BOOL sacl_present = FALSE;
252 BOOL sacl_defaulted = FALSE;
254 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),
257 if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
259 error = ::SetSecurityInfo(handle, type,
260 LABEL_SECURITY_INFORMATION, NULL, NULL, NULL,
263 error = ::GetLastError();
266 ::LocalFree(sec_desc);
268 return::GetLastError();
274 const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level) {
275 switch (integrity_level) {
276 case INTEGRITY_LEVEL_SYSTEM:
277 return L"S-1-16-16384";
278 case INTEGRITY_LEVEL_HIGH:
279 return L"S-1-16-12288";
280 case INTEGRITY_LEVEL_MEDIUM:
281 return L"S-1-16-8192";
282 case INTEGRITY_LEVEL_MEDIUM_LOW:
283 return L"S-1-16-6144";
284 case INTEGRITY_LEVEL_LOW:
285 return L"S-1-16-4096";
286 case INTEGRITY_LEVEL_BELOW_LOW:
287 return L"S-1-16-2048";
288 case INTEGRITY_LEVEL_UNTRUSTED:
290 case INTEGRITY_LEVEL_LAST:
297 DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) {
298 if (base::win::GetVersion() < base::win::VERSION_VISTA)
299 return ERROR_SUCCESS;
301 const wchar_t* integrity_level_str = GetIntegrityLevelString(integrity_level);
302 if (!integrity_level_str) {
303 // No mandatory level specified, we don't change it.
304 return ERROR_SUCCESS;
307 PSID integrity_sid = NULL;
308 if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid))
309 return ::GetLastError();
311 TOKEN_MANDATORY_LABEL label = {0};
312 label.Label.Attributes = SE_GROUP_INTEGRITY;
313 label.Label.Sid = integrity_sid;
315 DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid);
316 BOOL result = ::SetTokenInformation(token, TokenIntegrityLevel, &label,
318 ::LocalFree(integrity_sid);
320 return result ? ERROR_SUCCESS : ::GetLastError();
323 DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) {
324 if (base::win::GetVersion() < base::win::VERSION_VISTA)
325 return ERROR_SUCCESS;
327 // We don't check for an invalid level here because we'll just let it
328 // fail on the SetTokenIntegrityLevel call later on.
329 if (integrity_level == INTEGRITY_LEVEL_LAST) {
330 // No mandatory level specified, we don't change it.
331 return ERROR_SUCCESS;
335 if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT,
337 return ::GetLastError();
339 base::win::ScopedHandle token(token_handle);
341 return SetTokenIntegrityLevel(token.Get(), integrity_level);
344 } // namespace sandbox