forked from Pissandshittium/pissandshittium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent_verify_job.cc
304 lines (265 loc) · 9.69 KB
/
content_verify_job.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
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/content_verify_job.h"
#include <algorithm>
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/timer/elapsed_timer.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/secure_hash.h"
#include "crypto/sha2.h"
#include "extensions/browser/content_hash_reader.h"
#include "extensions/browser/content_verifier.h"
#include "extensions/browser/content_verifier/content_hash.h"
namespace extensions {
namespace {
bool g_ignore_verification_for_tests = false;
base::LazyInstance<scoped_refptr<ContentVerifyJob::TestObserver>>::Leaky
g_content_verify_job_test_observer = LAZY_INSTANCE_INITIALIZER;
scoped_refptr<ContentVerifyJob::TestObserver> GetTestObserver() {
if (!g_content_verify_job_test_observer.IsCreated())
return nullptr;
return g_content_verify_job_test_observer.Get();
}
class ScopedElapsedTimer {
public:
explicit ScopedElapsedTimer(base::TimeDelta* total) : total_(total) {
DCHECK(total_);
}
~ScopedElapsedTimer() { *total_ += timer.Elapsed(); }
private:
// Some total amount of time we should add our elapsed time to at
// destruction.
base::TimeDelta* total_;
// A timer for how long this object has been alive.
base::ElapsedTimer timer;
};
bool IsIgnorableReadError(MojoResult read_result) {
// Extension reload, for example, can cause benign MOJO_RESULT_ABORTED error.
// Do not incorrectly fail content verification in that case.
// See https://crbug.com/977805 for details.
return read_result == MOJO_RESULT_ABORTED;
}
} // namespace
ContentVerifyJob::ContentVerifyJob(const ExtensionId& extension_id,
const base::Version& extension_version,
const base::FilePath& extension_root,
const base::FilePath& relative_path,
FailureCallback failure_callback)
: done_reading_(false),
hashes_ready_(false),
total_bytes_read_(0),
current_block_(0),
current_hash_byte_count_(0),
extension_id_(extension_id),
extension_version_(extension_version),
extension_root_(extension_root),
relative_path_(relative_path),
failure_callback_(std::move(failure_callback)),
failed_(false) {}
ContentVerifyJob::~ContentVerifyJob() {
UMA_HISTOGRAM_COUNTS_1M("ExtensionContentVerifyJob.TimeSpentUS",
time_spent_.InMicroseconds());
}
void ContentVerifyJob::Start(ContentVerifier* verifier) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
base::AutoLock auto_lock(lock_);
verifier->GetContentHash(
extension_id_, extension_root_, extension_version_,
true /* force_missing_computed_hashes_creation */,
base::BindOnce(&ContentVerifyJob::DidGetContentHashOnIO, this));
}
void ContentVerifyJob::DidGetContentHashOnIO(
scoped_refptr<const ContentHash> content_hash) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
base::AutoLock auto_lock(lock_);
scoped_refptr<TestObserver> test_observer = GetTestObserver();
if (test_observer)
test_observer->JobStarted(extension_id_, relative_path_);
// Build |hash_reader_|.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ContentHashReader::Create, relative_path_, content_hash),
base::BindOnce(&ContentVerifyJob::OnHashesReady, this));
}
void ContentVerifyJob::Read(const char* data,
int count,
MojoResult read_result) {
base::AutoLock auto_lock(lock_);
DCHECK(!done_reading_);
ReadImpl(data, count, read_result);
}
void ContentVerifyJob::Done() {
base::AutoLock auto_lock(lock_);
ScopedElapsedTimer timer(&time_spent_);
if (failed_)
return;
if (g_ignore_verification_for_tests)
return;
DCHECK(!done_reading_);
done_reading_ = true;
if (!hashes_ready_)
return; // Wait for OnHashesReady.
const bool can_proceed = has_ignorable_read_error_ || FinishBlock();
if (can_proceed) {
scoped_refptr<TestObserver> test_observer = GetTestObserver();
if (test_observer)
test_observer->JobFinished(extension_id_, relative_path_, NONE);
} else {
DispatchFailureCallback(HASH_MISMATCH);
}
}
void ContentVerifyJob::ReadImpl(const char* data,
int count,
MojoResult read_result) {
ScopedElapsedTimer timer(&time_spent_);
if (failed_)
return;
if (g_ignore_verification_for_tests)
return;
if (IsIgnorableReadError(read_result))
has_ignorable_read_error_ = true;
if (has_ignorable_read_error_)
return;
if (!hashes_ready_) {
queue_.append(data, count);
return;
}
DCHECK_GE(count, 0);
int bytes_added = 0;
while (bytes_added < count) {
if (current_block_ >= hash_reader_->block_count())
return DispatchFailureCallback(HASH_MISMATCH);
if (!current_hash_) {
current_hash_byte_count_ = 0;
current_hash_ = crypto::SecureHash::Create(crypto::SecureHash::SHA256);
}
// Compute how many bytes we should hash, and add them to the current hash.
int bytes_to_hash =
std::min(hash_reader_->block_size() - current_hash_byte_count_,
count - bytes_added);
DCHECK_GT(bytes_to_hash, 0);
current_hash_->Update(data + bytes_added, bytes_to_hash);
bytes_added += bytes_to_hash;
current_hash_byte_count_ += bytes_to_hash;
total_bytes_read_ += bytes_to_hash;
// If we finished reading a block worth of data, finish computing the hash
// for it and make sure the expected hash matches.
if (current_hash_byte_count_ == hash_reader_->block_size() &&
!FinishBlock()) {
DispatchFailureCallback(HASH_MISMATCH);
return;
}
}
}
bool ContentVerifyJob::FinishBlock() {
DCHECK(!failed_);
if (current_hash_byte_count_ == 0) {
if (!done_reading_ ||
// If we have checked all blocks already, then nothing else to do here.
current_block_ == hash_reader_->block_count()) {
return true;
}
}
if (!current_hash_) {
// This happens when we fail to read the resource. Compute empty content's
// hash in this case.
current_hash_ = crypto::SecureHash::Create(crypto::SecureHash::SHA256);
}
std::string final(crypto::kSHA256Length, 0);
current_hash_->Finish(base::data(final), final.size());
current_hash_.reset();
current_hash_byte_count_ = 0;
int block = current_block_++;
const std::string* expected_hash = NULL;
if (!hash_reader_->GetHashForBlock(block, &expected_hash) ||
*expected_hash != final) {
return false;
}
return true;
}
void ContentVerifyJob::OnHashesReady(
std::unique_ptr<const ContentHashReader> hash_reader) {
base::AutoLock auto_lock(lock_);
hash_reader_ = std::move(hash_reader);
if (g_ignore_verification_for_tests)
return;
scoped_refptr<TestObserver> test_observer = GetTestObserver();
if (test_observer)
test_observer->OnHashesReady(extension_id_, relative_path_, *hash_reader_);
switch (hash_reader_->status()) {
case ContentHashReader::InitStatus::HASHES_MISSING: {
DispatchFailureCallback(MISSING_ALL_HASHES);
return;
}
case ContentHashReader::InitStatus::HASHES_DAMAGED: {
DispatchFailureCallback(CORRUPTED_HASHES);
return;
}
case ContentHashReader::InitStatus::NO_HASHES_FOR_NON_EXISTING_RESOURCE: {
// Ignore verification of non-existent resources.
test_observer = GetTestObserver();
if (test_observer)
test_observer->JobFinished(extension_id_, relative_path_, NONE);
return;
}
case ContentHashReader::InitStatus::NO_HASHES_FOR_RESOURCE: {
DispatchFailureCallback(NO_HASHES_FOR_FILE);
return;
}
case ContentHashReader::InitStatus::SUCCESS: {
// Just proceed with hashes in case of success.
}
}
DCHECK_EQ(ContentHashReader::InitStatus::SUCCESS, hash_reader_->status());
DCHECK(!failed_);
hashes_ready_ = true;
if (!queue_.empty()) {
std::string tmp;
queue_.swap(tmp);
ReadImpl(base::data(tmp), tmp.size(), MOJO_RESULT_OK);
if (failed_)
return;
}
if (done_reading_) {
ScopedElapsedTimer timer(&time_spent_);
if (!has_ignorable_read_error_ && !FinishBlock()) {
DispatchFailureCallback(HASH_MISMATCH);
} else {
test_observer = GetTestObserver();
if (test_observer)
test_observer->JobFinished(extension_id_, relative_path_, NONE);
}
}
}
// static
void ContentVerifyJob::SetIgnoreVerificationForTests(bool value) {
DCHECK_NE(g_ignore_verification_for_tests, value);
g_ignore_verification_for_tests = value;
}
// static
void ContentVerifyJob::SetObserverForTests(
scoped_refptr<TestObserver> observer) {
DCHECK(observer == nullptr ||
g_content_verify_job_test_observer.Get() == nullptr)
<< "SetObserverForTests does not support interleaving. Observers should "
<< "be set and then cleared one at a time.";
g_content_verify_job_test_observer.Get() = std::move(observer);
}
void ContentVerifyJob::DispatchFailureCallback(FailureReason reason) {
DCHECK(!failed_);
failed_ = true;
if (!failure_callback_.is_null()) {
VLOG(1) << "job failed for " << extension_id_ << " "
<< relative_path_.MaybeAsASCII() << " reason:" << reason;
std::move(failure_callback_).Run(reason);
}
scoped_refptr<TestObserver> test_observer = GetTestObserver();
if (test_observer)
test_observer->JobFinished(extension_id_, relative_path_, reason);
}
} // namespace extensions