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
<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
: {
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, 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 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.
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 &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(),
217 return ::GetLastError();
221 err_code
= job
.AssignProcessToJob(process_info
.process_handle());
222 if (ERROR_SUCCESS
!= err_code
) {
223 ::TerminateProcess(process_info
.process_handle(),
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.
248 DWORD error
= ERROR_SUCCESS
;
249 PSECURITY_DESCRIPTOR sec_desc
= NULL
;
252 BOOL sacl_present
= FALSE
;
253 BOOL sacl_defaulted
= FALSE
;
255 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl
.c_str(),
258 if (::GetSecurityDescriptorSacl(sec_desc
, &sacl_present
, &sacl
,
260 error
= ::SetSecurityInfo(handle
, type
,
261 LABEL_SECURITY_INFORMATION
, NULL
, NULL
, NULL
,
264 error
= ::GetLastError();
267 ::LocalFree(sec_desc
);
269 return::GetLastError();
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
:
291 case INTEGRITY_LEVEL_LAST
:
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
,
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
;
336 if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT
,
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
)
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
,
366 return ::GetLastError();
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
;
387 if (!::SetKernelObjectSecurity(token
, LABEL_SECURITY_INFORMATION
,
389 return ::GetLastError();
391 return ERROR_SUCCESS
;
394 DWORD
HardenProcessIntegrityLevelPolicy() {
395 if (base::win::GetVersion() < base::win::VERSION_WIN7
)
396 return ERROR_SUCCESS
;
399 if (!::OpenProcessToken(GetCurrentProcess(), READ_CONTROL
| WRITE_OWNER
,
401 return ::GetLastError();
403 base::win::ScopedHandle
token(token_handle
);
405 return HardenTokenIntegrityLevelPolicy(token
.Get());
408 } // namespace sandbox