Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsProfileStringTypes.h"
7 #include "nsProfileLock.h"
8 #include "nsCOMPtr.h"
10 #if defined(XP_MACOSX)
11 #include <Carbon/Carbon.h>
12 #include <CoreFoundation/CoreFoundation.h>
13 #endif
15 #ifdef XP_UNIX
16 #include <unistd.h>
17 #include <fcntl.h>
18 #include <errno.h>
19 #include <signal.h>
20 #include <stdlib.h>
21 #include "prnetdb.h"
22 #include "prsystem.h"
23 #include "prprf.h"
24 #include "prenv.h"
25 #endif
27 #ifdef VMS
28 #include <rmsdef.h>
29 #endif
31 // **********************************************************************
32 // class nsProfileLock
33 //
34 // This code was moved from profile/src/nsProfileAccess.
35 // **********************************************************************
37 #if defined (XP_UNIX)
38 static bool sDisableSignalHandling = false;
39 #endif
41 nsProfileLock::nsProfileLock() :
42 mHaveLock(false),
43 mReplacedLockTime(0)
44 #if defined (XP_WIN)
45 ,mLockFileHandle(INVALID_HANDLE_VALUE)
46 #elif defined (XP_UNIX)
47 ,mPidLockFileName(nullptr)
48 ,mLockFileDesc(-1)
49 #endif
50 {
51 #if defined (XP_UNIX)
52 next = prev = this;
53 sDisableSignalHandling = PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ? true : false;
54 #endif
55 }
58 nsProfileLock::nsProfileLock(nsProfileLock& src)
59 {
60 *this = src;
61 }
64 nsProfileLock& nsProfileLock::operator=(nsProfileLock& rhs)
65 {
66 Unlock();
68 mHaveLock = rhs.mHaveLock;
69 rhs.mHaveLock = false;
71 #if defined (XP_WIN)
72 mLockFileHandle = rhs.mLockFileHandle;
73 rhs.mLockFileHandle = INVALID_HANDLE_VALUE;
74 #elif defined (XP_UNIX)
75 mLockFileDesc = rhs.mLockFileDesc;
76 rhs.mLockFileDesc = -1;
77 mPidLockFileName = rhs.mPidLockFileName;
78 rhs.mPidLockFileName = nullptr;
79 if (mPidLockFileName)
80 {
81 // rhs had a symlink lock, therefore it was on the list.
82 PR_REMOVE_LINK(&rhs);
83 PR_APPEND_LINK(this, &mPidLockList);
84 }
85 #endif
87 return *this;
88 }
91 nsProfileLock::~nsProfileLock()
92 {
93 Unlock();
94 }
97 #if defined (XP_UNIX)
99 static int setupPidLockCleanup;
101 PRCList nsProfileLock::mPidLockList =
102 PR_INIT_STATIC_CLIST(&nsProfileLock::mPidLockList);
104 void nsProfileLock::RemovePidLockFiles(bool aFatalSignal)
105 {
106 while (!PR_CLIST_IS_EMPTY(&mPidLockList))
107 {
108 nsProfileLock *lock = static_cast<nsProfileLock*>(mPidLockList.next);
109 lock->Unlock(aFatalSignal);
110 }
111 }
113 static struct sigaction SIGHUP_oldact;
114 static struct sigaction SIGINT_oldact;
115 static struct sigaction SIGQUIT_oldact;
116 static struct sigaction SIGILL_oldact;
117 static struct sigaction SIGABRT_oldact;
118 static struct sigaction SIGSEGV_oldact;
119 static struct sigaction SIGTERM_oldact;
121 void nsProfileLock::FatalSignalHandler(int signo
122 #ifdef SA_SIGINFO
123 , siginfo_t *info, void *context
124 #endif
125 )
126 {
127 // Remove any locks still held.
128 RemovePidLockFiles(true);
130 // Chain to the old handler, which may exit.
131 struct sigaction *oldact = nullptr;
133 switch (signo) {
134 case SIGHUP:
135 oldact = &SIGHUP_oldact;
136 break;
137 case SIGINT:
138 oldact = &SIGINT_oldact;
139 break;
140 case SIGQUIT:
141 oldact = &SIGQUIT_oldact;
142 break;
143 case SIGILL:
144 oldact = &SIGILL_oldact;
145 break;
146 case SIGABRT:
147 oldact = &SIGABRT_oldact;
148 break;
149 case SIGSEGV:
150 oldact = &SIGSEGV_oldact;
151 break;
152 case SIGTERM:
153 oldact = &SIGTERM_oldact;
154 break;
155 default:
156 NS_NOTREACHED("bad signo");
157 break;
158 }
160 if (oldact) {
161 if (oldact->sa_handler == SIG_DFL) {
162 // Make sure the default sig handler is executed
163 // We need it to get Mozilla to dump core.
164 sigaction(signo,oldact, nullptr);
166 // Now that we've restored the default handler, unmask the
167 // signal and invoke it.
169 sigset_t unblock_sigs;
170 sigemptyset(&unblock_sigs);
171 sigaddset(&unblock_sigs, signo);
173 sigprocmask(SIG_UNBLOCK, &unblock_sigs, nullptr);
175 raise(signo);
176 }
177 #ifdef SA_SIGINFO
178 else if (oldact->sa_sigaction &&
179 (oldact->sa_flags & SA_SIGINFO) == SA_SIGINFO) {
180 oldact->sa_sigaction(signo, info, context);
181 }
182 #endif
183 else if (oldact->sa_handler && oldact->sa_handler != SIG_IGN)
184 {
185 oldact->sa_handler(signo);
186 }
187 }
189 // Backstop exit call, just in case.
190 _exit(signo);
191 }
193 nsresult nsProfileLock::LockWithFcntl(nsIFile *aLockFile)
194 {
195 nsresult rv = NS_OK;
197 nsAutoCString lockFilePath;
198 rv = aLockFile->GetNativePath(lockFilePath);
199 if (NS_FAILED(rv)) {
200 NS_ERROR("Could not get native path");
201 return rv;
202 }
204 aLockFile->GetLastModifiedTime(&mReplacedLockTime);
206 mLockFileDesc = open(lockFilePath.get(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
207 if (mLockFileDesc != -1)
208 {
209 struct flock lock;
210 lock.l_start = 0;
211 lock.l_len = 0; // len = 0 means entire file
212 lock.l_type = F_WRLCK;
213 lock.l_whence = SEEK_SET;
215 // If fcntl(F_GETLK) fails then the server does not support/allow fcntl(),
216 // return failure rather than access denied in this case so we fallback
217 // to using a symlink lock, bug 303633.
218 struct flock testlock = lock;
219 if (fcntl(mLockFileDesc, F_GETLK, &testlock) == -1)
220 {
221 close(mLockFileDesc);
222 mLockFileDesc = -1;
223 rv = NS_ERROR_FAILURE;
224 }
225 else if (fcntl(mLockFileDesc, F_SETLK, &lock) == -1)
226 {
227 close(mLockFileDesc);
228 mLockFileDesc = -1;
230 // With OS X, on NFS, errno == ENOTSUP
231 // XXX Check for that and return specific rv for it?
232 #ifdef DEBUG
233 printf("fcntl(F_SETLK) failed. errno = %d\n", errno);
234 #endif
235 if (errno == EAGAIN || errno == EACCES)
236 rv = NS_ERROR_FILE_ACCESS_DENIED;
237 else
238 rv = NS_ERROR_FAILURE;
239 }
240 else
241 mHaveLock = true;
242 }
243 else
244 {
245 NS_ERROR("Failed to open lock file.");
246 rv = NS_ERROR_FAILURE;
247 }
248 return rv;
249 }
251 static bool IsSymlinkStaleLock(struct in_addr* aAddr, const char* aFileName,
252 bool aHaveFcntlLock)
253 {
254 // the link exists; see if it's from this machine, and if
255 // so if the process is still active
256 char buf[1024];
257 int len = readlink(aFileName, buf, sizeof buf - 1);
258 if (len > 0)
259 {
260 buf[len] = '\0';
261 char *colon = strchr(buf, ':');
262 if (colon)
263 {
264 *colon++ = '\0';
265 unsigned long addr = inet_addr(buf);
266 if (addr != (unsigned long) -1)
267 {
268 if (colon[0] == '+' && aHaveFcntlLock) {
269 // This lock was placed by a Firefox build which would have
270 // taken the fnctl lock, and we've already taken the fcntl lock,
271 // so the process that created this obsolete lock must be gone
272 return true;
273 }
275 char *after = nullptr;
276 pid_t pid = strtol(colon, &after, 0);
277 if (pid != 0 && *after == '\0')
278 {
279 if (addr != aAddr->s_addr)
280 {
281 // Remote lock: give up even if stuck.
282 return false;
283 }
285 // kill(pid,0) is a neat trick to check if a
286 // process exists
287 if (kill(pid, 0) == 0 || errno != ESRCH)
288 {
289 // Local process appears to be alive, ass-u-me it
290 // is another Mozilla instance, or a compatible
291 // derivative, that's currently using the profile.
292 // XXX need an "are you Mozilla?" protocol
293 return false;
294 }
295 }
296 }
297 }
298 }
299 return true;
300 }
302 nsresult nsProfileLock::LockWithSymlink(nsIFile *aLockFile, bool aHaveFcntlLock)
303 {
304 nsresult rv;
305 nsAutoCString lockFilePath;
306 rv = aLockFile->GetNativePath(lockFilePath);
307 if (NS_FAILED(rv)) {
308 NS_ERROR("Could not get native path");
309 return rv;
310 }
312 // don't replace an existing lock time if fcntl already got one
313 if (!mReplacedLockTime)
314 aLockFile->GetLastModifiedTimeOfLink(&mReplacedLockTime);
316 struct in_addr inaddr;
317 inaddr.s_addr = htonl(INADDR_LOOPBACK);
319 char hostname[256];
320 PRStatus status = PR_GetSystemInfo(PR_SI_HOSTNAME, hostname, sizeof hostname);
321 if (status == PR_SUCCESS)
322 {
323 char netdbbuf[PR_NETDB_BUF_SIZE];
324 PRHostEnt hostent;
325 status = PR_GetHostByName(hostname, netdbbuf, sizeof netdbbuf, &hostent);
326 if (status == PR_SUCCESS)
327 memcpy(&inaddr, hostent.h_addr, sizeof inaddr);
328 }
330 char *signature =
331 PR_smprintf("%s:%s%lu", inet_ntoa(inaddr), aHaveFcntlLock ? "+" : "",
332 (unsigned long)getpid());
333 const char *fileName = lockFilePath.get();
334 int symlink_rv, symlink_errno = 0, tries = 0;
336 // use ns4.x-compatible symlinks if the FS supports them
337 while ((symlink_rv = symlink(signature, fileName)) < 0)
338 {
339 symlink_errno = errno;
340 if (symlink_errno != EEXIST)
341 break;
343 if (!IsSymlinkStaleLock(&inaddr, fileName, aHaveFcntlLock))
344 break;
346 // Lock seems to be bogus: try to claim it. Give up after a large
347 // number of attempts (100 comes from the 4.x codebase).
348 (void) unlink(fileName);
349 if (++tries > 100)
350 break;
351 }
353 PR_smprintf_free(signature);
354 signature = nullptr;
356 if (symlink_rv == 0)
357 {
358 // We exclusively created the symlink: record its name for eventual
359 // unlock-via-unlink.
360 rv = NS_OK;
361 mHaveLock = true;
362 mPidLockFileName = strdup(fileName);
363 if (mPidLockFileName)
364 {
365 PR_APPEND_LINK(this, &mPidLockList);
366 if (!setupPidLockCleanup++)
367 {
368 // Clean up on normal termination.
369 // This instanciates a dummy class, and will trigger the class
370 // destructor when libxul is unloaded. This is equivalent to atexit(),
371 // but gracefully handles dlclose().
372 static RemovePidLockFilesExiting r;
374 // Clean up on abnormal termination, using POSIX sigaction.
375 // Don't arm a handler if the signal is being ignored, e.g.,
376 // because mozilla is run via nohup.
377 if (!sDisableSignalHandling) {
378 struct sigaction act, oldact;
379 #ifdef SA_SIGINFO
380 act.sa_sigaction = FatalSignalHandler;
381 act.sa_flags = SA_SIGINFO;
382 #else
383 act.sa_handler = FatalSignalHandler;
384 #endif
385 sigfillset(&act.sa_mask);
387 #define CATCH_SIGNAL(signame) \
388 PR_BEGIN_MACRO \
389 if (sigaction(signame, nullptr, &oldact) == 0 && \
390 oldact.sa_handler != SIG_IGN) \
391 { \
392 sigaction(signame, &act, &signame##_oldact); \
393 } \
394 PR_END_MACRO
396 CATCH_SIGNAL(SIGHUP);
397 CATCH_SIGNAL(SIGINT);
398 CATCH_SIGNAL(SIGQUIT);
399 CATCH_SIGNAL(SIGILL);
400 CATCH_SIGNAL(SIGABRT);
401 CATCH_SIGNAL(SIGSEGV);
402 CATCH_SIGNAL(SIGTERM);
404 #undef CATCH_SIGNAL
405 }
406 }
407 }
408 }
409 else if (symlink_errno == EEXIST)
410 rv = NS_ERROR_FILE_ACCESS_DENIED;
411 else
412 {
413 #ifdef DEBUG
414 printf("symlink() failed. errno = %d\n", errno);
415 #endif
416 rv = NS_ERROR_FAILURE;
417 }
418 return rv;
419 }
420 #endif /* XP_UNIX */
422 nsresult nsProfileLock::GetReplacedLockTime(PRTime *aResult) {
423 *aResult = mReplacedLockTime;
424 return NS_OK;
425 }
427 nsresult nsProfileLock::Lock(nsIFile* aProfileDir,
428 nsIProfileUnlocker* *aUnlocker)
429 {
430 #if defined (XP_MACOSX)
431 NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
432 NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "parent.lock");
433 #elif defined (XP_UNIX)
434 NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "lock");
435 NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
436 #else
437 NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, "parent.lock");
438 #endif
440 nsresult rv;
441 if (aUnlocker)
442 *aUnlocker = nullptr;
444 NS_ENSURE_STATE(!mHaveLock);
446 bool isDir;
447 rv = aProfileDir->IsDirectory(&isDir);
448 if (NS_FAILED(rv))
449 return rv;
450 if (!isDir)
451 return NS_ERROR_FILE_NOT_DIRECTORY;
453 nsCOMPtr<nsIFile> lockFile;
454 rv = aProfileDir->Clone(getter_AddRefs(lockFile));
455 if (NS_FAILED(rv))
456 return rv;
458 rv = lockFile->Append(LOCKFILE_NAME);
459 if (NS_FAILED(rv))
460 return rv;
462 #if defined(XP_MACOSX)
463 // First, try locking using fcntl. It is more reliable on
464 // a local machine, but may not be supported by an NFS server.
466 rv = LockWithFcntl(lockFile);
467 if (NS_FAILED(rv) && (rv != NS_ERROR_FILE_ACCESS_DENIED))
468 {
469 // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
470 // assume we tried an NFS that does not support it. Now, try with symlink.
471 rv = LockWithSymlink(lockFile, false);
472 }
474 if (NS_SUCCEEDED(rv))
475 {
476 // Check for the old-style lock used by pre-mozilla 1.3 builds.
477 // Those builds used an earlier check to prevent the application
478 // from launching if another instance was already running. Because
479 // of that, we don't need to create an old-style lock as well.
480 struct LockProcessInfo
481 {
482 ProcessSerialNumber psn;
483 unsigned long launchDate;
484 };
486 PRFileDesc *fd = nullptr;
487 int32_t ioBytes;
488 ProcessInfoRec processInfo;
489 LockProcessInfo lockProcessInfo;
491 rv = lockFile->SetLeafName(OLD_LOCKFILE_NAME);
492 if (NS_FAILED(rv))
493 return rv;
494 rv = lockFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
495 if (NS_SUCCEEDED(rv))
496 {
497 ioBytes = PR_Read(fd, &lockProcessInfo, sizeof(LockProcessInfo));
498 PR_Close(fd);
500 if (ioBytes == sizeof(LockProcessInfo))
501 {
502 #ifdef __LP64__
503 processInfo.processAppRef = nullptr;
504 #else
505 processInfo.processAppSpec = nullptr;
506 #endif
507 processInfo.processName = nullptr;
508 processInfo.processInfoLength = sizeof(ProcessInfoRec);
509 if (::GetProcessInformation(&lockProcessInfo.psn, &processInfo) == noErr &&
510 processInfo.processLaunchDate == lockProcessInfo.launchDate)
511 {
512 return NS_ERROR_FILE_ACCESS_DENIED;
513 }
514 }
515 else
516 {
517 NS_WARNING("Could not read lock file - ignoring lock");
518 }
519 }
520 rv = NS_OK; // Don't propagate error from OpenNSPRFileDesc.
521 }
522 #elif defined(XP_UNIX)
523 // Get the old lockfile name
524 nsCOMPtr<nsIFile> oldLockFile;
525 rv = aProfileDir->Clone(getter_AddRefs(oldLockFile));
526 if (NS_FAILED(rv))
527 return rv;
528 rv = oldLockFile->Append(OLD_LOCKFILE_NAME);
529 if (NS_FAILED(rv))
530 return rv;
532 // First, try locking using fcntl. It is more reliable on
533 // a local machine, but may not be supported by an NFS server.
534 rv = LockWithFcntl(lockFile);
535 if (NS_SUCCEEDED(rv)) {
536 // Check to see whether there is a symlink lock held by an older
537 // Firefox build, and also place our own symlink lock --- but
538 // mark it "obsolete" so that other newer builds can break the lock
539 // if they obtain the fcntl lock
540 rv = LockWithSymlink(oldLockFile, true);
542 // If the symlink failed for some reason other than it already
543 // exists, then something went wrong e.g. the file system
544 // doesn't support symlinks, or we don't have permission to
545 // create a symlink there. In such cases we should just
546 // continue because it's unlikely there is an old build
547 // running with a symlink there and we've already successfully
548 // placed a fcntl lock.
549 if (rv != NS_ERROR_FILE_ACCESS_DENIED)
550 rv = NS_OK;
551 }
552 else if (rv != NS_ERROR_FILE_ACCESS_DENIED)
553 {
554 // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
555 // assume we tried an NFS that does not support it. Now, try with symlink
556 // using the old symlink path
557 rv = LockWithSymlink(oldLockFile, false);
558 }
560 #elif defined(XP_WIN)
561 nsAutoString filePath;
562 rv = lockFile->GetPath(filePath);
563 if (NS_FAILED(rv))
564 return rv;
566 lockFile->GetLastModifiedTime(&mReplacedLockTime);
568 // always create the profile lock and never delete it so we can use its
569 // modification timestamp to detect startup crashes
570 mLockFileHandle = CreateFileW(filePath.get(),
571 GENERIC_READ | GENERIC_WRITE,
572 0, // no sharing - of course
573 nullptr,
574 CREATE_ALWAYS,
575 0,
576 nullptr);
577 if (mLockFileHandle == INVALID_HANDLE_VALUE) {
578 // XXXbsmedberg: provide a profile-unlocker here!
579 return NS_ERROR_FILE_ACCESS_DENIED;
580 }
581 #elif defined(VMS)
582 nsAutoCString filePath;
583 rv = lockFile->GetNativePath(filePath);
584 if (NS_FAILED(rv))
585 return rv;
587 lockFile->GetLastModifiedTime(&mReplacedLockTime);
589 mLockFileDesc = open_noshr(filePath.get(), O_CREAT, 0666);
590 if (mLockFileDesc == -1)
591 {
592 if ((errno == EVMSERR) && (vaxc$errno == RMS$_FLK))
593 {
594 return NS_ERROR_FILE_ACCESS_DENIED;
595 }
596 else
597 {
598 NS_ERROR("Failed to open lock file.");
599 return NS_ERROR_FAILURE;
600 }
601 }
602 #endif
604 mHaveLock = true;
606 return rv;
607 }
610 nsresult nsProfileLock::Unlock(bool aFatalSignal)
611 {
612 nsresult rv = NS_OK;
614 if (mHaveLock)
615 {
616 #if defined (XP_WIN)
617 if (mLockFileHandle != INVALID_HANDLE_VALUE)
618 {
619 CloseHandle(mLockFileHandle);
620 mLockFileHandle = INVALID_HANDLE_VALUE;
621 }
622 #elif defined (XP_UNIX)
623 if (mPidLockFileName)
624 {
625 PR_REMOVE_LINK(this);
626 (void) unlink(mPidLockFileName);
628 // Only free mPidLockFileName if we're not in the fatal signal
629 // handler. The problem is that a call to free() might be the
630 // cause of this fatal signal. If so, calling free() might cause
631 // us to wait on the malloc implementation's lock. We're already
632 // holding this lock, so we'll deadlock. See bug 522332.
633 if (!aFatalSignal)
634 free(mPidLockFileName);
635 mPidLockFileName = nullptr;
636 }
637 if (mLockFileDesc != -1)
638 {
639 close(mLockFileDesc);
640 mLockFileDesc = -1;
641 // Don't remove it
642 }
643 #endif
645 mHaveLock = false;
646 }
648 return rv;
649 }