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. 4 5 #include <aclapi.h> 6 #include <sddl.h> 7 #include <vector> 8 9 #include "sandbox/win/src/restricted_token_utils.h" 10 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" 19 20 namespace sandbox { 21 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; 28 29 RestrictedToken restricted_token; 30 restricted_token.Init(NULL); // Initialized with the current process token 31 32 std::vector<base::string16> privilege_exceptions; 33 std::vector<Sid> sid_exceptions; 34 35 bool deny_sids = true; 36 bool remove_privileges = true; 37 38 switch (security_level) { 39 case USER_UNPROTECTED: { 40 deny_sids = false; 41 remove_privileges = false; 42 break; 43 } 44 case USER_RESTRICTED_SAME_ACCESS: { 45 deny_sids = false; 46 remove_privileges = false; 47 48 unsigned err_code = restricted_token.AddRestrictingSidAllSids(); 49 if (ERROR_SUCCESS != err_code) 50 return err_code; 51 52 break; 53 } 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; 61 } 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; 74 } 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); 83 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; 92 } 93 case USER_RESTRICTED: { 94 privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); 95 restricted_token.AddUserSidForDenyOnly(); 96 restricted_token.AddRestrictingSid(WinRestrictedCodeSid); 97 break; 98 } 99 case USER_LOCKDOWN: { 100 restricted_token.AddUserSidForDenyOnly(); 101 restricted_token.AddRestrictingSid(WinNullSid); 102 break; 103 } 104 default: { 105 return ERROR_BAD_ARGUMENTS; 106 } 107 } 108 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; 114 } 115 116 if (remove_privileges) { 117 err_code = restricted_token.DeleteAllPrivileges(&privilege_exceptions); 118 if (ERROR_SUCCESS != err_code) 119 return err_code; 120 } 121 122 restricted_token.SetIntegrityLevel(integrity_level); 123 124 switch (token_type) { 125 case PRIMARY: { 126 err_code = restricted_token.GetRestrictedTokenHandle(token_handle); 127 break; 128 } 129 case IMPERSONATION: { 130 err_code = restricted_token.GetRestrictedTokenHandleForImpersonation( 131 token_handle); 132 break; 133 } 134 default: { 135 err_code = ERROR_BAD_ARGUMENTS; 136 break; 137 } 138 } 139 140 return err_code; 141 } 142 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); 150 if (ERROR_SUCCESS != err_code) 151 return err_code; 152 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; 159 } 160 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; 169 } 170 base::win::ScopedHandle primary_token(primary_token_handle); 171 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; 181 } 182 base::win::ScopedHandle impersonation_token(impersonation_token_handle); 183 184 // Start the process 185 STARTUPINFO startup_info = {0}; 186 PROCESS_INFORMATION temp_process_info = {}; 187 DWORD flags = CREATE_SUSPENDED; 188 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; 193 } 194 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(); 207 } 208 base::win::ScopedProcessInformation process_info(temp_process_info); 209 210 // Change the token of the main thread of the new process for the 211 // impersonation token with more rights. 212 { 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(); 218 } 219 } 220 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(); 226 } 227 228 // Start the application 229 ::ResumeThread(process_info.thread_handle()); 230 231 (*job_handle_ret) = job.Detach(); 232 233 return ERROR_SUCCESS; 234 } 235 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")"; 247 248 DWORD error = ERROR_SUCCESS; 249 PSECURITY_DESCRIPTOR sec_desc = NULL; 250 251 PACL sacl = NULL; 252 BOOL sacl_present = FALSE; 253 BOOL sacl_defaulted = FALSE; 254 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(); 265 } 266 267 ::LocalFree(sec_desc); 268 } else { 269 return::GetLastError(); 270 } 271 272 return error; 273 } 274 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; 293 } 294 295 NOTREACHED(); 296 return NULL; 297 } 298 DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) { 299 if (base::win::GetVersion() < base::win::VERSION_VISTA) 300 return ERROR_SUCCESS; 301 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; 306 } 307 308 PSID integrity_sid = NULL; 309 if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid)) 310 return ::GetLastError(); 311 312 TOKEN_MANDATORY_LABEL label = {0}; 313 label.Label.Attributes = SE_GROUP_INTEGRITY; 314 label.Label.Sid = integrity_sid; 315 316 DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid); 317 BOOL result = ::SetTokenInformation(token, TokenIntegrityLevel, &label, 318 size); 319 ::LocalFree(integrity_sid); 320 321 return result ? ERROR_SUCCESS : ::GetLastError(); 322 } 323 324 DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) { 325 if (base::win::GetVersion() < base::win::VERSION_VISTA) 326 return ERROR_SUCCESS; 327 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; 333 } 334 335 HANDLE token_handle; 336 if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, 337 &token_handle)) 338 return ::GetLastError(); 339 340 base::win::ScopedHandle token(token_handle); 341 342 return SetTokenIntegrityLevel(token.Get(), integrity_level); 343 } 344 345 } // namespace sandbox 346