Line data Source code
1 : //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "ProtocolParser.h"
7 : #include "LookupCache.h"
8 : #include "nsNetCID.h"
9 : #include "mozilla/Logging.h"
10 : #include "prnetdb.h"
11 : #include "prprf.h"
12 :
13 : #include "nsUrlClassifierDBService.h"
14 : #include "nsUrlClassifierUtils.h"
15 : #include "nsPrintfCString.h"
16 : #include "mozilla/Base64.h"
17 : #include "RiceDeltaDecoder.h"
18 : #include "mozilla/EndianUtils.h"
19 : #include "mozilla/IntegerPrintfMacros.h"
20 : #include "mozilla/SizePrintfMacros.h"
21 :
22 : // MOZ_LOG=UrlClassifierProtocolParser:5
23 : mozilla::LazyLogModule gUrlClassifierProtocolParserLog("UrlClassifierProtocolParser");
24 : #define PARSER_LOG(args) MOZ_LOG(gUrlClassifierProtocolParserLog, mozilla::LogLevel::Debug, args)
25 :
26 : namespace mozilla {
27 : namespace safebrowsing {
28 :
29 : // Updates will fail if fed chunks larger than this
30 : const uint32_t MAX_CHUNK_SIZE = (1024 * 1024);
31 : // Updates will fail if the total number of tocuhed chunks is larger than this
32 : const uint32_t MAX_CHUNK_RANGE = 1000000;
33 :
34 : const uint32_t DOMAIN_SIZE = 4;
35 :
36 : // Parse one stringified range of chunks of the form "n" or "n-m" from a
37 : // comma-separated list of chunks. Upon return, 'begin' will point to the
38 : // next range of chunks in the list of chunks.
39 : static bool
40 6 : ParseChunkRange(nsACString::const_iterator& aBegin,
41 : const nsACString::const_iterator& aEnd,
42 : uint32_t* aFirst, uint32_t* aLast)
43 : {
44 6 : nsACString::const_iterator iter = aBegin;
45 6 : FindCharInReadable(',', iter, aEnd);
46 :
47 12 : nsAutoCString element(Substring(aBegin, iter));
48 6 : aBegin = iter;
49 6 : if (aBegin != aEnd)
50 0 : aBegin++;
51 :
52 6 : uint32_t numRead = PR_sscanf(element.get(), "%u-%u", aFirst, aLast);
53 6 : if (numRead == 2) {
54 0 : if (*aFirst > *aLast) {
55 0 : uint32_t tmp = *aFirst;
56 0 : *aFirst = *aLast;
57 0 : *aLast = tmp;
58 : }
59 0 : return true;
60 : }
61 :
62 6 : if (numRead == 1) {
63 6 : *aLast = *aFirst;
64 6 : return true;
65 : }
66 :
67 0 : return false;
68 : }
69 :
70 : ///////////////////////////////////////////////////////////////
71 : // ProtocolParser implementation
72 :
73 1 : ProtocolParser::ProtocolParser()
74 : : mUpdateStatus(NS_OK)
75 1 : , mUpdateWaitSec(0)
76 : {
77 1 : }
78 :
79 2 : ProtocolParser::~ProtocolParser()
80 : {
81 1 : CleanupUpdates();
82 1 : }
83 :
84 : nsresult
85 1 : ProtocolParser::Init(nsICryptoHash* aHasher)
86 : {
87 1 : mCryptoHash = aHasher;
88 1 : return NS_OK;
89 : }
90 :
91 : void
92 1 : ProtocolParser::CleanupUpdates()
93 : {
94 1 : for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
95 0 : delete mTableUpdates[i];
96 : }
97 1 : mTableUpdates.Clear();
98 1 : }
99 :
100 : TableUpdate *
101 6 : ProtocolParser::GetTableUpdate(const nsACString& aTable)
102 : {
103 21 : for (uint32_t i = 0; i < mTableUpdates.Length(); i++) {
104 15 : if (aTable.Equals(mTableUpdates[i]->TableName())) {
105 0 : return mTableUpdates[i];
106 : }
107 : }
108 :
109 : // We free automatically on destruction, ownership of these
110 : // updates can be transferred to DBServiceWorker, which passes
111 : // them back to Classifier when doing the updates, and that
112 : // will free them.
113 6 : TableUpdate *update = CreateTableUpdate(aTable);
114 6 : mTableUpdates.AppendElement(update);
115 6 : return update;
116 : }
117 :
118 : ///////////////////////////////////////////////////////////////////////
119 : // ProtocolParserV2
120 :
121 1 : ProtocolParserV2::ProtocolParserV2()
122 : : mState(PROTOCOL_STATE_CONTROL)
123 : , mResetRequested(false)
124 1 : , mTableUpdate(nullptr)
125 : {
126 1 : }
127 :
128 2 : ProtocolParserV2::~ProtocolParserV2()
129 : {
130 3 : }
131 :
132 : void
133 6 : ProtocolParserV2::SetCurrentTable(const nsACString& aTable)
134 : {
135 6 : auto update = GetTableUpdate(aTable);
136 6 : mTableUpdate = TableUpdate::Cast<TableUpdateV2>(update);
137 6 : }
138 :
139 : nsresult
140 1 : ProtocolParserV2::AppendStream(const nsACString& aData)
141 : {
142 1 : if (NS_FAILED(mUpdateStatus))
143 0 : return mUpdateStatus;
144 :
145 : nsresult rv;
146 1 : mPending.Append(aData);
147 : #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
148 1 : mRawUpdate.Append(aData);
149 : #endif
150 :
151 1 : bool done = false;
152 31 : while (!done) {
153 15 : if (nsUrlClassifierDBService::ShutdownHasStarted()) {
154 0 : return NS_ERROR_ABORT;
155 : }
156 :
157 15 : if (mState == PROTOCOL_STATE_CONTROL) {
158 8 : rv = ProcessControl(&done);
159 7 : } else if (mState == PROTOCOL_STATE_CHUNK) {
160 7 : rv = ProcessChunk(&done);
161 : } else {
162 0 : NS_ERROR("Unexpected protocol state");
163 0 : rv = NS_ERROR_FAILURE;
164 : }
165 15 : if (NS_FAILED(rv)) {
166 0 : mUpdateStatus = rv;
167 0 : return rv;
168 : }
169 : }
170 1 : return NS_OK;
171 : }
172 :
173 : void
174 1 : ProtocolParserV2::End()
175 : {
176 : // Inbound data has already been processed in every AppendStream() call.
177 1 : }
178 :
179 : nsresult
180 8 : ProtocolParserV2::ProcessControl(bool* aDone)
181 : {
182 : nsresult rv;
183 :
184 16 : nsAutoCString line;
185 8 : *aDone = true;
186 54 : while (NextLine(line)) {
187 30 : PARSER_LOG(("Processing %s\n", line.get()));
188 :
189 30 : if (StringBeginsWith(line, NS_LITERAL_CSTRING("i:"))) {
190 : // Set the table name from the table header line.
191 6 : SetCurrentTable(Substring(line, 2));
192 24 : } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("n:"))) {
193 6 : if (PR_sscanf(line.get(), "n:%d", &mUpdateWaitSec) != 1) {
194 0 : PARSER_LOG(("Error parsing n: '%s' (%d)", line.get(), mUpdateWaitSec));
195 0 : return NS_ERROR_FAILURE;
196 : }
197 18 : } else if (line.EqualsLiteral("r:pleasereset")) {
198 0 : mResetRequested = true;
199 18 : } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("u:"))) {
200 0 : rv = ProcessForward(line);
201 0 : NS_ENSURE_SUCCESS(rv, rv);
202 58 : } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("a:")) ||
203 40 : StringBeginsWith(line, NS_LITERAL_CSTRING("s:"))) {
204 7 : rv = ProcessChunkControl(line);
205 7 : NS_ENSURE_SUCCESS(rv, rv);
206 7 : *aDone = false;
207 7 : return NS_OK;
208 32 : } else if (StringBeginsWith(line, NS_LITERAL_CSTRING("ad:")) ||
209 21 : StringBeginsWith(line, NS_LITERAL_CSTRING("sd:"))) {
210 6 : rv = ProcessExpirations(line);
211 6 : NS_ENSURE_SUCCESS(rv, rv);
212 : }
213 : }
214 :
215 1 : *aDone = true;
216 1 : return NS_OK;
217 : }
218 :
219 : nsresult
220 6 : ProtocolParserV2::ProcessExpirations(const nsCString& aLine)
221 : {
222 6 : if (!mTableUpdate) {
223 0 : NS_WARNING("Got an expiration without a table.");
224 0 : return NS_ERROR_FAILURE;
225 : }
226 12 : const nsACString& list = Substring(aLine, 3);
227 6 : nsACString::const_iterator begin, end;
228 6 : list.BeginReading(begin);
229 6 : list.EndReading(end);
230 18 : while (begin != end) {
231 : uint32_t first, last;
232 6 : if (ParseChunkRange(begin, end, &first, &last)) {
233 6 : if (last < first) return NS_ERROR_FAILURE;
234 6 : if (last - first > MAX_CHUNK_RANGE) return NS_ERROR_FAILURE;
235 12 : for (uint32_t num = first; num <= last; num++) {
236 6 : if (aLine[0] == 'a') {
237 6 : nsresult rv = mTableUpdate->NewAddExpiration(num);
238 6 : if (NS_FAILED(rv)) {
239 0 : return rv;
240 : }
241 : } else {
242 0 : nsresult rv = mTableUpdate->NewSubExpiration(num);
243 0 : if (NS_FAILED(rv)) {
244 0 : return rv;
245 : }
246 : }
247 : }
248 : } else {
249 0 : return NS_ERROR_FAILURE;
250 : }
251 : }
252 6 : return NS_OK;
253 : }
254 :
255 : nsresult
256 7 : ProtocolParserV2::ProcessChunkControl(const nsCString& aLine)
257 : {
258 7 : if (!mTableUpdate) {
259 0 : NS_WARNING("Got a chunk before getting a table.");
260 0 : return NS_ERROR_FAILURE;
261 : }
262 :
263 7 : mState = PROTOCOL_STATE_CHUNK;
264 : char command;
265 :
266 7 : mChunkState.Clear();
267 :
268 7 : if (PR_sscanf(aLine.get(),
269 : "%c:%d:%d:%d",
270 : &command,
271 : &mChunkState.num, &mChunkState.hashSize, &mChunkState.length)
272 : != 4)
273 : {
274 0 : NS_WARNING(("PR_sscanf failed"));
275 0 : return NS_ERROR_FAILURE;
276 : }
277 :
278 7 : if (mChunkState.length > MAX_CHUNK_SIZE) {
279 0 : NS_WARNING("Invalid length specified in update.");
280 0 : return NS_ERROR_FAILURE;
281 : }
282 :
283 7 : if (!(mChunkState.hashSize == PREFIX_SIZE || mChunkState.hashSize == COMPLETE_SIZE)) {
284 0 : NS_WARNING("Invalid hash size specified in update.");
285 0 : return NS_ERROR_FAILURE;
286 : }
287 :
288 21 : if (StringEndsWith(mTableUpdate->TableName(),
289 42 : NS_LITERAL_CSTRING("-shavar")) ||
290 7 : StringEndsWith(mTableUpdate->TableName(),
291 21 : NS_LITERAL_CSTRING("-simple"))) {
292 : // Accommodate test tables ending in -simple for now.
293 7 : mChunkState.type = (command == 'a') ? CHUNK_ADD : CHUNK_SUB;
294 0 : } else if (StringEndsWith(mTableUpdate->TableName(),
295 0 : NS_LITERAL_CSTRING("-digest256"))) {
296 0 : mChunkState.type = (command == 'a') ? CHUNK_ADD_DIGEST : CHUNK_SUB_DIGEST;
297 : }
298 : nsresult rv;
299 7 : switch (mChunkState.type) {
300 : case CHUNK_ADD:
301 7 : rv = mTableUpdate->NewAddChunk(mChunkState.num);
302 7 : if (NS_FAILED(rv)) {
303 0 : return rv;
304 : }
305 7 : break;
306 : case CHUNK_SUB:
307 0 : rv = mTableUpdate->NewSubChunk(mChunkState.num);
308 0 : if (NS_FAILED(rv)) {
309 0 : return rv;
310 : }
311 0 : break;
312 : case CHUNK_ADD_DIGEST:
313 0 : rv = mTableUpdate->NewAddChunk(mChunkState.num);
314 0 : if (NS_FAILED(rv)) {
315 0 : return rv;
316 : }
317 0 : break;
318 : case CHUNK_SUB_DIGEST:
319 0 : rv = mTableUpdate->NewSubChunk(mChunkState.num);
320 0 : if (NS_FAILED(rv)) {
321 0 : return rv;
322 : }
323 0 : break;
324 : }
325 :
326 7 : return NS_OK;
327 : }
328 :
329 : nsresult
330 0 : ProtocolParserV2::ProcessForward(const nsCString& aLine)
331 : {
332 0 : const nsACString& forward = Substring(aLine, 2);
333 0 : return AddForward(forward);
334 : }
335 :
336 : nsresult
337 0 : ProtocolParserV2::AddForward(const nsACString& aUrl)
338 : {
339 0 : if (!mTableUpdate) {
340 0 : NS_WARNING("Forward without a table name.");
341 0 : return NS_ERROR_FAILURE;
342 : }
343 :
344 0 : ForwardedUpdate *forward = mForwards.AppendElement();
345 0 : forward->table = mTableUpdate->TableName();
346 0 : forward->url.Assign(aUrl);
347 :
348 0 : return NS_OK;
349 : }
350 :
351 : nsresult
352 7 : ProtocolParserV2::ProcessChunk(bool* aDone)
353 : {
354 7 : if (!mTableUpdate) {
355 0 : NS_WARNING("Processing chunk without an active table.");
356 0 : return NS_ERROR_FAILURE;
357 : }
358 :
359 7 : NS_ASSERTION(mChunkState.num != 0, "Must have a chunk number.");
360 :
361 7 : if (mPending.Length() < mChunkState.length) {
362 0 : *aDone = true;
363 0 : return NS_OK;
364 : }
365 :
366 : // Pull the chunk out of the pending stream data.
367 14 : nsAutoCString chunk;
368 7 : chunk.Assign(Substring(mPending, 0, mChunkState.length));
369 7 : mPending.Cut(0, mChunkState.length);
370 :
371 7 : *aDone = false;
372 7 : mState = PROTOCOL_STATE_CONTROL;
373 :
374 14 : if (StringEndsWith(mTableUpdate->TableName(),
375 14 : NS_LITERAL_CSTRING("-shavar"))) {
376 0 : return ProcessShaChunk(chunk);
377 : }
378 14 : if (StringEndsWith(mTableUpdate->TableName(),
379 14 : NS_LITERAL_CSTRING("-digest256"))) {
380 0 : return ProcessDigestChunk(chunk);
381 : }
382 7 : return ProcessPlaintextChunk(chunk);
383 : }
384 :
385 : /**
386 : * Process a plaintext chunk (currently only used in unit tests).
387 : */
388 : nsresult
389 7 : ProtocolParserV2::ProcessPlaintextChunk(const nsACString& aChunk)
390 : {
391 7 : if (!mTableUpdate) {
392 0 : NS_WARNING("Chunk received with no table.");
393 0 : return NS_ERROR_FAILURE;
394 : }
395 :
396 7 : PARSER_LOG(("Handling a %d-byte simple chunk", aChunk.Length()));
397 :
398 14 : nsTArray<nsCString> lines;
399 7 : ParseString(PromiseFlatCString(aChunk), '\n', lines);
400 :
401 : // non-hashed tables need to be hashed
402 14 : for (uint32_t i = 0; i < lines.Length(); i++) {
403 7 : nsCString& line = lines[i];
404 :
405 7 : if (mChunkState.type == CHUNK_ADD) {
406 7 : if (mChunkState.hashSize == COMPLETE_SIZE) {
407 : Completion hash;
408 7 : hash.FromPlaintext(line, mCryptoHash);
409 7 : nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash);
410 7 : if (NS_FAILED(rv)) {
411 0 : return rv;
412 : }
413 : } else {
414 0 : NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
415 : Prefix hash;
416 0 : hash.FromPlaintext(line, mCryptoHash);
417 0 : nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash);
418 0 : if (NS_FAILED(rv)) {
419 0 : return rv;
420 : }
421 : }
422 : } else {
423 0 : nsCString::const_iterator begin, iter, end;
424 0 : line.BeginReading(begin);
425 0 : line.EndReading(end);
426 0 : iter = begin;
427 : uint32_t addChunk;
428 0 : if (!FindCharInReadable(':', iter, end) ||
429 0 : PR_sscanf(lines[i].get(), "%d:", &addChunk) != 1) {
430 0 : NS_WARNING("Received sub chunk without associated add chunk.");
431 0 : return NS_ERROR_FAILURE;
432 : }
433 0 : iter++;
434 :
435 0 : if (mChunkState.hashSize == COMPLETE_SIZE) {
436 : Completion hash;
437 0 : hash.FromPlaintext(Substring(iter, end), mCryptoHash);
438 0 : nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
439 0 : if (NS_FAILED(rv)) {
440 0 : return rv;
441 : }
442 : } else {
443 0 : NS_ASSERTION(mChunkState.hashSize == 4, "Only 32- or 4-byte hashes can be used for add chunks.");
444 : Prefix hash;
445 0 : hash.FromPlaintext(Substring(iter, end), mCryptoHash);
446 0 : nsresult rv = mTableUpdate->NewSubPrefix(addChunk, hash, mChunkState.num);
447 0 : if (NS_FAILED(rv)) {
448 0 : return rv;
449 : }
450 : }
451 : }
452 : }
453 :
454 7 : return NS_OK;
455 : }
456 :
457 : nsresult
458 0 : ProtocolParserV2::ProcessShaChunk(const nsACString& aChunk)
459 : {
460 0 : uint32_t start = 0;
461 0 : while (start < aChunk.Length()) {
462 : // First four bytes are the domain key.
463 : Prefix domain;
464 0 : domain.Assign(Substring(aChunk, start, DOMAIN_SIZE));
465 0 : start += DOMAIN_SIZE;
466 :
467 : // Then a count of entries.
468 0 : uint8_t numEntries = static_cast<uint8_t>(aChunk[start]);
469 0 : start++;
470 :
471 0 : PARSER_LOG(("Handling a %d-byte shavar chunk containing %u entries"
472 : " for domain %X", aChunk.Length(), numEntries,
473 : domain.ToUint32()));
474 :
475 : nsresult rv;
476 0 : if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == PREFIX_SIZE) {
477 0 : rv = ProcessHostAdd(domain, numEntries, aChunk, &start);
478 0 : } else if (mChunkState.type == CHUNK_ADD && mChunkState.hashSize == COMPLETE_SIZE) {
479 0 : rv = ProcessHostAddComplete(numEntries, aChunk, &start);
480 0 : } else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == PREFIX_SIZE) {
481 0 : rv = ProcessHostSub(domain, numEntries, aChunk, &start);
482 0 : } else if (mChunkState.type == CHUNK_SUB && mChunkState.hashSize == COMPLETE_SIZE) {
483 0 : rv = ProcessHostSubComplete(numEntries, aChunk, &start);
484 : } else {
485 0 : NS_WARNING("Unexpected chunk type/hash size!");
486 0 : PARSER_LOG(("Got an unexpected chunk type/hash size: %s:%d",
487 : mChunkState.type == CHUNK_ADD ? "add" : "sub",
488 : mChunkState.hashSize));
489 0 : return NS_ERROR_FAILURE;
490 : }
491 0 : NS_ENSURE_SUCCESS(rv, rv);
492 : }
493 :
494 0 : return NS_OK;
495 : }
496 :
497 : nsresult
498 0 : ProtocolParserV2::ProcessDigestChunk(const nsACString& aChunk)
499 : {
500 0 : PARSER_LOG(("Handling a %d-byte digest256 chunk", aChunk.Length()));
501 :
502 0 : if (mChunkState.type == CHUNK_ADD_DIGEST) {
503 0 : return ProcessDigestAdd(aChunk);
504 : }
505 0 : if (mChunkState.type == CHUNK_SUB_DIGEST) {
506 0 : return ProcessDigestSub(aChunk);
507 : }
508 0 : return NS_ERROR_UNEXPECTED;
509 : }
510 :
511 : nsresult
512 0 : ProtocolParserV2::ProcessDigestAdd(const nsACString& aChunk)
513 : {
514 : // The ABNF format for add chunks is (HASH)+, where HASH is 32 bytes.
515 0 : MOZ_ASSERT(aChunk.Length() % 32 == 0,
516 : "Chunk length in bytes must be divisible by 4");
517 0 : uint32_t start = 0;
518 0 : while (start < aChunk.Length()) {
519 : Completion hash;
520 0 : hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
521 0 : start += COMPLETE_SIZE;
522 0 : nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash);
523 0 : if (NS_FAILED(rv)) {
524 0 : return rv;
525 : }
526 : }
527 0 : return NS_OK;
528 : }
529 :
530 : nsresult
531 0 : ProtocolParserV2::ProcessDigestSub(const nsACString& aChunk)
532 : {
533 : // The ABNF format for sub chunks is (ADDCHUNKNUM HASH)+, where ADDCHUNKNUM
534 : // is a 4 byte chunk number, and HASH is 32 bytes.
535 0 : MOZ_ASSERT(aChunk.Length() % 36 == 0,
536 : "Chunk length in bytes must be divisible by 36");
537 0 : uint32_t start = 0;
538 0 : while (start < aChunk.Length()) {
539 : // Read ADDCHUNKNUM
540 0 : const nsACString& addChunkStr = Substring(aChunk, start, 4);
541 0 : start += 4;
542 :
543 : uint32_t addChunk;
544 0 : memcpy(&addChunk, addChunkStr.BeginReading(), 4);
545 0 : addChunk = PR_ntohl(addChunk);
546 :
547 : // Read the hash
548 : Completion hash;
549 0 : hash.Assign(Substring(aChunk, start, COMPLETE_SIZE));
550 0 : start += COMPLETE_SIZE;
551 :
552 0 : nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
553 0 : if (NS_FAILED(rv)) {
554 0 : return rv;
555 : }
556 : }
557 0 : return NS_OK;
558 : }
559 :
560 : nsresult
561 0 : ProtocolParserV2::ProcessHostAdd(const Prefix& aDomain, uint8_t aNumEntries,
562 : const nsACString& aChunk, uint32_t* aStart)
563 : {
564 0 : NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
565 : "ProcessHostAdd should only be called for prefix hashes.");
566 :
567 0 : if (aNumEntries == 0) {
568 0 : nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, aDomain);
569 0 : if (NS_FAILED(rv)) {
570 0 : return rv;
571 : }
572 0 : return NS_OK;
573 : }
574 :
575 0 : if (*aStart + (PREFIX_SIZE * aNumEntries) > aChunk.Length()) {
576 0 : NS_WARNING("Chunk is not long enough to contain the expected entries.");
577 0 : return NS_ERROR_FAILURE;
578 : }
579 :
580 0 : for (uint8_t i = 0; i < aNumEntries; i++) {
581 : Prefix hash;
582 0 : hash.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
583 0 : PARSER_LOG(("Add prefix %X", hash.ToUint32()));
584 0 : nsresult rv = mTableUpdate->NewAddPrefix(mChunkState.num, hash);
585 0 : if (NS_FAILED(rv)) {
586 0 : return rv;
587 : }
588 0 : *aStart += PREFIX_SIZE;
589 : }
590 :
591 0 : return NS_OK;
592 : }
593 :
594 : nsresult
595 0 : ProtocolParserV2::ProcessHostSub(const Prefix& aDomain, uint8_t aNumEntries,
596 : const nsACString& aChunk, uint32_t *aStart)
597 : {
598 0 : NS_ASSERTION(mChunkState.hashSize == PREFIX_SIZE,
599 : "ProcessHostSub should only be called for prefix hashes.");
600 :
601 0 : if (aNumEntries == 0) {
602 0 : if ((*aStart) + 4 > aChunk.Length()) {
603 0 : NS_WARNING("Received a zero-entry sub chunk without an associated add.");
604 0 : return NS_ERROR_FAILURE;
605 : }
606 :
607 0 : const nsACString& addChunkStr = Substring(aChunk, *aStart, 4);
608 0 : *aStart += 4;
609 :
610 : uint32_t addChunk;
611 0 : memcpy(&addChunk, addChunkStr.BeginReading(), 4);
612 0 : addChunk = PR_ntohl(addChunk);
613 :
614 0 : PARSER_LOG(("Sub prefix (addchunk=%u)", addChunk));
615 0 : nsresult rv = mTableUpdate->NewSubPrefix(addChunk, aDomain, mChunkState.num);
616 0 : if (NS_FAILED(rv)) {
617 0 : return rv;
618 : }
619 0 : return NS_OK;
620 : }
621 :
622 0 : if (*aStart + ((PREFIX_SIZE + 4) * aNumEntries) > aChunk.Length()) {
623 0 : NS_WARNING("Chunk is not long enough to contain the expected entries.");
624 0 : return NS_ERROR_FAILURE;
625 : }
626 :
627 0 : for (uint8_t i = 0; i < aNumEntries; i++) {
628 0 : const nsACString& addChunkStr = Substring(aChunk, *aStart, 4);
629 0 : *aStart += 4;
630 :
631 : uint32_t addChunk;
632 0 : memcpy(&addChunk, addChunkStr.BeginReading(), 4);
633 0 : addChunk = PR_ntohl(addChunk);
634 :
635 : Prefix prefix;
636 0 : prefix.Assign(Substring(aChunk, *aStart, PREFIX_SIZE));
637 0 : *aStart += PREFIX_SIZE;
638 :
639 0 : PARSER_LOG(("Sub prefix %X (addchunk=%u)", prefix.ToUint32(), addChunk));
640 0 : nsresult rv = mTableUpdate->NewSubPrefix(addChunk, prefix, mChunkState.num);
641 0 : if (NS_FAILED(rv)) {
642 0 : return rv;
643 : }
644 : }
645 :
646 0 : return NS_OK;
647 : }
648 :
649 : nsresult
650 0 : ProtocolParserV2::ProcessHostAddComplete(uint8_t aNumEntries,
651 : const nsACString& aChunk, uint32_t* aStart)
652 : {
653 0 : NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
654 : "ProcessHostAddComplete should only be called for complete hashes.");
655 :
656 0 : if (aNumEntries == 0) {
657 : // this is totally comprehensible.
658 : // My sarcasm detector is going off!
659 0 : NS_WARNING("Expected > 0 entries for a 32-byte hash add.");
660 0 : return NS_OK;
661 : }
662 :
663 0 : if (*aStart + (COMPLETE_SIZE * aNumEntries) > aChunk.Length()) {
664 0 : NS_WARNING("Chunk is not long enough to contain the expected entries.");
665 0 : return NS_ERROR_FAILURE;
666 : }
667 :
668 0 : for (uint8_t i = 0; i < aNumEntries; i++) {
669 : Completion hash;
670 0 : hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
671 0 : nsresult rv = mTableUpdate->NewAddComplete(mChunkState.num, hash);
672 0 : if (NS_FAILED(rv)) {
673 0 : return rv;
674 : }
675 0 : *aStart += COMPLETE_SIZE;
676 : }
677 :
678 0 : return NS_OK;
679 : }
680 :
681 : nsresult
682 0 : ProtocolParserV2::ProcessHostSubComplete(uint8_t aNumEntries,
683 : const nsACString& aChunk, uint32_t* aStart)
684 : {
685 0 : NS_ASSERTION(mChunkState.hashSize == COMPLETE_SIZE,
686 : "ProcessHostSubComplete should only be called for complete hashes.");
687 :
688 0 : if (aNumEntries == 0) {
689 : // this is totally comprehensible.
690 0 : NS_WARNING("Expected > 0 entries for a 32-byte hash sub.");
691 0 : return NS_OK;
692 : }
693 :
694 0 : if (*aStart + ((COMPLETE_SIZE + 4) * aNumEntries) > aChunk.Length()) {
695 0 : NS_WARNING("Chunk is not long enough to contain the expected entries.");
696 0 : return NS_ERROR_FAILURE;
697 : }
698 :
699 0 : for (uint8_t i = 0; i < aNumEntries; i++) {
700 : Completion hash;
701 0 : hash.Assign(Substring(aChunk, *aStart, COMPLETE_SIZE));
702 0 : *aStart += COMPLETE_SIZE;
703 :
704 0 : const nsACString& addChunkStr = Substring(aChunk, *aStart, 4);
705 0 : *aStart += 4;
706 :
707 : uint32_t addChunk;
708 0 : memcpy(&addChunk, addChunkStr.BeginReading(), 4);
709 0 : addChunk = PR_ntohl(addChunk);
710 :
711 0 : nsresult rv = mTableUpdate->NewSubComplete(addChunk, hash, mChunkState.num);
712 0 : if (NS_FAILED(rv)) {
713 0 : return rv;
714 : }
715 : }
716 :
717 0 : return NS_OK;
718 : }
719 :
720 : bool
721 31 : ProtocolParserV2::NextLine(nsACString& aLine)
722 : {
723 31 : int32_t newline = mPending.FindChar('\n');
724 31 : if (newline == kNotFound) {
725 1 : return false;
726 : }
727 30 : aLine.Assign(Substring(mPending, 0, newline));
728 30 : mPending.Cut(0, newline + 1);
729 30 : return true;
730 : }
731 :
732 : TableUpdate*
733 6 : ProtocolParserV2::CreateTableUpdate(const nsACString& aTableName) const
734 : {
735 6 : return new TableUpdateV2(aTableName);
736 : }
737 :
738 : ///////////////////////////////////////////////////////////////////////
739 : // ProtocolParserProtobuf
740 :
741 0 : ProtocolParserProtobuf::ProtocolParserProtobuf()
742 : {
743 0 : }
744 :
745 0 : ProtocolParserProtobuf::~ProtocolParserProtobuf()
746 : {
747 0 : }
748 :
749 : void
750 0 : ProtocolParserProtobuf::SetCurrentTable(const nsACString& aTable)
751 : {
752 : // Should never occur.
753 0 : MOZ_ASSERT_UNREACHABLE("SetCurrentTable shouldn't be called");
754 : }
755 :
756 :
757 : TableUpdate*
758 0 : ProtocolParserProtobuf::CreateTableUpdate(const nsACString& aTableName) const
759 : {
760 0 : return new TableUpdateV4(aTableName);
761 : }
762 :
763 : nsresult
764 0 : ProtocolParserProtobuf::AppendStream(const nsACString& aData)
765 : {
766 : // Protobuf data cannot be parsed progressively. Just save the incoming data.
767 0 : mPending.Append(aData);
768 0 : return NS_OK;
769 : }
770 :
771 : void
772 0 : ProtocolParserProtobuf::End()
773 : {
774 : // mUpdateStatus will be updated to success as long as not all
775 : // the responses are invalid.
776 0 : mUpdateStatus = NS_ERROR_FAILURE;
777 :
778 0 : FetchThreatListUpdatesResponse response;
779 0 : if (!response.ParseFromArray(mPending.get(), mPending.Length())) {
780 0 : NS_WARNING("ProtocolParserProtobuf failed parsing data.");
781 0 : return;
782 : }
783 :
784 0 : auto minWaitDuration = response.minimum_wait_duration();
785 0 : mUpdateWaitSec = minWaitDuration.seconds() +
786 0 : minWaitDuration.nanos() / 1000000000;
787 :
788 0 : for (int i = 0; i < response.list_update_responses_size(); i++) {
789 0 : auto r = response.list_update_responses(i);
790 0 : nsresult rv = ProcessOneResponse(r);
791 0 : if (NS_SUCCEEDED(rv)) {
792 0 : mUpdateStatus = rv;
793 : } else {
794 0 : NS_WARNING("Failed to process one response.");
795 : }
796 : }
797 : }
798 :
799 : nsresult
800 0 : ProtocolParserProtobuf::ProcessOneResponse(const ListUpdateResponse& aResponse)
801 : {
802 : // A response must have a threat type.
803 0 : if (!aResponse.has_threat_type()) {
804 0 : NS_WARNING("Threat type not initialized. This seems to be an invalid response.");
805 0 : return NS_ERROR_FAILURE;
806 : }
807 :
808 : // Convert threat type to list name.
809 : nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
810 0 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
811 0 : nsCString possibleListNames;
812 0 : nsresult rv = urlUtil->ConvertThreatTypeToListNames(aResponse.threat_type(),
813 0 : possibleListNames);
814 0 : if (NS_FAILED(rv)) {
815 0 : PARSER_LOG(("Threat type to list name conversion error: %d",
816 : aResponse.threat_type()));
817 0 : return NS_ERROR_FAILURE;
818 : }
819 :
820 : // Match the table name we received with one of the ones we requested.
821 : // We ignore the case where a threat type matches more than one list
822 : // per provider and return the first one. See bug 1287059."
823 0 : nsCString listName;
824 0 : nsTArray<nsCString> possibleListNameArray;
825 0 : Classifier::SplitTables(possibleListNames, possibleListNameArray);
826 0 : for (auto possibleName : possibleListNameArray) {
827 0 : if (mRequestedTables.Contains(possibleName)) {
828 0 : listName = possibleName;
829 0 : break;
830 : }
831 : }
832 :
833 0 : if (listName.IsEmpty()) {
834 0 : PARSER_LOG(("We received an update for a list we didn't ask for. Ignoring it."));
835 0 : return NS_ERROR_FAILURE;
836 : }
837 :
838 : // Test if this is a full update.
839 0 : bool isFullUpdate = false;
840 0 : if (aResponse.has_response_type()) {
841 0 : isFullUpdate =
842 0 : aResponse.response_type() == ListUpdateResponse::FULL_UPDATE;
843 : } else {
844 0 : NS_WARNING("Response type not initialized.");
845 0 : return NS_ERROR_FAILURE;
846 : }
847 :
848 : // Warn if there's no new state.
849 0 : if (!aResponse.has_new_client_state()) {
850 0 : NS_WARNING("New state not initialized.");
851 0 : return NS_ERROR_FAILURE;
852 : }
853 :
854 0 : auto tu = GetTableUpdate(nsCString(listName.get()));
855 0 : auto tuV4 = TableUpdate::Cast<TableUpdateV4>(tu);
856 0 : NS_ENSURE_TRUE(tuV4, NS_ERROR_FAILURE);
857 :
858 0 : nsCString state(aResponse.new_client_state().c_str(),
859 0 : aResponse.new_client_state().size());
860 0 : tuV4->SetNewClientState(state);
861 :
862 0 : if (aResponse.has_checksum()) {
863 0 : tuV4->NewChecksum(aResponse.checksum().sha256());
864 : }
865 :
866 0 : PARSER_LOG(("==== Update for threat type '%d' ====", aResponse.threat_type()));
867 0 : PARSER_LOG(("* listName: %s\n", listName.get()));
868 0 : PARSER_LOG(("* newState: %s\n", aResponse.new_client_state().c_str()));
869 0 : PARSER_LOG(("* isFullUpdate: %s\n", (isFullUpdate ? "yes" : "no")));
870 0 : PARSER_LOG(("* hasChecksum: %s\n", (aResponse.has_checksum() ? "yes" : "no")));
871 :
872 0 : tuV4->SetFullUpdate(isFullUpdate);
873 :
874 0 : rv = ProcessAdditionOrRemoval(*tuV4, aResponse.additions(), true /*aIsAddition*/);
875 0 : NS_ENSURE_SUCCESS(rv, rv);
876 0 : rv = ProcessAdditionOrRemoval(*tuV4, aResponse.removals(), false);
877 0 : NS_ENSURE_SUCCESS(rv, rv);
878 :
879 0 : PARSER_LOG(("\n\n"));
880 :
881 0 : return NS_OK;
882 : }
883 :
884 : nsresult
885 0 : ProtocolParserProtobuf::ProcessAdditionOrRemoval(TableUpdateV4& aTableUpdate,
886 : const ThreatEntrySetList& aUpdate,
887 : bool aIsAddition)
888 : {
889 0 : nsresult ret = NS_OK;
890 :
891 0 : for (int i = 0; i < aUpdate.size(); i++) {
892 0 : auto update = aUpdate.Get(i);
893 0 : if (!update.has_compression_type()) {
894 0 : NS_WARNING(nsPrintfCString("%s with no compression type.",
895 0 : aIsAddition ? "Addition" : "Removal").get());
896 0 : continue;
897 : }
898 :
899 0 : switch (update.compression_type()) {
900 : case COMPRESSION_TYPE_UNSPECIFIED:
901 0 : NS_WARNING("Unspecified compression type.");
902 0 : break;
903 :
904 : case RAW:
905 0 : ret = (aIsAddition ? ProcessRawAddition(aTableUpdate, update)
906 : : ProcessRawRemoval(aTableUpdate, update));
907 0 : break;
908 :
909 : case RICE:
910 0 : ret = (aIsAddition ? ProcessEncodedAddition(aTableUpdate, update)
911 : : ProcessEncodedRemoval(aTableUpdate, update));
912 0 : break;
913 : }
914 : }
915 :
916 0 : return ret;
917 : }
918 :
919 : nsresult
920 0 : ProtocolParserProtobuf::ProcessRawAddition(TableUpdateV4& aTableUpdate,
921 : const ThreatEntrySet& aAddition)
922 : {
923 0 : if (!aAddition.has_raw_hashes()) {
924 0 : PARSER_LOG(("* No raw addition."));
925 0 : return NS_OK;
926 : }
927 :
928 0 : auto rawHashes = aAddition.raw_hashes();
929 0 : if (!rawHashes.has_prefix_size()) {
930 0 : NS_WARNING("Raw hash has no prefix size");
931 0 : return NS_OK;
932 : }
933 :
934 0 : auto prefixes = rawHashes.raw_hashes();
935 0 : if (4 == rawHashes.prefix_size()) {
936 : // Process fixed length prefixes separately.
937 0 : uint32_t* fixedLengthPrefixes = (uint32_t*)prefixes.c_str();
938 0 : size_t numOfFixedLengthPrefixes = prefixes.size() / 4;
939 0 : PARSER_LOG(("* Raw addition (4 bytes)"));
940 0 : PARSER_LOG((" - # of prefixes: %" PRIuSIZE, numOfFixedLengthPrefixes));
941 0 : PARSER_LOG((" - Memory address: 0x%p", fixedLengthPrefixes));
942 : } else {
943 : // TODO: Process variable length prefixes including full hashes.
944 : // See Bug 1283009.
945 0 : PARSER_LOG((" Raw addition (%d bytes)", rawHashes.prefix_size()));
946 : }
947 :
948 0 : if (!rawHashes.mutable_raw_hashes()) {
949 0 : PARSER_LOG(("Unable to get mutable raw hashes. Can't perform a string move."));
950 0 : return NS_ERROR_FAILURE;
951 : }
952 :
953 0 : aTableUpdate.NewPrefixes(rawHashes.prefix_size(),
954 0 : *rawHashes.mutable_raw_hashes());
955 :
956 0 : return NS_OK;
957 : }
958 :
959 : nsresult
960 0 : ProtocolParserProtobuf::ProcessRawRemoval(TableUpdateV4& aTableUpdate,
961 : const ThreatEntrySet& aRemoval)
962 : {
963 0 : if (!aRemoval.has_raw_indices()) {
964 0 : NS_WARNING("A removal has no indices.");
965 0 : return NS_OK;
966 : }
967 :
968 : // indices is an array of int32.
969 0 : auto indices = aRemoval.raw_indices().indices();
970 0 : PARSER_LOG(("* Raw removal"));
971 0 : PARSER_LOG((" - # of removal: %d", indices.size()));
972 :
973 0 : aTableUpdate.NewRemovalIndices((const uint32_t*)indices.data(),
974 0 : indices.size());
975 :
976 0 : return NS_OK;
977 : }
978 :
979 : static nsresult
980 0 : DoRiceDeltaDecode(const RiceDeltaEncoding& aEncoding,
981 : nsTArray<uint32_t>& aDecoded)
982 : {
983 0 : if (!aEncoding.has_first_value()) {
984 0 : PARSER_LOG(("The encoding info is incomplete."));
985 0 : return NS_ERROR_FAILURE;
986 : }
987 0 : if (aEncoding.num_entries() > 0 &&
988 0 : (!aEncoding.has_rice_parameter() || !aEncoding.has_encoded_data())) {
989 0 : PARSER_LOG(("Rice parameter or encoded data is missing."));
990 0 : return NS_ERROR_FAILURE;
991 : }
992 :
993 0 : PARSER_LOG(("* Encoding info:"));
994 0 : PARSER_LOG((" - First value: %" PRId64, aEncoding.first_value()));
995 0 : PARSER_LOG((" - Num of entries: %d", aEncoding.num_entries()));
996 0 : PARSER_LOG((" - Rice parameter: %d", aEncoding.rice_parameter()));
997 :
998 : // Set up the input buffer. Note that the bits should be read
999 : // from LSB to MSB so that we in-place reverse the bits before
1000 : // feeding to the decoder.
1001 0 : auto encoded = const_cast<RiceDeltaEncoding&>(aEncoding).mutable_encoded_data();
1002 0 : RiceDeltaDecoder decoder((uint8_t*)encoded->c_str(), encoded->size());
1003 :
1004 : // Setup the output buffer. The "first value" is included in
1005 : // the output buffer.
1006 0 : aDecoded.SetLength(aEncoding.num_entries() + 1);
1007 :
1008 : // Decode!
1009 0 : bool rv = decoder.Decode(aEncoding.rice_parameter(),
1010 0 : aEncoding.first_value(), // first value.
1011 0 : aEncoding.num_entries(), // # of entries (first value not included).
1012 0 : &aDecoded[0]);
1013 :
1014 0 : NS_ENSURE_TRUE(rv, NS_ERROR_FAILURE);
1015 :
1016 0 : return NS_OK;
1017 : }
1018 :
1019 : nsresult
1020 0 : ProtocolParserProtobuf::ProcessEncodedAddition(TableUpdateV4& aTableUpdate,
1021 : const ThreatEntrySet& aAddition)
1022 : {
1023 0 : if (!aAddition.has_rice_hashes()) {
1024 0 : PARSER_LOG(("* No rice encoded addition."));
1025 0 : return NS_OK;
1026 : }
1027 :
1028 0 : nsTArray<uint32_t> decoded;
1029 0 : nsresult rv = DoRiceDeltaDecode(aAddition.rice_hashes(), decoded);
1030 0 : if (NS_FAILED(rv)) {
1031 0 : PARSER_LOG(("Failed to parse encoded prefixes."));
1032 0 : return rv;
1033 : }
1034 :
1035 : // Say we have the following raw prefixes
1036 : // BE LE
1037 : // 00 00 00 01 1 16777216
1038 : // 00 00 02 00 512 131072
1039 : // 00 03 00 00 196608 768
1040 : // 04 00 00 00 67108864 4
1041 : //
1042 : // which can be treated as uint32 (big-endian) sorted in increasing order:
1043 : //
1044 : // [1, 512, 196608, 67108864]
1045 : //
1046 : // According to https://developers.google.com/safe-browsing/v4/compression,
1047 : // the following should be done prior to compression:
1048 : //
1049 : // 1) re-interpret in little-endian ==> [16777216, 131072, 768, 4]
1050 : // 2) sort in increasing order ==> [4, 768, 131072, 16777216]
1051 : //
1052 : // In order to get the original byte stream from |decoded|
1053 : // ([4, 768, 131072, 16777216] in this case), we have to:
1054 : //
1055 : // 1) sort in big-endian order ==> [16777216, 131072, 768, 4]
1056 : // 2) copy each uint32 in little-endian to the result string
1057 : //
1058 :
1059 : // The 4-byte prefixes have to be re-sorted in Big-endian increasing order.
1060 : struct CompareBigEndian
1061 : {
1062 0 : bool Equals(const uint32_t& aA, const uint32_t& aB) const
1063 : {
1064 0 : return aA == aB;
1065 : }
1066 :
1067 0 : bool LessThan(const uint32_t& aA, const uint32_t& aB) const
1068 : {
1069 0 : return NativeEndian::swapToBigEndian(aA) <
1070 0 : NativeEndian::swapToBigEndian(aB);
1071 : }
1072 : };
1073 0 : decoded.Sort(CompareBigEndian());
1074 :
1075 : // The encoded prefixes are always 4 bytes.
1076 0 : std::string prefixes;
1077 0 : for (size_t i = 0; i < decoded.Length(); i++) {
1078 : // Note that the third argument is the number of elements we want
1079 : // to copy (and swap) but not the number of bytes we want to copy.
1080 : char p[4];
1081 0 : NativeEndian::copyAndSwapToLittleEndian(p, &decoded[i], 1);
1082 0 : prefixes.append(p, 4);
1083 : }
1084 :
1085 0 : aTableUpdate.NewPrefixes(4, prefixes);
1086 :
1087 0 : return NS_OK;
1088 : }
1089 :
1090 : nsresult
1091 0 : ProtocolParserProtobuf::ProcessEncodedRemoval(TableUpdateV4& aTableUpdate,
1092 : const ThreatEntrySet& aRemoval)
1093 : {
1094 0 : if (!aRemoval.has_rice_indices()) {
1095 0 : PARSER_LOG(("* No rice encoded removal."));
1096 0 : return NS_OK;
1097 : }
1098 :
1099 0 : nsTArray<uint32_t> decoded;
1100 0 : nsresult rv = DoRiceDeltaDecode(aRemoval.rice_indices(), decoded);
1101 0 : if (NS_FAILED(rv)) {
1102 0 : PARSER_LOG(("Failed to decode encoded removal indices."));
1103 0 : return rv;
1104 : }
1105 :
1106 : // The encoded prefixes are always 4 bytes.
1107 0 : aTableUpdate.NewRemovalIndices(&decoded[0], decoded.Length());
1108 :
1109 0 : return NS_OK;
1110 : }
1111 :
1112 : } // namespace safebrowsing
1113 : } // namespace mozilla
|