Bug 1467523 prevent setting host/origin to restricted domains, r=aswan
authorShane Caraveo <scaraveo@mozilla.com>
Fri, 27 Jul 2018 09:37:12 -0300
changeset 823525 72c957a8df54e0b6725b236f246fbb1f94e55327
parent 823524 8e2712a6cb77854398bf40e121d8a5771c786825
child 823526 17d02fcdf8d5b316da603371916c9e80b867c601
push id117712
push userrwood@mozilla.com
push dateFri, 27 Jul 2018 15:10:54 +0000
reviewersaswan
bugs1467523
milestone63.0a1
Bug 1467523 prevent setting host/origin to restricted domains, r=aswan
toolkit/modules/addons/WebRequest.jsm
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -93,17 +93,17 @@ class HeaderChanger {
         return false;
       }
 
       return (typeof header.value === "string" ||
               Array.isArray(header.binaryValue));
     });
   }
 
-  applyChanges(headers) {
+  applyChanges(headers, opts = {}) {
     if (!this.validateHeaders(headers)) {
       /* globals uneval */
       Cu.reportError(`Invalid header array: ${uneval(headers)}`);
       return;
     }
 
     let newHeaders = new Set(headers.map(
       ({name}) => name.toLowerCase()));
@@ -131,40 +131,56 @@ class HeaderChanger {
         value = String.fromCharCode(...binaryValue);
       }
 
       let lowerCaseName = name.toLowerCase();
       let original = origHeaders.get(lowerCaseName);
 
       if (!original || value !== original.value) {
         let shouldMerge = headersAlreadySet.has(lowerCaseName);
-        this.setHeader(name, value, shouldMerge);
+        this.setHeader(name, value, shouldMerge, opts);
       }
 
       headersAlreadySet.add(lowerCaseName);
     }
   }
 }
 
+const checkRestrictedHeaderValue = (value, opts = {}) => {
+  let uri = Services.io.newURI(`https://${value}/`);
+  let {extension} = opts;
+
+  if (extension && !extension.allowedOrigins.matches(uri)) {
+    throw new Error(`Unable to set host header, url missing from permissions.`);
+  }
+
+  if (WebExtensionPolicy.isRestrictedURI(uri)) {
+    throw new Error(`Unable to set host header to restricted url.`);
+  }
+};
+
 class RequestHeaderChanger extends HeaderChanger {
-  setHeader(name, value, merge) {
+  setHeader(name, value, merge, opts = {}) {
     try {
+      if (name === "host") {
+        checkRestrictedHeaderValue(value, opts);
+      }
       this.channel.setRequestHeader(name, value, merge);
     } catch (e) {
       Cu.reportError(new Error(`Error setting request header ${name}: ${e}`));
     }
   }
 
   readHeaders() {
     return this.channel.getRequestHeaders();
   }
 }
 
 class ResponseHeaderChanger extends HeaderChanger {
-  setHeader(name, value, merge) {
+  setHeader(name, value, merge, opts = {}) {
     try {
       this.channel.setResponseHeader(name, value, merge);
     } catch (e) {
       Cu.reportError(new Error(`Error setting response header ${name}: ${e}`));
     }
   }
 
   readHeaders() {
@@ -860,21 +876,21 @@ HttpObserverManager = {
           try {
             channel.upgradeToSecure();
           } catch (e) {
             Cu.reportError(e);
           }
         }
 
         if (opts.requestHeaders && result.requestHeaders && requestHeaders) {
-          requestHeaders.applyChanges(result.requestHeaders);
+          requestHeaders.applyChanges(result.requestHeaders, opts);
         }
 
         if (opts.responseHeaders && result.responseHeaders && responseHeaders) {
-          responseHeaders.applyChanges(result.responseHeaders);
+          responseHeaders.applyChanges(result.responseHeaders, opts);
         }
       }
 
       // If a listener did not cancel the request or provide credentials, we
       // forward the auth request to the base handler.
       if (kind === "authRequired" && channel.authPromptForward) {
         channel.authPromptForward();
       }