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 |