Bug 1500805 Part 4 - Watch for bogus pointers when checking for constant compile time strings, r=mccr8.
authorBrian Hackett <bhackett1024@gmail.com>
Sun, 21 Oct 2018 15:04:18 -0600
changeset 491289 9f6bb5ca3b3718a2e074077210fc60c6f5b9d0e8
parent 491288 ac631ceaa2b95d5c44f470cffba8bd1b339b0403
child 491290 22e55baef1fc05c63ad96694f1b8c5915fd16e0d
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersmccr8
bugs1500805
milestone65.0a1
Bug 1500805 Part 4 - Watch for bogus pointers when checking for constant compile time strings, r=mccr8.
toolkit/recordreplay/MemorySnapshot.cpp
toolkit/recordreplay/MemorySnapshot.h
toolkit/recordreplay/ProcessRedirectDarwin.cpp
--- a/toolkit/recordreplay/MemorySnapshot.cpp
+++ b/toolkit/recordreplay/MemorySnapshot.cpp
@@ -657,16 +657,27 @@ HandleDirtyMemoryFault(uint8_t* aAddress
   // it so that execution can proceed.
   uint8_t* original = AllocatePageCopy();
   MemoryMove(original, aAddress, PageSize);
   gMemoryInfo->mActiveDirty.insert(aAddress, DirtyPage(aAddress, original, executable));
   DirectUnprotectMemory(aAddress, PageSize, executable);
   return true;
 }
 
+bool
+MemoryRangeIsTracked(void* aAddress, size_t aSize)
+{
+  for (uint8_t* ptr = PageBase(aAddress); ptr < (uint8_t*) aAddress + aSize; ptr += PageSize) {
+    if (!IsTrackedAddress(ptr, nullptr)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 void
 UnrecoverableSnapshotFailure()
 {
   if (gMemoryInfo) {
     AutoSpinLock lock(gMemoryInfo->mTrackedRegionsLock);
     DirectUnprotectMemory(PageBase(&errno), PageSize, false);
     for (auto region : gMemoryInfo->mTrackedRegionsByAllocationOrder) {
       DirectUnprotectMemory(region.mBase, region.mSize, region.mExecutable,
--- a/toolkit/recordreplay/MemorySnapshot.h
+++ b/toolkit/recordreplay/MemorySnapshot.h
@@ -45,16 +45,21 @@ void* AllocateMemoryTryAddress(void* aAd
 // Note a range of memory that was just allocated from the system, and the
 // kind of memory allocation that was performed.
 void RegisterAllocatedMemory(void* aBaseAddress, size_t aSize, MemoryKind aKind);
 
 // Exclude a region of memory from snapshots, before the first checkpoint has
 // been reached.
 void AddInitialUntrackedMemoryRegion(uint8_t* aBase, size_t aSize);
 
+// Return whether a range of memory is in a tracked region. This excludes
+// memory that was allocated after the last checkpoint and is not write
+// protected.
+bool MemoryRangeIsTracked(void* aAddress, size_t aSize);
+
 // Initialize the memory snapshots system.
 void InitializeMemorySnapshots();
 
 // Take the first heap memory snapshot.
 void TakeFirstMemorySnapshot();
 
 // Take a differential heap memory snapshot compared to the last one,
 // associated with the last saved checkpoint.
--- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp
+++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp
@@ -690,37 +690,31 @@ ReplayInvokeCallback(size_t aCallbackId)
     MOZ_CRASH();
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Middleman Call Helpers
 ///////////////////////////////////////////////////////////////////////////////
 
-static bool
-TestObjCObjectClass(id aObj, const char* aName)
-{
-  Class cls = object_getClass(aObj);
-  while (cls) {
-    const char* className = class_getName(cls);
-    if (!strcmp(className, aName)) {
-      return true;
-    }
-    cls = class_getSuperclass(cls);
-  }
-  return false;
-}
-
 // Inputs that originate from static data in the replaying process itself
 // rather than from previous middleman calls.
 enum class ObjCInputKind {
   StaticClass,
   ConstantString,
 };
 
+// Internal layout of a constant compile time CFStringRef.
+struct CFConstantString {
+  Class mClass;
+  size_t mStuff;
+  char* mData;
+  size_t mLength;
+};
+
 // Capture an Objective C or CoreFoundation input to a call, which may come
 // either from another middleman call, or from static data in the replaying
 // process.
 static void
 Middleman_ObjCInput(MiddlemanCallContext& aCx, id* aThingPtr)
 {
   MOZ_RELEASE_ASSERT(aCx.AccessPreface());
 
@@ -754,32 +748,33 @@ Middleman_ObjCInput(MiddlemanCallContext
         size_t len = strlen(className) + 1;
         aCx.WriteInputScalar(len);
         aCx.WriteInputBytes(className, len);
         return;
       }
     }
 
     // Watch for constant compile time strings baked into the generated code or
-    // stored in system libraries. We can crash here if the object came from
-    // e.g. a replayed pointer from the recording, as can happen if not enough
-    // redirections have middleman call hooks. We could do better here to make
-    // sure the pointer looks like it could be a constant string, but it seems
-    // better and simpler to crash more reliably here than mask problems due to
-    // missing middleman call hooks.
-    if (TestObjCObjectClass(*aThingPtr, "NSString")) {
-      AutoPassThroughThreadEvents pt;
-      CFIndex len = CFStringGetLength((CFStringRef)*aThingPtr);
-      InfallibleVector<UniChar> buffer;
-      buffer.appendN(0, len);
-      CFStringGetCharacters((CFStringRef)*aThingPtr, { 0, len }, buffer.begin());
-      aCx.WriteInputScalar((size_t) ObjCInputKind::ConstantString);
-      aCx.WriteInputScalar(len);
-      aCx.WriteInputBytes(buffer.begin(), len * sizeof(UniChar));
-      return;
+    // stored in system libraries. Be careful when accessing the pointer as in
+    // the case where a middleman call hook for a function is missing the
+    // pointer could have originated from the recording and its address may not
+    // be mapped. In this case we would rather gracefully recover and fail to
+    // paint, instead of crashing.
+    if (MemoryRangeIsTracked(*aThingPtr, sizeof(CFConstantString))) {
+      CFConstantString* str = (CFConstantString*) *aThingPtr;
+      if (str->mClass == objc_lookUpClass("__NSCFConstantString") &&
+          str->mLength <= 4096 && // Sanity check.
+          MemoryRangeIsTracked(str->mData, str->mLength)) {
+        InfallibleVector<UniChar> buffer;
+        NS_ConvertUTF8toUTF16 converted(str->mData, str->mLength);
+        aCx.WriteInputScalar((size_t) ObjCInputKind::ConstantString);
+        aCx.WriteInputScalar(str->mLength);
+        aCx.WriteInputBytes(converted.get(), str->mLength * sizeof(UniChar));
+        return;
+      }
     }
 
     aCx.MarkAsFailed();
     return;
   }
 
   switch ((ObjCInputKind) aCx.ReadInputScalar()) {
   case ObjCInputKind::StaticClass: {