| OLD | NEW |
| (Empty) |
| 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/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/src/job.h" | |
| 16 #include "sandbox/src/restricted_token.h" | |
| 17 #include "sandbox/src/security_level.h" | |
| 18 #include "sandbox/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 | |
| OLD | NEW |