Fix allocations escaping malloc debug.

When using a FILE object for some malloc debug functions, calling
fprintf will trigger an allocation to be put in the object. The problem
is that these allocations were not allocated by the malloc debug
wrapper and they get freed during the fclose as if they are malloc
debug allocation. In most cases, the code will detect the bad pointer
and leak the memory, but it might also cause a crash.

The fix is to avoid using fprintf so that no allocations are made
in the object that survive and need to be freed in the fclose call.

Change the MallocXmlElem.h to use a file decsriptor not a FILE object.

Add new unit and system tests to detect this case.

Bug: 143742907

Test: Ran unit and system tests.
Test: Ran bionic unit tests.
Change-Id: I524392de822a29483aa5be8f14c680e70033eba2
diff --git a/libc/malloc_debug/PointerData.cpp b/libc/malloc_debug/PointerData.cpp
index ec7e42d..b1e28b7 100644
--- a/libc/malloc_debug/PointerData.cpp
+++ b/libc/malloc_debug/PointerData.cpp
@@ -554,7 +554,7 @@
   return pointers_.count(pointer) != 0;
 }
 
-void PointerData::DumpLiveToFile(FILE* fp) {
+void PointerData::DumpLiveToFile(int fd) {
   std::vector<ListInfoType> list;
 
   std::lock_guard<std::mutex> pointer_guard(pointer_mutex_);
@@ -566,13 +566,13 @@
     total_memory += info.size * info.num_allocations;
   }
 
-  fprintf(fp, "Total memory: %zu\n", total_memory);
-  fprintf(fp, "Allocation records: %zd\n", list.size());
-  fprintf(fp, "Backtrace size: %zu\n", g_debug->config().backtrace_frames());
-  fprintf(fp, "\n");
+  dprintf(fd, "Total memory: %zu\n", total_memory);
+  dprintf(fd, "Allocation records: %zd\n", list.size());
+  dprintf(fd, "Backtrace size: %zu\n", g_debug->config().backtrace_frames());
+  dprintf(fd, "\n");
 
   for (const auto& info : list) {
-    fprintf(fp, "z %d  sz %8zu  num    %zu  bt", (info.zygote_child_alloc) ? 1 : 0, info.size,
+    dprintf(fd, "z %d  sz %8zu  num    %zu  bt", (info.zygote_child_alloc) ? 1 : 0, info.size,
             info.num_allocations);
     FrameInfoType* frame_info = info.frame_info;
     if (frame_info != nullptr) {
@@ -580,22 +580,22 @@
         if (frame_info->frames[i] == 0) {
           break;
         }
-        fprintf(fp, " %" PRIxPTR, frame_info->frames[i]);
+        dprintf(fd, " %" PRIxPTR, frame_info->frames[i]);
       }
     }
-    fprintf(fp, "\n");
+    dprintf(fd, "\n");
     if (info.backtrace_info != nullptr) {
-      fprintf(fp, "  bt_info");
+      dprintf(fd, "  bt_info");
       for (const auto& frame : *info.backtrace_info) {
-        fprintf(fp, " {");
+        dprintf(fd, " {");
         if (frame.map_info != nullptr && !frame.map_info->name.empty()) {
-          fprintf(fp, "\"%s\"", frame.map_info->name.c_str());
+          dprintf(fd, "\"%s\"", frame.map_info->name.c_str());
         } else {
-          fprintf(fp, "\"\"");
+          dprintf(fd, "\"\"");
         }
-        fprintf(fp, " %" PRIx64, frame.rel_pc);
+        dprintf(fd, " %" PRIx64, frame.rel_pc);
         if (frame.function_name.empty()) {
-          fprintf(fp, " \"\" 0}");
+          dprintf(fd, " \"\" 0}");
         } else {
           char* demangled_name = __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr,
                                                 nullptr);
@@ -605,11 +605,11 @@
           } else {
             name = frame.function_name.c_str();
           }
-          fprintf(fp, " \"%s\" %" PRIx64 "}", name, frame.function_offset);
+          dprintf(fd, " \"%s\" %" PRIx64 "}", name, frame.function_offset);
           free(demangled_name);
         }
       }
-      fprintf(fp, "\n");
+      dprintf(fd, "\n");
     }
   }
 }
