Bug 909022 - Mark all executables as coming from the Internet zone on Windows. r=enn
☠☠ backed out by ae42fd34f893 ☠ ☠
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Mon, 09 Sep 2013 15:45:29 +0200
changeset 146144 7d73a40c6ee92f5bbc9975eee13b046554583135
parent 146143 3d50a4b732af31f5d607be382b26c70bb5092913
child 146145 493dd25e60c2a89ba9630089487453c9a74eb9ec
push id2551
push userpaolo.mozmail@amadzone.org
push dateMon, 09 Sep 2013 13:47:19 +0000
treeherderfx-team@493dd25e60c2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersenn
bugs909022
milestone26.0a1
Bug 909022 - Mark all executables as coming from the Internet zone on Windows. r=enn
modules/libpref/src/init/all.js
toolkit/components/jsdownloads/src/DownloadIntegration.jsm
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -142,16 +142,23 @@ pref("browser.display.focus_ring_on_anyt
 // focus ring border style.
 // 0 = solid border, 1 = dotted border
 pref("browser.display.focus_ring_style", 1);
 
 pref("browser.helperApps.alwaysAsk.force",  false);
 pref("browser.helperApps.neverAsk.saveToDisk", "");
 pref("browser.helperApps.neverAsk.openFile", "");
 
+#ifdef XP_WIN
+// By default, security zone information is stored in the Alternate Data Stream
+// of downloaded executable files on Windows.  This preference allows disabling
+// this feature, and thus the associated system-level execution prompts.
+pref("browser.download.saveZoneInformation", true);
+#endif
+
 // xxxbsmedberg: where should prefs for the toolkit go?
 pref("browser.chrome.toolbar_tips",         true);
 // 0 = Pictures Only, 1 = Text Only, 2 = Pictures and Text
 pref("browser.chrome.toolbar_style",        2);
 // max image size for which it is placed in the tab icon for tabbrowser.
 // if 0, no images are used for tab icons for image documents.
 pref("browser.chrome.image_icons.max_size", 1024);
 
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -61,16 +61,24 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyGetter(this, "gParentalControlsService", function() {
   if ("@mozilla.org/parental-controls-service;1" in Cc) {
     return Cc["@mozilla.org/parental-controls-service;1"]
       .createInstance(Ci.nsIParentalControlsService);
   }
   return null;
 });
 
+/**
+ * ArrayBufferView representing the bytes to be written to the "Zone.Identifier"
+ * Alternate Data Stream to mark a file as coming from the Internet zone.
+ */
+XPCOMUtils.defineLazyGetter(this, "gInternetZoneIdentifier", function() {
+  return new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=3\r\n");
+});
+
 const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
                                      "initWithCallback");
 
 /**
  * Indicates the delay between a change to the downloads data and the related
  * save operation.  This value is the result of a delicate trade-off, assuming
  * the host application uses the browser history instead of the download store
  * to save completed downloads.
@@ -384,25 +392,56 @@ this.DownloadIntegration = {
    * aParam aDownload
    *        The Download object.
    *
    * @return {Promise}
    * @resolves When all the operations completed successfully.
    * @rejects JavaScript exception if any of the operations failed.
    */
   downloadDone: function(aDownload) {
-    try {
+    return Task.spawn(function () {
+#ifdef XP_WIN
+      // On Windows, we mark any executable file saved to the NTFS file system
+      // as coming from the Internet security zone.  We do this by writing to
+      // the "Zone.Identifier" Alternate Data Stream directly, because the Save
+      // method of the IAttachmentExecute interface would trigger operations
+      // that may cause the application to hang, or other performance issues.
+      // The stream created in this way is forward-compatible with all the
+      // current and future versions of Windows.
+      if (Services.prefs.getBoolPref("browser.download.saveZoneInformation")) {
+        let file = new FileUtils.File(aDownload.target.path);
+        if (file.isExecutable()) {
+          try {
+            let streamPath = aDownload.target.path + ":Zone.Identifier";
+            let stream = yield OS.File.open(streamPath, { create: true });
+            try {
+              yield stream.write(gInternetZoneIdentifier);
+            } finally {
+              yield stream.close();
+            }
+          } catch (ex) {
+            // If writing to the stream fails, we ignore the error and continue.
+            // The Windows API error 123 (ERROR_INVALID_NAME) is expected to
+            // occur when working on a file system that does not support
+            // Alternate Data Streams, like FAT32, thus we don't report this
+            // specific error.
+            if (!(ex instanceof OS.File.Error) || ex.winLastError != 123) {
+              Cu.reportError(ex);
+            }
+          }
+        }
+      }
+#endif
+
       gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url),
                                      new FileUtils.File(aDownload.target.path),
-                                     aDownload.contentType, aDownload.source.isPrivate);
+                                     aDownload.contentType,
+                                     aDownload.source.isPrivate);
       this.downloadDoneCalled = true;
-      return Promise.resolve();
-    } catch(ex) {
-      return Promise.reject(ex);
-    }
+    }.bind(this));
   },
 
   /**
    * Determines whether it's a Windows Metro app.
    */
   _isImmersiveProcess: function() {
     // TODO: to be implemented
     return false;
@@ -427,33 +466,36 @@ this.DownloadIntegration = {
    *           launched.
    * @rejects  JavaScript exception if there was an error trying to launch
    *           the file.
    */
   launchDownload: function (aDownload) {
     let deferred = Task.spawn(function DI_launchDownload_task() {
       let file = new FileUtils.File(aDownload.target.path);
 
-      // Ask for confirmation if the file is executable.  We do this here,
-      // instead of letting the caller handle the prompt separately in the user
-      // interface layer, for two reasons.  The first is because of its security
-      // nature, so that add-ons cannot forget to do this check.  The second is
-      // that the system-level security prompt, if enabled, would be displayed
-      // at launch time in any case.
+#ifndef XP_WIN
+      // Ask for confirmation if the file is executable, except on Windows where
+      // the operating system will show the prompt based on the security zone.
+      // We do this here, instead of letting the caller handle the prompt
+      // separately in the user interface layer, for two reasons.  The first is
+      // because of its security nature, so that add-ons cannot forget to do
+      // this check.  The second is that the system-level security prompt would
+      // be displayed at launch time in any case.
       if (file.isExecutable() && !this.dontOpenFileAndFolder) {
         // We don't anchor the prompt to a specific window intentionally, not
         // only because this is the same behavior as the system-level prompt,
         // but also because the most recently active window is the right choice
         // in basically all cases.
         let shouldLaunch = yield DownloadUIHelper.getPrompter()
                                    .confirmLaunchExecutable(file.path);
         if (!shouldLaunch) {
           return;
         }
       }
+#endif
 
       // In case of a double extension, like ".tar.gz", we only
       // consider the last one, because the MIME service cannot
       // handle multiple extensions.
       let fileExtension = null, mimeInfo = null;
       let match = file.leafName.match(/\.([^.]+)$/);
       if (match) {
         fileExtension = match[1];