Bug 1011738 - Theme support for b2g/gaia, Part 4 : security checks r=bent,bz
authorFabrice Desré <fabrice@mozilla.com>
Thu, 28 Aug 2014 17:20:27 -0700
changeset 202426 c87e98b19ffae6674318eb4a092e387a6fdf8347
parent 202425 cc01e00b86a58ec0a6eae492d52c452a9772948b
child 202427 0ec69047db59ec776c21c7b117706f55a8bc266f
push id27399
push userryanvm@gmail.com
push dateFri, 29 Aug 2014 19:31:08 +0000
treeherdermozilla-central@2a354048f964 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent, bz
bugs1011738
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1011738 - Theme support for b2g/gaia, Part 4 : security checks r=bent,bz
caps/nsScriptSecurityManager.cpp
content/base/src/nsCSPService.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/PContent.ipdl
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/moz.build
netwerk/protocol/app/AppProtocolHandler.cpp
netwerk/protocol/app/moz.build
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -689,21 +689,37 @@ nsScriptSecurityManager::CheckLoadURIWit
 
         bool hasFlags;
         rv = NS_URIChainHasFlags(targetBaseURI,
                                  nsIProtocolHandler::URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM,
                                  &hasFlags);
         NS_ENSURE_SUCCESS(rv, rv);
 
         if (hasFlags) {
+            // Let apps load the whitelisted theme resources even if they don't
+            // have the webapps-manage permission but have the themeable one.
+            // Resources from the theme origin are also allowed to load from
+            // the theme origin (eg. stylesheets using images from the theme).
+            auto themeOrigin = Preferences::GetCString("b2g.theme.origin");
+            if (themeOrigin) {
+                nsAutoCString targetOrigin;
+                nsPrincipal::GetOriginForURI(targetBaseURI, getter_Copies(targetOrigin));
+                if (targetOrigin.Equals(themeOrigin)) {
+                    nsAutoCString pOrigin;
+                    aPrincipal->GetOrigin(getter_Copies(pOrigin));
+                    return nsContentUtils::IsExactSitePermAllow(aPrincipal, "themeable") ||
+                           pOrigin.Equals(themeOrigin)
+                        ? NS_OK : NS_ERROR_DOM_BAD_URI;
+                }
+            }
             // In this case, we allow opening only if the source and target URIS
             // are on the same domain, or the opening URI has the webapps
             // permision granted
-            if (!SecurityCompareURIs(sourceBaseURI,targetBaseURI) &&
-                !nsContentUtils::IsExactSitePermAllow(aPrincipal,WEBAPPS_PERM_NAME)){
+            if (!SecurityCompareURIs(sourceBaseURI, targetBaseURI) &&
+                !nsContentUtils::IsExactSitePermAllow(aPrincipal, WEBAPPS_PERM_NAME)) {
                 return NS_ERROR_DOM_BAD_URI;
             }
         }
         return NS_OK;
     }
 
     // If the schemes don't match, the policy is specified by the protocol
     // flags on the target URI.  Note that the order of policy checks here is
--- a/content/base/src/nsCSPService.cpp
+++ b/content/base/src/nsCSPService.cpp
@@ -18,16 +18,17 @@
 #include "nsIWritablePropertyBag2.h"
 #include "nsError.h"
 #include "nsChannelProperties.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsAsyncRedirectVerifyHelper.h"
 #include "mozilla/Preferences.h"
 #include "nsIScriptError.h"
 #include "nsContentUtils.h"
