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