Bug 786631 - Get processes with non-default permissions on the prelaunch fast path. r=dhylands, r=jlebar, a=tef+
authorChris Jones <jones.chris.g@gmail.com>
Thu, 17 Jan 2013 12:06:36 -0800
changeset 118260 d9d299d9e6cf4fefd158a00a9f1e409b8553f590
parent 118259 d810d8e8b72387817a7002784e54f0c52df41e2b
child 118261 1f70721fb7832dd55a6ec2707c3c660d3db4391f
push id308
push userryanvm@gmail.com
push dateSun, 20 Jan 2013 00:10:28 +0000
reviewersdhylands, jlebar, tef
bugs786631
milestone18.0
Bug 786631 - Get processes with non-default permissions on the prelaunch fast path. r=dhylands, r=jlebar, a=tef+ Contains these patches Bug 786631, part 1: Refactor privilege adjustment. r=dhylands Bug 786631, part 2: Make the prelaunch process totipotent and specialize when it's taken. r=jlebar
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
ipc/chromium/src/base/process_util.h
ipc/chromium/src/base/process_util_linux.cc
ipc/chromium/src/base/process_util_mac.mm
ipc/glue/GeckoChildProcessHost.cpp
ipc/glue/GeckoChildProcessHost.h
ipc/glue/IPCMessageUtils.h
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -489,16 +489,27 @@ ContentChild::AllocPCompositor(mozilla::
 
 PImageBridgeChild*
 ContentChild::AllocPImageBridge(mozilla::ipc::Transport* aTransport,
                                 base::ProcessId aOtherProcess)
 {
     return ImageBridgeChild::StartUpInChildProcess(aTransport, aOtherProcess);
 }
 
+bool
+ContentChild::RecvSetProcessPrivileges(const ChildPrivileges& aPrivs)
+{
+  ChildPrivileges privs = (aPrivs == PRIVILEGES_DEFAULT) ?
+                          GeckoChildProcessHost::DefaultChildPrivileges() :
+                          aPrivs;
+  // If this fails, we die.
+  SetCurrentProcessPrivileges(privs);
+  return true;
+}
+
 static CancelableTask* sFirstIdleTask;
 
 static void FirstIdle(void)
 {
     MOZ_ASSERT(sFirstIdleTask);
     sFirstIdleTask = nullptr;
     ContentChild::GetSingleton()->SendFirstIdle();
 }
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -75,16 +75,18 @@ public:
 
     PCompositorChild*
     AllocPCompositor(mozilla::ipc::Transport* aTransport,
                      base::ProcessId aOtherProcess) MOZ_OVERRIDE;
     PImageBridgeChild*
     AllocPImageBridge(mozilla::ipc::Transport* aTransport,
                       base::ProcessId aOtherProcess) MOZ_OVERRIDE;
 
+    virtual bool RecvSetProcessPrivileges(const ChildPrivileges& aPrivs);
+
     virtual PBrowserChild* AllocPBrowser(const IPCTabContext &aContext,
                                          const uint32_t &chromeFlags);
     virtual bool DeallocPBrowser(PBrowserChild*);
 
     virtual PDeviceStorageRequestChild* AllocPDeviceStorageRequest(const DeviceStorageParams&);
     virtual bool DeallocPDeviceStorageRequest(PDeviceStorageRequestChild*);
 
     virtual PBlobChild* AllocPBlob(const BlobConstructorParams& aParams);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -200,17 +200,19 @@ ContentParent::PreallocateAppProcess()
         // We were called directly while a delayed task was scheduled.
         sPreallocateAppProcessTask->Cancel();
         sPreallocateAppProcessTask = nullptr;
     }
 
     sPreallocatedAppProcess =
         new ContentParent(MAGIC_PREALLOCATED_APP_MANIFEST_URL,
                           /*isBrowserElement=*/false,
-                          base::PRIVILEGES_DEFAULT);
+                          // Final privileges are set when we
+                          // transform into our app.
+                          base::PRIVILEGES_INHERIT);
     sPreallocatedAppProcess->Init();
 }
 
 /*static*/ void
 ContentParent::DelayedPreallocateAppProcess()
 {
     sPreallocateAppProcessTask = nullptr;
     if (!sPreallocatedAppProcess) {
@@ -418,30 +420,24 @@ ContentParent::CreateBrowserOrApp(const 
     if (NS_FAILED(ownApp->GetManifestURL(manifestURL))) {
         NS_ERROR("Failed to get manifest URL");
         return nullptr;
     }
 
     nsRefPtr<ContentParent> p = gAppContentParents->Get(manifestURL);
     if (!p) {
         ChildPrivileges privs = PrivilegesForApp(ownApp);
-        if (privs != base::PRIVILEGES_DEFAULT) {
+        p = MaybeTakePreallocatedAppProcess();
+        if (p) {
+            p->TransformPreallocatedIntoApp(manifestURL, privs);            
+        } else {
+            NS_WARNING("Unable to use pre-allocated app process");
             p = new ContentParent(manifestURL, /* isBrowserElement = */ false,
                                   privs);
             p->Init();
-        } else {
-            p = MaybeTakePreallocatedAppProcess();
-            if (p) {
-                p->SetManifestFromPreallocated(manifestURL);
-            } else {
-                NS_WARNING("Unable to use pre-allocated app process");
-                p = new ContentParent(manifestURL, /* isBrowserElement = */ false,
-                                      base::PRIVILEGES_DEFAULT);
-                p->Init();
-            }
         }
         gAppContentParents->Put(manifestURL, p);
     }
 
     nsRefPtr<TabParent> tp = new TabParent(aContext);
     PBrowserParent* browser = p->SendPBrowserConstructor(
         tp.forget().get(), // DeallocPBrowserParent() releases this ref.
         aContext.AsIPCTabContext(),
@@ -514,22 +510,25 @@ ContentParent::Init()
     }
 #endif
 
     DebugOnly<FileUpdateDispatcher*> observer = FileUpdateDispatcher::GetSingleton();
     NS_ASSERTION(observer, "FileUpdateDispatcher is null");
 }
 
 void
-ContentParent::SetManifestFromPreallocated(const nsAString& aAppManifestURL)
+ContentParent::TransformPreallocatedIntoApp(const nsAString& aAppManifestURL,
+                                            ChildPrivileges aPrivs)
 {
     MOZ_ASSERT(mAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL);
     // Clients should think of mAppManifestURL as const ... we're
     // bending the rules here just for the preallocation hack.
     const_cast<nsString&>(mAppManifestURL) = aAppManifestURL;
+    // If this fails, the child process died.
+    unused << SendSetProcessPrivileges(aPrivs);
 }
 
 void
 ContentParent::ShutDownProcess()
 {
   if (!mIsDestroyed) {
     const InfallibleTArray<PIndexedDBParent*>& idbParents =
       ManagedPIndexedDBParent();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -172,17 +172,18 @@ private:
     ContentParent(const nsAString& aAppManifestURL, bool aIsForBrowser,
                   ChildOSPrivileges aOSPrivileges = base::PRIVILEGES_DEFAULT);
     virtual ~ContentParent();
 
     void Init();
 
     // Transform a pre-allocated app process into a "real" app
     // process, for the specified manifest URL.
-    void SetManifestFromPreallocated(const nsAString& aAppManifestURL);
+    void TransformPreallocatedIntoApp(const nsAString& aAppManifestURL,
+                                      ChildPrivileges aPrivs);
 
     /**
      * Mark this ContentParent as dead for the purposes of Get*().
      * This method is idempotent.
      */
     void MarkAsDead();
 
     /**
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -31,16 +31,17 @@ include "mozilla/net/NeckoMessageUtils.h
 include "nsGeoPositionIPCSerialiser.h";
 
 using GeoPosition;
 using PrefTuple;
 
 using ChromePackage;
 using ResourceMapping;
 using OverrideMapping;
+using base::ChildPrivileges;
 using IPC::Permission;
 using IPC::Principal;
 using mozilla::null_t;
 using mozilla::void_t;
 using mozilla::dom::AudioChannelType;
 using mozilla::dom::NativeThreadId;
 using mozilla::layout::ScrollingBehavior;
 using gfxIntSize;
@@ -240,16 +241,23 @@ both:
     // privileges by requesting a PBrowser corresponding to a highly-privileged
     // app; the child can only request privileges for an app which the child has
     // access to (in the form of a TabChild).
     async PBrowser(IPCTabContext context, uint32_t chromeFlags);
 
     async PBlob(BlobConstructorParams params);
 
 child:
+    /**
+     * Update OS process privileges to |privs|.  Can usually only be
+     * performed zero or one times.  The child will abnormally exit if
+     * the privilege update fails.
+     */
+    async SetProcessPrivileges(ChildPrivileges privs);
+
     PMemoryReportRequest();
 
     /**
      * Notify the AudioChannelService in the child processes.
      */
     async AudioChannelNotify();
 
     /**
--- a/ipc/chromium/src/base/process_util.h
+++ b/ipc/chromium/src/base/process_util.h
@@ -126,17 +126,18 @@ void SetAllFDsToCloseOnExec();
 void CloseSuperfluousFds(const base::InjectiveMultimap& saved_map);
 #endif
 
 enum ChildPrivileges {
   PRIVILEGES_DEFAULT,
   PRIVILEGES_UNPRIVILEGED,
   PRIVILEGES_CAMERA,
   PRIVILEGES_VIDEO,
-  PRIVILEGES_INHERIT
+  PRIVILEGES_INHERIT,
+  PRIVILEGES_LAST
 };
 
 #if defined(OS_WIN)
 // Runs the given application name with the given command line. Normally, the
 // first command line argument should be the path to the process, and don't
 // forget to quote it.
 //
 // If wait is true, it will block and wait for the other process to finish,
@@ -175,16 +176,20 @@ bool LaunchApp(const std::vector<std::st
                bool wait, ProcessHandle* process_handle,
                ProcessArchitecture arch=GetCurrentProcessArchitecture());
 bool LaunchApp(const std::vector<std::string>& argv,
                const file_handle_mapping_vector& fds_to_remap,
                const environment_map& env_vars_to_set,
                bool wait, ProcessHandle* process_handle,
                ProcessArchitecture arch=GetCurrentProcessArchitecture());
 
+// Adjust the privileges of this process to match |privs|.  Only
+// returns if privileges were successfully adjusted.
+void SetCurrentProcessPrivileges(ChildPrivileges privs);
+
 #endif
 
 // Executes the application specified by cl. This function delegates to one
 // of the above two platform-specific functions.
 bool LaunchApp(const CommandLine& cl,
                bool wait, bool start_hidden, ProcessHandle* process_handle);
 
 #if defined(OS_WIN)
--- a/ipc/chromium/src/base/process_util_linux.cc
+++ b/ipc/chromium/src/base/process_util_linux.cc
@@ -226,74 +226,17 @@ bool LaunchApp(const std::vector<std::st
       _exit(127);
 
     CloseSuperfluousFds(fd_shuffle2);
 
     for (size_t i = 0; i < argv.size(); i++)
       argv_cstr[i] = const_cast<char*>(argv[i].c_str());
     argv_cstr[argv.size()] = NULL;
 
-    if (privs != PRIVILEGES_INHERIT) {
-      gid_t gid = CHILD_UNPRIVILEGED_GID;
-      uid_t uid = CHILD_UNPRIVILEGED_UID;
-#ifdef MOZ_WIDGET_GONK
-      {
-        static bool checked_pix_max, pix_max_ok;
-        if (!checked_pix_max) {
-          checked_pix_max = true;
-          int fd = open("/proc/sys/kernel/pid_max", O_CLOEXEC | O_RDONLY);
-          if (fd < 0) {
-            DLOG(ERROR) << "Failed to open pid_max";
-            _exit(127);
-          }
-          char buf[PATH_MAX];
-          ssize_t len = read(fd, buf, sizeof(buf) - 1);
-          close(fd);
-          if (len < 0) {
-            DLOG(ERROR) << "Failed to read pid_max";
-            _exit(127);
-          }
-          buf[len] = '\0';
-          int pid_max = atoi(buf);
-          pix_max_ok =
-            (pid_max + CHILD_UNPRIVILEGED_UID > CHILD_UNPRIVILEGED_UID);
-        }
-        if (!pix_max_ok) {
-          DLOG(ERROR) << "Can't safely get unique uid/gid";
-          _exit(127);
-        }
-        gid += getpid();
-        uid += getpid();
-      }
-      if (privs == PRIVILEGES_CAMERA) {
-        gid_t groups[] = { AID_AUDIO, AID_CAMERA, AID_SDCARD_RW };
-        if (setgroups(sizeof(groups) / sizeof(groups[0]), groups) != 0) {
-          DLOG(ERROR) << "FAILED TO setgroups() CHILD PROCESS, path: " << argv_cstr[0];
-          _exit(127);
-        }
-      }
-      else if (privs == PRIVILEGES_VIDEO) {
-        gid_t groups[] = { AID_AUDIO, AID_MEDIA };
-        if (setgroups(sizeof(groups) / sizeof(groups[0]), groups) != 0) {
-          DLOG(ERROR) << "FAILED TO setgroups() CHILD PROCESS, path: " << argv_cstr[0];
-          _exit(127);
-        }
-      }
-#endif
-      if (setgid(gid) != 0) {
-        DLOG(ERROR) << "FAILED TO setgid() CHILD PROCESS, path: " << argv_cstr[0];
-        _exit(127);
-      }
-      if (setuid(uid) != 0) {
-        DLOG(ERROR) << "FAILED TO setuid() CHILD PROCESS, path: " << argv_cstr[0];
-        _exit(127);
-      }
-      if (chdir("/") != 0)
-        gProcessLog.print("==> could not chdir()\n");
-    }
+    SetCurrentProcessPrivileges(privs);
 
 #ifdef HAVE_PR_DUPLICATE_ENVIRONMENT
     execve(argv_cstr[0], argv_cstr.get(), envp);
 #else
     for (environment_map::const_iterator it = env_vars_to_set.begin();
          it != env_vars_to_set.end(); ++it) {
       if (setenv(it->first.c_str(), it->second.c_str(), 1/*overwrite*/))
         _exit(127);
@@ -318,16 +261,79 @@ bool LaunchApp(const std::vector<std::st
 
 bool LaunchApp(const CommandLine& cl,
                bool wait, bool start_hidden,
                ProcessHandle* process_handle) {
   file_handle_mapping_vector no_files;
   return LaunchApp(cl.argv(), no_files, wait, process_handle);
 }
 
+void SetCurrentProcessPrivileges(ChildPrivileges privs) {
+  if (privs == PRIVILEGES_INHERIT) {
+    return;
+  }
+
+  gid_t gid = CHILD_UNPRIVILEGED_GID;
+  uid_t uid = CHILD_UNPRIVILEGED_UID;
+#ifdef MOZ_WIDGET_GONK
+  {
+    static bool checked_pix_max, pix_max_ok;
+    if (!checked_pix_max) {
+      checked_pix_max = true;
+      int fd = open("/proc/sys/kernel/pid_max", O_CLOEXEC | O_RDONLY);
+      if (fd < 0) {
+        DLOG(ERROR) << "Failed to open pid_max";
+        _exit(127);
+      }
+      char buf[PATH_MAX];
+      ssize_t len = read(fd, buf, sizeof(buf) - 1);
+      close(fd);
+      if (len < 0) {
+        DLOG(ERROR) << "Failed to read pid_max";
+        _exit(127);
+      }
+      buf[len] = '\0';
+      int pid_max = atoi(buf);
+      pix_max_ok =
+        (pid_max + CHILD_UNPRIVILEGED_UID > CHILD_UNPRIVILEGED_UID);
+    }
+    if (!pix_max_ok) {
+      DLOG(ERROR) << "Can't safely get unique uid/gid";
+      _exit(127);
+    }
+    gid += getpid();
+    uid += getpid();
+  }
+  if (privs == PRIVILEGES_CAMERA) {
+    gid_t groups[] = { AID_AUDIO, AID_CAMERA, AID_SDCARD_RW };
+    if (setgroups(sizeof(groups) / sizeof(groups[0]), groups) != 0) {
+      DLOG(ERROR) << "FAILED TO setgroups() CHILD PROCESS";
+      _exit(127);
+    }
+  }
+  else if (privs == PRIVILEGES_VIDEO) {
+    gid_t groups[] = { AID_AUDIO, AID_MEDIA };
+    if (setgroups(sizeof(groups) / sizeof(groups[0]), groups) != 0) {
+      DLOG(ERROR) << "FAILED TO setgroups() CHILD PROCESS";
+      _exit(127);
+    }
+  }
+#endif
+  if (setgid(gid) != 0) {
+    DLOG(ERROR) << "FAILED TO setgid() CHILD PROCESS";
+    _exit(127);
+  }
+  if (setuid(uid) != 0) {
+    DLOG(ERROR) << "FAILED TO setuid() CHILD PROCESS";
+    _exit(127);
+  }
+  if (chdir("/") != 0)
+    gProcessLog.print("==> could not chdir()\n");
+}
+
 NamedProcessIterator::NamedProcessIterator(const std::wstring& executable_name,
                                            const ProcessFilter* filter)
     : executable_name_(executable_name), filter_(filter) {
   procfs_dir_ = opendir("/proc");
 }
 
 NamedProcessIterator::~NamedProcessIterator() {
   if (procfs_dir_) {
--- a/ipc/chromium/src/base/process_util_mac.mm
+++ b/ipc/chromium/src/base/process_util_mac.mm
@@ -183,16 +183,20 @@ bool LaunchApp(const std::vector<std::st
 
 bool LaunchApp(const CommandLine& cl,
                bool wait, bool start_hidden, ProcessHandle* process_handle) {
   // TODO(playmobil): Do we need to respect the start_hidden flag?
   file_handle_mapping_vector no_files;
   return LaunchApp(cl.argv(), no_files, wait, process_handle);
 }
 
+void SetCurrentProcessPrivileges(ChildPrivileges privs) {
+
+}
+
 NamedProcessIterator::NamedProcessIterator(const std::wstring& executable_name,
                                            const ProcessFilter* filter)
   : executable_name_(executable_name),
     index_of_kinfo_proc_(0),
     filter_(filter) {
   // Get a snapshot of all of my processes (yes, as we loop it can go stale, but
   // but trying to find where we were in a constantly changing list is basically
   // impossible.
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -69,16 +69,24 @@ ShouldHaveDirectoryService()
 
 template<>
 struct RunnableMethodTraits<GeckoChildProcessHost>
 {
     static void RetainCallee(GeckoChildProcessHost* obj) { }
     static void ReleaseCallee(GeckoChildProcessHost* obj) { }
 };
 
+/*static*/
+base::ChildPrivileges
+GeckoChildProcessHost::DefaultChildPrivileges()
+{
+  return (kLowRightsSubprocesses ?
+          base::PRIVILEGES_UNPRIVILEGED : base::PRIVILEGES_INHERIT);
+}
+
 GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType,
                                              ChildPrivileges aPrivileges)
   : ChildProcessHost(RENDER_PROCESS), // FIXME/cjones: we should own this enum
     mProcessType(aProcessType),
     mPrivileges(aPrivileges),
     mMonitor("mozilla.ipc.GeckChildProcessHost.mMonitor"),
     mProcessState(CREATING_CHANNEL),
     mDelegate(nullptr),
@@ -451,18 +459,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
   // configuration, because chromium is compiled with -fno-short-wchar
   // and passing wstrings from one config to the other is unsafe.  So
   // we split the logic here.
 
 #if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD)
   base::environment_map newEnvVars;
   ChildPrivileges privs = mPrivileges;
   if (privs == base::PRIVILEGES_DEFAULT) {
-    privs = kLowRightsSubprocesses ?
-            base::PRIVILEGES_UNPRIVILEGED : base::PRIVILEGES_INHERIT;
+    privs = DefaultChildPrivileges();
   }
   // XPCOM may not be initialized in some subprocesses.  We don't want
   // to initialize XPCOM just for the directory service, especially
   // since LD_LIBRARY_PATH is already set correctly in subprocesses
   // (meaning that we don't need to set that up in the environment).
   if (ShouldHaveDirectoryService()) {
     nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
     NS_ASSERTION(directoryService, "Expected XPCOM to be available");
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -24,16 +24,18 @@ class GeckoChildProcessHost : public Chi
 protected:
   typedef mozilla::Monitor Monitor;
   typedef std::vector<std::string> StringVector;
 
 public:
   typedef base::ChildPrivileges ChildPrivileges;
   typedef base::ProcessHandle ProcessHandle;
 
+  static ChildPrivileges DefaultChildPrivileges();
+
   GeckoChildProcessHost(GeckoProcessType aProcessType,
                         ChildPrivileges aPrivileges=base::PRIVILEGES_DEFAULT);
 
   ~GeckoChildProcessHost();
 
   static nsresult GetArchitecturesForBinary(const char *path, uint32_t *result);
 
   static uint32_t GetSupportedArchitecturesForProcessType(GeckoProcessType type);
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -2,16 +2,17 @@
 /* vim: set sw=2 ts=8 et tw=80 : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __IPC_GLUE_IPCMESSAGEUTILS_H__
 #define __IPC_GLUE_IPCMESSAGEUTILS_H__
 
+#include "base/process_util.h"
 #include "chrome/common/ipc_message_utils.h"
 
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Util.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/StandardInteger.h"
 
 #include "nsID.h"
@@ -142,16 +143,23 @@ struct EnumSerializer {
        !IsLegalValue(paramType(value))) {
       return false;
     }
     *aResult = paramType(value);
     return true;
   }
 };
 
+template <>
+struct ParamTraits<base::ChildPrivileges>
+  : public EnumSerializer<base::ChildPrivileges,
+                          base::PRIVILEGES_DEFAULT,
+                          base::PRIVILEGES_LAST>
+{ };
+
 template<>
 struct ParamTraits<int8_t>
 {
   typedef int8_t paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     aMsg->WriteBytes(&aParam, sizeof(aParam));