Home | History | Annotate | Download | only in src
      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
    345