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