1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/ipc/NodeChannel.h"
8 #include "chrome/common/ipc_message.h"
9 #include "chrome/common/ipc_message_utils.h"
10 #include "mojo/core/ports/name.h"
11 #include "mozilla/ipc/BrowserProcessSubThread.h"
12 #include "mozilla/ipc/ProtocolMessageUtils.h"
13 #include "mozilla/ipc/ProtocolUtils.h"
14 #include "nsThreadUtils.h"
15 #include "nsXULAppAPI.h"
16 
17 template <>
18 struct IPC::ParamTraits<mozilla::ipc::NodeChannel::Introduction> {
19   using paramType = mozilla::ipc::NodeChannel::Introduction;
WriteIPC::ParamTraits20   static void Write(MessageWriter* aWriter, paramType&& aParam) {
21     WriteParam(aWriter, aParam.mName);
22     WriteParam(aWriter, std::move(aParam.mHandle));
23     WriteParam(aWriter, aParam.mMode);
24     WriteParam(aWriter, aParam.mMyPid);
25     WriteParam(aWriter, aParam.mOtherPid);
26   }
ReadIPC::ParamTraits27   static bool Read(MessageReader* aReader, paramType* aResult) {
28     return ReadParam(aReader, &aResult->mName) &&
29            ReadParam(aReader, &aResult->mHandle) &&
30            ReadParam(aReader, &aResult->mMode) &&
31            ReadParam(aReader, &aResult->mMyPid) &&
32            ReadParam(aReader, &aResult->mOtherPid);
33   }
34 };
35 
36 namespace mozilla::ipc {
37 
NodeChannel(const NodeName & aName,UniquePtr<IPC::Channel> aChannel,Listener * aListener,int32_t aPid)38 NodeChannel::NodeChannel(const NodeName& aName,
39                          UniquePtr<IPC::Channel> aChannel, Listener* aListener,
40                          int32_t aPid)
41     : mListener(aListener),
42       mName(aName),
43       mOtherPid(aPid),
44       mChannel(std::move(aChannel)) {}
45 
~NodeChannel()46 NodeChannel::~NodeChannel() {
47   AssertIOThread();
48   if (!mClosed) {
49     mChannel->Close();
50   }
51 }
52 
53 // Called when the NodeChannel's refcount drops to `0`.
Destroy()54 void NodeChannel::Destroy() {
55   // Dispatch the `delete` operation to the IO thread. We need to do this even
56   // if we're already on the IO thread, as we could be in an `IPC::Channel`
57   // callback which unfortunately will not hold a strong reference to keep
58   // `this` alive.
59   MessageLoop* ioThread = XRE_GetIOMessageLoop();
60   if (ioThread->IsAcceptingTasks()) {
61     ioThread->PostTask(NewNonOwningRunnableMethod("NodeChannel::Destroy", this,
62                                                   &NodeChannel::FinalDestroy));
63     return;
64   }
65 
66   // If the IOThread has already been destroyed, we must be shutting it down and
67   // need to synchronously invoke `FinalDestroy` to ensure we're cleaned up
68   // before the thread dies. This is safe as we can't be in a non-owning
69   // IPC::Channel callback at this point.
70   if (MessageLoop::current() == ioThread) {
71     FinalDestroy();
72     return;
73   }
74 
75   MOZ_ASSERT_UNREACHABLE("Leaking NodeChannel after IOThread destroyed!");
76 }
77 
FinalDestroy()78 void NodeChannel::FinalDestroy() {
79   AssertIOThread();
80   delete this;
81 }
82 
Start(bool aCallConnect)83 void NodeChannel::Start(bool aCallConnect) {
84   AssertIOThread();
85 
86   mExistingListener = mChannel->set_listener(this);
87 
88   std::queue<IPC::Message> pending;
89   if (mExistingListener) {
90     mExistingListener->GetQueuedMessages(pending);
91   }
92 
93   if (aCallConnect) {
94     MOZ_ASSERT(pending.empty(), "unopened channel with pending messages?");
95     if (!mChannel->Connect()) {
96       OnChannelError();
97     }
98   } else {
99     // Check if our channel has already been connected, and knows the other PID.
100     int32_t otherPid = mChannel->OtherPid();
101     if (otherPid != -1) {
102       SetOtherPid(otherPid);
103     }
104 
105     // Handle any events the previous listener had queued up. Make sure to stop
106     // if an error causes our channel to become closed.
107     while (!pending.empty() && !mClosed) {
108       OnMessageReceived(std::move(pending.front()));
109       pending.pop();
110     }
111   }
112 }
113 
Close()114 void NodeChannel::Close() {
115   AssertIOThread();
116 
117   if (!mClosed) {
118     mChannel->Close();
119     mChannel->set_listener(mExistingListener);
120   }
121   mClosed = true;
122 }
123 
SetOtherPid(int32_t aNewPid)124 void NodeChannel::SetOtherPid(int32_t aNewPid) {
125   AssertIOThread();
126   MOZ_ASSERT(aNewPid != -1);
127 
128   int32_t previousPid = -1;
129   if (!mOtherPid.compare_exchange_strong(previousPid, aNewPid)) {
130     // The PID was already set before this call, double-check that it's correct.
131     MOZ_RELEASE_ASSERT(previousPid == aNewPid,
132                        "Different sources disagree on the correct pid?");
133   }
134 }
135 
136 #ifdef XP_MACOSX
SetMachTaskPort(task_t aTask)137 void NodeChannel::SetMachTaskPort(task_t aTask) {
138   AssertIOThread();
139 
140   if (!mClosed) {
141     mChannel->SetOtherMachTask(aTask);
142   }
143 }
144 #endif
145 
SendEventMessage(UniquePtr<IPC::Message> aMessage)146 void NodeChannel::SendEventMessage(UniquePtr<IPC::Message> aMessage) {
147   // Make sure we're not sending a message with one of our special internal
148   // types ,as those should only be sent using the corresponding methods on
149   // NodeChannel.
150   MOZ_DIAGNOSTIC_ASSERT(aMessage->type() != BROADCAST_MESSAGE_TYPE &&
151                         aMessage->type() != INTRODUCE_MESSAGE_TYPE &&
152                         aMessage->type() != REQUEST_INTRODUCTION_MESSAGE_TYPE &&
153                         aMessage->type() != ACCEPT_INVITE_MESSAGE_TYPE);
154   SendMessage(std::move(aMessage));
155 }
156 
RequestIntroduction(const NodeName & aPeerName)157 void NodeChannel::RequestIntroduction(const NodeName& aPeerName) {
158   MOZ_ASSERT(aPeerName != mojo::core::ports::kInvalidNodeName);
159   auto message = MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL,
160                                           REQUEST_INTRODUCTION_MESSAGE_TYPE);
161   IPC::MessageWriter writer(*message);
162   WriteParam(&writer, aPeerName);
163   SendMessage(std::move(message));
164 }
165 
Introduce(Introduction aIntroduction)166 void NodeChannel::Introduce(Introduction aIntroduction) {
167   auto message =
168       MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, INTRODUCE_MESSAGE_TYPE);
169   IPC::MessageWriter writer(*message);
170   WriteParam(&writer, std::move(aIntroduction));
171   SendMessage(std::move(message));
172 }
173 
Broadcast(UniquePtr<IPC::Message> aMessage)174 void NodeChannel::Broadcast(UniquePtr<IPC::Message> aMessage) {
175   MOZ_DIAGNOSTIC_ASSERT(aMessage->type() == BROADCAST_MESSAGE_TYPE,
176                         "Can only broadcast messages with the correct type");
177   SendMessage(std::move(aMessage));
178 }
179 
AcceptInvite(const NodeName & aRealName,const PortName & aInitialPort)180 void NodeChannel::AcceptInvite(const NodeName& aRealName,
181                                const PortName& aInitialPort) {
182   MOZ_ASSERT(aRealName != mojo::core::ports::kInvalidNodeName);
183   MOZ_ASSERT(aInitialPort != mojo::core::ports::kInvalidPortName);
184   auto message =
185       MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, ACCEPT_INVITE_MESSAGE_TYPE);
186   IPC::MessageWriter writer(*message);
187   WriteParam(&writer, aRealName);
188   WriteParam(&writer, aInitialPort);
189   SendMessage(std::move(message));
190 }
191 
SendMessage(UniquePtr<IPC::Message> aMessage)192 void NodeChannel::SendMessage(UniquePtr<IPC::Message> aMessage) {
193   if (aMessage->size() > IPC::Channel::kMaximumMessageSize) {
194     CrashReporter::AnnotateCrashReport(
195         CrashReporter::Annotation::IPCMessageName,
196         nsDependentCString(aMessage->name()));
197     CrashReporter::AnnotateCrashReport(
198         CrashReporter::Annotation::IPCMessageSize,
199         static_cast<unsigned int>(aMessage->size()));
200     MOZ_CRASH("IPC message size is too large");
201   }
202   aMessage->AssertAsLargeAsHeader();
203 
204   XRE_GetIOMessageLoop()->PostTask(
205       NewRunnableMethod<StoreCopyPassByRRef<UniquePtr<IPC::Message>>>(
206           "NodeChannel::DoSendMessage", this, &NodeChannel::DoSendMessage,
207           std::move(aMessage)));
208 }
209 
DoSendMessage(UniquePtr<IPC::Message> aMessage)210 void NodeChannel::DoSendMessage(UniquePtr<IPC::Message> aMessage) {
211   AssertIOThread();
212   if (mClosed) {
213     NS_WARNING("Dropping message as channel has been closed");
214     return;
215   }
216 
217   if (!mChannel->Send(std::move(aMessage))) {
218     NS_WARNING("Call to Send() failed");
219     OnChannelError();
220   }
221 }
222 
OnMessageReceived(IPC::Message && aMessage)223 void NodeChannel::OnMessageReceived(IPC::Message&& aMessage) {
224   AssertIOThread();
225 
226   if (!aMessage.is_valid()) {
227     NS_WARNING("Received an invalid message");
228     OnChannelError();
229     return;
230   }
231 
232   IPC::MessageReader reader(aMessage);
233   switch (aMessage.type()) {
234     case REQUEST_INTRODUCTION_MESSAGE_TYPE: {
235       NodeName name;
236       if (IPC::ReadParam(&reader, &name)) {
237         mListener->OnRequestIntroduction(mName, name);
238         return;
239       }
240       break;
241     }
242     case INTRODUCE_MESSAGE_TYPE: {
243       Introduction introduction;
244       if (IPC::ReadParam(&reader, &introduction)) {
245         mListener->OnIntroduce(mName, std::move(introduction));
246         return;
247       }
248       break;
249     }
250     case BROADCAST_MESSAGE_TYPE: {
251       mListener->OnBroadcast(mName,
252                              MakeUnique<IPC::Message>(std::move(aMessage)));
253       return;
254     }
255     case ACCEPT_INVITE_MESSAGE_TYPE: {
256       NodeName realName;
257       PortName initialPort;
258       if (IPC::ReadParam(&reader, &realName) &&
259           IPC::ReadParam(&reader, &initialPort)) {
260         mListener->OnAcceptInvite(mName, realName, initialPort);
261         return;
262       }
263       break;
264     }
265     // Assume all unrecognized types are intended as user event messages, and
266     // deliver them to our listener as such. This allows us to use the same type
267     // field for both internal messages and protocol messages.
268     //
269     // FIXME: Consider doing something cleaner in the future?
270     case EVENT_MESSAGE_TYPE:
271     default: {
272       mListener->OnEventMessage(mName,
273                                 MakeUnique<IPC::Message>(std::move(aMessage)));
274       return;
275     }
276   }
277 
278   // If we got to this point without early returning the message was malformed
279   // in some way. Report an error.
280 
281   NS_WARNING("NodeChannel received a malformed message");
282   OnChannelError();
283 }
284 
OnChannelConnected(int32_t aPeerPid)285 void NodeChannel::OnChannelConnected(int32_t aPeerPid) {
286   AssertIOThread();
287 
288   SetOtherPid(aPeerPid);
289 
290   // We may need to tell our original listener (which will be the process launch
291   // code) that the the channel has been connected to unblock completing the
292   // process launch.
293   // FIXME: This is super sketchy, but it's also what we were already doing. We
294   // should swap this out for something less sketchy.
295   if (mExistingListener) {
296     mExistingListener->OnChannelConnected(aPeerPid);
297   }
298 }
299 
OnChannelError()300 void NodeChannel::OnChannelError() {
301   AssertIOThread();
302 
303   // Clean up the channel and make sure we're no longer the active listener.
304   mChannel->Close();
305   MOZ_ALWAYS_TRUE(this == mChannel->set_listener(mExistingListener));
306   mClosed = true;
307 
308   // Tell our listener about the error.
309   mListener->OnChannelError(mName);
310 }
311 
312 }  // namespace mozilla::ipc
313