Bug 1000253 - Background tabs with persistent device permissions can access devices without the user noticing, r=jesup,MattN, a=sylvestre.
authorFlorian Quèze <florian@queze.net>
Mon, 16 Jun 2014 12:13:14 +0200
changeset 208090 00594a5ed74de18eb6aaa0c2a6506fb830d68309
parent 208089 52a2762028881300dff9d4645865886ba6a72a1b
child 208091 f2413cf1965ec1339c6ef68ad8e7e84c60fcc9e7
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup, MattN, sylvestre
bugs1000253
milestone32.0a2
Bug 1000253 - Background tabs with persistent device permissions can access devices without the user noticing, r=jesup,MattN, a=sylvestre.
browser/base/content/test/general/browser_devices_get_user_media.js
browser/modules/webrtcUI.jsm
dom/media/MediaManager.cpp
--- a/browser/base/content/test/general/browser_devices_get_user_media.js
+++ b/browser/base/content/test/general/browser_devices_get_user_media.js
@@ -588,16 +588,19 @@ let gTests = [
         expectObserverCalled("getUserMedia:response:deny");
         expectObserverCalled("recording-window-ended");
       }
       else {
         let expectedMessage = aExpectStream ? "ok" : "error: PERMISSION_DENIED";
         yield promiseMessage(expectedMessage, gum);
 
         if (expectedMessage == "ok") {
+          expectObserverCalled("getUserMedia:request");
+          yield promiseNoPopupNotification("webRTC-shareDevices");
+          expectObserverCalled("getUserMedia:response:allow");
           expectObserverCalled("recording-device-events");
 
           // Check what's actually shared.
           let expected = [];
           if (aAllowVideo && aRequestVideo)
             expected.push("Camera");
           if (aAllowAudio && aRequestAudio)
             expected.push("Microphone");
@@ -682,16 +685,18 @@ let gTests = [
       // Initially set both permissions to 'allow'.
       Perms.add(uri, "microphone", Perms.ALLOW_ACTION);
       Perms.add(uri, "camera", Perms.ALLOW_ACTION);
 
       // Start sharing what's been requested.
       yield promiseMessage("ok", () => {
         content.wrappedJSObject.requestDevice(aRequestAudio, aRequestVideo);
       });
+      expectObserverCalled("getUserMedia:request");
+      expectObserverCalled("getUserMedia:response:allow");
       expectObserverCalled("recording-device-events");
       yield checkSharingUI();
 
       PopupNotifications.getNotification("webRTC-sharingDevices").reshow();
       let expectedIcon = "webRTC-sharingDevices";
       if (aRequestAudio && !aRequestVideo)
         expectedIcon = "webRTC-sharingMicrophone";
       is(PopupNotifications.getNotification("webRTC-sharingDevices").anchorID,
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -187,16 +187,48 @@ function prompt(aContentWindow, aCallID,
         let PopupNotifications = chromeDoc.defaultView.PopupNotifications;
         let popupId = requestType == "Microphone" ? "Microphone" : "Devices";
         PopupNotifications.panel.firstChild.setAttribute("popupid", "webRTC-share" + popupId);
       }
 
       if (aTopic != "showing")
         return false;
 
+      // DENY_ACTION is handled immediately by MediaManager, but handling
+      // of ALLOW_ACTION is delayed until the popupshowing event
+      // to avoid granting permissions automatically to background tabs.
+      if (aSecure) {
+        let perms = Services.perms;
+
+        let micPerm = perms.testExactPermission(uri, "microphone");
+        if (micPerm == perms.PROMPT_ACTION)
+          micPerm = perms.UNKNOWN_ACTION;
+
+        let camPerm = perms.testExactPermission(uri, "camera");
+        if (camPerm == perms.PROMPT_ACTION)
+          camPerm = perms.UNKNOWN_ACTION;
+
+        // We don't check that permissions are set to ALLOW_ACTION in this
+        // test; only that they are set. This is because if audio is allowed
+        // and video is denied persistently, we don't want to show the prompt,
+        // and will grant audio access immediately.
+        if ((!audioDevices.length || micPerm) && (!videoDevices.length || camPerm)) {
+          // All permissions we were about to request are already persistently set.
+          let allowedDevices = Cc["@mozilla.org/supports-array;1"]
+                                 .createInstance(Ci.nsISupportsArray);
+          if (videoDevices.length && camPerm == perms.ALLOW_ACTION)
+            allowedDevices.AppendElement(videoDevices[0]);
+          if (audioDevices.length && micPerm == perms.ALLOW_ACTION)
+            allowedDevices.AppendElement(audioDevices[0]);
+          Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
+          this.remove();
+          return true;
+        }
+      }
+
       function listDevices(menupopup, devices) {
         while (menupopup.lastChild)
           menupopup.removeChild(menupopup.lastChild);
 
         let deviceIndex = 0;
         for (let device of devices) {
           addDeviceToList(menupopup, device.name, deviceIndex);
           deviceIndex++;
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1494,58 +1494,28 @@ MediaManager::GetUserMedia(bool aPrivile
       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
     if (IsOn(c.mAudio)) {
       rv = permManager->TestExactPermissionFromPrincipal(
         aWindow->GetExtantDoc()->NodePrincipal(), "microphone", &audioPerm);
       NS_ENSURE_SUCCESS(rv, rv);
-      if (audioPerm == nsIPermissionManager::PROMPT_ACTION) {
-        audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
-      }
-      if (audioPerm == nsIPermissionManager::ALLOW_ACTION) {
-        if (!isHTTPS) {
-          audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
-        }
-      }
     }
 
     uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
     if (IsOn(c.mVideo)) {
       rv = permManager->TestExactPermissionFromPrincipal(
         aWindow->GetExtantDoc()->NodePrincipal(), "camera", &videoPerm);
       NS_ENSURE_SUCCESS(rv, rv);
-      if (videoPerm == nsIPermissionManager::PROMPT_ACTION) {
-        videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
-      }
-      if (videoPerm == nsIPermissionManager::ALLOW_ACTION) {
-        if (!isHTTPS) {
-          videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
-        }
-      }
     }
 
-    if ((!IsOn(c.mAudio) || audioPerm != nsIPermissionManager::UNKNOWN_ACTION) &&
-        (!IsOn(c.mVideo) || videoPerm != nsIPermissionManager::UNKNOWN_ACTION)) {
-      // All permissions we were about to request already have a saved value.
-      if (IsOn(c.mAudio) && audioPerm == nsIPermissionManager::DENY_ACTION) {
-        c.mAudio.SetAsBoolean() = false;
-        runnable->SetContraints(c);
-      }
-      if (IsOn(c.mVideo) && videoPerm == nsIPermissionManager::DENY_ACTION) {
-        c.mVideo.SetAsBoolean() = false;
-        runnable->SetContraints(c);
-      }
-
-      if (!IsOn(c.mAudio) && !IsOn(c.mVideo)) {
-        return runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
-      }
-
-      return mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+    if ((!IsOn(c.mAudio) || audioPerm == nsIPermissionManager::DENY_ACTION) &&
+        (!IsOn(c.mVideo) || videoPerm == nsIPermissionManager::DENY_ACTION)) {
+      return runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
     }
 
     // Ask for user permission, and dispatch runnable (or not) when a response
     // is received via an observer notification. Each call is paired with its
     // runnable by a GUID.
     nsCOMPtr<nsIUUIDGenerator> uuidgen =
       do_GetService("@mozilla.org/uuid-generator;1", &rv);
     NS_ENSURE_SUCCESS(rv, rv);