diff --git a/libc/malloc_debug/PointerData.h b/libc/malloc_debug/PointerData.h
index c7958f3..78f0ed8 100644
--- a/libc/malloc_debug/PointerData.h
+++ b/libc/malloc_debug/PointerData.h
@@ -152,7 +152,7 @@
 
   static void GetAllocList(std::vector<ListInfoType>* list);
   static void LogLeaks();
-  static void DumpLiveToFile(FILE* fp);
+  static void DumpLiveToFile(int fd);
 
   static void GetInfo(uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory,
                       size_t* backtrace_size);
diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp
index c030d54..3c0e630 100644
--- a/libc/malloc_debug/malloc_debug.cpp
+++ b/libc/malloc_debug/malloc_debug.cpp
@@ -785,16 +785,23 @@
   if (DebugCallsDisabled() || !g_debug->TrackPointers()) {
     return g_dispatch->malloc_info(options, fp);
   }
+
+  // Make sure any pending output is written to the file.
+  fflush(fp);
+
   ScopedConcurrentLock lock;
   ScopedDisableDebugCalls disable;
 
-  MallocXmlElem root(fp, "malloc", "version=\"debug-malloc-1\"");
+  // Avoid any issues where allocations are made that will be freed
+  // in the fclose.
+  int fd = fileno(fp);
+  MallocXmlElem root(fd, "malloc", "version=\"debug-malloc-1\"");
   std::vector<ListInfoType> list;
   PointerData::GetAllocList(&list);
 
   size_t alloc_num = 0;
   for (size_t i = 0; i < list.size(); i++) {
-    MallocXmlElem alloc(fp, "allocation", "nr=\"%zu\"", alloc_num);
+    MallocXmlElem alloc(fd, "allocation", "nr=\"%zu\"", alloc_num);
 
     size_t total = 1;
     size_t size = list[i].size;
@@ -802,8 +809,8 @@
       i++;
       total++;
     }
-    MallocXmlElem(fp, "size").Contents("%zu", list[i].size);
-    MallocXmlElem(fp, "total").Contents("%zu", total);
+    MallocXmlElem(fd, "size").Contents("%zu", list[i].size);
+    MallocXmlElem(fd, "total").Contents("%zu", total);
     alloc_num++;
   }
   return 0;
@@ -905,25 +912,28 @@
 
 static std::mutex g_dump_lock;
 
-static void write_dump(FILE* fp) {
-  fprintf(fp, "Android Native Heap Dump v1.2\n\n");
+static void write_dump(int fd) {
+  dprintf(fd, "Android Native Heap Dump v1.2\n\n");
 
   std::string fingerprint = android::base::GetProperty("ro.build.fingerprint", "unknown");
-  fprintf(fp, "Build fingerprint: '%s'\n\n", fingerprint.c_str());
+  dprintf(fd, "Build fingerprint: '%s'\n\n", fingerprint.c_str());
 
-  PointerData::DumpLiveToFile(fp);
+  PointerData::DumpLiveToFile(fd);
 
-  fprintf(fp, "MAPS\n");
+  dprintf(fd, "MAPS\n");
   std::string content;
   if (!android::base::ReadFileToString("/proc/self/maps", &content)) {
-    fprintf(fp, "Could not open /proc/self/maps\n");
+    dprintf(fd, "Could not open /proc/self/maps\n");
   } else {
-    fprintf(fp, "%s", content.c_str());
+    dprintf(fd, "%s", content.c_str());
   }
-  fprintf(fp, "END\n");
+  dprintf(fd, "END\n");
 }
 
 bool debug_write_malloc_leak_info(FILE* fp) {
+  // Make sure any pending output is written to the file.
+  fflush(fp);
+
   ScopedConcurrentLock lock;
   ScopedDisableDebugCalls disable;
 
@@ -933,7 +943,8 @@
     return false;
   }
 
-  write_dump(fp);
+  write_dump(fileno(fp));
+
   return true;
 }
 
