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<std::wstring> 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 base::win::ScopedProcessInformation 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 process_info.Receive())) { 206 return ::GetLastError(); 207 } 208 209 // Change the token of the main thread of the new process for the 210 // impersonation token with more rights. 211 { 212 HANDLE temp_thread = process_info.thread_handle(); 213 if (!::SetThreadToken(&temp_thread, impersonation_token.Get())) { 214 ::TerminateProcess(process_info.process_handle(), 215 0); // exit code 216 return ::GetLastError(); 217 } 218 } 219 220 err_code = job.AssignProcessToJob(process_info.process_handle()); 221 if (ERROR_SUCCESS != err_code) { 222 ::TerminateProcess(process_info.process_handle(), 223 0); // exit code 224 return ::GetLastError(); 225 } 226 227 // Start the application 228 ::ResumeThread(process_info.thread_handle()); 229 230 (*job_handle_ret) = job.Detach(); 231 232 return ERROR_SUCCESS; 233 } 234 235 DWORD SetObjectIntegrityLabel(HANDLE handle, SE_OBJECT_TYPE type, 236 const wchar_t* ace_access, 237 const wchar_t* integrity_level_sid) { 238 // Build the SDDL string for the label. 239 std::wstring sddl = L"S:("; // SDDL for a SACL. 240 sddl += SDDL_MANDATORY_LABEL; // Ace Type is "Mandatory Label". 241 sddl += L";;"; // No Ace Flags. 242 sddl += ace_access; // Add the ACE access. 243 sddl += L";;;"; // No ObjectType and Inherited Object Type. 244 sddl += integrity_level_sid; // Trustee Sid. 245 sddl += L")"; 246 247 DWORD error = ERROR_SUCCESS; 248 PSECURITY_DESCRIPTOR sec_desc = NULL; 249 250 PACL sacl = NULL; 251 BOOL sacl_present = FALSE; 252 BOOL sacl_defaulted = FALSE; 253 254 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), 255 SDDL_REVISION, 256 &sec_desc, NULL)) { 257 if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl, 258 &sacl_defaulted)) { 259 error = ::SetSecurityInfo(handle, type, 260 LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, 261 sacl); 262 } else { 263 error = ::GetLastError(); 264 } 265 266 ::LocalFree(sec_desc); 267 } else { 268 return::GetLastError(); 269 } 270 271 return error; 272 } 273 274 const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level) { 275 switch (integrity_level) { 276 case INTEGRITY_LEVEL_SYSTEM: 277 return L"S-1-16-16384"; 278 case INTEGRITY_LEVEL_HIGH: 279 return L"S-1-16-12288"; 280 case INTEGRITY_LEVEL_MEDIUM: 281 return L"S-1-16-8192"; 282 case INTEGRITY_LEVEL_MEDIUM_LOW: 283 return L"S-1-16-6144"; 284 case INTEGRITY_LEVEL_LOW: 285 return L"S-1-16-4096"; 286 case INTEGRITY_LEVEL_BELOW_LOW: 287 return L"S-1-16-2048"; 288 case INTEGRITY_LEVEL_UNTRUSTED: 289 return L"S-1-16-0"; 290 case INTEGRITY_LEVEL_LAST: 291 return NULL; 292 } 293 294 NOTREACHED(); 295 return NULL; 296 } 297 DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) { 298 if (base::win::GetVersion() < base::win::VERSION_VISTA) 299 return ERROR_SUCCESS; 300 301 const wchar_t* integrity_level_str = GetIntegrityLevelString(integrity_level); 302 if (!integrity_level_str) { 303 // No mandatory level specified, we don't change it. 304 return ERROR_SUCCESS; 305 } 306 307 PSID integrity_sid = NULL; 308 if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid)) 309 return ::GetLastError(); 310 311 TOKEN_MANDATORY_LABEL label = {0}; 312 label.Label.Attributes = SE_GROUP_INTEGRITY; 313 label.Label.Sid = integrity_sid; 314 315 DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid); 316 BOOL result = ::SetTokenInformation(token, TokenIntegrityLevel, &label, 317 size); 318 ::LocalFree(integrity_sid); 319 320 return result ? ERROR_SUCCESS : ::GetLastError(); 321 } 322 323 DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) { 324 if (base::win::GetVersion() < base::win::VERSION_VISTA) 325 return ERROR_SUCCESS; 326 327 // We don't check for an invalid level here because we'll just let it 328 // fail on the SetTokenIntegrityLevel call later on. 329 if (integrity_level == INTEGRITY_LEVEL_LAST) { 330 // No mandatory level specified, we don't change it. 331 return ERROR_SUCCESS; 332 } 333 334 HANDLE token_handle; 335 if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, 336 &token_handle)) 337 return ::GetLastError(); 338 339 base::win::ScopedHandle token(token_handle); 340 341 return SetTokenIntegrityLevel(token.Get(), integrity_level); 342 } 343 344 } // namespace sandbox 345