profile/dirserviceprovider/src/nsProfileLock.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

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 }

mercurial