Home | History | Annotate | Download | only in syscall_broker
      1 // Copyright 2014 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 "sandbox/linux/syscall_broker/broker_file_permission.h"
      6 
      7 #include <fcntl.h>
      8 #include <stddef.h>
      9 #include <string.h>
     10 
     11 #include <string>
     12 
     13 #include "base/logging.h"
     14 #include "sandbox/linux/syscall_broker/broker_common.h"
     15 
     16 namespace sandbox {
     17 
     18 namespace syscall_broker {
     19 
     20 // Async signal safe
     21 bool BrokerFilePermission::ValidatePath(const char* path) {
     22   if (!path)
     23     return false;
     24 
     25   const size_t len = strlen(path);
     26   // No empty paths
     27   if (len == 0)
     28     return false;
     29   // Paths must be absolute and not relative
     30   if (path[0] != '/')
     31     return false;
     32   // No trailing / (but "/" is valid)
     33   if (len > 1 && path[len - 1] == '/')
     34     return false;
     35   // No trailing /..
     36   if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' &&
     37       path[len - 1] == '.')
     38     return false;
     39   // No /../ anywhere
     40   for (size_t i = 0; i < len; i++) {
     41     if (path[i] == '/' && (len - i) > 3) {
     42       if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') {
     43         return false;
     44       }
     45     }
     46   }
     47   return true;
     48 }
     49 
     50 // Async signal safe
     51 // Calls std::string::c_str(), strncmp and strlen. All these
     52 // methods are async signal safe in common standard libs.
     53 // TODO(leecam): remove dependency on std::string
     54 bool BrokerFilePermission::MatchPath(const char* requested_filename) const {
     55   const char* path = path_.c_str();
     56   if ((recursive_ && strncmp(requested_filename, path, strlen(path)) == 0)) {
     57     // Note: This prefix match will allow any path under the whitelisted
     58     // path, for any number of directory levels. E.g. if the whitelisted
     59     // path is /good/ then the following will be permitted by the policy.
     60     //   /good/file1
     61     //   /good/folder/file2
     62     //   /good/folder/folder2/file3
     63     // If an attacker could make 'folder' a symlink to ../../ they would have
     64     // access to the entire filesystem.
     65     // Whitelisting with multiple depths is useful, e.g /proc/ but
     66     // the system needs to ensure symlinks can not be created!
     67     // That said if an attacker can convert any of the absolute paths
     68     // to a symlink they can control any file on the system also.
     69     return true;
     70   } else if (strcmp(requested_filename, path) == 0) {
     71     return true;
     72   }
     73   return false;
     74 }
     75 
     76 // Async signal safe.
     77 // External call to std::string::c_str() is
     78 // called in MatchPath.
     79 // TODO(leecam): remove dependency on std::string
     80 bool BrokerFilePermission::CheckAccess(const char* requested_filename,
     81                                        int mode,
     82                                        const char** file_to_access) const {
     83   // First, check if |mode| is existence, ability to read or ability
     84   // to write. We do not support X_OK.
     85   if (mode != F_OK && mode & ~(R_OK | W_OK)) {
     86     return false;
     87   }
     88 
     89   if (!ValidatePath(requested_filename))
     90     return false;
     91 
     92   if (!MatchPath(requested_filename)) {
     93     return false;
     94   }
     95   bool allowed = false;
     96   switch (mode) {
     97     case F_OK:
     98       if (allow_read_ || allow_write_)
     99         allowed = true;
    100       break;
    101     case R_OK:
    102       if (allow_read_)
    103         allowed = true;
    104       break;
    105     case W_OK:
    106       if (allow_write_)
    107         allowed = true;
    108       break;
    109     case R_OK | W_OK:
    110       if (allow_read_ && allow_write_)
    111         allowed = true;
    112       break;
    113     default:
    114       return false;
    115   }
    116 
    117   if (allowed && file_to_access) {
    118     if (!recursive_)
    119       *file_to_access = path_.c_str();
    120     else
    121       *file_to_access = requested_filename;
    122   }
    123   return allowed;
    124 }
    125 
    126 // Async signal safe.
    127 // External call to std::string::c_str() is
    128 // called in MatchPath.
    129 // TODO(leecam): remove dependency on std::string
    130 bool BrokerFilePermission::CheckOpen(const char* requested_filename,
    131                                      int flags,
    132                                      const char** file_to_open,
    133                                      bool* unlink_after_open) const {
    134   if (!ValidatePath(requested_filename))
    135     return false;
    136 
    137   if (!MatchPath(requested_filename)) {
    138     return false;
    139   }
    140 
    141   // First, check the access mode is valid.
    142   const int access_mode = flags & O_ACCMODE;
    143   if (access_mode != O_RDONLY && access_mode != O_WRONLY &&
    144       access_mode != O_RDWR) {
    145     return false;
    146   }
    147 
    148   // Check if read is allowed
    149   if (!allow_read_ && (access_mode == O_RDONLY || access_mode == O_RDWR)) {
    150     return false;
    151   }
    152 
    153   // Check if write is allowed
    154   if (!allow_write_ && (access_mode == O_WRONLY || access_mode == O_RDWR)) {
    155     return false;
    156   }
    157 
    158   // Check if file creation is allowed.
    159   if (!allow_create_ && (flags & O_CREAT)) {
    160     return false;
    161   }
    162 
    163   // If O_CREAT is present, ensure O_EXCL
    164   if ((flags & O_CREAT) && !(flags & O_EXCL)) {
    165     return false;
    166   }
    167 
    168   // If this file is to be unlinked, ensure it's created.
    169   if (unlink_ && !(flags & O_CREAT)) {
    170     return false;
    171   }
    172 
    173   // Some flags affect the behavior of the current process. We don't support
    174   // them and don't allow them for now.
    175   if (flags & kCurrentProcessOpenFlagsMask) {
    176     return false;
    177   }
    178 
    179   // Now check that all the flags are known to us.
    180   const int creation_and_status_flags = flags & ~O_ACCMODE;
    181 
    182   const int known_flags = O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT |
    183                           O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME |
    184                           O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY |
    185                           O_SYNC | O_TRUNC;
    186 
    187   const int unknown_flags = ~known_flags;
    188   const bool has_unknown_flags = creation_and_status_flags & unknown_flags;
    189 
    190   if (has_unknown_flags)
    191     return false;
    192 
    193   if (file_to_open) {
    194     if (!recursive_)
    195       *file_to_open = path_.c_str();
    196     else
    197       *file_to_open = requested_filename;
    198   }
    199   if (unlink_after_open)
    200     *unlink_after_open = unlink_;
    201 
    202   return true;
    203 }
    204 
    205 const char* BrokerFilePermission::GetErrorMessageForTests() {
    206   static char kInvalidBrokerFileString[] = "Invalid BrokerFilePermission";
    207   return kInvalidBrokerFileString;
    208 }
    209 
    210 BrokerFilePermission::BrokerFilePermission(const std::string& path,
    211                                            bool recursive,
    212                                            bool unlink,
    213                                            bool allow_read,
    214                                            bool allow_write,
    215                                            bool allow_create)
    216     : path_(path),
    217       recursive_(recursive),
    218       unlink_(unlink),
    219       allow_read_(allow_read),
    220       allow_write_(allow_write),
    221       allow_create_(allow_create) {
    222   // Validate this permission and die if invalid!
    223 
    224   // Must have enough length for a '/'
    225   CHECK(path_.length() > 0) << GetErrorMessageForTests();
    226   // Whitelisted paths must be absolute.
    227   CHECK(path_[0] == '/') << GetErrorMessageForTests();
    228 
    229   // Don't allow unlinking on creation without create permission
    230   if (unlink_) {
    231     CHECK(allow_create) << GetErrorMessageForTests();
    232   }
    233   const char last_char = *(path_.rbegin());
    234   // Recursive paths must have a trailing slash
    235   if (recursive_) {
    236     CHECK(last_char == '/') << GetErrorMessageForTests();
    237   } else {
    238     CHECK(last_char != '/') << GetErrorMessageForTests();
    239   }
    240 }
    241 
    242 }  // namespace syscall_broker
    243 
    244 }  // namespace sandbox