NodeChannel.cpp (9853B)
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/IOThread.h" 12 #include "mozilla/ipc/GeckoChildProcessHost.h" 13 #include "mozilla/ipc/ProtocolMessageUtils.h" 14 #include "mozilla/ipc/ProtocolUtils.h" 15 #include "nsThreadUtils.h" 16 #include "nsXULAppAPI.h" 17 18 #ifdef FUZZING_SNAPSHOT 19 # include "mozilla/fuzzing/IPCFuzzController.h" 20 #endif 21 22 template <> 23 struct IPC::ParamTraits<mozilla::ipc::NodeChannel::Introduction> { 24 using paramType = mozilla::ipc::NodeChannel::Introduction; 25 static void Write(MessageWriter* aWriter, paramType&& aParam) { 26 WriteParam(aWriter, aParam.mName); 27 WriteParam(aWriter, std::move(aParam.mHandle)); 28 WriteParam(aWriter, aParam.mMyPid); 29 WriteParam(aWriter, aParam.mOtherPid); 30 } 31 static bool Read(MessageReader* aReader, paramType* aResult) { 32 return ReadParam(aReader, &aResult->mName) && 33 ReadParam(aReader, &aResult->mHandle) && 34 ReadParam(aReader, &aResult->mMyPid) && 35 ReadParam(aReader, &aResult->mOtherPid); 36 } 37 }; 38 39 namespace mozilla::ipc { 40 41 NodeChannel::NodeChannel(const NodeName& aName, IPC::Channel* aChannel, 42 Listener* aListener, base::ProcessId aPid, 43 GeckoChildProcessHost* aChildProcessHost) 44 : mListener(aListener), 45 mName(aName), 46 mOtherPid(aPid), 47 mChannel(std::move(aChannel)), 48 mChildProcessHost(aChildProcessHost) {} 49 50 NodeChannel::~NodeChannel() { Close(); } 51 52 // Called when the NodeChannel's refcount drops to `0`. 53 void NodeChannel::Destroy() { 54 // We want to dispatch the `delete` operation to the IO thread. We need to do 55 // this even if we're already on the IO thread, as we could be in an 56 // `IPC::Channel` callback which unfortunately will not hold a strong 57 // reference to keep `this` alive. 58 nsISerialEventTarget* ioThread = XRE_GetAsyncIOEventTarget(); 59 60 // Synchronously invoke `FinalDestroy` if we're already shutting the IO thread 61 // down to ensure we're cleaned up before the thread dies. This is safe as we 62 // can't be in a non-owning IPC::Channel callback at this point. 63 if (ioThread->IsOnCurrentThread() && MessageLoop::current() && 64 !MessageLoop::current()->IsAcceptingTasks()) { 65 FinalDestroy(); 66 return; 67 } 68 69 MOZ_ALWAYS_SUCCEEDS(ioThread->Dispatch(NewNonOwningRunnableMethod( 70 "NodeChannel::Destroy", this, &NodeChannel::FinalDestroy))); 71 } 72 73 void NodeChannel::FinalDestroy() { 74 AssertIOThread(); 75 delete this; 76 } 77 78 void NodeChannel::Start() { 79 AssertIOThread(); 80 81 if (!mChannel->Connect(this)) { 82 OnChannelError(); 83 } 84 } 85 86 void NodeChannel::Close() { 87 AssertIOThread(); 88 89 if (mState.exchange(State::Closed) != State::Closed) { 90 mChannel->Close(); 91 } 92 } 93 94 void NodeChannel::SetOtherPid(base::ProcessId aNewPid) { 95 AssertIOThread(); 96 MOZ_ASSERT(aNewPid != base::kInvalidProcessId); 97 98 base::ProcessId previousPid = base::kInvalidProcessId; 99 if (!mOtherPid.compare_exchange_strong(previousPid, aNewPid)) { 100 // The PID was already set before this call, double-check that it's correct. 101 MOZ_RELEASE_ASSERT(previousPid == aNewPid, 102 "Different sources disagree on the correct pid?"); 103 } 104 105 mChannel->SetOtherPid(aNewPid); 106 } 107 108 #ifdef XP_DARWIN 109 void NodeChannel::SetMachTaskPort(task_t aTask) { 110 AssertIOThread(); 111 112 if (mState != State::Closed) { 113 mChannel->SetOtherMachTask(aTask); 114 } 115 } 116 #endif 117 118 void NodeChannel::SendEventMessage(UniquePtr<IPC::Message> aMessage) { 119 // Make sure we're not sending a message with one of our special internal 120 // types ,as those should only be sent using the corresponding methods on 121 // NodeChannel. 122 MOZ_DIAGNOSTIC_ASSERT(aMessage->type() != BROADCAST_MESSAGE_TYPE && 123 aMessage->type() != INTRODUCE_MESSAGE_TYPE && 124 aMessage->type() != REQUEST_INTRODUCTION_MESSAGE_TYPE && 125 aMessage->type() != ACCEPT_INVITE_MESSAGE_TYPE); 126 SendMessage(std::move(aMessage)); 127 } 128 129 void NodeChannel::RequestIntroduction(const NodeName& aPeerName) { 130 MOZ_ASSERT(aPeerName != mojo::core::ports::kInvalidNodeName); 131 auto message = MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, 132 REQUEST_INTRODUCTION_MESSAGE_TYPE); 133 IPC::MessageWriter writer(*message); 134 WriteParam(&writer, aPeerName); 135 SendMessage(std::move(message)); 136 } 137 138 void NodeChannel::Introduce(Introduction aIntroduction) { 139 auto message = 140 MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, INTRODUCE_MESSAGE_TYPE); 141 IPC::MessageWriter writer(*message); 142 WriteParam(&writer, std::move(aIntroduction)); 143 SendMessage(std::move(message)); 144 } 145 146 void NodeChannel::Broadcast(UniquePtr<IPC::Message> aMessage) { 147 MOZ_DIAGNOSTIC_ASSERT(aMessage->type() == BROADCAST_MESSAGE_TYPE, 148 "Can only broadcast messages with the correct type"); 149 SendMessage(std::move(aMessage)); 150 } 151 152 void NodeChannel::AcceptInvite(const NodeName& aRealName, 153 const PortName& aInitialPort) { 154 MOZ_ASSERT(aRealName != mojo::core::ports::kInvalidNodeName); 155 MOZ_ASSERT(aInitialPort != mojo::core::ports::kInvalidPortName); 156 auto message = 157 MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, ACCEPT_INVITE_MESSAGE_TYPE); 158 IPC::MessageWriter writer(*message); 159 WriteParam(&writer, aRealName); 160 WriteParam(&writer, aInitialPort); 161 SendMessage(std::move(message)); 162 } 163 164 void NodeChannel::SendMessage(UniquePtr<IPC::Message> aMessage) { 165 if (aMessage->size() > IPC::Channel::kMaximumMessageSize) { 166 CrashReporter::RecordAnnotationCString( 167 CrashReporter::Annotation::IPCMessageName, aMessage->name()); 168 CrashReporter::RecordAnnotationU32( 169 CrashReporter::Annotation::IPCMessageSize, aMessage->size()); 170 CrashReporter::RecordAnnotationU32( 171 CrashReporter::Annotation::IPCMessageLargeBufferShmemFailureSize, 172 aMessage->LargeBufferShmemFailureSize()); 173 MOZ_CRASH("IPC message size is too large"); 174 } 175 aMessage->AssertAsLargeAsHeader(); 176 177 #ifdef FUZZING_SNAPSHOT 178 if (mBlockSendRecv) { 179 return; 180 } 181 #endif 182 183 if (mState != State::Active) { 184 NS_WARNING("Dropping message as channel has been closed"); 185 return; 186 } 187 188 // NOTE: As this is not guaranteed to be running on the I/O thread, the 189 // channel may have become closed since we checked above. IPC::Channel will 190 // handle that and return `false` here, so we can re-check `mState`. 191 if (!mChannel->Send(std::move(aMessage))) { 192 NS_WARNING("Call to Send() failed"); 193 194 // If we're still active, update `mState` to `State::Closing`, and dispatch 195 // a runnable to actually close our channel. 196 State expected = State::Active; 197 if (mState.compare_exchange_strong(expected, State::Closing)) { 198 XRE_GetAsyncIOEventTarget()->Dispatch( 199 NewRunnableMethod("NodeChannel::CloseForSendError", this, 200 &NodeChannel::OnChannelError)); 201 } 202 } 203 } 204 205 void NodeChannel::OnMessageReceived(UniquePtr<IPC::Message> aMessage) { 206 AssertIOThread(); 207 208 #ifdef FUZZING_SNAPSHOT 209 if (mBlockSendRecv && !aMessage->IsFuzzMsg()) { 210 return; 211 } 212 #endif 213 214 IPC::MessageReader reader(*aMessage); 215 switch (aMessage->type()) { 216 case REQUEST_INTRODUCTION_MESSAGE_TYPE: { 217 NodeName name; 218 if (IPC::ReadParam(&reader, &name)) { 219 mListener->OnRequestIntroduction(mName, name); 220 return; 221 } 222 break; 223 } 224 case INTRODUCE_MESSAGE_TYPE: { 225 Introduction introduction; 226 if (IPC::ReadParam(&reader, &introduction)) { 227 mListener->OnIntroduce(mName, std::move(introduction)); 228 return; 229 } 230 break; 231 } 232 case BROADCAST_MESSAGE_TYPE: { 233 mListener->OnBroadcast(mName, std::move(aMessage)); 234 return; 235 } 236 case ACCEPT_INVITE_MESSAGE_TYPE: { 237 NodeName realName; 238 PortName initialPort; 239 if (IPC::ReadParam(&reader, &realName) && 240 IPC::ReadParam(&reader, &initialPort)) { 241 mListener->OnAcceptInvite(mName, realName, initialPort); 242 return; 243 } 244 break; 245 } 246 // Assume all unrecognized types are intended as user event messages, and 247 // deliver them to our listener as such. This allows us to use the same type 248 // field for both internal messages and protocol messages. 249 // 250 // FIXME: Consider doing something cleaner in the future? 251 case EVENT_MESSAGE_TYPE: 252 default: { 253 #ifdef FUZZING_SNAPSHOT 254 if (!fuzzing::IPCFuzzController::instance().ObserveIPCMessage( 255 this, *aMessage)) { 256 return; 257 } 258 #endif 259 260 mListener->OnEventMessage(mName, std::move(aMessage)); 261 return; 262 } 263 } 264 265 // If we got to this point without early returning the message was malformed 266 // in some way. Report an error. 267 268 NS_WARNING("NodeChannel received a malformed message"); 269 OnChannelError(); 270 } 271 272 void NodeChannel::OnChannelConnected(base::ProcessId aPeerPid) { 273 AssertIOThread(); 274 275 SetOtherPid(aPeerPid); 276 277 // We may need to tell the GeckoChildProcessHost which we were created by that 278 // the channel has been connected to unblock completing the process launch. 279 if (mChildProcessHost) { 280 mChildProcessHost->OnChannelConnected(aPeerPid); 281 } 282 } 283 284 void NodeChannel::OnChannelError() { 285 AssertIOThread(); 286 287 State prev = mState.exchange(State::Closed); 288 if (prev == State::Closed) { 289 return; 290 } 291 292 // Clean up the channel. 293 mChannel->Close(); 294 295 // Tell our listener about the error. 296 mListener->OnChannelError(mName); 297 } 298 299 } // namespace mozilla::ipc