@@ -943,13 +954,13 @@
 
   std::lock_guard<std::mutex> guard(g_dump_lock);
 
-  FILE* fp = fopen(file_name, "w+e");
-  if (fp == nullptr) {
+  int fd = open(file_name, O_RDWR | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0644);
+  if (fd == -1) {
     error_log("Unable to create file: %s", file_name);
     return;
   }
 
   error_log("Dumping to file: %s\n", file_name);
-  write_dump(fp);
-  fclose(fp);
+  write_dump(fd);
+  close(fd);
 }
diff --git a/libc/malloc_debug/tests/malloc_debug_system_tests.cpp b/libc/malloc_debug/tests/malloc_debug_system_tests.cpp
index 0716758..67bb8d9 100644
--- a/libc/malloc_debug/tests/malloc_debug_system_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_system_tests.cpp
@@ -37,6 +37,7 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
@@ -174,6 +175,7 @@
 }
 
 static void FindStrings(pid_t pid, std::vector<const char*> match_strings,
+                        std::vector<const char*> no_match_strings = std::vector<const char*>{},
                         time_t timeout_seconds = kTimeoutSeconds) {
   std::string log_str;
   time_t start = time(nullptr);
@@ -181,12 +183,18 @@
   while (true) {
     GetLogStr(pid, &log_str);
     found_all = true;
+    // Look for the expected strings.
     for (auto str : match_strings) {
       if (log_str.find(str) == std::string::npos) {
         found_all = false;
         break;
       }
     }
+
+    // Verify the unexpected strings are not present.
+    for (auto str : no_match_strings) {
+      ASSERT_TRUE(log_str.find(str) == std::string::npos) << "Unexpectedly found '" << str << "' in log output:\n" << log_str;
+    }
     if (found_all) {
       return;
     }
@@ -194,7 +202,7 @@
       break;
     }
   }
-  ASSERT_TRUE(found_all) << "Didn't find expected log output:\n" + log_str;
+  ASSERT_TRUE(found_all) << "Didn't find expected log output:\n" << log_str;
 }
 
 TEST(MallocTests, DISABLED_smoke) {}
@@ -464,3 +472,45 @@
         << "Found crash in log.\nLog message: " << log_str;
   }
 }
+
+TEST(MallocTests, DISABLED_write_leak_info) {
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+
+  FILE* fp = fdopen(tf.fd, "w+");
+  if (fp == nullptr) {
+    printf("Unable to create %s\n", tf.path);
+    _exit(1);
+  }
+  tf.release();
+
+  void* ptr = malloc(1000);
+  if (ptr == nullptr) {
+    printf("malloc failed\n");
+    _exit(1);
+  }
+  memset(ptr, 0, 1000);
+
+  android_mallopt(M_WRITE_MALLOC_LEAK_INFO_TO_FILE, fp, sizeof(fp));
+
+  fclose(fp);
+
+  free(ptr);
+}
+
+TEST(MallocDebugSystemTest, write_leak_info_no_header) {
+  pid_t pid;
+  ASSERT_NO_FATAL_FAILURE(Exec("MallocTests.DISABLED_write_leak_info", "verbose backtrace", &pid, 0));
+
+  ASSERT_NO_FATAL_FAILURE(FindStrings(pid, std::vector<const char*>{"malloc debug enabled"},
+
+                          std::vector<const char*>{" HAS INVALID TAG ", "USED AFTER FREE ", "UNKNOWN POINTER "}));
+}
+
+TEST(MallocDebugSystemTest, write_leak_info_header) {
+  pid_t pid;
+  ASSERT_NO_FATAL_FAILURE(Exec("MallocTests.DISABLED_write_leak_info", "verbose backtrace guard", &pid, 0));
+
+  ASSERT_NO_FATAL_FAILURE(FindStrings(pid, std::vector<const char*>{"malloc debug enabled"},
+                          std::vector<const char*>{" HAS INVALID TAG ", "USED AFTER FREE ", "UNKNOWN POINTER "}));
+}
diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
index 0238d10..70457b9 100644
--- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
@@ -27,6 +27,8 @@
 
 #include <algorithm>
 #include <memory>
