Bug 1607297 - Don't include file contents from the firefox install directory in recordings, r=jlast.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 08 Jan 2020 12:46:19 +0000
changeset 509356 0330a82604e89cebd09eeabc261770b0bde070cc
parent 509355 c26be29b4ae824f0edfc6b1d1694f24781accea4
child 509357 9902221ac1749e0a1ca7d434d249f72b85129da0
push id104577
push userbhackett@mozilla.com
push dateWed, 08 Jan 2020 12:47:00 +0000
treeherderautoland@0330a82604e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlast
bugs1607297
milestone74.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1607297 - Don't include file contents from the firefox install directory in recordings, r=jlast. Differential Revision: https://phabricator.services.mozilla.com/D58874
devtools/server/actors/replay/control.js
toolkit/recordreplay/ProcessRecordReplay.cpp
toolkit/recordreplay/ProcessRecordReplay.h
toolkit/recordreplay/ProcessRedirectDarwin.cpp
--- a/devtools/server/actors/replay/control.js
+++ b/devtools/server/actors/replay/control.js
@@ -357,16 +357,17 @@ ChildProcess.prototype = {
       RecordReplayControl.crashHangedChild(this.rootId, this.forkId);
 
       // Treat the child as crashed even if we aren't able to get it to crash.
       ChildCrashed(this.rootId, this.forkId);
     } else {
       const id = gNextPingId++;
       RecordReplayControl.ping(this.rootId, this.forkId, id);
       this.pings.push({ id });
+      this.lastPingTime = now;
     }
   },
 
   pingResponse(id, progress) {
     for (const entry of this.pings) {
       if (entry.id == id) {
         entry.progress = progress;
         break;
--- a/toolkit/recordreplay/ProcessRecordReplay.cpp
+++ b/toolkit/recordreplay/ProcessRecordReplay.cpp
@@ -59,16 +59,24 @@ static int gRecordingPid;
 static bool gSpewEnabled;
 
 // Whether this is the main child.
 static bool gMainChild;
 
 // Whether we are replaying on a cloud machine.
 static bool gReplayingInCloud;
 
+// Firefox installation directory.
+static char* gInstallDirectory;
+
+// Location within the installation directory where the current executable
+// should be running.
+static const char gExecutableSuffix[] =
+    "Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container";
+
 static void InitializeCrashDetector();
 
 extern "C" {
 
 MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) {
   // Parse command line options for the process kind and recording file.
   Maybe<ProcessKind> processKind;
   Maybe<char*> recordingFile;
@@ -126,16 +134,30 @@ MOZ_EXPORT void RecordReplayInterface_In
 
   InitializeRedirections();
 
   if (!IsRecordingOrReplaying()) {
     InitializeExternalCalls();
     return;
   }
 
+  // Try to determine the install directory from the current executable path.
+  const char* executable = aArgv[0];
+  size_t executableLength = strlen(executable);
+  size_t suffixLength = strlen(gExecutableSuffix);
+  if (executableLength >= suffixLength) {
+    const char* suffixStart = executable + executableLength - suffixLength;
+    if (!strcmp(suffixStart, gExecutableSuffix)) {
+      size_t directoryLength = suffixStart - executable;
+      gInstallDirectory = new char[directoryLength + 1];
+      memcpy(gInstallDirectory, executable, directoryLength);
+      gInstallDirectory[directoryLength] = 0;
+    }
+  }
+
   InitializeCurrentTime();
 
   gRecording = new Recording();
 
   ApplyLibraryRedirections(nullptr);
 
   Thread::InitializeThreads();
 
@@ -318,16 +340,17 @@ const char* ThreadEventName(ThreadEvent 
 
 int GetRecordingPid() { return gRecordingPid; }
 
 void ResetPid() { gPid = getpid(); }
 
 bool IsMainChild() { return gMainChild; }
 void SetMainChild() { gMainChild = true; }
 bool ReplayingInCloud() { return gReplayingInCloud; }
+const char* InstallDirectory() { return gInstallDirectory; }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Record/Replay Assertions
 ///////////////////////////////////////////////////////////////////////////////
 
 extern "C" {
 
 MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayAssert(
--- a/toolkit/recordreplay/ProcessRecordReplay.h
+++ b/toolkit/recordreplay/ProcessRecordReplay.h
@@ -104,16 +104,19 @@ size_t RecordingEndpoint();
 // Access the flag for whether this is the main child. The main child never
 // rewinds and sends graphics updates to the middleman while running forward.
 bool IsMainChild();
 void SetMainChild();
 
 // Whether we are replaying a recording on a machine in the cloud.
 bool ReplayingInCloud();
 
+// Location of the firefox install directory.
+const char* InstallDirectory();
+
 // Get the process kind and recording file specified at the command line.
 // These are available in the middleman as well as while recording/replaying.
 extern ProcessKind gProcessKind;
 extern char* gRecordingFilename;
 
 ///////////////////////////////////////////////////////////////////////////////
 // Helper Functions
 ///////////////////////////////////////////////////////////////////////////////
--- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp
+++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp
@@ -431,16 +431,68 @@ static PreambleResult MiddlemanPreamble_
   auto msg = aArguments->Arg<1, msghdr*>();
   for (int i = 0; i < msg->msg_iovlen; i++) {
     totalSize += msg->msg_iov[i].iov_len;
   }
   aArguments->Rval<size_t>() = totalSize;
   return PreambleResult::Veto;
 }
 
+// When recording, this tracks open file descriptors referring to files within
+// the installation directory.
+struct InstallDirectoryFd {
+  size_t mFd;
+  UniquePtr<char[]> mSuffix;
+
+  InstallDirectoryFd(size_t aFd, UniquePtr<char[]> aSuffix)
+      : mFd(aFd), mSuffix(std::move(aSuffix)) {}
+};
+static StaticInfallibleVector<InstallDirectoryFd> gInstallDirectoryFds;
+static SpinLock gInstallDirectoryFdsLock;
+
+static void RR_open(Stream& aEvents, CallArguments* aArguments,
+                    ErrorType* aError) {
+  RR_SaveRvalHadErrorNegative(aEvents, aArguments, aError);
+  auto fd = aArguments->Rval<ssize_t>();
+
+  // Keep track of which fds refer to a file within the install directory,
+  // in case they are mmap'ed later.
+  if (IsRecording() && fd >= 0) {
+    auto path = aArguments->Arg<0, const char*>();
+    const char* installDirectory = InstallDirectory();
+    if (installDirectory) {
+      size_t installLength = strlen(installDirectory);
+      if (!strncmp(installDirectory, path, installLength)) {
+        size_t pathLength = strlen(path);
+        UniquePtr<char[]> suffix(new char[pathLength - installLength + 1]);
+        strcpy(suffix.get(), path + installLength);
+
+        AutoSpinLock lock(gInstallDirectoryFdsLock);
+        gInstallDirectoryFds.emplaceBack(fd, std::move(suffix));
+      }
+    }
+  }
+}
+
+static void RR_close(Stream& aEvents, CallArguments* aArguments,
+                    ErrorType* aError) {
+  RR_SaveRvalHadErrorNegative(aEvents, aArguments, aError);
+
+  if (IsRecording()) {
+    auto fd = aArguments->Arg<0, size_t>();
+    AutoSpinLock lock(gInstallDirectoryFdsLock);
+    for (auto& info : gInstallDirectoryFds) {
+      if (info.mFd == fd) {
+        gInstallDirectoryFds.erase(&info);
+        break;
+      }
+    }
+  }
+}
+
 static PreambleResult Preamble_mmap(CallArguments* aArguments) {
   auto& address = aArguments->Arg<0, void*>();
   auto& size = aArguments->Arg<1, size_t>();
   auto& prot = aArguments->Arg<2, size_t>();
   auto& flags = aArguments->Arg<3, size_t>();
   auto& fd = aArguments->Arg<4, size_t>();
   auto& offset = aArguments->Arg<5, size_t>();
 
@@ -458,19 +510,67 @@ static PreambleResult Preamble_mmap(Call
     flags &= ~MAP_SHARED;
     flags |= MAP_PRIVATE;
   }
 
   void* memory = CallFunction<void*>(gOriginal_mmap, address, size, prot, flags,
                                      fd, offset);
 
   if (mappingFile) {
-    // Include the data just mapped in the recording.
-    MOZ_RELEASE_ASSERT(memory && memory != (void*)-1);
-    RecordReplayBytes(memory, size);
+    // When replaying, we need to be able to recover the file contents.
+    // If the file descriptor is associated with a file in the install
+    // directory, we only need to include its suffix and a hash.
+    // Otherwise include all contents of the mapped file.
+    MOZ_RELEASE_ASSERT(memory != MAP_FAILED);
+
+    if (IsRecording()) {
+      AutoSpinLock lock(gInstallDirectoryFdsLock);
+      bool found = false;
+      for (const auto& info : gInstallDirectoryFds) {
+        if (info.mFd == fd) {
+          size_t len = strlen(info.mSuffix.get()) + 1;
+          RecordReplayValue(len);
+          RecordReplayBytes(info.mSuffix.get(), len);
+          RecordReplayValue(HashBytes(memory, size));
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        RecordReplayValue(0);
+        RecordReplayBytes(memory, size);
+      }
+    } else {
+      size_t len = RecordReplayValue(0);
+      if (len) {
+        const char* installDirectory = InstallDirectory();
+        MOZ_RELEASE_ASSERT(installDirectory);
+
+        size_t installLength = strlen(installDirectory);
+
+        UniquePtr<char[]> path(new char[installLength + len]);
+        strcpy(path.get(), installDirectory);
+        RecordReplayBytes(path.get() + installLength, len);
+        size_t hash = RecordReplayValue(0);
+
+        int fd = DirectOpenFile(path.get(), /* aWriting */ false);
+        void* fileContents = CallFunction<void*>(gOriginal_mmap, nullptr, size,
+                                                 PROT_READ, MAP_PRIVATE,
+                                                 fd, offset);
+        MOZ_RELEASE_ASSERT(fileContents != MAP_FAILED);
+
+        memcpy(memory, fileContents, size);
+        MOZ_RELEASE_ASSERT(hash == HashBytes(memory, size));
+
+        munmap(fileContents, size);
+        DirectCloseFile(fd);
+      } else {
+        RecordReplayBytes(memory, size);
+      }
+    }
   }
 
   aArguments->Rval<void*>() = memory;
   return PreambleResult::Veto;
 }
 
 static PreambleResult MiddlemanPreamble_write(CallArguments* aArguments) {
   // Silently pretend that writes succeed after diverging from the recording.
@@ -2054,30 +2154,30 @@ static SystemRedirection gSystemRedirect
     {"read", RR_SaveRvalHadErrorNegative<RR_WriteBufferViaRval<1, 2>>, nullptr,
      nullptr, Preamble_SetError<EIO>},
     {"__read_nocancel",
      RR_SaveRvalHadErrorNegative<RR_WriteBufferViaRval<1, 2>>},
     {"pread", RR_SaveRvalHadErrorNegative<RR_WriteBufferViaRval<1, 2>>},
     {"write", RR_SaveRvalHadErrorNegative, nullptr, nullptr,
      MiddlemanPreamble_write},
     {"__write_nocancel", RR_SaveRvalHadErrorNegative},
-    {"open", RR_SaveRvalHadErrorNegative},
+    {"open", RR_open},
     {"__open_nocancel", RR_SaveRvalHadErrorNegative},
     {"recv", RR_SaveRvalHadErrorNegative<RR_WriteBufferViaRval<1, 2>>},
     {"recvmsg", RR_SaveRvalHadErrorNegative<RR_recvmsg>, nullptr, nullptr,
      Preamble_WaitForever},
     {"sendmsg", RR_SaveRvalHadErrorNegative, nullptr, nullptr,
      MiddlemanPreamble_sendmsg},
     {"shm_open", RR_SaveRvalHadErrorNegative},
     {"socket", RR_SaveRvalHadErrorNegative},
     {"kqueue", RR_SaveRvalHadErrorNegative},
     {"pipe",
      RR_SaveRvalHadErrorNegative<RR_WriteBufferFixedSize<0, 2 * sizeof(int)>>,
      nullptr, nullptr, Preamble_SetError},
-    {"close", RR_SaveRvalHadErrorNegative, nullptr, nullptr, Preamble_Veto<0>},
+    {"close", RR_close, nullptr, nullptr, Preamble_Veto<0>},
     {"__close_nocancel", RR_SaveRvalHadErrorNegative},
     {"mkdir", RR_SaveRvalHadErrorNegative},
     {"dup", RR_SaveRvalHadErrorNegative},
     {"access", RR_SaveRvalHadErrorNegative, nullptr, nullptr,
      Preamble_SetError<EACCES>},
     {"lseek", RR_SaveRvalHadErrorNegative},
     {"select$DARWIN_EXTSN",
      RR_SaveRvalHadErrorNegative<RR_Compose<RR_OutParam<1, fd_set>,