Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / sandbox / win / src / restricted_token_utils.cc
blob5e06daa426598333770b951f31e2b4913aa37ef0
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.
5 #include <aclapi.h>
6 #include <sddl.h>
7 #include <vector>
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"
20 namespace sandbox {
22 DWORD 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;
29 RestrictedToken restricted_token;
30 restricted_token.Init(NULL); // Initialized with the current process token
32 std::vector<base::string16> 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: {
40 deny_sids = false;
41 remove_privileges = false;
42 break;
44 case USER_RESTRICTED_SAME_ACCESS: {
45 deny_sids = false;
46 remove_privileges = false;
48 unsigned err_code = restricted_token.AddRestrictingSidAllSids();
49 if (ERROR_SUCCESS != err_code)
50 return err_code;
52 break;
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;
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;
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);
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;
93 case USER_RESTRICTED: {
94 privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
95 restricted_token.AddUserSidForDenyOnly();
96 restricted_token.AddRestrictingSid(WinRestrictedCodeSid);
97 break;
99 case USER_LOCKDOWN: {
100 restricted_token.AddUserSidForDenyOnly();
101 restricted_token.AddRestrictingSid(WinNullSid);
102 break;
104 default: {
105 return ERROR_BAD_ARGUMENTS;
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;
116 if (remove_privileges) {
117 err_code = restricted_token.DeleteAllPrivileges(&privilege_exceptions);
118 if (ERROR_SUCCESS != err_code)
119 return err_code;
122 restricted_token.SetIntegrityLevel(integrity_level);
124 switch (token_type) {
125 case PRIMARY: {
126 err_code = restricted_token.GetRestrictedTokenHandle(token_handle);
127 break;
129 case IMPERSONATION: {
130 err_code = restricted_token.GetRestrictedTokenHandleForImpersonation(
131 token_handle);
132 break;
134 default: {
135 err_code = ERROR_BAD_ARGUMENTS;
136 break;
140 return err_code;
143 DWORD 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;
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;
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;
170 base::win::ScopedHandle primary_token(primary_token_handle);
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;
182 base::win::ScopedHandle impersonation_token(impersonation_token_handle);
184 // Start the process
185 STARTUPINFO startup_info = {0};
186 PROCESS_INFORMATION temp_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.
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();
208 base::win::ScopedProcessInformation process_info(temp_process_info);
210 // Change the token of the main thread of the new process for the
211 // impersonation token with more rights.
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();
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();
228 // Start the application
229 ::ResumeThread(process_info.thread_handle());
231 (*job_handle_ret) = job.Detach();
233 return ERROR_SUCCESS;
236 DWORD 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")";
248 DWORD error = ERROR_SUCCESS;
249 PSECURITY_DESCRIPTOR sec_desc = NULL;
251 PACL sacl = NULL;
252 BOOL sacl_present = FALSE;
253 BOOL sacl_defaulted = FALSE;
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();
267 ::LocalFree(sec_desc);
268 } else {
269 return::GetLastError();
272 return error;
275 const 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;
295 NOTREACHED();
296 return NULL;
298 DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) {
299 if (base::win::GetVersion() < base::win::VERSION_VISTA)
300 return ERROR_SUCCESS;
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;
308 PSID integrity_sid = NULL;
309 if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid))
310 return ::GetLastError();
312 TOKEN_MANDATORY_LABEL label = {0};
313 label.Label.Attributes = SE_GROUP_INTEGRITY;
314 label.Label.Sid = integrity_sid;
316 DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid);
317 BOOL result = ::SetTokenInformation(token, TokenIntegrityLevel, &label,
318 size);
319 ::LocalFree(integrity_sid);
321 return result ? ERROR_SUCCESS : ::GetLastError();
324 DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) {
325 if (base::win::GetVersion() < base::win::VERSION_VISTA)
326 return ERROR_SUCCESS;
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;
335 HANDLE token_handle;
336 if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT,
337 &token_handle))
338 return ::GetLastError();
340 base::win::ScopedHandle token(token_handle);
342 return SetTokenIntegrityLevel(token.Get(), integrity_level);
345 DWORD HardenTokenIntegrityLevelPolicy(HANDLE token) {
346 if (base::win::GetVersion() < base::win::VERSION_WIN7)
347 return ERROR_SUCCESS;
349 DWORD last_error = 0;
350 DWORD length_needed = 0;
352 ::GetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION,
353 NULL, 0, &length_needed);
355 last_error = ::GetLastError();
356 if (last_error != ERROR_INSUFFICIENT_BUFFER)
357 return last_error;
359 std::vector<char> security_desc_buffer(length_needed);
360 PSECURITY_DESCRIPTOR security_desc =
361 reinterpret_cast<PSECURITY_DESCRIPTOR>(&security_desc_buffer[0]);
363 if (!::GetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION,
364 security_desc, length_needed,
365 &length_needed))
366 return ::GetLastError();
368 PACL sacl = NULL;
369 BOOL sacl_present = FALSE;
370 BOOL sacl_defaulted = FALSE;
372 if (!::GetSecurityDescriptorSacl(security_desc, &sacl_present,
373 &sacl, &sacl_defaulted))
374 return ::GetLastError();
376 for (DWORD ace_index = 0; ace_index < sacl->AceCount; ++ace_index) {
377 PSYSTEM_MANDATORY_LABEL_ACE ace;
379 if (::GetAce(sacl, ace_index, reinterpret_cast<LPVOID*>(&ace))
380 && ace->Header.AceType == SYSTEM_MANDATORY_LABEL_ACE_TYPE) {
381 ace->Mask |= SYSTEM_MANDATORY_LABEL_NO_READ_UP
382 | SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP;
383 break;
387 if (!::SetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION,
388 security_desc))
389 return ::GetLastError();
391 return ERROR_SUCCESS;
394 DWORD HardenProcessIntegrityLevelPolicy() {
395 if (base::win::GetVersion() < base::win::VERSION_WIN7)
396 return ERROR_SUCCESS;
398 HANDLE token_handle;
399 if (!::OpenProcessToken(GetCurrentProcess(), READ_CONTROL | WRITE_OWNER,
400 &token_handle))
401 return ::GetLastError();
403 base::win::ScopedHandle token(token_handle);
405 return HardenTokenIntegrityLevelPolicy(token.Get());
408 } // namespace sandbox