Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / sandbox / win / src / restricted_token_utils.cc
blob5f9719210341a0ab2f5c7a7fd71577aadbf802ab
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(TokenLevel security_level,
23 IntegrityLevel integrity_level,
24 TokenType token_type,
25 base::win::ScopedHandle* token) {
26 RestrictedToken restricted_token;
27 restricted_token.Init(NULL); // Initialized with the current process token
29 std::vector<base::string16> privilege_exceptions;
30 std::vector<Sid> sid_exceptions;
32 bool deny_sids = true;
33 bool remove_privileges = true;
35 switch (security_level) {
36 case USER_UNPROTECTED: {
37 deny_sids = false;
38 remove_privileges = false;
39 break;
41 case USER_RESTRICTED_SAME_ACCESS: {
42 deny_sids = false;
43 remove_privileges = false;
45 unsigned err_code = restricted_token.AddRestrictingSidAllSids();
46 if (ERROR_SUCCESS != err_code)
47 return err_code;
49 break;
51 case USER_NON_ADMIN: {
52 sid_exceptions.push_back(WinBuiltinUsersSid);
53 sid_exceptions.push_back(WinWorldSid);
54 sid_exceptions.push_back(WinInteractiveSid);
55 sid_exceptions.push_back(WinAuthenticatedUserSid);
56 privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
57 break;
59 case USER_INTERACTIVE: {
60 sid_exceptions.push_back(WinBuiltinUsersSid);
61 sid_exceptions.push_back(WinWorldSid);
62 sid_exceptions.push_back(WinInteractiveSid);
63 sid_exceptions.push_back(WinAuthenticatedUserSid);
64 privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
65 restricted_token.AddRestrictingSid(WinBuiltinUsersSid);
66 restricted_token.AddRestrictingSid(WinWorldSid);
67 restricted_token.AddRestrictingSid(WinRestrictedCodeSid);
68 restricted_token.AddRestrictingSidCurrentUser();
69 restricted_token.AddRestrictingSidLogonSession();
70 break;
72 case USER_LIMITED: {
73 sid_exceptions.push_back(WinBuiltinUsersSid);
74 sid_exceptions.push_back(WinWorldSid);
75 sid_exceptions.push_back(WinInteractiveSid);
76 privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
77 restricted_token.AddRestrictingSid(WinBuiltinUsersSid);
78 restricted_token.AddRestrictingSid(WinWorldSid);
79 restricted_token.AddRestrictingSid(WinRestrictedCodeSid);
81 // This token has to be able to create objects in BNO.
82 // Unfortunately, on vista, it needs the current logon sid
83 // in the token to achieve this. You should also set the process to be
84 // low integrity level so it can't access object created by other
85 // processes.
86 if (base::win::GetVersion() >= base::win::VERSION_VISTA)
87 restricted_token.AddRestrictingSidLogonSession();
88 break;
90 case USER_RESTRICTED: {
91 privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME);
92 restricted_token.AddUserSidForDenyOnly();
93 restricted_token.AddRestrictingSid(WinRestrictedCodeSid);
94 break;
96 case USER_LOCKDOWN: {
97 restricted_token.AddUserSidForDenyOnly();
98 restricted_token.AddRestrictingSid(WinNullSid);
99 break;
101 default: {
102 return ERROR_BAD_ARGUMENTS;
106 DWORD err_code = ERROR_SUCCESS;
107 if (deny_sids) {
108 err_code = restricted_token.AddAllSidsForDenyOnly(&sid_exceptions);
109 if (ERROR_SUCCESS != err_code)
110 return err_code;
113 if (remove_privileges) {
114 err_code = restricted_token.DeleteAllPrivileges(&privilege_exceptions);
115 if (ERROR_SUCCESS != err_code)
116 return err_code;
119 restricted_token.SetIntegrityLevel(integrity_level);
121 switch (token_type) {
122 case PRIMARY: {
123 err_code = restricted_token.GetRestrictedToken(token);
124 break;
126 case IMPERSONATION: {
127 err_code = restricted_token.GetRestrictedTokenForImpersonation(token);
128 break;
130 default: {
131 err_code = ERROR_BAD_ARGUMENTS;
132 break;
136 return err_code;
139 DWORD StartRestrictedProcessInJob(wchar_t *command_line,
140 TokenLevel primary_level,
141 TokenLevel impersonation_level,
142 JobLevel job_level,
143 HANDLE *const job_handle_ret) {
144 Job job;
145 DWORD err_code = job.Init(job_level, NULL, 0, 0);
146 if (ERROR_SUCCESS != err_code)
147 return err_code;
149 if (JOB_UNPROTECTED != job_level) {
150 // Share the Desktop handle to be able to use MessageBox() in the sandboxed
151 // application.
152 err_code = job.UserHandleGrantAccess(GetDesktopWindow());
153 if (ERROR_SUCCESS != err_code)
154 return err_code;
157 // Create the primary (restricted) token for the process
158 base::win::ScopedHandle primary_token;
159 err_code = CreateRestrictedToken(primary_level, INTEGRITY_LEVEL_LAST,
160 PRIMARY, &primary_token);
161 if (ERROR_SUCCESS != err_code)
162 return err_code;
165 // Create the impersonation token (restricted) to be able to start the
166 // process.
167 base::win::ScopedHandle impersonation_token;
168 err_code = CreateRestrictedToken(impersonation_level, INTEGRITY_LEVEL_LAST,
169 IMPERSONATION, &impersonation_token);
170 if (ERROR_SUCCESS != err_code)
171 return err_code;
173 // Start the process
174 STARTUPINFO startup_info = {0};
175 PROCESS_INFORMATION temp_process_info = {};
176 DWORD flags = CREATE_SUSPENDED;
178 if (base::win::GetVersion() < base::win::VERSION_WIN8) {
179 // Windows 8 implements nested jobs, but for older systems we need to
180 // break out of any job we're in to enforce our restrictions.
181 flags |= CREATE_BREAKAWAY_FROM_JOB;
184 if (!::CreateProcessAsUser(primary_token.Get(),
185 NULL, // No application name.
186 command_line,
187 NULL, // No security attribute.
188 NULL, // No thread attribute.
189 FALSE, // Do not inherit handles.
190 flags,
191 NULL, // Use the environment of the caller.
192 NULL, // Use current directory of the caller.
193 &startup_info,
194 &temp_process_info)) {
195 return ::GetLastError();
197 base::win::ScopedProcessInformation process_info(temp_process_info);
199 // Change the token of the main thread of the new process for the
200 // impersonation token with more rights.
202 HANDLE temp_thread = process_info.thread_handle();
203 if (!::SetThreadToken(&temp_thread, impersonation_token.Get())) {
204 ::TerminateProcess(process_info.process_handle(),
205 0); // exit code
206 return ::GetLastError();
210 err_code = job.AssignProcessToJob(process_info.process_handle());
211 if (ERROR_SUCCESS != err_code) {
212 ::TerminateProcess(process_info.process_handle(),
213 0); // exit code
214 return ::GetLastError();
217 // Start the application
218 ::ResumeThread(process_info.thread_handle());
220 (*job_handle_ret) = job.Detach();
222 return ERROR_SUCCESS;
225 DWORD SetObjectIntegrityLabel(HANDLE handle, SE_OBJECT_TYPE type,
226 const wchar_t* ace_access,
227 const wchar_t* integrity_level_sid) {
228 // Build the SDDL string for the label.
229 base::string16 sddl = L"S:("; // SDDL for a SACL.
230 sddl += SDDL_MANDATORY_LABEL; // Ace Type is "Mandatory Label".
231 sddl += L";;"; // No Ace Flags.
232 sddl += ace_access; // Add the ACE access.
233 sddl += L";;;"; // No ObjectType and Inherited Object Type.
234 sddl += integrity_level_sid; // Trustee Sid.
235 sddl += L")";
237 DWORD error = ERROR_SUCCESS;
238 PSECURITY_DESCRIPTOR sec_desc = NULL;
240 PACL sacl = NULL;
241 BOOL sacl_present = FALSE;
242 BOOL sacl_defaulted = FALSE;
244 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),
245 SDDL_REVISION,
246 &sec_desc, NULL)) {
247 if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
248 &sacl_defaulted)) {
249 error = ::SetSecurityInfo(handle, type,
250 LABEL_SECURITY_INFORMATION, NULL, NULL, NULL,
251 sacl);
252 } else {
253 error = ::GetLastError();
256 ::LocalFree(sec_desc);
257 } else {
258 return::GetLastError();
261 return error;
264 const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level) {
265 switch (integrity_level) {
266 case INTEGRITY_LEVEL_SYSTEM:
267 return L"S-1-16-16384";
268 case INTEGRITY_LEVEL_HIGH:
269 return L"S-1-16-12288";
270 case INTEGRITY_LEVEL_MEDIUM:
271 return L"S-1-16-8192";
272 case INTEGRITY_LEVEL_MEDIUM_LOW:
273 return L"S-1-16-6144";
274 case INTEGRITY_LEVEL_LOW:
275 return L"S-1-16-4096";
276 case INTEGRITY_LEVEL_BELOW_LOW:
277 return L"S-1-16-2048";
278 case INTEGRITY_LEVEL_UNTRUSTED:
279 return L"S-1-16-0";
280 case INTEGRITY_LEVEL_LAST:
281 return NULL;
284 NOTREACHED();
285 return NULL;
287 DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) {
288 if (base::win::GetVersion() < base::win::VERSION_VISTA)
289 return ERROR_SUCCESS;
291 const wchar_t* integrity_level_str = GetIntegrityLevelString(integrity_level);
292 if (!integrity_level_str) {
293 // No mandatory level specified, we don't change it.
294 return ERROR_SUCCESS;
297 PSID integrity_sid = NULL;
298 if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid))
299 return ::GetLastError();
301 TOKEN_MANDATORY_LABEL label = {};
302 label.Label.Attributes = SE_GROUP_INTEGRITY;
303 label.Label.Sid = integrity_sid;
305 DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid);
306 BOOL result = ::SetTokenInformation(token, TokenIntegrityLevel, &label,
307 size);
308 ::LocalFree(integrity_sid);
310 return result ? ERROR_SUCCESS : ::GetLastError();
313 DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) {
314 if (base::win::GetVersion() < base::win::VERSION_VISTA)
315 return ERROR_SUCCESS;
317 // We don't check for an invalid level here because we'll just let it
318 // fail on the SetTokenIntegrityLevel call later on.
319 if (integrity_level == INTEGRITY_LEVEL_LAST) {
320 // No mandatory level specified, we don't change it.
321 return ERROR_SUCCESS;
324 HANDLE token_handle;
325 if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT,
326 &token_handle))
327 return ::GetLastError();
329 base::win::ScopedHandle token(token_handle);
331 return SetTokenIntegrityLevel(token.Get(), integrity_level);
334 DWORD HardenTokenIntegrityLevelPolicy(HANDLE token) {
335 if (base::win::GetVersion() < base::win::VERSION_WIN7)
336 return ERROR_SUCCESS;
338 DWORD last_error = 0;
339 DWORD length_needed = 0;
341 ::GetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION,
342 NULL, 0, &length_needed);
344 last_error = ::GetLastError();
345 if (last_error != ERROR_INSUFFICIENT_BUFFER)
346 return last_error;
348 std::vector<char> security_desc_buffer(length_needed);
349 PSECURITY_DESCRIPTOR security_desc =
350 reinterpret_cast<PSECURITY_DESCRIPTOR>(&security_desc_buffer[0]);
352 if (!::GetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION,
353 security_desc, length_needed,
354 &length_needed))
355 return ::GetLastError();
357 PACL sacl = NULL;
358 BOOL sacl_present = FALSE;
359 BOOL sacl_defaulted = FALSE;
361 if (!::GetSecurityDescriptorSacl(security_desc, &sacl_present,
362 &sacl, &sacl_defaulted))
363 return ::GetLastError();
365 for (DWORD ace_index = 0; ace_index < sacl->AceCount; ++ace_index) {
366 PSYSTEM_MANDATORY_LABEL_ACE ace;
368 if (::GetAce(sacl, ace_index, reinterpret_cast<LPVOID*>(&ace))
369 && ace->Header.AceType == SYSTEM_MANDATORY_LABEL_ACE_TYPE) {
370 ace->Mask |= SYSTEM_MANDATORY_LABEL_NO_READ_UP
371 | SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP;
372 break;
376 if (!::SetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION,
377 security_desc))
378 return ::GetLastError();
380 return ERROR_SUCCESS;
383 DWORD HardenProcessIntegrityLevelPolicy() {
384 if (base::win::GetVersion() < base::win::VERSION_WIN7)
385 return ERROR_SUCCESS;
387 HANDLE token_handle;
388 if (!::OpenProcessToken(GetCurrentProcess(), READ_CONTROL | WRITE_OWNER,
389 &token_handle))
390 return ::GetLastError();
392 base::win::ScopedHandle token(token_handle);
394 return HardenTokenIntegrityLevelPolicy(token.Get());
397 } // namespace sandbox