tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_backup_recovery.js (9796B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 // This tests are for a sessionstore.js atomic backup.
      5 // Each test will wait for a write to the Session Store
      6 // before executing.
      7 
      8 const PREF_SS_INTERVAL = "browser.sessionstore.interval";
      9 const Paths = SessionFile.Paths;
     10 
     11 // Global variables that contain sessionstore.jsonlz4 and sessionstore.baklz4 data for
     12 // comparison between tests.
     13 var gSSData;
     14 var gSSBakData;
     15 
     16 function promiseRead(path) {
     17  return IOUtils.readUTF8(path, { decompress: true });
     18 }
     19 
     20 async function reInitSessionFile() {
     21  await SessionFile.wipe();
     22  await SessionFile.read();
     23 }
     24 
     25 add_setup(async function () {
     26  // Make sure that we are not racing with SessionSaver's time based
     27  // saves.
     28  Services.prefs.setIntPref(PREF_SS_INTERVAL, 10000000);
     29  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_SS_INTERVAL));
     30 });
     31 
     32 add_task(async function test_creation() {
     33  // Cancel all pending session saves so they won't get in our way.
     34  SessionSaver.cancel();
     35 
     36  // Create dummy sessionstore backups
     37  let OLD_BACKUP = PathUtils.join(PathUtils.profileDir, "sessionstore.baklz4");
     38  let OLD_UPGRADE_BACKUP = PathUtils.join(
     39    PathUtils.profileDir,
     40    "sessionstore.baklz4-0000000"
     41  );
     42 
     43  await IOUtils.writeUTF8(OLD_BACKUP, "sessionstore.bak");
     44  await IOUtils.writeUTF8(OLD_UPGRADE_BACKUP, "sessionstore upgrade backup");
     45 
     46  await reInitSessionFile();
     47 
     48  // Ensure none of the sessionstore files and backups exists
     49  for (let k of Paths.loadOrder) {
     50    ok(
     51      !(await IOUtils.exists(Paths[k])),
     52      "After wipe " + k + " sessionstore file doesn't exist"
     53    );
     54  }
     55  ok(
     56    !(await IOUtils.exists(OLD_BACKUP)),
     57    "After wipe, old backup doesn't exist"
     58  );
     59  ok(
     60    !(await IOUtils.exists(OLD_UPGRADE_BACKUP)),
     61    "After wipe, old upgrade backup doesn't exist"
     62  );
     63 
     64  // Open a new tab, save session, ensure that the correct files exist.
     65  let URL_BASE =
     66    "http://example.com/?atomic_backup_test_creation=" + Math.random();
     67  let URL = URL_BASE + "?first_write";
     68  let tab = BrowserTestUtils.addTab(gBrowser, URL);
     69 
     70  info("Testing situation after a single write");
     71  await promiseBrowserLoaded(tab.linkedBrowser);
     72  await TabStateFlusher.flush(tab.linkedBrowser);
     73  await SessionSaver.run();
     74 
     75  ok(
     76    await IOUtils.exists(Paths.recovery),
     77    "After write, recovery sessionstore file exists again"
     78  );
     79  ok(
     80    !(await IOUtils.exists(Paths.recoveryBackup)),
     81    "After write, recoveryBackup sessionstore doesn't exist"
     82  );
     83  ok(
     84    (await promiseRead(Paths.recovery)).includes(URL),
     85    "Recovery sessionstore file contains the required tab"
     86  );
     87  ok(
     88    !(await IOUtils.exists(Paths.clean)),
     89    "After first write, clean shutdown " +
     90      "sessionstore doesn't exist, since we haven't shutdown yet"
     91  );
     92 
     93  // Open a second tab, save session, ensure that the correct files exist.
     94  info("Testing situation after a second write");
     95  let URL2 = URL_BASE + "?second_write";
     96  BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, URL2);
     97  await promiseBrowserLoaded(tab.linkedBrowser);
     98  await TabStateFlusher.flush(tab.linkedBrowser);
     99  await SessionSaver.run();
    100 
    101  ok(
    102    await IOUtils.exists(Paths.recovery),
    103    "After second write, recovery sessionstore file still exists"
    104  );
    105  ok(
    106    (await promiseRead(Paths.recovery)).includes(URL2),
    107    "Recovery sessionstore file contains the latest url"
    108  );
    109  ok(
    110    await IOUtils.exists(Paths.recoveryBackup),
    111    "After write, recoveryBackup sessionstore now exists"
    112  );
    113  let backup = await promiseRead(Paths.recoveryBackup);
    114  ok(!backup.includes(URL2), "Recovery backup doesn't contain the latest url");
    115  ok(backup.includes(URL), "Recovery backup contains the original url");
    116  ok(
    117    !(await IOUtils.exists(Paths.clean)),
    118    "After first write, clean shutdown " +
    119      "sessionstore doesn't exist, since we haven't shutdown yet"
    120  );
    121 
    122  info("Reinitialize, ensure that we haven't leaked sensitive files");
    123  await SessionFile.read(); // Reinitializes SessionFile
    124  await SessionSaver.run();
    125  ok(
    126    !(await IOUtils.exists(Paths.clean)),
    127    "After second write, clean shutdown " +
    128      "sessionstore doesn't exist, since we haven't shutdown yet"
    129  );
    130  Assert.strictEqual(
    131    Paths.upgradeBackup,
    132    "",
    133    "After second write, clean " +
    134      "shutdown sessionstore doesn't exist, since we haven't shutdown yet"
    135  );
    136  ok(
    137    !(await IOUtils.exists(Paths.nextUpgradeBackup)),
    138    "After second write, clean " +
    139      "shutdown sessionstore doesn't exist, since we haven't shutdown yet"
    140  );
    141 
    142  gBrowser.removeTab(tab);
    143 });
    144 
    145 var promiseSource = async function (name) {
    146  let URL =
    147    "http://example.com/?atomic_backup_test_recovery=" +
    148    Math.random() +
    149    "&name=" +
    150    name;
    151  let tab = BrowserTestUtils.addTab(gBrowser, URL);
    152 
    153  await promiseBrowserLoaded(tab.linkedBrowser);
    154  await TabStateFlusher.flush(tab.linkedBrowser);
    155  await SessionSaver.run();
    156  gBrowser.removeTab(tab);
    157 
    158  let SOURCE = await promiseRead(Paths.recovery);
    159  await SessionFile.wipe();
    160  return SOURCE;
    161 };
    162 
    163 add_task(async function test_recovery() {
    164  await reInitSessionFile();
    165  info("Attempting to recover from the recovery file");
    166 
    167  // Create Paths.recovery, ensure that we can recover from it.
    168  let SOURCE = await promiseSource("Paths.recovery");
    169  await IOUtils.makeDirectory(Paths.backups);
    170  await IOUtils.writeUTF8(Paths.recovery, SOURCE, { compress: true });
    171  is(
    172    (await SessionFile.read()).source,
    173    SOURCE,
    174    "Recovered the correct source from the recovery file"
    175  );
    176 
    177  info("Corrupting recovery file, attempting to recover from recovery backup");
    178  SOURCE = await promiseSource("Paths.recoveryBackup");
    179  await IOUtils.makeDirectory(Paths.backups);
    180  await IOUtils.writeUTF8(Paths.recoveryBackup, SOURCE, { compress: true });
    181  await IOUtils.writeUTF8(Paths.recovery, "<Invalid JSON>", { compress: true });
    182  is(
    183    (await SessionFile.read()).source,
    184    SOURCE,
    185    "Recovered the correct source from the recovery file"
    186  );
    187 });
    188 
    189 add_task(async function test_recovery_inaccessible() {
    190  // Can't do chmod() on non-UNIX platforms, we need that for this test.
    191  if (AppConstants.platform != "macosx" && AppConstants.platform != "linux") {
    192    return;
    193  }
    194 
    195  await reInitSessionFile();
    196  info(
    197    "Making recovery file inaccessible, attempting to recover from recovery backup"
    198  );
    199  let SOURCE_RECOVERY = await promiseSource("Paths.recovery");
    200  let SOURCE = await promiseSource("Paths.recoveryBackup");
    201  await IOUtils.makeDirectory(Paths.backups);
    202  await IOUtils.writeUTF8(Paths.recoveryBackup, SOURCE, { compress: true });
    203 
    204  // Write a valid recovery file but make it inaccessible.
    205  await IOUtils.writeUTF8(Paths.recovery, SOURCE_RECOVERY, { compress: true });
    206  await IOUtils.setPermissions(Paths.recovery, 0);
    207 
    208  is(
    209    (await SessionFile.read()).source,
    210    SOURCE,
    211    "Recovered the correct source from the recovery file"
    212  );
    213  await IOUtils.setPermissions(Paths.recovery, 0o644);
    214 });
    215 
    216 add_task(async function test_clean() {
    217  await reInitSessionFile();
    218  let SOURCE = await promiseSource("Paths.clean");
    219  await IOUtils.writeUTF8(Paths.clean, SOURCE, { compress: true });
    220  await SessionFile.read();
    221  await SessionSaver.run();
    222  is(
    223    await promiseRead(Paths.cleanBackup),
    224    SOURCE,
    225    "After first read/write, " +
    226      "clean shutdown file has been moved to cleanBackup"
    227  );
    228 });
    229 
    230 /**
    231 * Tests loading of sessionstore when format version is known.
    232 */
    233 add_task(async function test_version() {
    234  info("Preparing sessionstore");
    235  let SOURCE = await promiseSource("Paths.clean");
    236 
    237  // Check there's a format version number
    238  is(
    239    JSON.parse(SOURCE).version[0],
    240    "sessionrestore",
    241    "Found sessionstore format version"
    242  );
    243 
    244  // Create Paths.clean file
    245  await IOUtils.makeDirectory(Paths.backups);
    246  await IOUtils.writeUTF8(Paths.clean, SOURCE, { compress: true });
    247 
    248  info("Attempting to recover from the clean file");
    249  // Ensure that we can recover from Paths.recovery
    250  is(
    251    (await SessionFile.read()).source,
    252    SOURCE,
    253    "Recovered the correct source from the clean file"
    254  );
    255 });
    256 
    257 /**
    258 * Tests fallback to previous backups if format version is unknown.
    259 */
    260 add_task(async function test_version_fallback() {
    261  await reInitSessionFile();
    262  info("Preparing data, making sure that it has a version number");
    263  let SOURCE = await promiseSource("Paths.clean");
    264  let BACKUP_SOURCE = await promiseSource("Paths.cleanBackup");
    265 
    266  is(
    267    JSON.parse(SOURCE).version[0],
    268    "sessionrestore",
    269    "Found sessionstore format version"
    270  );
    271  is(
    272    JSON.parse(BACKUP_SOURCE).version[0],
    273    "sessionrestore",
    274    "Found backup sessionstore format version"
    275  );
    276 
    277  await IOUtils.makeDirectory(Paths.backups);
    278 
    279  info(
    280    "Modifying format version number to something incorrect, to make sure that we disregard the file."
    281  );
    282  let parsedSource = JSON.parse(SOURCE);
    283  parsedSource.version[0] = "bookmarks";
    284  await IOUtils.writeJSON(Paths.clean, parsedSource, { compress: true });
    285  await IOUtils.writeUTF8(Paths.cleanBackup, BACKUP_SOURCE, { compress: true });
    286  is(
    287    (await SessionFile.read()).source,
    288    BACKUP_SOURCE,
    289    "Recovered the correct source from the backup recovery file"
    290  );
    291 
    292  info(
    293    "Modifying format version number to a future version, to make sure that we disregard the file."
    294  );
    295  parsedSource = JSON.parse(SOURCE);
    296  parsedSource.version[1] = Number.MAX_SAFE_INTEGER;
    297  await IOUtils.writeJSON(Paths.clean, parsedSource, { compress: true });
    298  await IOUtils.writeUTF8(Paths.cleanBackup, BACKUP_SOURCE, { compress: true });
    299  is(
    300    (await SessionFile.read()).source,
    301    BACKUP_SOURCE,
    302    "Recovered the correct source from the backup recovery file"
    303  );
    304 });
    305 
    306 add_task(async function cleanup() {
    307  await reInitSessionFile();
    308 });