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(TokenLevel security_level
,
23 IntegrityLevel integrity_level
,
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
: {
38 remove_privileges
= false;
41 case USER_RESTRICTED_SAME_ACCESS
: {
43 remove_privileges
= false;
45 unsigned err_code
= restricted_token
.AddRestrictingSidAllSids();
46 if (ERROR_SUCCESS
!= err_code
)
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
);
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();
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
86 if (base::win::GetVersion() >= base::win::VERSION_VISTA
)
87 restricted_token
.AddRestrictingSidLogonSession();
90 case USER_RESTRICTED
: {
91 privilege_exceptions
.push_back(SE_CHANGE_NOTIFY_NAME
);
92 restricted_token
.AddUserSidForDenyOnly();
93 restricted_token
.AddRestrictingSid(WinRestrictedCodeSid
);
97 restricted_token
.AddUserSidForDenyOnly();
98 restricted_token
.AddRestrictingSid(WinNullSid
);
102 return ERROR_BAD_ARGUMENTS
;
106 DWORD err_code
= ERROR_SUCCESS
;
108 err_code
= restricted_token
.AddAllSidsForDenyOnly(&sid_exceptions
);
109 if (ERROR_SUCCESS
!= err_code
)
113 if (remove_privileges
) {
114 err_code
= restricted_token
.DeleteAllPrivileges(&privilege_exceptions
);
115 if (ERROR_SUCCESS
!= err_code
)
119 restricted_token
.SetIntegrityLevel(integrity_level
);
121 switch (token_type
) {
123 err_code
= restricted_token
.GetRestrictedToken(token
);
126 case IMPERSONATION
: {
127 err_code
= restricted_token
.GetRestrictedTokenForImpersonation(token
);
131 err_code
= ERROR_BAD_ARGUMENTS
;
139 DWORD
StartRestrictedProcessInJob(wchar_t *command_line
,
140 TokenLevel primary_level
,
141 TokenLevel impersonation_level
,
143 HANDLE
*const job_handle_ret
) {
145 DWORD err_code
= job
.Init(job_level
, NULL
, 0, 0);
146 if (ERROR_SUCCESS
!= err_code
)
149 if (JOB_UNPROTECTED
!= job_level
) {
150 // Share the Desktop handle to be able to use MessageBox() in the sandboxed
152 err_code
= job
.UserHandleGrantAccess(GetDesktopWindow());
153 if (ERROR_SUCCESS
!= 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
)
165 // Create the impersonation token (restricted) to be able to start the
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
)
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.
187 NULL
, // No security attribute.
188 NULL
, // No thread attribute.
189 FALSE
, // Do not inherit handles.
191 NULL
, // Use the environment of the caller.
192 NULL
, // Use current directory of the caller.
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(),
206 return ::GetLastError();
210 err_code
= job
.AssignProcessToJob(process_info
.process_handle());
211 if (ERROR_SUCCESS
!= err_code
) {
212 ::TerminateProcess(process_info
.process_handle(),
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.
237 DWORD error
= ERROR_SUCCESS
;
238 PSECURITY_DESCRIPTOR sec_desc
= NULL
;
241 BOOL sacl_present
= FALSE
;
242 BOOL sacl_defaulted
= FALSE
;
244 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl
.c_str(),
247 if (::GetSecurityDescriptorSacl(sec_desc
, &sacl_present
, &sacl
,
249 error
= ::SetSecurityInfo(handle
, type
,
250 LABEL_SECURITY_INFORMATION
, NULL
, NULL
, NULL
,
253 error
= ::GetLastError();
256 ::LocalFree(sec_desc
);
258 return::GetLastError();
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
:
280 case INTEGRITY_LEVEL_LAST
:
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
,
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
;
325 if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT
,
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
)
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
,
355 return ::GetLastError();
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
;
376 if (!::SetKernelObjectSecurity(token
, LABEL_SECURITY_INFORMATION
,
378 return ::GetLastError();
380 return ERROR_SUCCESS
;
383 DWORD
HardenProcessIntegrityLevelPolicy() {
384 if (base::win::GetVersion() < base::win::VERSION_WIN7
)
385 return ERROR_SUCCESS
;
388 if (!::OpenProcessToken(GetCurrentProcess(), READ_CONTROL
| WRITE_OWNER
,
390 return ::GetLastError();
392 base::win::ScopedHandle
token(token_handle
);
394 return HardenTokenIntegrityLevelPolicy(token
.Get());
397 } // namespace sandbox