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