Bug 1345978, part 2 - Check build ids early in content startup. r=billm
authorAndrew McCreight <continuation@gmail.com>
Fri, 10 Mar 2017 17:01:38 -0800
changeset 395494 4ccdf8cf5a1412b21afa49b3d6bb8497c49e86c5
parent 395493 7b3260b7e95a9a3a56ea5d6f58971c9d3aeb0e95
child 395495 86d53419eab713b764bd5866266c1e3e085dd934
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1345978
milestone55.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 1345978, part 2 - Check build ids early in content startup. r=billm If Firefox is updated while it is running, the content process can end up being a different version than the parent process. This can cause odd crashes, that will happen repeatedly until the user restarts Firefox. To handle this better, this patch adds a special build ID message that is sent early in content process startup. The parent process intentionally crashes if the build ID for the child process does not match that of the parent process. MozReview-Commit-ID: 7D3ggkaLxNS
dom/ipc/ContentChild.cpp
ipc/glue/MessageChannel.cpp
ipc/glue/MessageChannel.h
ipc/glue/ProtocolUtils.h
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -573,16 +573,21 @@ ContentChild::Init(MessageLoop* aIOLoop,
 
   // If communications with the parent have broken down, take the process
   // down so it's not hanging around.
   GetIPCChannel()->SetAbortOnError(true);
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_A11Y_REENTRY);
 #endif
 
+  // This must be sent before any IPDL message, which may hit sentinel
+  // errors due to parent and content processes having different
+  // versions.
+  GetIPCChannel()->SendBuildID();
+
 #ifdef MOZ_X11
   // Send the parent our X socket to act as a proxy reference for our X
   // resources.
   int xSocketFd = ConnectionNumber(DefaultXDisplay());
   SendBackUpXResources(FileDescriptor(xSocketFd));
 #endif
 
 #ifdef MOZ_CRASHREPORTER
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -13,16 +13,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Move.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Logging.h"
 #include "mozilla/TimeStamp.h"
+#include "nsAppRunner.h"
 #include "nsAutoPtr.h"
 #include "nsDebug.h"
 #include "nsISupportsImpl.h"
 #include "nsContentUtils.h"
 #include <math.h>
 
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
@@ -814,32 +815,87 @@ MessageChannel::Send(Message* aMsg)
     if (!Connected()) {
         ReportConnectionError("MessageChannel", msg);
         return false;
     }
     mLink->SendMessage(msg.forget());
     return true;
 }
 
+class BuildIDMessage : public IPC::Message
+{
+public:
+    BuildIDMessage()
+        : IPC::Message(MSG_ROUTING_NONE, BUILD_ID_MESSAGE_TYPE)
+    {
+    }
+    void Log(const std::string& aPrefix, FILE* aOutf) const
+    {
+        fputs("(special `Build ID' message)", aOutf);
+    }
+};
+
+// Send the parent a special async message to allow it to detect if
+// this process is running a different build. This is a minor
+// variation on MessageChannel::Send(Message* aMsg).
+void
+MessageChannel::SendBuildID()
+{
+    MOZ_ASSERT(!XRE_IsParentProcess());
+    nsAutoPtr<BuildIDMessage> msg(new BuildIDMessage());
+    nsCString buildID(mozilla::PlatformBuildID());
+    IPC::WriteParam(msg, buildID);
+
+    MOZ_RELEASE_ASSERT(!msg->is_sync());
+    MOZ_RELEASE_ASSERT(msg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC);
+
+    AssertWorkerThread();
+    mMonitor->AssertNotCurrentThreadOwns();
+    // Don't check for MSG_ROUTING_NONE.
+
+    MonitorAutoLock lock(*mMonitor);
+    if (!Connected()) {
+        ReportConnectionError("MessageChannel", msg);
+        MOZ_CRASH();
+    }
+    mLink->SendMessage(msg.forget());
+}
+
 class CancelMessage : public IPC::Message
 {
 public:
     explicit CancelMessage(int transaction) :
         IPC::Message(MSG_ROUTING_NONE, CANCEL_MESSAGE_TYPE)
     {
         set_transaction_id(transaction);
     }
     static bool Read(const Message* msg) {
         return true;
     }
     void Log(const std::string& aPrefix, FILE* aOutf) const {
         fputs("(special `Cancel' message)", aOutf);
     }
 };
 
