|
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 |