tor-browser

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

TorRequestWatch.sys.mjs (3401B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const log = console.createInstance({
      6  maxLogLevelPref: "browser.torRequestWatch.log_level",
      7  prefix: "TorRequestWatch",
      8 });
      9 
     10 /**
     11 * This request observer blocks all the cross-site requests to *.tor.onion
     12 * domains to prevent fingerprinting Onion alias mechanisms (or their lack).
     13 */
     14 class RequestObserver {
     15  static #topics = [
     16    "http-on-modify-request",
     17    "http-on-examine-response",
     18    "http-on-examine-cached-response",
     19    "http-on-examine-merged-response",
     20  ];
     21  #asObserver(addOrRemove) {
     22    const action = Services.obs[`${addOrRemove}Observer`].bind(Services.obs);
     23    for (const topic of RequestObserver.#topics) {
     24      action(this, topic);
     25    }
     26  }
     27 
     28  start() {
     29    this.#asObserver("add");
     30    log.debug("Started");
     31  }
     32  stop() {
     33    this.#asObserver("remove");
     34    log.debug("Stopped");
     35  }
     36 
     37  // nsIObserver implementation
     38  observe(subject, topic) {
     39    try {
     40      let channel = ChannelWrapper.get(
     41        subject.QueryInterface(Ci.nsIHttpChannel)
     42      );
     43      switch (topic) {
     44        case "http-on-modify-request":
     45          this.onRequest(channel);
     46          break;
     47        case "http-on-examine-cached-response":
     48        case "http-on-examine-merged-response":
     49          channel.isCached = true;
     50        // falls through
     51        case "http-on-examine-response":
     52          this.onResponse(channel);
     53          break;
     54      }
     55    } catch (e) {
     56      log.error(e);
     57    }
     58  }
     59 
     60  onRequest(channel) {
     61    if (this.shouldBlind(channel, channel.documentURL)) {
     62      log.warn(`Blocking cross-site ${channel.finalURL} ${channel.type} load.`);
     63      channel.cancel(Cr.NS_ERROR_ABORT);
     64    }
     65  }
     66  onResponse(channel) {
     67    if (!channel.documentURL && this.shouldBlind(channel, channel.originURL)) {
     68      const COOP = "cross-origin-opener-policy";
     69      // we break window.opener references if needed to mitigate XS-Leaks
     70      for (let h of channel.getResponseHeaders()) {
     71        if (h.name.toLowerCase() === COOP && h.value === "same-origin") {
     72          log.debug(`${COOP} is already same-origin, nothing to do.`);
     73          return;
     74        }
     75      }
     76      log.warn(`Blinding cross-site ${channel.finalURL} load.`);
     77      channel.setResponseHeader(COOP, "same-origin-allow-popups");
     78    }
     79  }
     80 
     81  isCrossOrigin(url1, url2) {
     82    const origin1 = URL.parse(url1)?.origin;
     83    const origin2 = URL.parse(url2)?.origin;
     84 
     85    if (!origin1 || !origin2) {
     86      return true;
     87    }
     88 
     89    return origin1 !== origin2;
     90  }
     91  shouldBlindCrossOrigin(uri) {
     92    try {
     93      let { host } = uri;
     94      if (host.endsWith(".onion")) {
     95        const previousPart = host.slice(-10, -6);
     96        return (
     97          previousPart && (previousPart === ".tor" || previousPart === ".bit")
     98        );
     99      }
    100    } catch (e) {
    101      // no host
    102    }
    103    return false;
    104  }
    105  shouldBlind(channel, sourceURL) {
    106    return (
    107      sourceURL &&
    108      this.shouldBlindCrossOrigin(channel.finalURI) &&
    109      this.isCrossOrigin(channel.finalURL, sourceURL)
    110    );
    111  }
    112 }
    113 
    114 let observer;
    115 export const TorRequestWatch = {
    116  start() {
    117    if (!observer) {
    118      (observer = new RequestObserver()).start();
    119    }
    120  },
    121  stop() {
    122    if (observer) {
    123      observer.stop();
    124      observer = null;
    125    }
    126  },
    127 };