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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "RDDProcessHost.h"
7
8 #include "mozilla/ipc/ProcessUtils.h"
9 #include "RDDChild.h"
10 #include "chrome/common/process_watcher.h"
11 #include "mozilla/Preferences.h"
12 #include "mozilla/StaticPrefs_media.h"
13
14 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
15 # include "mozilla/Sandbox.h"
16 #endif
17
18 namespace mozilla {
19
20 using namespace ipc;
21
22 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
23 bool RDDProcessHost::sLaunchWithMacSandbox = false;
24 #endif
25
RDDProcessHost(Listener * aListener)26 RDDProcessHost::RDDProcessHost(Listener* aListener)
27 : GeckoChildProcessHost(GeckoProcessType_RDD),
28 mListener(aListener),
29 mLiveToken(new media::Refcountable<bool>(true)) {
30 MOZ_COUNT_CTOR(RDDProcessHost);
31
32 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
33 if (!sLaunchWithMacSandbox) {
34 sLaunchWithMacSandbox = (PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX") == nullptr);
35 }
36 mDisableOSActivityMode = sLaunchWithMacSandbox;
37 #endif
38 }
39
~RDDProcessHost()40 RDDProcessHost::~RDDProcessHost() { MOZ_COUNT_DTOR(RDDProcessHost); }
41
Launch(StringVector aExtraOpts)42 bool RDDProcessHost::Launch(StringVector aExtraOpts) {
43 MOZ_ASSERT(NS_IsMainThread());
44
45 MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched);
46 MOZ_ASSERT(!mRDDChild);
47
48 mPrefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>();
49 if (!mPrefSerializer->SerializeToSharedMemory()) {
50 return false;
51 }
52 mPrefSerializer->AddSharedPrefCmdLineArgs(*this, aExtraOpts);
53
54 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
55 mSandboxLevel = Preferences::GetInt("security.sandbox.rdd.level");
56 #endif
57
58 mLaunchPhase = LaunchPhase::Waiting;
59 mLaunchTime = TimeStamp::Now();
60
61 int32_t timeoutMs = StaticPrefs::media_rdd_process_startup_timeout_ms();
62
63 // If one of the following environment variables are set we can
64 // effectively ignore the timeout - as we can guarantee the RDD
65 // process will be terminated
66 if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS") ||
67 PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) {
68 timeoutMs = 0;
69 }
70 if (timeoutMs) {
71 // We queue a delayed task. If that task runs before the
72 // WhenProcessHandleReady promise gets resolved, we will abort the launch.
73 GetMainThreadSerialEventTarget()->DelayedDispatch(
74 NS_NewRunnableFunction(
75 "RDDProcessHost::Launchtimeout",
76 [this, liveToken = mLiveToken]() {
77 if (!*liveToken || mTimerChecked) {
78 // We have been deleted or the runnable has already started, we
79 // can abort.
80 return;
81 }
82 InitAfterConnect(false);
83 MOZ_ASSERT(mTimerChecked,
84 "InitAfterConnect must have acted on the promise");
85 }),
86 timeoutMs);
87 }
88
89 if (!GeckoChildProcessHost::AsyncLaunch(aExtraOpts)) {
90 mLaunchPhase = LaunchPhase::Complete;
91 mPrefSerializer = nullptr;
92 return false;
93 }
94 return true;
95 }
96
LaunchPromise()97 RefPtr<GenericNonExclusivePromise> RDDProcessHost::LaunchPromise() {
98 MOZ_ASSERT(NS_IsMainThread());
99
100 if (mLaunchPromise) {
101 return mLaunchPromise;
102 }
103 mLaunchPromise = MakeRefPtr<GenericNonExclusivePromise::Private>(__func__);
104 WhenProcessHandleReady()->Then(
105 GetCurrentSerialEventTarget(), __func__,
106 [this, liveToken = mLiveToken](
107 const ipc::ProcessHandlePromise::ResolveOrRejectValue& aResult) {
108 if (!*liveToken) {
109 // The RDDProcessHost got deleted. Abort. The promise would have
110 // already been rejected.
111 return;
112 }
113 if (mTimerChecked) {
114 // We hit the timeout earlier, abort.
115 return;
116 }
117 mTimerChecked = true;
118 if (aResult.IsReject()) {
119 RejectPromise();
120 }
121 // If aResult.IsResolve() then we have succeeded in launching the
122 // RDD process. The promise will be resolved once the channel has
123 // connected (or failed to) later.
124 });
125 return mLaunchPromise;
126 }
127
OnChannelConnected(int32_t peer_pid)128 void RDDProcessHost::OnChannelConnected(int32_t peer_pid) {
129 MOZ_ASSERT(!NS_IsMainThread());
130
131 GeckoChildProcessHost::OnChannelConnected(peer_pid);
132
133 NS_DispatchToMainThread(NS_NewRunnableFunction(
134 "RDDProcessHost::OnChannelConnected", [this, liveToken = mLiveToken]() {
135 if (*liveToken && mLaunchPhase == LaunchPhase::Waiting) {
136 InitAfterConnect(true);
137 }
138 }));
139 }
140
OnChannelError()141 void RDDProcessHost::OnChannelError() {
142 MOZ_ASSERT(!NS_IsMainThread());
143
144 GeckoChildProcessHost::OnChannelError();
145
146 NS_DispatchToMainThread(NS_NewRunnableFunction(
147 "RDDProcessHost::OnChannelError", [this, liveToken = mLiveToken]() {
148 if (*liveToken && mLaunchPhase == LaunchPhase::Waiting) {
149 InitAfterConnect(false);
150 }
151 }));
152 }
153
154 static uint64_t sRDDProcessTokenCounter = 0;
155
InitAfterConnect(bool aSucceeded)156 void RDDProcessHost::InitAfterConnect(bool aSucceeded) {
157 MOZ_ASSERT(NS_IsMainThread());
158
159 MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting);
160 MOZ_ASSERT(!mRDDChild);
161
162 mLaunchPhase = LaunchPhase::Complete;
163
164 if (!aSucceeded) {
165 RejectPromise();
166 return;
167 }
168 mProcessToken = ++sRDDProcessTokenCounter;
169 mRDDChild = MakeUnique<RDDChild>(this);
170 DebugOnly<bool> rv = mRDDChild->Open(
171 TakeInitialPort(), base::GetProcId(GetChildProcessHandle()));
172 MOZ_ASSERT(rv);
173
174 // Only clear mPrefSerializer in the success case to avoid a
175 // possible race in the case case of a timeout on Windows launch.
176 // See Bug 1555076 comment 7:
177 // https://bugzilla.mozilla.org/show_bug.cgi?id=1555076#c7
178 mPrefSerializer = nullptr;
179
180 if (!mRDDChild->Init()) {
181 // Can't just kill here because it will create a timing race that
182 // will crash the tab. We don't really want to crash the tab just
183 // because RDD linux sandbox failed to initialize. In this case,
184 // we'll close the child channel which will cause the RDD process
185 // to shutdown nicely avoiding the tab crash (which manifests as
186 // Bug 1535335).
187 mRDDChild->Close();
188 RejectPromise();
189 } else {
190 ResolvePromise();
191 }
192 }
193
Shutdown()194 void RDDProcessHost::Shutdown() {
195 MOZ_ASSERT(NS_IsMainThread());
196 MOZ_ASSERT(!mShutdownRequested);
197
198 RejectPromise();
199
200 if (mRDDChild) {
201 // OnChannelClosed uses this to check if the shutdown was expected or
202 // unexpected.
203 mShutdownRequested = true;
204
205 // The channel might already be closed if we got here unexpectedly.
206 if (!mChannelClosed) {
207 mRDDChild->Close();
208 }
209
210 #ifndef NS_FREE_PERMANENT_DATA
211 // No need to communicate shutdown, the RDD process doesn't need to
212 // communicate anything back.
213 KillHard("NormalShutdown");
214 #endif
215
216 // If we're shutting down unexpectedly, we're in the middle of handling an
217 // ActorDestroy for PRDDChild, which is still on the stack. We'll return
218 // back to OnChannelClosed.
219 //
220 // Otherwise, we'll wait for OnChannelClose to be called whenever PRDDChild
221 // acknowledges shutdown.
222 return;
223 }
224
225 DestroyProcess();
226 }
227
OnChannelClosed()228 void RDDProcessHost::OnChannelClosed() {
229 MOZ_ASSERT(NS_IsMainThread());
230
231 mChannelClosed = true;
232 RejectPromise();
233
234 if (!mShutdownRequested && mListener) {
235 // This is an unclean shutdown. Notify our listener that we're going away.
236 mListener->OnProcessUnexpectedShutdown(this);
237 } else {
238 DestroyProcess();
239 }
240
241 // Release the actor.
242 RDDChild::Destroy(std::move(mRDDChild));
243 }
244
KillHard(const char * aReason)245 void RDDProcessHost::KillHard(const char* aReason) {
246 MOZ_ASSERT(NS_IsMainThread());
247
248 ProcessHandle handle = GetChildProcessHandle();
249 if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER, false)) {
250 NS_WARNING("failed to kill subprocess!");
251 }
252
253 SetAlreadyDead();
254 }
255
GetProcessToken() const256 uint64_t RDDProcessHost::GetProcessToken() const {
257 MOZ_ASSERT(NS_IsMainThread());
258 return mProcessToken;
259 }
260
DestroyProcess()261 void RDDProcessHost::DestroyProcess() {
262 MOZ_ASSERT(NS_IsMainThread());
263 RejectPromise();
264
265 // Any pending tasks will be cancelled from now on.
266 *mLiveToken = false;
267
268 NS_DispatchToMainThread(
269 NS_NewRunnableFunction("DestroyProcessRunnable", [this] { Destroy(); }));
270 }
271
ResolvePromise()272 void RDDProcessHost::ResolvePromise() {
273 MOZ_ASSERT(NS_IsMainThread());
274
275 if (!mLaunchPromiseSettled) {
276 mLaunchPromise->Resolve(true, __func__);
277 mLaunchPromiseSettled = true;
278 }
279 // We have already acted on the promise; the timeout runnable no longer needs
280 // to interrupt anything.
281 mTimerChecked = true;
282 }
283
RejectPromise()284 void RDDProcessHost::RejectPromise() {
285 MOZ_ASSERT(NS_IsMainThread());
286
287 if (!mLaunchPromiseSettled) {
288 mLaunchPromise->Reject(NS_ERROR_FAILURE, __func__);
289 mLaunchPromiseSettled = true;
290 }
291 // We have already acted on the promise; the timeout runnable no longer needs
292 // to interrupt anything.
293 mTimerChecked = true;
294 }
295
296 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
FillMacSandboxInfo(MacSandboxInfo & aInfo)297 bool RDDProcessHost::FillMacSandboxInfo(MacSandboxInfo& aInfo) {
298 GeckoChildProcessHost::FillMacSandboxInfo(aInfo);
299 if (!aInfo.shouldLog && PR_GetEnv("MOZ_SANDBOX_RDD_LOGGING")) {
300 aInfo.shouldLog = true;
301 }
302 return true;
303 }
304
305 /* static */
GetMacSandboxType()306 MacSandboxType RDDProcessHost::GetMacSandboxType() {
307 return MacSandboxType_RDD;
308 }
309 #endif
310
311 } // namespace mozilla
312