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