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