• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CreateRestrictedToken(HANDLE * token_handle,TokenLevel security_level,IntegrityLevel integrity_level,TokenType token_type)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 
StartRestrictedProcessInJob(wchar_t * command_line,TokenLevel primary_level,TokenLevel impersonation_level,JobLevel job_level,HANDLE * const job_handle_ret)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 
SetObjectIntegrityLabel(HANDLE handle,SE_OBJECT_TYPE type,const wchar_t * ace_access,const wchar_t * integrity_level_sid)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 
GetIntegrityLevelString(IntegrityLevel integrity_level)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 }
SetTokenIntegrityLevel(HANDLE token,IntegrityLevel integrity_level)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 
SetProcessIntegrityLevel(IntegrityLevel integrity_level)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