Commit 1cbaba54 authored by ma1's avatar ma1 Committed by Richard Pospesel
Browse files

Bug 8324: Prevent DNS proxy bypasses caused by Drag&Drop

parent 9f7a57d1
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -1903,7 +1903,11 @@ XPCOMUtils.defineLazyGetter(PlacesUIUtils, "URI_FLAVORS", () => {
  return [PlacesUtils.TYPE_X_MOZ_URL, TAB_DROP_TYPE, PlacesUtils.TYPE_UNICODE];
});
XPCOMUtils.defineLazyGetter(PlacesUIUtils, "SUPPORTED_FLAVORS", () => {
  return [...PlacesUIUtils.PLACES_FLAVORS, ...PlacesUIUtils.URI_FLAVORS];
  return [
    ...PlacesUIUtils.PLACES_FLAVORS,
    ...PlacesUIUtils.URI_FLAVORS,
    "application/x-torbrowser-opaque",
  ];
});

XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() {
+1 −0
Original line number Diff line number Diff line
@@ -1251,6 +1251,7 @@ PlacesController.prototype = {
    [
      PlacesUtils.TYPE_X_MOZ_PLACE,
      PlacesUtils.TYPE_X_MOZ_URL,
      "application/x-torbrowser-opaque",
      PlacesUtils.TYPE_UNICODE,
    ].forEach(type => xferable.addDataFlavor(type));

+18 −2
Original line number Diff line number Diff line
@@ -5,6 +5,16 @@
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");

const { XPCOMUtils } = ChromeUtils.import(
  "resource://gre/modules/XPCOMUtils.jsm"
);

XPCOMUtils.defineLazyGetter(this, "gOpaqueDrag", () => {
  return Cc["@torproject.org/torbutton-dragDropFilter;1"].getService(
    Ci.nsISupports
  ).wrappedJSObject.opaqueDrag;
});

// This component is used for handling dragover and drop of urls.
//
// It checks to see whether a drop of a url is allowed. For instance, a url
@@ -43,10 +53,15 @@ ContentAreaDropListener.prototype = {
      }
    }

    type = "text/x-moz-url";
    if (types.contains(type)) {
    for (let type of ["text/x-moz-url", "application/x-torbrowser-opaque"]) {
      if (!types.contains(type)) {
        continue;
      }
      data = dt.mozGetDataAt(type, i);
      if (data) {
        if (type === "application/x-torbrowser-opaque") {
          ({ type, value: data = "" } = gOpaqueDrag.get(data));
        }
        let lines = data.split("\n");
        for (let i = 0, length = lines.length; i < length; i += 2) {
          this._addLink(links, lines[i], lines[i + 1], type);
@@ -250,6 +265,7 @@ ContentAreaDropListener.prototype = {
    if (
      !types.includes("application/x-moz-file") &&
      !types.includes("text/x-moz-url") &&
      !types.includes("application/x-torbrowser-opaque") &&
      !types.includes("text/uri-list") &&
      !types.includes("text/x-moz-text-internal") &&
      !types.includes("text/plain")
+9 −0
Original line number Diff line number Diff line
@@ -32,6 +32,12 @@ XPCOMUtils.defineLazyGetter(this, "gCryptoHash", () => {
  return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
});

XPCOMUtils.defineLazyGetter(this, "gOpaqueDrag", () => {
  return Cc["@torproject.org/torbutton-dragDropFilter;1"].getService(
    Ci.nsISupports
  ).wrappedJSObject.opaqueDrag;
});

// On Mac OSX, the transferable system converts "\r\n" to "\n\n", where
// we really just want "\n". On other platforms, the transferable system
// converts "\r\n" to "\n".
@@ -1132,6 +1138,9 @@ var PlacesUtils = {
  unwrapNodes: function PU_unwrapNodes(blob, type) {
    // We split on "\n"  because the transferable system converts "\r\n" to "\n"
    var nodes = [];
    if (type === "application/x-torbrowser-opaque") {
      ({ value: blob, type } = gOpaqueDrag.get(blob));
    }
    switch (type) {
      case this.TYPE_X_MOZ_PLACE:
      case this.TYPE_X_MOZ_PLACE_SEPARATOR:
+155 −0
Original line number Diff line number Diff line
/*************************************************************************
 * Drag and Drop Handler.
 *
 * Implements an observer that filters drag events to prevent OS
 * access to URLs (a potential proxy bypass vector).
 *************************************************************************/

const { XPCOMUtils } = ChromeUtils.import(
  "resource://gre/modules/XPCOMUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetters(this, {
  ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
});
XPCOMUtils.defineLazyGlobalGetters(this, ["crypto"]);

// Module specific constants
const kMODULE_NAME = "Torbutton Drag and Drop Handler";
const kCONTRACT_ID = "@torproject.org/torbutton-dragDropFilter;1";
const kMODULE_CID = Components.ID("f605ec27-d867-44b5-ad97-2a29276642c3");

const kInterfaces = [Ci.nsIObserver, Ci.nsIClassInfo];

const URLISH_TYPES = Object.freeze([
  "text/x-moz-url",
  "text/x-moz-url-data",
  "text/uri-list",
  "application/x-moz-file-promise-url",
]);

const MAIN_PROCESS =
  Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;

const EMPTY_PAYLOAD = {};
const OpaqueDrag = {
  listening: false,
  payload: EMPTY_PAYLOAD,
  store(value, type) {
    let opaqueKey = crypto.randomUUID();
    this.payload = { opaqueKey, value, type };
    if (!this.listening && MAIN_PROCESS) {
      Services.ppmm.addMessageListener(
        "DragDropFilter:GetOpaqueDrag",
        () => this.payload
      );
      this.listening = true;
    }
    return opaqueKey;
  },
  retrieve(key) {
    let { opaqueKey, value, type } = this.payload;
    if (opaqueKey === key) {
      return { value, type };
    }
    if (!MAIN_PROCESS) {
      this.payload = Services.cpmm.sendSyncMessage(
        "DragDropFilter:GetOpaqueDrag"
      )[0];
      if (key === this.payload.opaqueKey) {
        return this.retrieve(key);
      }
    }
    return EMPTY_PAYLOAD;
  },
};

function DragDropFilter() {
  this.logger = Cc["@torproject.org/torbutton-logger;1"].getService(
    Ci.nsISupports
  ).wrappedJSObject;
  this.logger.log(3, "Component Load 0: New DragDropFilter.");
  if (MAIN_PROCESS) {
    // We want to update our status in the main process only, in order to
    // serve the same opaque drag payload in every process.
    try {
      Services.obs.addObserver(this, "on-datatransfer-available");
    } catch (e) {
      this.logger.log(5, "Failed to register drag observer");
    }
  }
}

DragDropFilter.prototype = {
  QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),

  // make this an nsIClassInfo object
  flags: Ci.nsIClassInfo.DOM_OBJECT,
  classDescription: kMODULE_NAME,
  contractID: kCONTRACT_ID,
  classID: kMODULE_CID,

  // method of nsIClassInfo
  getInterfaces(count) {
    count.value = kInterfaces.length;
    return kInterfaces;
  },

  // method of nsIClassInfo
  getHelperForLanguage(count) {
    return null;
  },

  // method of nsIObserver
  observe(subject, topic, data) {
    if (topic === "on-datatransfer-available") {
      this.logger.log(3, "The DataTransfer is available");
      this.filterDataTransferURLs(subject);
    }
  },

  filterDataTransferURLs(aDataTransfer) {
    for (let i = 0, count = aDataTransfer.mozItemCount; i < count; ++i) {
      this.logger.log(3, `Inspecting the data transfer: ${i}.`);
      const types = aDataTransfer.mozTypesAt(i);
      for (const type of types) {
        this.logger.log(3, `Type is: ${type}.`);
        if (URLISH_TYPES.includes(type)) {
          this.logger.log(
            3,
            `Removing transfer data ${aDataTransfer.mozGetDataAt(type, i)}`
          );
          const urlType = "text/x-moz-url";
          // Fallback url type, to be parsed by this browser but not externally
          const INTERNAL_FALLBACK = "application/x-torbrowser-opaque";
          if (types.contains(urlType)) {
            const link = aDataTransfer.mozGetDataAt(urlType, i);
            const opaqueKey = OpaqueDrag.store(link, urlType);
            aDataTransfer.mozSetDataAt(INTERNAL_FALLBACK, opaqueKey, i);
          }
          for (const type of types) {
            if (
              type !== INTERNAL_FALLBACK &&
              type !== "text/x-moz-place" // don't touch bookmarks
            ) {
              aDataTransfer.mozClearDataAt(type, i);
            }
          }
          break;
        }
      }
    }
  },

  opaqueDrag: {
    get(opaqueKey) {
      return OpaqueDrag.retrieve(opaqueKey);
    },
  },
};

// Assign factory to global object.
const NSGetFactory = XPCOMUtils.generateNSGetFactory
  ? XPCOMUtils.generateNSGetFactory([DragDropFilter])
  : ComponentUtils.generateNSGetFactory([DragDropFilter]);
Loading