+#include "nsPrincipal.h"
 
 using namespace mozilla;
 
 /* Keeps track of whether or not CSP is enabled */
 bool CSPService::sCSPEnabled = true;
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gCspPRLog;
@@ -116,29 +117,34 @@ CSPService::ShouldLoad(uint32_t aContent
   aContentLocation->GetPrePath(contentOrigin);
   if (aRequestPrincipal && !mAppStatusCache.Get(contentOrigin, &status)) {
     aRequestPrincipal->GetAppStatus(&status);
     mAppStatusCache.Put(contentOrigin, status);
   }
 
   if (status == nsIPrincipal::APP_STATUS_CERTIFIED) {
     // The CSP for certified apps is :
-    // "default-src *; script-src 'self'; object-src 'none'; style-src 'self'"
+    // "default-src *; script-src 'self'; object-src 'none'; style-src 'self' app://theme.gaiamobile.org:*"
     // That means we can optimize for this case by:
-    // - loading only same origin scripts and stylesheets.
+    // - loading same origin scripts and stylesheets, and stylesheets from the
+    //   theme url space.
     // - never loading objects.
     // - accepting everything else.
 
     switch (aContentType) {
       case nsIContentPolicy::TYPE_SCRIPT:
       case nsIContentPolicy::TYPE_STYLESHEET:
         {
+          // Whitelist the theme resources.
+          auto themeOrigin = Preferences::GetCString("b2g.theme.origin");
           nsAutoCString sourceOrigin;
           aRequestOrigin->GetPrePath(sourceOrigin);
-          if (!sourceOrigin.Equals(contentOrigin)) {
+
+          if (!(sourceOrigin.Equals(contentOrigin) ||
+                (themeOrigin && themeOrigin.Equals(contentOrigin)))) {
             *aDecision = nsIContentPolicy::REJECT_SERVER;
           }
         }
         break;
 
       case nsIContentPolicy::TYPE_OBJECT:
         *aDecision = nsIContentPolicy::REJECT_SERVER;
         break;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2006,16 +2006,26 @@ ContentChild::RecvNuwaFork()
     MessageLoop* ioloop = XRE_GetIOMessageLoop();
     ioloop->PostTask(FROM_HERE, NewRunnableFunction(RunNuwaFork));
     return true;
 #else
     return false; // Makes the underlying IPC channel abort.
 #endif
 }
 
+bool
+ContentChild::RecvOnAppThemeChanged()
+{
+    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+    if (os) {
+        os->NotifyObservers(nullptr, "app-theme-changed", nullptr);
+    }
+    return true;
+}
+
 } // namespace dom
 } // namespace mozilla
 
 extern "C" {
 
 #if defined(MOZ_NUWA_PROCESS)
 NS_EXPORT void
 GetProtoFdInfos(NuwaProtoFdInfo* aInfoList,
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -313,16 +313,19 @@ public:
 
     virtual bool RecvNotifyPhoneStateChange(const nsString& state) MOZ_OVERRIDE;
 
     void AddIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS);
     void RemoveIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS);
     virtual bool RecvNotifyIdleObserver(const uint64_t& aObserver,
                                         const nsCString& aTopic,
                                         const nsString& aData) MOZ_OVERRIDE;
+
+    virtual bool RecvOnAppThemeChanged() MOZ_OVERRIDE;
+
 #ifdef ANDROID
     gfxIntSize GetScreenSize() { return mScreenSize; }
 #endif
 
     // Get the directory for IndexedDB files. We query the parent for this and
     // cache the value
     nsString &GetIndexedDBPath();
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -583,16 +583,17 @@ static const char* sObserverTopics[] = {
     "file-watcher-update",
 #ifdef MOZ_WIDGET_GONK
     NS_VOLUME_STATE_CHANGED,
     "phone-state-changed",
 #endif
 #ifdef ACCESSIBILITY
     "a11y-init-or-shutdown",
 #endif
+    "app-theme-changed",
 };
 
 /* static */ already_AddRefed<ContentParent>
 ContentParent::RunNuwaProcess()
 {
     MOZ_ASSERT(NS_IsMainThread());
     nsRefPtr<ContentParent> nuwaProcess =
         new ContentParent(/* aApp = */ nullptr,
@@ -2739,17 +2740,19 @@ ContentParent::Observe(nsISupports* aSub
 #ifdef ACCESSIBILITY
     // Make sure accessibility is running in content process when accessibility
     // gets initiated in chrome process.
     else if (aData && (*aData == '1') &&
              !strcmp(aTopic, "a11y-init-or-shutdown")) {
         unused << SendActivateA11y();
     }
 #endif
-
+    else if (!strcmp(aTopic, "app-theme-changed")) {
+        unused << SendOnAppThemeChanged();
+    }
     return NS_OK;
 }
 
 PCompositorParent*
 ContentParent::AllocPCompositorParent(mozilla::ipc::Transport* aTransport,
                                       base::ProcessId aOtherProcess)
 {
     return CompositorParent::Create(aTransport, aOtherProcess);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -442,16 +442,22 @@ child:
     async UnregisterSheet(URIParams uri, uint32_t type);
 
     NotifyPhoneStateChange(nsString newState);
 
     /**
      * Notify idle observers in the child
      */
     NotifyIdleObserver(uint64_t observerId, nsCString topic, nsString str);
+
+    /**
+     * Notify windows in the child to apply a new app style.
+     */
+    OnAppThemeChanged();
+
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -31,16 +31,17 @@
 #include "nsPrintfCString.h"
 #include "nsHTMLDNSPrefetch.h"
 #include "nsIAppsService.h"
 #include "nsEscape.h"
 #include "RemoteOpenFileParent.h"
 #include "SerializedLoadContext.h"
 #include "nsAuthInformationHolder.h"
 #include "nsIAuthPromptCallback.h"
+#include "nsPrincipal.h"
 
 using mozilla::dom::ContentParent;
 using mozilla::dom::TabParent;
 using mozilla::net::PTCPSocketParent;
 using mozilla::dom::TCPSocketParent;
 using mozilla::net::PTCPServerSocketParent;
 using mozilla::dom::TCPServerSocketParent;
 using mozilla::net::PUDPSocketParent;
@@ -551,17 +552,35 @@ NeckoParent::AllocPRemoteOpenFileParent(
       netErrorURI = Preferences::GetString("b2g.neterror.url");
       if (netErrorURI) {
         nsAutoCString spec;
         appUri->GetSpec(spec);
         netErrorWhiteList = spec.Equals(NS_ConvertUTF16toUTF8(netErrorURI).get());
       }
     }
 
-    if (hasManage || netErrorWhiteList) {
+    // Check if we load a resource from the shared theme url space.
+    // If we try to load the theme but have no permission, refuse to load.
+    bool themeWhitelist = false;
+    if (Preferences::GetBool("dom.mozApps.themable") && appUri) {
+      nsAutoCString origin;
+      nsPrincipal::GetOriginForURI(appUri, getter_Copies(origin));
+      nsAutoCString themeOrigin;
+      themeOrigin = Preferences::GetCString("b2g.theme.origin");
+      themeWhitelist = origin.Equals(themeOrigin);
+      if (themeWhitelist) {
+        bool hasThemePerm = false;
+        mozApp->HasPermission("themeable", &hasThemePerm);
+        if (!hasThemePerm) {
+          return nullptr;
+        }
+      }
+    }
+
+    if (hasManage || netErrorWhiteList || themeWhitelist) {
       // webapps-manage permission means allow reading any application.zip file
       // in either the regular webapps directory, or the core apps directory (if
       // we're using one).
       NS_NAMED_LITERAL_CSTRING(appzip, "/application.zip");
       nsAutoCString pathEnd;
       requestedPath.Right(pathEnd, appzip.Length());
       if (!pathEnd.Equals(appzip)) {
         return nullptr;
--- a/netwerk/ipc/moz.build
+++ b/netwerk/ipc/moz.build
@@ -42,10 +42,11 @@ FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '../base/src',
     '../protocol/http',
+    '/caps',
     '/modules/libjar',
 ]
--- a/netwerk/protocol/app/AppProtocolHandler.cpp
+++ b/netwerk/protocol/app/AppProtocolHandler.cpp
@@ -6,18 +6,22 @@
 
 #include "AppProtocolHandler.h"
 #include "nsBaseChannel.h"
 #include "nsJARChannel.h"
 #include "nsNetCID.h"
 #include "nsIAppsService.h"
 #include "nsILoadInfo.h"
 #include "nsXULAppAPI.h"
+#include "nsPrincipal.h"
 
 #include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
 
 /**
   * This dummy channel implementation only provides enough functionality
   * to return a fake 404 error when the caller asks for an app:// URL
   * containing an unknown appId.
   */
 class DummyChannel : public nsIJARChannel
                           , nsRunnable
@@ -378,16 +382,33 @@ AppProtocolHandler::NewChannel(nsIURI* a
 {
   NS_ENSURE_ARG_POINTER(aUri);
   nsRefPtr<nsJARChannel> channel = new nsJARChannel();
 
   nsAutoCString host;
   nsresult rv = aUri->GetHost(host);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (Preferences::GetBool("dom.mozApps.themable")) {
+    nsAutoCString origin;
+    nsPrincipal::GetOriginForURI(aUri, getter_Copies(origin));
+    nsAdoptingCString themeOrigin;
+    themeOrigin = Preferences::GetCString("b2g.theme.origin");
+    if (themeOrigin.Equals(origin)) {
+      // We are trying to load a theme resource. Check whether we have a
+      // package registered as a theme provider to override the file path.
+      nsAdoptingCString selectedTheme;
+      selectedTheme = Preferences::GetCString("dom.mozApps.selected_theme");
+      if (!selectedTheme.IsEmpty()) {
+        // Substitute the path with the actual theme.
+        host = selectedTheme;
+      }
+    }
+  }
+
   nsAutoCString fileSpec;
   nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
   rv = url->GetFilePath(fileSpec);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mozilla::dom::AppInfo *appInfo;
 
   if (!mAppInfoCache.Get(host, &appInfo)) {
--- a/netwerk/protocol/app/moz.build
+++ b/netwerk/protocol/app/moz.build
@@ -9,10 +9,11 @@ SOURCES += [
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '../../../modules/libjar',
     '../../base/src',
+    '/caps',
 ]