-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstore_impl.cc
329 lines (292 loc) · 10.7 KB
/
store_impl.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "featured/store_impl.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <brillo/secure_blob.h>
#include <brillo/files/file_util.h>
#include <brillo/secure_string.h>
#include <featured/proto_bindings/featured.pb.h>
#include <libhwsec-foundation/crypto/hmac.h>
#include <sys/stat.h>
namespace featured {
constexpr char kStorePath[] = "/var/lib/featured/store";
// No longer used; this constant is only here to clean up older usage of this
// file.
constexpr char kStoreHMACPath[] = "/var/lib/featured/store_hmac";
constexpr mode_t kSystemFeaturedFilesMode = 0760;
constexpr size_t kTpmSeedSize = 32;
// File where the TPM seed is stored, that we have to read from.
constexpr char kTpmSeedTmpFile[] = "/run/featured_seed/tpm_seed";
namespace {
// Walks the directory tree to make sure we avoid symlinks.
// Creates |path| if it does not exist.
//
// All parent parts must already exist else we return false.
bool ValidatePathAndOpen(const base::FilePath& path,
int* outfd,
int flags = 0) {
std::vector<std::string> components = path.GetComponents();
if (components.empty()) {
LOG(ERROR) << "Cannot open an empty path";
return false;
}
int parentfd = AT_FDCWD;
for (auto it = components.begin(); it != components.end(); ++it) {
std::string component = *it;
int fd;
if (it == components.end() - 1) {
// Check that the last component is a valid file and open it for reading
// and writing.
fd = openat(parentfd, component.c_str(),
O_CREAT | O_RDWR | O_NOFOLLOW | O_CLOEXEC | flags,
kSystemFeaturedFilesMode);
} else {
// Check that all components except the last are a valid directory.
fd = openat(parentfd, component.c_str(),
O_NOFOLLOW | O_CLOEXEC | O_PATH | O_DIRECTORY);
}
if (fd < 0) {
PLOG(ERROR) << "Unable to access path: " << path.value() << " ("
<< component << ")";
if (parentfd != AT_FDCWD) {
close(parentfd);
}
return false;
}
if (parentfd != AT_FDCWD) {
close(parentfd);
}
parentfd = fd;
}
*outfd = parentfd;
return true;
}
// Validates |file_path| according to |ValidatePathAndOpen| and reads the
// contents into |file_content|. Creates |file_path| if it does not exist.
//
// Returns false if validating, opening, or reading |file_path| fail.
//
// NOTE: While |file_path| could be recreated if reading fails, doing so is
// risky since deletion could have unintended consequences (eg. the file is a
// symlink).
bool ValidatePathAndRead(const base::FilePath& file_path,
std::string& file_content) {
int fd;
if (!ValidatePathAndOpen(file_path, &fd)) {
LOG(ERROR) << "Failed to validate and open " << file_path;
return false;
} else {
// Constructing with |fd| instead of |file_path| to avoid potential
// TOCTOU (time-of-check/time-of-use) vulnerabilities between calling
// |ValidatePathAndOpen| and constructing |file|.
base::File file(fd);
std::vector<uint8_t> buffer(file.GetLength());
if (!file.ReadAndCheck(/*offset=*/0, base::make_span(buffer))) {
LOG(ERROR) << "Failed to read file contents";
return false;
}
file_content = std::string(buffer.begin(), buffer.end());
}
return true;
}
// Overwrite the file's contents before deleting, to ensure data is wiped.
void SafeDeleteFile(const base::FilePath& seed_path) {
brillo::Blob all_zero(kTpmSeedSize);
if (!base::WriteFile(seed_path, all_zero)) {
PLOG(WARNING) << "Failed to write zeroes to the TPM seed file.";
}
if (!brillo::DeleteFile(seed_path)) {
PLOG(WARNING) << "Failed to delete the TPM seed file.";
}
}
std::optional<brillo::SecureBlob> GetTpmSeed(const base::FilePath& seed_path) {
brillo::SecureBlob tpm_seed(kTpmSeedSize);
int bytes_read = base::ReadFile(
seed_path, reinterpret_cast<char*>(tpm_seed.data()), tpm_seed.size());
SafeDeleteFile(seed_path);
if (bytes_read != kTpmSeedSize) {
LOG(ERROR) << "Failed to read TPM seed from tmpfile, size expected: "
<< kTpmSeedSize << ", size got: " << bytes_read << ".";
return std::nullopt;
}
return tpm_seed;
}
} // namespace
StoreImpl::StoreImpl(const Store& store,
const base::FilePath& store_path,
std::optional<brillo::SecureBlob>&& tpm_seed,
const OverridesSet& overrides)
: store_(store),
store_path_(store_path),
tpm_seed_(tpm_seed),
overrides_(overrides) {}
void StoreImpl::ComputeHMACAndUpdate() {
if (!tpm_seed_.has_value()) {
LOG(WARNING) << "Couldn't compute HMAC because there's no key; continuing.";
return;
}
brillo::SecureBlob hash = hwsec_foundation::HmacSha256(
tpm_seed_.value(), brillo::BlobFromString(store_.overrides()));
store_.set_overrides_hmac(hash.to_string());
}
// Updates hmac, writes store and hmac to disk.
bool StoreImpl::WriteDisk() {
ComputeHMACAndUpdate();
std::string serialized_store;
bool serialized = store_.SerializeToString(&serialized_store);
if (!serialized) {
LOG(ERROR) << "Could not serialize protobuf";
return false;
}
int store_fd;
if (!ValidatePathAndOpen(store_path_, &store_fd, O_TRUNC)) {
PLOG(ERROR) << "Could not reopen " << store_path_;
return false;
}
// Write store to disk.
base::File store_file(store_fd);
if (!store_file.WriteAtCurrentPosAndCheck(
base::as_bytes(base::make_span(serialized_store)))) {
PLOG(ERROR) << "Could not write new store to disk";
return false;
}
return true;
}
std::unique_ptr<StoreInterface> StoreImpl::Create() {
if (base::PathExists(base::FilePath(kStoreHMACPath))) {
// Clean up after oureselves from prior implementation.
if (!brillo::DeleteFile(base::FilePath(kStoreHMACPath))) {
PLOG(ERROR) << "Failed to delete HMAC file";
}
}
return Create(base::FilePath(kStorePath), base::FilePath(kTpmSeedTmpFile));
}
std::unique_ptr<StoreInterface> StoreImpl::Create(
base::FilePath store_path, base::FilePath tpm_seed_path) {
// Do this first so that we always clean up the seed.
std::optional<brillo::SecureBlob> tpm_seed = GetTpmSeed(tpm_seed_path);
if (!tpm_seed.has_value()) {
LOG(ERROR) << "Failed to get TPM seed. Overrides updates will fail.";
}
// Read the store.
// Open store file or create if it does not exist.
std::string store_content;
if (!ValidatePathAndRead(store_path, store_content)) {
LOG(ERROR) << "Failed to validate and read from " << store_path;
return nullptr;
}
// Deserialize the proto and store it in memory.
Store store;
bool deserialized_store = store.ParseFromString(store_content);
bool write_back = false;
if (!deserialized_store) {
LOG(ERROR) << "Failed to deserialize store";
store.Clear();
// Write the cleared store back to disk.
write_back = true;
}
// Verify the HMAC, falling back to no overrides if it fails to verify
// (or is missing).
brillo::SecureBlob hash;
if (tpm_seed.has_value()) {
// Only attempt to compute the hash if there is a seed. (The seed won't be
// available if featured crashes and restarts -- see GetTmpSeed().)
hash = hwsec_foundation::HmacSha256(
tpm_seed.value(), brillo::BlobFromString(store.overrides()));
}
// Mark as verified only if:
// 1) There is a tpm_seed
// 2) The stored HMAC in the proto has the right length
// 3) The HMAC match.
bool verified =
hash.size() == store.overrides_hmac().size() &&
(brillo::SecureMemcmp(hash.data(), store.overrides_hmac().data(),
hash.size())) == 0;
if (!verified) {
if (!store.overrides().empty()) {
// If the hash fails *and* there were overrides, reset them so that we
// don't use them this run of featured.
LOG(ERROR)
<< "HMAC verification failed; falling back to default overrides";
store.clear_overrides();
store.clear_overrides_hmac();
if (tpm_seed.has_value()) {
// Write cleared state back to disk to reset any corruption / reset any
// attacker-modified state.
// Even though we cleared overrides_hmac, WriteDisk will recompute it.
// *ONLY* do this if there was a seed; otherwise it's most likely that
// featured crashed and restarted, and we shouldn't make destructive
// changes.
// (featured deletes the seed immediately after reading it to prevent
// malicious processes from reading it; see GetTpmSeed above.)
write_back = true;
}
}
}
OverridesSet overrides;
if (!overrides.ParseFromString(store.overrides())) {
LOG(ERROR) << "Overrides deserialization failed; falling back to default "
"overrides";
store.clear_overrides();
store.clear_overrides_hmac();
write_back = true;
}
auto store_impl = std::unique_ptr<StoreImpl>(
new StoreImpl(store, store_path, std::move(tpm_seed), overrides));
if (write_back) {
if (!store_impl->WriteDisk()) {
// Fail more quickly, since, if writing fails, the class won't be able to
// provide any mutating functions.
return nullptr;
}
}
return store_impl;
}
uint32_t StoreImpl::GetBootAttemptsSinceLastUpdate() {
return store_.boot_attempts_since_last_seed_update();
}
bool StoreImpl::IncrementBootAttemptsSinceLastUpdate() {
uint32_t boot_attempts = GetBootAttemptsSinceLastUpdate();
store_.set_boot_attempts_since_last_seed_update(boot_attempts + 1);
if (!WriteDisk()) {
LOG(ERROR) << "Failed to increment boot attempts to disk.";
return false;
}
return true;
}
bool StoreImpl::ClearBootAttemptsSinceLastUpdate() {
store_.set_boot_attempts_since_last_seed_update(0);
if (!WriteDisk()) {
LOG(ERROR) << "Failed to increment boot attempts to disk.";
return false;
}
return true;
}
SeedDetails StoreImpl::GetLastGoodSeed() {
return store_.last_good_seed();
}
bool StoreImpl::SetLastGoodSeed(const SeedDetails& seed) {
*store_.mutable_last_good_seed() = seed;
if (!WriteDisk()) {
LOG(ERROR) << "Failed to increment boot attempts to disk.";
return false;
}
return true;
}
// TODO(kendraketsui): implement.
std::vector<FeatureOverride> StoreImpl::GetOverrides() {
return std::vector<FeatureOverride>();
}
// TODO(kendraketsui): implement.
void StoreImpl::AddOverride(const FeatureOverride& override) {}
// TODO(kendraketsui): implement.
void StoreImpl::RemoveOverrideFor(const std::string& name) {}
} // namespace featured