tor-browser

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

browser_content_sandbox_fs_tests.js (19721B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 /* import-globals-from browser_content_sandbox_utils.js */
      4 "use strict";
      5 
      6 const lazy = {};
      7 
      8 /* getLibcConstants is only present on *nix */
      9 ChromeUtils.defineLazyGetter(lazy, "LIBC", () =>
     10  ChromeUtils.getLibcConstants()
     11 );
     12 
     13 // Test if the content process can create in $HOME, this should fail
     14 async function createFileInHome() {
     15  let browser = gBrowser.selectedBrowser;
     16  let homeFile = fileInHomeDir();
     17  let path = homeFile.path;
     18  let fileCreated = await SpecialPowers.spawn(browser, [path], createFile);
     19  ok(!fileCreated.ok, "creating a file in home dir failed");
     20  is(
     21    fileCreated.code,
     22    Cr.NS_ERROR_FILE_ACCESS_DENIED,
     23    "creating a file in home dir failed with access denied"
     24  );
     25  if (fileCreated.ok) {
     26    // content process successfully created the file, now remove it
     27    homeFile.remove(false);
     28  }
     29 }
     30 
     31 // Test if the content process can create a temp file, this is forbidden on all
     32 // platforms. Also test that the content process cannot create symlinks on
     33 // macOS/Linux or delete files.
     34 async function createTempFile() {
     35  // On Windows we allow access to the temp dir for DEBUG builds, because of
     36  // logging that uses that dir.
     37  let isDbgWin = isWin() && SpecialPowers.isDebugBuild;
     38 
     39  let browser = gBrowser.selectedBrowser;
     40  let path = fileInTempDir().path;
     41  let fileCreated = await SpecialPowers.spawn(browser, [path], createFile);
     42  if (isDbgWin) {
     43    ok(fileCreated.ok, "creating a file in temp suceeded");
     44  } else {
     45    ok(!fileCreated.ok, "creating a file in temp failed");
     46    is(
     47      fileCreated.code,
     48      Cr.NS_ERROR_FILE_ACCESS_DENIED,
     49      "creating a file in temp failed with access denied"
     50    );
     51  }
     52 
     53  // now delete the file
     54  let fileDeleted = await SpecialPowers.spawn(browser, [path], deleteFile);
     55  if (isDbgWin) {
     56    ok(fileDeleted.ok, "deleting a file in temp succeeded");
     57  } else {
     58    ok(!fileDeleted.ok, "deleting a file in temp failed");
     59    const expectedError = isLinux()
     60      ? Cr.NS_ERROR_FILE_ACCESS_DENIED
     61      : Cr.NS_ERROR_FILE_NOT_FOUND;
     62    is(
     63      fileDeleted.code,
     64      expectedError,
     65      "deleting a file in temp failed with access denied"
     66    );
     67  }
     68 
     69  // Test that symlink creation is not allowed on macOS/Linux.
     70  if (isMac() || isLinux()) {
     71    let path = fileInTempDir().path;
     72    let symlinkCreated = await SpecialPowers.spawn(
     73      browser,
     74      [path],
     75      createSymlink
     76    );
     77    ok(!symlinkCreated.ok, "created a symlink in temp failed");
     78    const expectedError = isLinux() ? lazy.LIBC.EACCES : lazy.LIBC.EPERM;
     79    is(
     80      symlinkCreated.code,
     81      expectedError,
     82      "created a symlink in temp failed with access denied"
     83    );
     84  }
     85 }
     86 
     87 // Test reading files and dirs from web and file content processes.
     88 async function testFileAccessAllPlatforms() {
     89  let webBrowser = GetWebBrowser();
     90  let fileContentProcessEnabled = isFileContentProcessEnabled();
     91  let fileBrowser = GetFileBrowser();
     92 
     93  // Directories/files to test accessing from content processes.
     94  // For directories, we test whether a directory listing is allowed
     95  // or blocked. For files, we test if we can read from the file.
     96  // Each entry in the array represents a test file or directory
     97  // that will be read from either a web or file process.
     98  let tests = [];
     99 
    100  let profileDir = GetProfileDir();
    101  tests.push({
    102    desc: "profile dir", // description
    103    ok: false, // expected to succeed?
    104    browser: webBrowser, // browser to run test in
    105    file: profileDir, // nsIFile object
    106    minLevel: minProfileReadSandboxLevel(), // min level to enable test
    107    func: readDir,
    108  });
    109  if (fileContentProcessEnabled) {
    110    tests.push({
    111      desc: "profile dir",
    112      ok: true,
    113      browser: fileBrowser,
    114      file: profileDir,
    115      minLevel: 0,
    116      func: readDir,
    117    });
    118  }
    119 
    120  let homeDir = GetHomeDir();
    121  tests.push({
    122    desc: "home dir",
    123    ok: false,
    124    browser: webBrowser,
    125    file: homeDir,
    126    minLevel: minHomeReadSandboxLevel(),
    127    func: readDir,
    128  });
    129  if (fileContentProcessEnabled) {
    130    tests.push({
    131      desc: "home dir",
    132      ok: true,
    133      browser: fileBrowser,
    134      file: homeDir,
    135      minLevel: 0,
    136      func: readDir,
    137    });
    138  }
    139 
    140  let extensionsDir = GetProfileEntry("extensions");
    141  if (extensionsDir.exists() && extensionsDir.isDirectory()) {
    142    tests.push({
    143      desc: "extensions dir",
    144      ok: true,
    145      browser: webBrowser,
    146      file: extensionsDir,
    147      minLevel: 0,
    148      func: readDir,
    149    });
    150  } else {
    151    ok(false, `${extensionsDir.path} is a valid dir`);
    152  }
    153 
    154  let chromeDir = GetProfileEntry("chrome");
    155  if (chromeDir.exists() && chromeDir.isDirectory()) {
    156    tests.push({
    157      desc: "chrome dir",
    158      ok: true,
    159      browser: webBrowser,
    160      file: chromeDir,
    161      minLevel: 0,
    162      func: readDir,
    163    });
    164  } else {
    165    ok(false, `${chromeDir.path} is valid dir`);
    166  }
    167 
    168  let cookiesFile = GetProfileEntry("cookies.sqlite");
    169  if (cookiesFile.exists() && !cookiesFile.isDirectory()) {
    170    tests.push({
    171      desc: "cookies file",
    172      ok: false,
    173      browser: webBrowser,
    174      file: cookiesFile,
    175      minLevel: minProfileReadSandboxLevel(),
    176      func: readFile,
    177    });
    178    if (fileContentProcessEnabled) {
    179      tests.push({
    180        desc: "cookies file",
    181        ok: true,
    182        browser: fileBrowser,
    183        file: cookiesFile,
    184        minLevel: 0,
    185        func: readFile,
    186      });
    187    }
    188  } else {
    189    ok(false, `${cookiesFile.path} is a valid file`);
    190  }
    191 
    192  if (isMac() || isLinux()) {
    193    let varDir = GetDir("/var");
    194 
    195    if (isMac()) {
    196      // Mac sandbox rules use /private/var because /var is a symlink
    197      // to /private/var on OS X. Make sure that hasn't changed.
    198      varDir.normalize();
    199      Assert.strictEqual(
    200        varDir.path,
    201        "/private/var",
    202        "/var resolves to /private/var"
    203      );
    204    }
    205 
    206    tests.push({
    207      desc: "/var",
    208      ok: false,
    209      browser: webBrowser,
    210      file: varDir,
    211      minLevel: minHomeReadSandboxLevel(),
    212      func: readDir,
    213    });
    214    if (fileContentProcessEnabled) {
    215      tests.push({
    216        desc: "/var",
    217        ok: true,
    218        browser: fileBrowser,
    219        file: varDir,
    220        minLevel: 0,
    221        func: readDir,
    222      });
    223    }
    224  }
    225 
    226  await runTestsList(tests);
    227 }
    228 
    229 async function testFileAccessMacOnly() {
    230  if (!isMac()) {
    231    return;
    232  }
    233 
    234  let webBrowser = GetWebBrowser();
    235  let fileContentProcessEnabled = isFileContentProcessEnabled();
    236  let fileBrowser = GetFileBrowser();
    237  let level = GetSandboxLevel();
    238 
    239  let tests = [];
    240 
    241  // If ~/Library/Caches/TemporaryItems exists, when level <= 2 we
    242  // make sure it's readable. For level 3, we make sure it isn't.
    243  let homeTempDir = GetHomeDir();
    244  homeTempDir.appendRelativePath("Library/Caches/TemporaryItems");
    245  if (homeTempDir.exists()) {
    246    let shouldBeReadable, minLevel;
    247    if (level >= minHomeReadSandboxLevel()) {
    248      shouldBeReadable = false;
    249      minLevel = minHomeReadSandboxLevel();
    250    } else {
    251      shouldBeReadable = true;
    252      minLevel = 0;
    253    }
    254    tests.push({
    255      desc: "home library cache temp dir",
    256      ok: shouldBeReadable,
    257      browser: webBrowser,
    258      file: homeTempDir,
    259      minLevel,
    260      func: readDir,
    261    });
    262  }
    263 
    264  // Test if we can read from $TMPDIR because we expect it
    265  // to be within /private/var. Reading from it should be
    266  // prevented in a 'web' process.
    267  let macTempDir = GetDirFromEnvVariable("TMPDIR");
    268 
    269  macTempDir.normalize();
    270  Assert.ok(
    271    macTempDir.path.startsWith("/private/var"),
    272    "$TMPDIR is in /private/var"
    273  );
    274 
    275  tests.push({
    276    desc: `$TMPDIR (${macTempDir.path})`,
    277    ok: false,
    278    browser: webBrowser,
    279    file: macTempDir,
    280    minLevel: minHomeReadSandboxLevel(),
    281    func: readDir,
    282  });
    283  if (fileContentProcessEnabled) {
    284    tests.push({
    285      desc: `$TMPDIR (${macTempDir.path})`,
    286      ok: true,
    287      browser: fileBrowser,
    288      file: macTempDir,
    289      minLevel: 0,
    290      func: readDir,
    291    });
    292  }
    293 
    294  // The font registry directory is in the Darwin user cache dir which is
    295  // accessible with the getconf(1) library call using DARWIN_USER_CACHE_DIR.
    296  // For this test, assume the cache dir is located at $TMPDIR/../C and use
    297  // the $TMPDIR to derive the path to the registry.
    298  let fontRegistryDir = macTempDir.parent.clone();
    299  fontRegistryDir.appendRelativePath("C/com.apple.FontRegistry");
    300  if (fontRegistryDir.exists()) {
    301    tests.push({
    302      desc: `FontRegistry (${fontRegistryDir.path})`,
    303      ok: true,
    304      browser: webBrowser,
    305      file: fontRegistryDir,
    306      minLevel: minHomeReadSandboxLevel(),
    307      func: readDir,
    308    });
    309    // Check that we can read the file named `font` which typically
    310    // exists in the the font registry directory.
    311    let fontFile = fontRegistryDir.clone();
    312    fontFile.appendRelativePath("font");
    313    if (fontFile.exists()) {
    314      tests.push({
    315        desc: `FontRegistry file (${fontFile.path})`,
    316        ok: true,
    317        browser: webBrowser,
    318        file: fontFile,
    319        minLevel: minHomeReadSandboxLevel(),
    320        func: readFile,
    321      });
    322    }
    323  }
    324 
    325  // Test that we cannot read from /Volumes at level 3
    326  let volumes = GetDir("/Volumes");
    327  tests.push({
    328    desc: "/Volumes",
    329    ok: false,
    330    browser: webBrowser,
    331    file: volumes,
    332    minLevel: minHomeReadSandboxLevel(),
    333    func: readDir,
    334  });
    335 
    336  // Test that we cannot read from /Users at level 3
    337  let users = GetDir("/Users");
    338  tests.push({
    339    desc: "/Users",
    340    ok: false,
    341    browser: webBrowser,
    342    file: users,
    343    minLevel: minHomeReadSandboxLevel(),
    344    func: readDir,
    345  });
    346 
    347  // Test that we can stat /Users at level 3
    348  tests.push({
    349    desc: "/Users",
    350    ok: true,
    351    browser: webBrowser,
    352    file: users,
    353    minLevel: minHomeReadSandboxLevel(),
    354    func: statPath,
    355  });
    356 
    357  // Test that we can stat /Library at level 3, but can't get a
    358  // directory listing of /Library. This test uses "/Library"
    359  // because it's a path that is expected to always be present.
    360  let libraryDir = GetDir("/Library");
    361  tests.push({
    362    desc: "/Library",
    363    ok: true,
    364    browser: webBrowser,
    365    file: libraryDir,
    366    minLevel: minHomeReadSandboxLevel(),
    367    func: statPath,
    368  });
    369  tests.push({
    370    desc: "/Library",
    371    ok: false,
    372    browser: webBrowser,
    373    file: libraryDir,
    374    minLevel: minHomeReadSandboxLevel(),
    375    func: readDir,
    376  });
    377 
    378  // Similarly, test that we can stat /private, but not /private/etc.
    379  let privateDir = GetDir("/private");
    380  tests.push({
    381    desc: "/private",
    382    ok: true,
    383    browser: webBrowser,
    384    file: privateDir,
    385    minLevel: minHomeReadSandboxLevel(),
    386    func: statPath,
    387  });
    388 
    389  await runTestsList(tests);
    390 }
    391 
    392 async function testFileAccessLinuxOnly() {
    393  if (!isLinux()) {
    394    return;
    395  }
    396 
    397  let webBrowser = GetWebBrowser();
    398  let fileContentProcessEnabled = isFileContentProcessEnabled();
    399  let fileBrowser = GetFileBrowser();
    400 
    401  let tests = [];
    402 
    403  // Test /proc/self/fd, because that can be used to unfreeze
    404  // frozen shared memory.
    405  let selfFdDir = GetDir("/proc/self/fd");
    406  tests.push({
    407    desc: "/proc/self/fd",
    408    ok: false,
    409    browser: webBrowser,
    410    file: selfFdDir,
    411    minLevel: isContentFileIOSandboxed(),
    412    func: readDir,
    413  });
    414 
    415  let cacheFontConfigDir = GetHomeSubdir(".cache/fontconfig/");
    416  tests.push({
    417    desc: `$HOME/.cache/fontconfig/ (${cacheFontConfigDir.path})`,
    418    ok: true,
    419    browser: webBrowser,
    420    file: cacheFontConfigDir,
    421    minLevel: minHomeReadSandboxLevel(),
    422    func: readDir,
    423  });
    424 
    425  // allows to handle both $HOME/.config/ or $XDG_CONFIG_HOME
    426  let configDir = GetHomeSubdir(".config");
    427 
    428  const xdgConfigHome = Services.env.get("XDG_CONFIG_HOME");
    429  if (xdgConfigHome) {
    430    configDir = GetDir(xdgConfigHome);
    431    configDir.normalize();
    432  }
    433 
    434  tests.push({
    435    desc: `$XDG_CONFIG_HOME (${configDir.path})`,
    436    ok: true, // access should not be granted outside of XDG support
    437    browser: webBrowser,
    438    file: configDir,
    439    minLevel: minHomeReadSandboxLevel(),
    440    func: readDir,
    441  });
    442 
    443  tests.push({
    444    desc: `XDG_CONFIG_HOME=${configDir.path} dir should have rdonly`,
    445    ok: true, // should be allowed only if XDG support is there
    446    browser: webBrowser,
    447    file: configDir,
    448    minLevel: minHomeReadSandboxLevel(),
    449    func: readDir,
    450  });
    451 
    452  if (fileContentProcessEnabled) {
    453    tests.push({
    454      desc: `${configDir.path} dir`,
    455      ok: true, // should be allowed only if XDG support is there
    456      browser: fileBrowser,
    457      file: configDir,
    458      minLevel: 0,
    459      func: readDir,
    460    });
    461  }
    462 
    463  if (isXdgEnabled() && xdgConfigHome) {
    464    const homeConfigDir = GetHomeSubdir(".config");
    465    tests.push({
    466      desc: `XDG_CONFIG_HOME=${homeConfigDir.path} dir should deny $HOME/.config`,
    467      ok: false,
    468      browser: webBrowser,
    469      file: homeConfigDir,
    470      minLevel: minHomeReadSandboxLevel(),
    471      func: readDir,
    472    });
    473    if (fileContentProcessEnabled) {
    474      tests.push({
    475        desc: `${homeConfigDir.path} dir`,
    476        ok: true,
    477        browser: fileBrowser,
    478        file: homeConfigDir,
    479        minLevel: 0,
    480        func: readDir,
    481      });
    482    }
    483  } else {
    484    // WWhen XDG_CONFIG_HOME is not set, verify we do not allow $HOME/.configlol
    485    // (i.e., check allow the dir and not the prefix)
    486    //
    487    // Checking $HOME/.config is already done above.
    488    const homeConfigPrefix = GetHomeSubdir(".configlol");
    489    tests.push({
    490      desc: `No XDG_CONFIG_HOME we dont allow ${homeConfigPrefix.path} access`,
    491      ok: false,
    492      browser: webBrowser,
    493      file: homeConfigPrefix,
    494      minLevel: minHomeReadSandboxLevel(),
    495      func: readDir,
    496    });
    497    if (fileContentProcessEnabled) {
    498      tests.push({
    499        desc: `No XDG_CONFIG_HOME we dont allow ${homeConfigPrefix.path} access`,
    500        ok: false,
    501        browser: fileBrowser,
    502        file: homeConfigPrefix,
    503        minLevel: 0,
    504        func: readDir,
    505      });
    506    }
    507  }
    508 
    509  // Create a file under $HOME/.config/ or $XDG_CONFIG_HOME and ensure we can
    510  // read it
    511  let fileUnderConfig = GetSubdirFile(configDir);
    512  await IOUtils.writeUTF8(fileUnderConfig.path, "TEST FILE DUMMY DATA");
    513  ok(
    514    await IOUtils.exists(fileUnderConfig.path),
    515    `File ${fileUnderConfig.path} was properly created`
    516  );
    517 
    518  tests.push({
    519    desc: `${configDir.path}/xxx is readable (${fileUnderConfig.path})`,
    520    ok: true,
    521    browser: webBrowser,
    522    file: fileUnderConfig,
    523    minLevel: minHomeReadSandboxLevel(),
    524    func: readFile,
    525    cleanup: aPath => IOUtils.remove(aPath),
    526  });
    527 
    528  let configFile = GetSubdirFile(configDir);
    529  tests.push({
    530    desc: `${configDir.path} file write`,
    531    ok: false,
    532    browser: webBrowser,
    533    file: configFile,
    534    minLevel: minHomeReadSandboxLevel(),
    535    func: createFile,
    536  });
    537  if (fileContentProcessEnabled) {
    538    tests.push({
    539      desc: `${configDir.path} file write`,
    540      ok: false,
    541      browser: fileBrowser,
    542      file: configFile,
    543      minLevel: 0,
    544      func: createFile,
    545    });
    546  }
    547 
    548  // Create a $HOME/.config/mozilla/ or $XDG_CONFIG_HOME/mozilla/ if none
    549  // exists and assert content process cannot access it
    550  let configMozilla = GetSubdir(configDir, "mozilla");
    551  const emptyFileName = ".test_run_browser_sandbox.tmp";
    552  let emptyFile = configMozilla.clone();
    553  emptyFile.appendRelativePath(emptyFileName);
    554 
    555  let populateFakeConfigMozilla = async aPath => {
    556    // called with configMozilla
    557    await IOUtils.makeDirectory(aPath, { permissions: 0o700 });
    558    await IOUtils.writeUTF8(emptyFile.path, "");
    559    ok(
    560      await IOUtils.exists(emptyFile.path),
    561      `Temp file ${emptyFile.path} was created`
    562    );
    563  };
    564 
    565  let unpopulateFakeConfigMozilla = async aPath => {
    566    // called with emptyFile
    567    await IOUtils.remove(aPath);
    568    ok(!(await IOUtils.exists(aPath)), `Temp file ${aPath} was removed`);
    569    const parentDir = PathUtils.parent(aPath);
    570    try {
    571      await IOUtils.remove(parentDir, { recursive: false });
    572    } catch (ex) {
    573      if (
    574        !DOMException.isInstance(ex) ||
    575        ex.name !== "OperationError" ||
    576        /Could not remove the non-empty directory/.test(ex.message)
    577      ) {
    578        // If we get here it means the directory was not empty and since we assert
    579        // earlier we removed the temp file we created it means we should not
    580        // worrying about removing this directory ...
    581        throw ex;
    582      }
    583    }
    584  };
    585 
    586  await populateFakeConfigMozilla(configMozilla.path);
    587 
    588  tests.push({
    589    desc: `stat ${configDir.path}/mozilla (${configMozilla.path})`,
    590    ok: false,
    591    browser: webBrowser,
    592    file: configMozilla,
    593    minLevel: minHomeReadSandboxLevel(),
    594    func: statPath,
    595  });
    596 
    597  tests.push({
    598    desc: `read ${configDir.path}/mozilla (${configMozilla.path})`,
    599    ok: false,
    600    browser: webBrowser,
    601    file: configMozilla,
    602    minLevel: minHomeReadSandboxLevel(),
    603    func: readDir,
    604  });
    605 
    606  tests.push({
    607    desc: `stat ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
    608    ok: false,
    609    browser: webBrowser,
    610    file: emptyFile,
    611    minLevel: minHomeReadSandboxLevel(),
    612    func: statPath,
    613  });
    614 
    615  tests.push({
    616    desc: `read ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
    617    ok: false,
    618    browser: webBrowser,
    619    file: emptyFile,
    620    minLevel: minHomeReadSandboxLevel(),
    621    func: readFile,
    622    cleanup: unpopulateFakeConfigMozilla,
    623  });
    624 
    625  // Only needed to perform cleanup
    626  if (isXdgEnabled()) {
    627    tests.push({
    628      desc: `$XDG_CONFIG_HOME (${configDir.path}) cleanup`,
    629      ok: true,
    630      browser: webBrowser,
    631      file: configDir,
    632      minLevel: minHomeReadSandboxLevel(),
    633      func: readDir,
    634    });
    635  }
    636 
    637  await runTestsList(tests);
    638 }
    639 
    640 async function testFileAccessLinuxSnap() {
    641  let webBrowser = GetWebBrowser();
    642 
    643  let tests = [];
    644 
    645  // Assert that if we run with SNAP= env, then we allow access to it in the
    646  // content process
    647  let snap = Services.env.get("SNAP");
    648  let snapExpectedResult = false;
    649  if (snap.length > 1) {
    650    snapExpectedResult = true;
    651  } else {
    652    snap = "/tmp/.snap_firefox_current/";
    653  }
    654 
    655  let snapDir = GetDir(snap);
    656  snapDir.normalize();
    657 
    658  let snapFile = GetSubdirFile(snapDir);
    659  await createFile(snapFile.path);
    660  ok(await IOUtils.exists(snapFile.path), `SNAP ${snapFile.path} was created`);
    661  info(`SNAP (file) ${snapFile.path} was created`);
    662 
    663  tests.push({
    664    desc: `$SNAP (${snapDir.path} => ${snapFile.path})`,
    665    ok: snapExpectedResult,
    666    browser: webBrowser,
    667    file: snapFile,
    668    minLevel: minHomeReadSandboxLevel(),
    669    func: readFile,
    670  });
    671 
    672  await runTestsList(tests);
    673 }
    674 
    675 async function testFileAccessWindowsOnly() {
    676  if (!isWin()) {
    677    return;
    678  }
    679 
    680  let webBrowser = GetWebBrowser();
    681 
    682  let tests = [];
    683 
    684  let extDir = GetPerUserExtensionDir();
    685  // We used to unconditionally create this directory from Firefox, but that
    686  // was dropped in bug 2001887. The value of this directory is questionable;
    687  // the test was added in Firefox 56 (bug 1403744) to cover legacy add-ons,
    688  // but legacy add-on support was discontinued in Firefox 57, and we stopped
    689  // sideloading add-ons from this directory on all builds except ESR in
    690  // Firefox 74 (bug 1602840).
    691  await IOUtils.makeDirectory(extDir.path);
    692  tests.push({
    693    desc: "per-user extensions dir",
    694    ok: true,
    695    browser: webBrowser,
    696    file: extDir,
    697    minLevel: minHomeReadSandboxLevel(),
    698    func: readDir,
    699  });
    700 
    701  await runTestsList(tests);
    702 }
    703 
    704 function cleanupBrowserTabs() {
    705  let fileBrowser = GetFileBrowser();
    706  if (fileBrowser.selectedTab) {
    707    gBrowser.removeTab(fileBrowser.selectedTab);
    708  }
    709 
    710  let webBrowser = GetWebBrowser();
    711  if (webBrowser.selectedTab) {
    712    gBrowser.removeTab(webBrowser.selectedTab);
    713  }
    714 
    715  let tab1 = gBrowser.tabs[1];
    716  if (tab1) {
    717    gBrowser.removeTab(tab1);
    718  }
    719 }