Home | History | Annotate | Download | only in src
      1 // Copyright (c) 2011 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 <string>
      6 
      7 #include "sandbox/win/src/filesystem_policy.h"
      8 
      9 #include "base/logging.h"
     10 #include "base/win/scoped_handle.h"
     11 #include "sandbox/win/src/ipc_tags.h"
     12 #include "sandbox/win/src/policy_engine_opcodes.h"
     13 #include "sandbox/win/src/policy_params.h"
     14 #include "sandbox/win/src/sandbox_utils.h"
     15 #include "sandbox/win/src/sandbox_types.h"
     16 #include "sandbox/win/src/win_utils.h"
     17 
     18 namespace {
     19 
     20 NTSTATUS NtCreateFileInTarget(HANDLE* target_file_handle,
     21                               ACCESS_MASK desired_access,
     22                               OBJECT_ATTRIBUTES* obj_attributes,
     23                               IO_STATUS_BLOCK* io_status_block,
     24                               ULONG file_attributes,
     25                               ULONG share_access,
     26                               ULONG create_disposition,
     27                               ULONG create_options,
     28                               PVOID ea_buffer,
     29                               ULONG ea_lenght,
     30                               HANDLE target_process) {
     31   NtCreateFileFunction NtCreateFile = NULL;
     32   ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile);
     33 
     34   HANDLE local_handle = INVALID_HANDLE_VALUE;
     35   NTSTATUS status = NtCreateFile(&local_handle, desired_access, obj_attributes,
     36                                  io_status_block, NULL, file_attributes,
     37                                  share_access, create_disposition,
     38                                  create_options, ea_buffer, ea_lenght);
     39   if (!NT_SUCCESS(status)) {
     40     return status;
     41   }
     42 
     43   if (!sandbox::SameObject(local_handle, obj_attributes->ObjectName->Buffer)) {
     44     // The handle points somewhere else. Fail the operation.
     45     ::CloseHandle(local_handle);
     46     return STATUS_ACCESS_DENIED;
     47   }
     48 
     49   if (!::DuplicateHandle(::GetCurrentProcess(), local_handle,
     50                          target_process, target_file_handle, 0, FALSE,
     51                          DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
     52     return STATUS_ACCESS_DENIED;
     53   }
     54   return STATUS_SUCCESS;
     55 }
     56 
     57 }  // namespace.
     58 
     59 namespace sandbox {
     60 
     61 bool FileSystemPolicy::GenerateRules(const wchar_t* name,
     62                                      TargetPolicy::Semantics semantics,
     63                                      LowLevelPolicy* policy) {
     64   base::string16 mod_name(name);
     65   if (mod_name.empty()) {
     66     return false;
     67   }
     68 
     69   // Don't do any pre-processing if the name starts like the the native
     70   // object manager style.
     71   if (0 != _wcsnicmp(mod_name.c_str(), kNTObjManPrefix, kNTObjManPrefixLen)) {
     72     // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the
     73     // infrastructure to normalize names. In any case we need to escape the
     74     // question marks.
     75     if (!PreProcessName(mod_name, &mod_name)) {
     76       // The path to be added might contain a reparse point.
     77       NOTREACHED();
     78       return false;
     79     }
     80 
     81     mod_name = FixNTPrefixForMatch(mod_name);
     82     name = mod_name.c_str();
     83   }
     84 
     85   EvalResult result = ASK_BROKER;
     86 
     87   // List of supported calls for the filesystem.
     88   const unsigned kCallNtCreateFile = 0x1;
     89   const unsigned kCallNtOpenFile = 0x2;
     90   const unsigned kCallNtQueryAttributesFile = 0x4;
     91   const unsigned kCallNtQueryFullAttributesFile = 0x8;
     92   const unsigned kCallNtSetInfoRename = 0x10;
     93 
     94   DWORD  rule_to_add = kCallNtOpenFile | kCallNtCreateFile |
     95                        kCallNtQueryAttributesFile |
     96                        kCallNtQueryFullAttributesFile | kCallNtSetInfoRename;
     97 
     98   PolicyRule create(result);
     99   PolicyRule open(result);
    100   PolicyRule query(result);
    101   PolicyRule query_full(result);
    102   PolicyRule rename(result);
    103 
    104   switch (semantics) {
    105     case TargetPolicy::FILES_ALLOW_DIR_ANY: {
    106       open.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND);
    107       create.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND);
    108       break;
    109     }
    110     case TargetPolicy::FILES_ALLOW_READONLY: {
    111       // We consider all flags that are not known to be readonly as potentially
    112       // used for write.
    113       DWORD allowed_flags = FILE_READ_DATA | FILE_READ_ATTRIBUTES |
    114                             FILE_READ_EA | SYNCHRONIZE | FILE_EXECUTE |
    115                             GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL;
    116       DWORD restricted_flags = ~allowed_flags;
    117       open.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND);
    118       create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND);
    119 
    120       // Read only access don't work for rename.
    121       rule_to_add &= ~kCallNtSetInfoRename;
    122       break;
    123     }
    124     case TargetPolicy::FILES_ALLOW_QUERY: {
    125       // Here we don't want to add policy for the open or the create.
    126       rule_to_add &= ~(kCallNtOpenFile | kCallNtCreateFile |
    127                        kCallNtSetInfoRename);
    128       break;
    129     }
    130     case TargetPolicy::FILES_ALLOW_ANY: {
    131       break;
    132     }
    133     default: {
    134       NOTREACHED();
    135       return false;
    136     }
    137   }
    138 
    139   if ((rule_to_add & kCallNtCreateFile) &&
    140       (!create.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) ||
    141        !policy->AddRule(IPC_NTCREATEFILE_TAG, &create))) {
    142     return false;
    143   }
    144 
    145   if ((rule_to_add & kCallNtOpenFile) &&
    146       (!open.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) ||
    147        !policy->AddRule(IPC_NTOPENFILE_TAG, &open))) {
    148     return false;
    149   }
    150 
    151   if ((rule_to_add & kCallNtQueryAttributesFile) &&
    152       (!query.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) ||
    153        !policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &query))) {
    154     return false;
    155   }
    156 
    157   if ((rule_to_add & kCallNtQueryFullAttributesFile) &&
    158       (!query_full.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE)
    159        || !policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG,
    160                            &query_full))) {
    161     return false;
    162   }
    163 
    164   if ((rule_to_add & kCallNtSetInfoRename) &&
    165       (!rename.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) ||
    166        !policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &rename))) {
    167     return false;
    168   }
    169 
    170   return true;
    171 }
    172 
    173 // Right now we insert two rules, to be evaluated before any user supplied rule:
    174 // - go to the broker if the path doesn't look like the paths that we push on
    175 //    the policy (namely \??\something).
    176 // - go to the broker if it looks like this is a short-name path.
    177 //
    178 // It is possible to add a rule to go to the broker in any case; it would look
    179 // something like:
    180 //    rule = new PolicyRule(ASK_BROKER);
    181 //    rule->AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND);
    182 //    policy->AddRule(service, rule);
    183 bool FileSystemPolicy::SetInitialRules(LowLevelPolicy* policy) {
    184   PolicyRule format(ASK_BROKER);
    185   PolicyRule short_name(ASK_BROKER);
    186 
    187   bool rv = format.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND);
    188   rv &= format.AddStringMatch(IF_NOT, FileName::NAME, L"\\/?/?\\*",
    189                               CASE_SENSITIVE);
    190 
    191   rv &= short_name.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND);
    192   rv &= short_name.AddStringMatch(IF, FileName::NAME, L"*~*", CASE_SENSITIVE);
    193 
    194   if (!rv || !policy->AddRule(IPC_NTCREATEFILE_TAG, &format))
    195     return false;
    196 
    197   if (!policy->AddRule(IPC_NTCREATEFILE_TAG, &short_name))
    198     return false;
    199 
    200   if (!policy->AddRule(IPC_NTOPENFILE_TAG, &format))
    201     return false;
    202 
    203   if (!policy->AddRule(IPC_NTOPENFILE_TAG, &short_name))
    204     return false;
    205 
    206   if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &format))
    207     return false;
    208 
    209   if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &short_name))
    210     return false;
    211 
    212   if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &format))
    213     return false;
    214 
    215   if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &short_name))
    216     return false;
    217 
    218   if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &format))
    219     return false;
    220 
    221   if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &short_name))
    222     return false;
    223 
    224   return true;
    225 }
    226 
    227 bool FileSystemPolicy::CreateFileAction(EvalResult eval_result,
    228                                         const ClientInfo& client_info,
    229                                         const base::string16 &file,
    230                                         uint32 attributes,
    231                                         uint32 desired_access,
    232                                         uint32 file_attributes,
    233                                         uint32 share_access,
    234                                         uint32 create_disposition,
    235                                         uint32 create_options,
    236                                         HANDLE *handle,
    237                                         NTSTATUS* nt_status,
    238                                         ULONG_PTR *io_information) {
    239   // The only action supported is ASK_BROKER which means create the requested
    240   // file as specified.
    241   if (ASK_BROKER != eval_result) {
    242     *nt_status = STATUS_ACCESS_DENIED;
    243     return false;
    244   }
    245   IO_STATUS_BLOCK io_block = {0};
    246   UNICODE_STRING uni_name = {0};
    247   OBJECT_ATTRIBUTES obj_attributes = {0};
    248   InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name);
    249   *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes,
    250                                     &io_block, file_attributes, share_access,
    251                                     create_disposition, create_options, NULL,
    252                                     0, client_info.process);
    253 
    254   *io_information = io_block.Information;
    255   return true;
    256 }
    257 
    258 bool FileSystemPolicy::OpenFileAction(EvalResult eval_result,
    259                                       const ClientInfo& client_info,
    260                                       const base::string16 &file,
    261                                       uint32 attributes,
    262                                       uint32 desired_access,
    263                                       uint32 share_access,
    264                                       uint32 open_options,
    265                                       HANDLE *handle,
    266                                       NTSTATUS* nt_status,
    267                                       ULONG_PTR *io_information) {
    268   // The only action supported is ASK_BROKER which means open the requested
    269   // file as specified.
    270   if (ASK_BROKER != eval_result) {
    271     *nt_status = STATUS_ACCESS_DENIED;
    272     return true;
    273   }
    274   // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and
    275   // CreateDisposition = FILE_OPEN.
    276   IO_STATUS_BLOCK io_block = {0};
    277   UNICODE_STRING uni_name = {0};
    278   OBJECT_ATTRIBUTES obj_attributes = {0};
    279   InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name);
    280   *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes,
    281                                     &io_block, 0, share_access, FILE_OPEN,
    282                                     open_options, NULL, 0,
    283                                     client_info.process);
    284 
    285   *io_information = io_block.Information;
    286   return true;
    287 }
    288 
    289 bool FileSystemPolicy::QueryAttributesFileAction(
    290     EvalResult eval_result,
    291     const ClientInfo& client_info,
    292     const base::string16 &file,
    293     uint32 attributes,
    294     FILE_BASIC_INFORMATION* file_info,
    295     NTSTATUS* nt_status) {
    296   // The only action supported is ASK_BROKER which means query the requested
    297   // file as specified.
    298   if (ASK_BROKER != eval_result) {
    299     *nt_status = STATUS_ACCESS_DENIED;
    300     return true;
    301   }
    302 
    303   NtQueryAttributesFileFunction NtQueryAttributesFile = NULL;
    304   ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile);
    305 
    306   UNICODE_STRING uni_name = {0};
    307   OBJECT_ATTRIBUTES obj_attributes = {0};
    308   InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name);
    309   *nt_status = NtQueryAttributesFile(&obj_attributes, file_info);
    310 
    311   return true;
    312 }
    313 
    314 bool FileSystemPolicy::QueryFullAttributesFileAction(
    315     EvalResult eval_result,
    316     const ClientInfo& client_info,
    317     const base::string16 &file,
    318     uint32 attributes,
    319     FILE_NETWORK_OPEN_INFORMATION* file_info,
    320     NTSTATUS* nt_status) {
    321   // The only action supported is ASK_BROKER which means query the requested
    322   // file as specified.
    323   if (ASK_BROKER != eval_result) {
    324     *nt_status = STATUS_ACCESS_DENIED;
    325     return true;
    326   }
    327 
    328   NtQueryFullAttributesFileFunction NtQueryFullAttributesFile = NULL;
    329   ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile);
    330 
    331   UNICODE_STRING uni_name = {0};
    332   OBJECT_ATTRIBUTES obj_attributes = {0};
    333   InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name);
    334   *nt_status = NtQueryFullAttributesFile(&obj_attributes, file_info);
    335 
    336   return true;
    337 }
    338 
    339 bool FileSystemPolicy::SetInformationFileAction(
    340     EvalResult eval_result, const ClientInfo& client_info,
    341     HANDLE target_file_handle, void* file_info, uint32 length,
    342     uint32 info_class, IO_STATUS_BLOCK* io_block,
    343     NTSTATUS* nt_status) {
    344   // The only action supported is ASK_BROKER which means open the requested
    345   // file as specified.
    346   if (ASK_BROKER != eval_result) {
    347     *nt_status = STATUS_ACCESS_DENIED;
    348     return true;
    349   }
    350 
    351   NtSetInformationFileFunction NtSetInformationFile = NULL;
    352   ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile);
    353 
    354   HANDLE local_handle = NULL;
    355   if (!::DuplicateHandle(client_info.process, target_file_handle,
    356                          ::GetCurrentProcess(), &local_handle, 0, FALSE,
    357                          DUPLICATE_SAME_ACCESS)) {
    358     *nt_status = STATUS_ACCESS_DENIED;
    359     return true;
    360   }
    361 
    362   base::win::ScopedHandle handle(local_handle);
    363 
    364   FILE_INFORMATION_CLASS file_info_class =
    365       static_cast<FILE_INFORMATION_CLASS>(info_class);
    366   *nt_status = NtSetInformationFile(local_handle, io_block, file_info, length,
    367                                     file_info_class);
    368 
    369   return true;
    370 }
    371 
    372 bool PreProcessName(const base::string16& path, base::string16* new_path) {
    373   ConvertToLongPath(path, new_path);
    374 
    375   bool reparsed = false;
    376   if (ERROR_SUCCESS != IsReparsePoint(*new_path, &reparsed))
    377     return false;
    378 
    379   // We can't process reparsed file.
    380   return !reparsed;
    381 }
    382 
    383 base::string16 FixNTPrefixForMatch(const base::string16& name) {
    384   base::string16 mod_name = name;
    385 
    386   // NT prefix escaped for rule matcher
    387   const wchar_t kNTPrefixEscaped[] = L"\\/?/?\\";
    388   const int kNTPrefixEscapedLen = arraysize(kNTPrefixEscaped) - 1;
    389 
    390   if (0 != mod_name.compare(0, kNTPrefixLen, kNTPrefix)) {
    391     if (0 != mod_name.compare(0, kNTPrefixEscapedLen, kNTPrefixEscaped)) {
    392       // TODO(nsylvain): Find a better way to do name resolution. Right now we
    393       // take the name and we expand it.
    394       mod_name.insert(0, kNTPrefixEscaped);
    395     }
    396   } else {
    397     // Start of name matches NT prefix, replace with escaped format
    398     // Fixes bug: 334882
    399     mod_name.replace(0, kNTPrefixLen, kNTPrefixEscaped);
    400   }
    401 
    402   return mod_name;
    403 }
    404 
    405 }  // namespace sandbox
    406