+MOZ_NEVER_INLINE static void
+CheckChildProcessBuildID(const IPC::Message& aMsg)
+{
+    MOZ_ASSERT(XRE_IsParentProcess());
+    nsCString childBuildID;
+    PickleIterator msgIter(aMsg);
+    MOZ_ALWAYS_TRUE(IPC::ReadParam(&aMsg, &msgIter, &childBuildID));
+    aMsg.EndRead(msgIter);
+
+    nsCString parentBuildID(mozilla::PlatformBuildID());
+
+    // This assert can fail if the child process has been updated
+    // to a newer version while the parent process was running.
+    MOZ_RELEASE_ASSERT(parentBuildID == childBuildID);
+}
+
 bool
 MessageChannel::MaybeInterceptSpecialIOMessage(const Message& aMsg)
 {
     AssertLinkThread();
     mMonitor->AssertCurrentThreadOwns();
 
     if (MSG_ROUTING_NONE == aMsg.routing_id()) {
         if (GOODBYE_MESSAGE_TYPE == aMsg.type()) {
@@ -851,16 +907,20 @@ MessageChannel::MaybeInterceptSpecialIOM
                        (mSide == ChildSide) ? "child" : "parent");
             }
             return true;
         } else if (CANCEL_MESSAGE_TYPE == aMsg.type()) {
             IPC_LOG("Cancel from message");
             CancelTransaction(aMsg.transaction_id());
             NotifyWorkerThread();
             return true;
+        } else if (BUILD_ID_MESSAGE_TYPE == aMsg.type()) {
+            IPC_LOG("Build ID message");
+            CheckChildProcessBuildID(aMsg);
+            return true;
         }
     }
     return false;
 }
 
 bool
 MessageChannel::ShouldDeferMessage(const Message& aMsg)
 {
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -148,16 +148,18 @@ class MessageChannel : HasResultCodes
       REQUIRE_A11Y_REENTRY                    = 1 << 1,
     };
     void SetChannelFlags(ChannelFlags aFlags) { mFlags = aFlags; }
     ChannelFlags GetChannelFlags() { return mFlags; }
 
     // Asynchronously send a message to the other side of the channel
     bool Send(Message* aMsg);
 
+    void SendBuildID();
+
     // Asynchronously deliver a message back to this side of the
     // channel
     bool Echo(Message* aMsg);
 
     // Synchronously send |msg| (i.e., wait for |reply|)
     bool Send(Message* aMsg, Message* aReply);
 
     // Make an Interrupt call to the other side of the channel
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -41,16 +41,17 @@ template<typename T> class nsPtrHashKey;
 namespace {
 // XXX the max message ID is actually kuint32max now ... when this
 // changed, the assumptions of the special message IDs changed in that
 // they're not carving out messages from likely-unallocated space, but
 // rather carving out messages from the end of space allocated to
 // protocol 0.  Oops!  We can get away with this until protocol 0
 // starts approaching its 65,536th message.
 enum {
+    BUILD_ID_MESSAGE_TYPE = kuint16max - 7,
     CHANNEL_OPENED_MESSAGE_TYPE = kuint16max - 6,
     SHMEM_DESTROYED_MESSAGE_TYPE = kuint16max - 5,
     SHMEM_CREATED_MESSAGE_TYPE = kuint16max - 4,
     GOODBYE_MESSAGE_TYPE       = kuint16max - 3,
     CANCEL_MESSAGE_TYPE        = kuint16max - 2,
 
     // kuint16max - 1 is used by ipc_channel.h.
 };