Home | History | Annotate | Download | only in cloud
      1 // Copyright (c) 2013 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 "components/policy/core/common/cloud/resource_cache.h"
      6 
      7 #include "base/base64.h"
      8 #include "base/callback.h"
      9 #include "base/files/file_enumerator.h"
     10 #include "base/files/file_util.h"
     11 #include "base/logging.h"
     12 #include "base/numerics/safe_conversions.h"
     13 #include "base/sequenced_task_runner.h"
     14 #include "base/strings/string_util.h"
     15 
     16 namespace policy {
     17 
     18 namespace {
     19 
     20 // Verifies that |value| is not empty and encodes it into base64url format,
     21 // which is safe to use as a file name on all platforms.
     22 bool Base64UrlEncode(const std::string& value, std::string* encoded) {
     23   DCHECK(!value.empty());
     24   if (value.empty())
     25     return false;
     26   base::Base64Encode(value, encoded);
     27   base::ReplaceChars(*encoded, "+", "-", encoded);
     28   base::ReplaceChars(*encoded, "/", "_", encoded);
     29   // Note: this encoding keeps the padding chars, though the "Baset64 with safe
     30   // URL alphabet" encoding trims them. See Base64UrlDecode below.
     31   return true;
     32 }
     33 
     34 // Decodes all elements of |input| from base64url format and stores the decoded
     35 // elements in |output|.
     36 bool Base64UrlEncode(const std::set<std::string>& input,
     37                      std::set<std::string>* output) {
     38   output->clear();
     39   for (std::set<std::string>::const_iterator it = input.begin();
     40        it != input.end(); ++it) {
     41     std::string encoded;
     42     if (!Base64UrlEncode(*it, &encoded)) {
     43       output->clear();
     44       return false;
     45     }
     46     output->insert(encoded);
     47   }
     48   return true;
     49 }
     50 
     51 // Decodes |encoded| from base64url format and verifies that the result is not
     52 // emtpy.
     53 bool Base64UrlDecode(const std::string& encoded, std::string* value) {
     54   std::string buffer;
     55   base::ReplaceChars(encoded, "-", "+", &buffer);
     56   base::ReplaceChars(buffer, "_", "/", &buffer);
     57   return base::Base64Decode(buffer, value) && !value->empty();
     58 }
     59 
     60 }  // namespace
     61 
     62 ResourceCache::ResourceCache(
     63     const base::FilePath& cache_dir,
     64     scoped_refptr<base::SequencedTaskRunner> task_runner)
     65     : cache_dir_(cache_dir),
     66       task_runner_(task_runner) {
     67 }
     68 
     69 ResourceCache::~ResourceCache() {
     70   DCHECK(task_runner_->RunsTasksOnCurrentThread());
     71 }
     72 
     73 bool ResourceCache::Store(const std::string& key,
     74                           const std::string& subkey,
     75                           const std::string& data) {
     76   DCHECK(task_runner_->RunsTasksOnCurrentThread());
     77   base::FilePath subkey_path;
     78   // Delete the file before writing to it. This ensures that the write does not
     79   // follow a symlink planted at |subkey_path|, clobbering a file outside the
     80   // cache directory. The mechanism is meant to foil file-system-level attacks
     81   // where a symlink is planted in the cache directory before Chrome has
     82   // started. An attacker controlling a process running concurrently with Chrome
     83   // would be able to race against the protection by re-creating the symlink
     84   // between these two calls. There is nothing in file_util that could be used
     85   // to protect against such races, especially as the cache is cross-platform
     86   // and therefore cannot use any POSIX-only tricks.
     87   int size = base::checked_cast<int>(data.size());
     88   return VerifyKeyPathAndGetSubkeyPath(key, true, subkey, &subkey_path) &&
     89          base::DeleteFile(subkey_path, false) &&
     90          (base::WriteFile(subkey_path, data.data(), size) == size);
     91 }
     92 
     93 bool ResourceCache::Load(const std::string& key,
     94                          const std::string& subkey,
     95                          std::string* data) {
     96   DCHECK(task_runner_->RunsTasksOnCurrentThread());
     97   base::FilePath subkey_path;
     98   // Only read from |subkey_path| if it is not a symlink.
     99   if (!VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path) ||
    100       base::IsLink(subkey_path)) {
    101     return false;
    102   }
    103   data->clear();
    104   return base::ReadFileToString(subkey_path, data);
    105 }
    106 
    107 void ResourceCache::LoadAllSubkeys(
    108     const std::string& key,
    109     std::map<std::string, std::string>* contents) {
    110   DCHECK(task_runner_->RunsTasksOnCurrentThread());
    111   contents->clear();
    112   base::FilePath key_path;
    113   if (!VerifyKeyPath(key, false, &key_path))
    114     return;
    115 
    116   base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
    117   for (base::FilePath path = enumerator.Next(); !path.empty();
    118        path = enumerator.Next()) {
    119     const std::string encoded_subkey = path.BaseName().MaybeAsASCII();
    120     std::string subkey;
    121     std::string data;
    122     // Only read from |subkey_path| if it is not a symlink and its name is
    123     // a base64-encoded string.
    124     if (!base::IsLink(path) &&
    125         Base64UrlDecode(encoded_subkey, &subkey) &&
    126         base::ReadFileToString(path, &data)) {
    127       (*contents)[subkey].swap(data);
    128     }
    129   }
    130 }
    131 
    132 void ResourceCache::Delete(const std::string& key, const std::string& subkey) {
    133   DCHECK(task_runner_->RunsTasksOnCurrentThread());
    134   base::FilePath subkey_path;
    135   if (VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path))
    136     base::DeleteFile(subkey_path, false);
    137   // Delete() does nothing if the directory given to it is not empty. Hence, the
    138   // call below deletes the directory representing |key| if its last subkey was
    139   // just removed and does nothing otherwise.
    140   base::DeleteFile(subkey_path.DirName(), false);
    141 }
    142 
    143 void ResourceCache::Clear(const std::string& key) {
    144   DCHECK(task_runner_->RunsTasksOnCurrentThread());
    145   base::FilePath key_path;
    146   if (VerifyKeyPath(key, false, &key_path))
    147     base::DeleteFile(key_path, true);
    148 }
    149 
    150 void ResourceCache::FilterSubkeys(const std::string& key,
    151                                   const SubkeyFilter& test) {
    152   DCHECK(task_runner_->RunsTasksOnCurrentThread());
    153 
    154   base::FilePath key_path;
    155   if (!VerifyKeyPath(key, false, &key_path))
    156     return;
    157 
    158   base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
    159   for (base::FilePath subkey_path = enumerator.Next();
    160        !subkey_path.empty(); subkey_path = enumerator.Next()) {
    161     std::string subkey;
    162     // Delete files with invalid names, and files whose subkey doesn't pass the
    163     // filter.
    164     if (!Base64UrlDecode(subkey_path.BaseName().MaybeAsASCII(), &subkey) ||
    165         test.Run(subkey)) {
    166       base::DeleteFile(subkey_path, true);
    167     }
    168   }
    169 
    170   // Delete() does nothing if the directory given to it is not empty. Hence, the
    171   // call below deletes the directory representing |key| if all of its subkeys
    172   // were just removed and does nothing otherwise.
    173   base::DeleteFile(key_path, false);
    174 }
    175 
    176 void ResourceCache::PurgeOtherKeys(const std::set<std::string>& keys_to_keep) {
    177   DCHECK(task_runner_->RunsTasksOnCurrentThread());
    178   std::set<std::string> encoded_keys_to_keep;
    179   if (!Base64UrlEncode(keys_to_keep, &encoded_keys_to_keep))
    180     return;
    181 
    182   base::FileEnumerator enumerator(
    183       cache_dir_, false, base::FileEnumerator::DIRECTORIES);
    184   for (base::FilePath path = enumerator.Next(); !path.empty();
    185        path = enumerator.Next()) {
    186     const std::string name(path.BaseName().MaybeAsASCII());
    187     if (encoded_keys_to_keep.find(name) == encoded_keys_to_keep.end())
    188       base::DeleteFile(path, true);
    189   }
    190 }
    191 
    192 void ResourceCache::PurgeOtherSubkeys(
    193     const std::string& key,
    194     const std::set<std::string>& subkeys_to_keep) {
    195   DCHECK(task_runner_->RunsTasksOnCurrentThread());
    196   base::FilePath key_path;
    197   if (!VerifyKeyPath(key, false, &key_path))
    198     return;
    199 
    200   std::set<std::string> encoded_subkeys_to_keep;
    201   if (!Base64UrlEncode(subkeys_to_keep, &encoded_subkeys_to_keep))
    202     return;
    203 
    204   base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
    205   for (base::FilePath path = enumerator.Next(); !path.empty();
    206        path = enumerator.Next()) {
    207     const std::string name(path.BaseName().MaybeAsASCII());
    208     if (encoded_subkeys_to_keep.find(name) == encoded_subkeys_to_keep.end())
    209       base::DeleteFile(path, false);
    210   }
    211   // Delete() does nothing if the directory given to it is not empty. Hence, the
    212   // call below deletes the directory representing |key| if all of its subkeys
    213   // were just removed and does nothing otherwise.
    214   base::DeleteFile(key_path, false);
    215 }
    216 
    217 bool ResourceCache::VerifyKeyPath(const std::string& key,
    218                                   bool allow_create,
    219                                   base::FilePath* path) {
    220   std::string encoded;
    221   if (!Base64UrlEncode(key, &encoded))
    222     return false;
    223   *path = cache_dir_.AppendASCII(encoded);
    224   return allow_create ? base::CreateDirectory(*path) :
    225                         base::DirectoryExists(*path);
    226 }
    227 
    228 bool ResourceCache::VerifyKeyPathAndGetSubkeyPath(const std::string& key,
    229                                                   bool allow_create_key,
    230                                                   const std::string& subkey,
    231                                                   base::FilePath* path) {
    232   base::FilePath key_path;
    233   std::string encoded;
    234   if (!VerifyKeyPath(key, allow_create_key, &key_path) ||
    235       !Base64UrlEncode(subkey, &encoded)) {
    236     return false;
    237   }
    238   *path = key_path.AppendASCII(encoded);
    239   return true;
    240 }
    241 
    242 
    243 }  // namespace policy
    244