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 |