+#include <string>
+#include <string_view>
 #include <thread>
 #include <vector>
 #include <utility>
@@ -73,6 +75,9 @@
 void* debug_valloc(size_t);
 #endif
 
+bool debug_write_malloc_leak_info(FILE*);
+void debug_dump_heap(const char*);
+
 __END_DECLS
 
 constexpr char DIVIDER[] =
@@ -1302,6 +1307,10 @@
 }
 
 static std::string SanitizeHeapData(const std::string& data) {
+  if (data.empty()) {
+    return data;
+  }
+
   // Remove the map data since it's not consistent.
   std::string sanitized;
   bool skip_map_data = false;
@@ -1329,7 +1338,7 @@
       sanitized += line + '\n';
     }
   }
-  return sanitized;
+  return android::base::Trim(sanitized);
 }
 
 void MallocDebugTest::BacktraceDumpOnSignal(bool trigger_with_alloc) {
@@ -1402,9 +1411,7 @@
 z 1  sz       40  num    1  bt 300 400
 MAPS
 MAP_DATA
-END
-
-)";
+END)";
   ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual;
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -1463,9 +1470,7 @@
 z 0  sz      300  num    1  bt 100 200
 MAPS
 MAP_DATA
-END
-
-)";
+END)";
   ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual;
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -1513,9 +1518,7 @@
 z 0  sz      300  num    2  bt 100 200
 MAPS
 MAP_DATA
-END
-
-)";
+END)";
   ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual;
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -1575,9 +1578,7 @@
   bt_info {"" 100 "fake1" a} {"" 200 "fake2" 14}
 MAPS
 MAP_DATA
-END
-
-)";
+END)";
   ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual;
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -2472,15 +2473,19 @@
 TEST_F(MallocDebugTest, malloc_info_no_pointer_tracking) {
   Init("fill");
 
-  char* buffer;
-  size_t size;
-  FILE* memstream = open_memstream(&buffer, &size);
-  ASSERT_TRUE(memstream != nullptr);
-  ASSERT_EQ(0, debug_malloc_info(0, memstream));
-  ASSERT_EQ(0, fclose(memstream));
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  FILE* fp = fdopen(tf.fd, "w+");
+  tf.release();
+  ASSERT_TRUE(fp != nullptr);
+  ASSERT_EQ(0, debug_malloc_info(0, fp));
+  ASSERT_EQ(0, fclose(fp));
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
 
   tinyxml2::XMLDocument doc;
-  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(buffer));
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(contents.c_str()));
   auto root = doc.FirstChildElement();
   ASSERT_TRUE(root != nullptr);
   ASSERT_STREQ("malloc", root->Name());
@@ -2501,17 +2506,21 @@
   std::unique_ptr<void, decltype(debug_free)*> ptr4(debug_malloc(1200), debug_free);
   ASSERT_TRUE(ptr4.get() != nullptr);
 
-  char* buffer;
-  size_t size;
-  FILE* memstream = open_memstream(&buffer, &size);
-  ASSERT_TRUE(memstream != nullptr);
-  ASSERT_EQ(0, debug_malloc_info(0, memstream));
-  ASSERT_EQ(0, fclose(memstream));
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  FILE* fp = fdopen(tf.fd, "w+");
+  tf.release();
+  ASSERT_TRUE(fp != nullptr);
+  ASSERT_EQ(0, debug_malloc_info(0, fp));
+  ASSERT_EQ(0, fclose(fp));
 
-  SCOPED_TRACE(testing::Message() << "Output:\n" << buffer);
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
+
+  SCOPED_TRACE(testing::Message() << "Output:\n" << contents);
 
   tinyxml2::XMLDocument doc;
-  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(buffer));
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(contents.c_str()));
   auto root = doc.FirstChildElement();
   ASSERT_TRUE(root != nullptr);
   ASSERT_STREQ("malloc", root->Name());
