- add sources.
[platform/framework/web/crosswalk.git] / src / sandbox / win / src / restricted_token_utils.cc
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