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.
5 #include "components/policy/core/common/cloud/resource_cache.h"
7 #include "base/base64.h"
8 #include "base/callback.h"
9 #include "base/files/file_enumerator.h"
10 #include "base/files/file_util.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"
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());
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.
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
) {
39 for (std::set
<std::string
>::const_iterator it
= input
.begin();
40 it
!= input
.end(); ++it
) {
42 if (!Base64UrlEncode(*it
, &encoded
)) {
46 output
->insert(encoded
);
51 // Decodes |encoded| from base64url format and verifies that the result is not
53 bool Base64UrlDecode(const std::string
& encoded
, std::string
* value
) {
55 base::ReplaceChars(encoded
, "-", "+", &buffer
);
56 base::ReplaceChars(buffer
, "_", "/", &buffer
);
57 return base::Base64Decode(buffer
, value
) && !value
->empty();
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
) {
69 ResourceCache::~ResourceCache() {
70 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
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
);
93 bool ResourceCache::Load(const std::string
& key
,
94 const std::string
& subkey
,
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
)) {
104 return base::ReadFileToString(subkey_path
, data
);
107 void ResourceCache::LoadAllSubkeys(
108 const std::string
& key
,
109 std::map
<std::string
, std::string
>* contents
) {
110 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
112 base::FilePath key_path
;
113 if (!VerifyKeyPath(key
, false, &key_path
))
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();
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
);
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);
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);
150 void ResourceCache::FilterSubkeys(const std::string
& key
,
151 const SubkeyFilter
& test
) {
152 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
154 base::FilePath key_path
;
155 if (!VerifyKeyPath(key
, false, &key_path
))
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()) {
162 // Delete files with invalid names, and files whose subkey doesn't pass the
164 if (!Base64UrlDecode(subkey_path
.BaseName().MaybeAsASCII(), &subkey
) ||
166 base::DeleteFile(subkey_path
, true);
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);
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
))
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);
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
))
200 std::set
<std::string
> encoded_subkeys_to_keep
;
201 if (!Base64UrlEncode(subkeys_to_keep
, &encoded_subkeys_to_keep
))
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);
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);
217 bool ResourceCache::VerifyKeyPath(const std::string
& key
,
219 base::FilePath
* path
) {
221 if (!Base64UrlEncode(key
, &encoded
))
223 *path
= cache_dir_
.AppendASCII(encoded
);
224 return allow_create
? base::CreateDirectory(*path
) :
225 base::DirectoryExists(*path
);
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
;
234 if (!VerifyKeyPath(key
, allow_create_key
, &key_path
) ||
235 !Base64UrlEncode(subkey
, &encoded
)) {
238 *path
= key_path
.AppendASCII(encoded
);
243 } // namespace policy