@@ -2548,3 +2557,134 @@
   ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->FirstChildElement("total")->QueryIntText(&val));
   ASSERT_EQ(1, val);
 }
+
+static void AllocPtrsWithBacktrace(std::vector<void*>* ptrs) {
+  backtrace_fake_add(std::vector<uintptr_t> {0xf, 0xe, 0xd, 0xc});
+  void* ptr = debug_malloc(1024);
+  ASSERT_TRUE(ptr != nullptr);
+  memset(ptr, 0, 1024);
+  ptrs->push_back(ptr);
+
+  backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xbc001, 0xbc002});
+  ptr = debug_malloc(500);
+  ASSERT_TRUE(ptr != nullptr);
+  memset(ptr, 0, 500);
+  ptrs->push_back(ptr);
+
+  backtrace_fake_add(std::vector<uintptr_t> {0x104});
+  ptr = debug_malloc(100);
+  ASSERT_TRUE(ptr != nullptr);
+  memset(ptr, 0, 100);
+  ptrs->push_back(ptr);
+}
+
+static constexpr std::string_view kDumpInfo = R"(Android Native Heap Dump v1.2
+
+Build fingerprint: ''
+
+Total memory: 1624
+Allocation records: 3
+Backtrace size: 16
+
+z 0  sz     1024  num    1  bt f e d c
+z 0  sz      500  num    1  bt bc000 bc001 bc002
+z 0  sz      100  num    1  bt 104
+MAPS
+MAP_DATA
+END)";
+
+TEST_F(MallocDebugTest, debug_write_malloc_leak_info) {
+  Init("backtrace=16");
+
+  std::vector<void*> ptrs;
+  AllocPtrsWithBacktrace(&ptrs);
+
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  close(tf.fd);
+  tf.release();
+  FILE* fp = fopen(tf.path, "w+");
+  ASSERT_TRUE(fp != nullptr);
+
+  ASSERT_TRUE(debug_write_malloc_leak_info(fp));
+
+  fclose(fp);
+
+  for (auto ptr : ptrs) {
+    debug_free(ptr);
+  }
+  ptrs.clear();
+
+  std::string expected(kDumpInfo);
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
+  contents = SanitizeHeapData(contents);
+  ASSERT_EQ(expected, contents);
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(MallocDebugTest, debug_write_malloc_leak_info_extra_data) {
+  Init("backtrace=16");
+
+  std::vector<void*> ptrs;
+  AllocPtrsWithBacktrace(&ptrs);
+
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  close(tf.fd);
+  tf.release();
+  FILE* fp = fopen(tf.path, "w+");
+  ASSERT_TRUE(fp != nullptr);
+
+  fprintf(fp, "This message should appear before the output.\n");
+  ASSERT_TRUE(debug_write_malloc_leak_info(fp));
+  fprintf(fp, "This message should appear after the output.\n");
+
+  fclose(fp);
+
+  for (auto ptr : ptrs) {
+    debug_free(ptr);
+  }
+  ptrs.clear();
+
+  std::string expected = "This message should appear before the output.\n"
+                         + std::string(kDumpInfo)
+                         + "\nThis message should appear after the output.";
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
+  contents = SanitizeHeapData(contents);
+  ASSERT_EQ(expected, contents);
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(MallocDebugTest, dump_heap) {
+  Init("backtrace=16");
+
+  std::vector<void*> ptrs;
+  AllocPtrsWithBacktrace(&ptrs);
+
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  close(tf.fd);
+  tf.release();
+  debug_dump_heap(tf.path);
+
+  for (auto ptr : ptrs) {
+    debug_free(ptr);
+  }
+  ptrs.clear();
+
+  std::string expected(kDumpInfo);
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
+  contents = SanitizeHeapData(contents);
+  ASSERT_EQ(expected, contents);
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  std::string expected_log = std::string("6 malloc_debug Dumping to file: ") + tf.path + "\n\n";
+  ASSERT_EQ(expected_log, getFakeLogPrint());
+}