summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_dump.js
blob: 3cbd6073395d584f1b7ff397f5467d63eddd7b4c (plain)
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
/* Any copyright is dedicated to the Public Domain.
 * https://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/**
 * @fileOverview Verifies that the MLBF dump of the addons blocklist is
 * correctly registered.
 */

Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);

const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();

// A known blocked version from bug 1626602.
const blockedAddon = {
  id: "{6f62927a-e380-401a-8c9e-c485b7d87f0d}",
  version: "9.2.0",
  // The following date is the date of the first checked in MLBF. Any MLBF
  // generated in the future should be generated after this date, to be useful.
  signedDate: new Date(1588098908496), // 2020-04-28 (dummy date)
  signedState: AddonManager.SIGNEDSTATE_SIGNED,
};

// A known add-on that is not blocked, as of writing. It is likely not going
// to be blocked because it does not have any executable code.
const nonBlockedAddon = {
  id: "disable-ctrl-q-and-cmd-q@robwu.nl",
  version: "1",
  signedDate: new Date(1482430349000), // 2016-12-22 (actual signing time).
  signedState: AddonManager.SIGNEDSTATE_SIGNED,
};

async function sha256(arrayBuffer) {
  Cu.importGlobalProperties(["crypto"]);
  let hash = await crypto.subtle.digest("SHA-256", arrayBuffer);
  const toHex = b => b.toString(16).padStart(2, "0");
  return Array.from(new Uint8Array(hash), toHex).join("");
}

// A list of { inputRecord, downloadPromise }:
// - inputRecord is the record that was used for looking up the MLBF.
// - downloadPromise is the result of trying to download it.
const observed = [];

add_task(async function setup() {
  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
  ExtensionBlocklistMLBF.ensureInitialized();

  // Tapping into the internals of ExtensionBlocklistMLBF._fetchMLBF to observe
  // MLBF request details.

  // Despite being called "download", this does not actually access the network
  // when there is a valid dump.
  const originalImpl = ExtensionBlocklistMLBF._client.attachments.download;
  ExtensionBlocklistMLBF._client.attachments.download = function (record) {
    let downloadPromise = originalImpl.apply(this, arguments);
    observed.push({ inputRecord: record, downloadPromise });
    return downloadPromise;
  };

  await promiseStartupManager();
});

async function verifyBlocklistWorksWithDump() {
  Assert.equal(
    await Blocklist.getAddonBlocklistState(blockedAddon),
    Ci.nsIBlocklistService.STATE_BLOCKED,
    "A add-on that is known to be on the blocklist should be blocked"
  );
  Assert.equal(
    await Blocklist.getAddonBlocklistState(nonBlockedAddon),
    Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
    "A known non-blocked add-on should not be blocked"
  );
}

add_task(async function verify_dump_first_run() {
  await verifyBlocklistWorksWithDump();
  Assert.equal(observed.length, 1, "expected number of MLBF download requests");

  const { inputRecord, downloadPromise } = observed.pop();

  Assert.ok(inputRecord, "addons-bloomfilters collection dump exists");

  const downloadResult = await downloadPromise;

  // Verify that the "download" result really originates from the local dump.
  // "dump_match" means that the record exists in the collection and that an
  // attachment was found.
  //
  // If this fails:
  // - "dump_fallback" means that the MLBF attachment is out of sync with the
  //   collection data.
  // - undefined could mean that the implementation of Attachments.sys.mjs changed.
  Assert.equal(
    downloadResult._source,
    "dump_match",
    "MLBF attachment should match the RemoteSettings collection"
  );

  Assert.equal(
    await sha256(downloadResult.buffer),
    inputRecord.attachment.hash,
    "The content of the attachment should actually matches the record"
  );
});

add_task(async function use_dump_fallback_when_collection_is_out_of_sync() {
  await AddonTestUtils.loadBlocklistRawData({
    // last_modified higher than any value in addons-bloomfilters.json.
    extensionsMLBF: [{ last_modified: Date.now() }],
  });
  Assert.equal(observed.length, 1, "Expected new download on update");

  const { inputRecord, downloadPromise } = observed.pop();
  Assert.equal(inputRecord, null, "No MLBF record found");

  const downloadResult = await downloadPromise;
  Assert.equal(
    downloadResult._source,
    "dump_fallback",
    "should have used fallback despite the absence of a MLBF record"
  );

  await verifyBlocklistWorksWithDump();
  Assert.equal(observed.length, 0, "Blocklist uses cached result");
});

// Verifies that the dump would supersede local data. This can happen after an
// application upgrade, where the local database contains outdated records from
// a previous application version.
add_task(async function verify_dump_supersedes_old_dump() {
  // Delete in-memory value; otherwise the cached record from the previous test
  // task would be re-used and nothing would be downloaded.
  delete ExtensionBlocklistMLBF._mlbfData;

  await AddonTestUtils.loadBlocklistRawData({
    // last_modified lower than any value in addons-bloomfilters.json.
    extensionsMLBF: [{ last_modified: 1 }],
  });
  Assert.equal(observed.length, 1, "Expected new download on update");

  const { inputRecord, downloadPromise } = observed.pop();
  Assert.ok(inputRecord, "should have read from addons-bloomfilters dump");

  const downloadResult = await downloadPromise;
  Assert.equal(
    downloadResult._source,
    "dump_match",
    "Should have replaced outdated collection records with dump"
  );

  await verifyBlocklistWorksWithDump();
  Assert.equal(observed.length, 0, "Blocklist uses cached result");
});