Bug 1291768 - Avoid SIGSEGV trying to read ELF headers of libraries with a non-null base virtual address. r?froydnj draft
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 18 Aug 2016 07:16:40 +0900
changeset 402887 e067e2ed2fd3746dbe5bec357f8fabacdf41c44b
parent 402378 df38e89c572044e53ef457b0446adf92b40745dd
child 528777 f448f96ecace046330779fa99990ec598096a02a
push id26772
push userbmo:mh+mozilla@glandium.org
push dateThu, 18 Aug 2016 22:07:32 +0000
reviewersfroydnj
bugs1291768
milestone51.0a1
Bug 1291768 - Avoid SIGSEGV trying to read ELF headers of libraries with a non-null base virtual address. r?froydnj
mozglue/linker/ElfLoader.cpp
--- a/mozglue/linker/ElfLoader.cpp
+++ b/mozglue/linker/ElfLoader.cpp
@@ -3,16 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <string>
 #include <cstring>
 #include <cstdlib>
 #include <cstdio>
 #include <dlfcn.h>
 #include <unistd.h>
+#include <errno.h>
 #include <algorithm>
 #include <fcntl.h>
 #include "ElfLoader.h"
 #include "BaseElf.h"
 #include "CustomElf.h"
 #include "Mappable.h"
 #include "Logging.h"
 #include <inttypes.h>
@@ -104,29 +105,77 @@ int
 }
 
 int
 __wrap_dl_iterate_phdr(dl_phdr_cb callback, void *data)
 {
   if (!ElfLoader::Singleton.dbg)
     return -1;
 
+  int pipefd[2];
+  bool valid_pipe = (pipe(pipefd) == 0);
+  AutoCloseFD read_fd(pipefd[0]);
+  AutoCloseFD write_fd(pipefd[1]);
+
   for (ElfLoader::DebuggerHelper::iterator it = ElfLoader::Singleton.dbg.begin();
        it < ElfLoader::Singleton.dbg.end(); ++it) {
     dl_phdr_info info;
     info.dlpi_addr = reinterpret_cast<Elf::Addr>(it->l_addr);
     info.dlpi_name = it->l_name;
     info.dlpi_phdr = nullptr;
     info.dlpi_phnum = 0;
 
     // Assuming l_addr points to Elf headers (in most cases, this is true),
     // get the Phdr location from there.
-    uint8_t mapped;
-    // If the page is not mapped, mincore returns an error.
-    if (!mincore(const_cast<void*>(it->l_addr), PageSize(), &mapped)) {
+    // Unfortunately, when l_addr doesn't point to Elf headers, it may point
+    // to unmapped memory, or worse, unreadable memory. The only way to detect
+    // the latter without causing a SIGSEGV is to use the pointer in a system
+    // call that will try to read from there, and return an EFAULT error if
+    // it can't. One such system call is write(). It used to be possible to
+    // use a file descriptor on /dev/null for these kind of things, but recent
+    // Linux kernels never return an EFAULT error when using /dev/null.
+    // So instead, we use a self pipe. We do however need to read() from the
+    // read end of the pipe as well so as to not fill up the pipe buffer and
+    // block on subsequent writes.
+    // In the unlikely event reads from or write to the pipe fail for some
+    // other reason than EFAULT, we don't try any further and just skip setting
+    // the Phdr location for all subsequent libraries, rather than trying to
+    // start over with a new pipe.
+    int can_read = true;
+    if (valid_pipe) {
+      int ret;
+      char raw_ehdr[sizeof(Elf::Ehdr)];
+      static_assert(sizeof(raw_ehdr) < PIPE_BUF, "PIPE_BUF is too small");
+      do {
+        // writes are atomic when smaller than PIPE_BUF, per POSIX.1-2008.
+        ret = write(write_fd, it->l_addr, sizeof(raw_ehdr));
+      } while (ret == -1 && errno == EINTR);
+      if (ret != sizeof(raw_ehdr)) {
+        if (ret == -1 && errno == EFAULT) {
+          can_read = false;
+        } else {
+          valid_pipe = false;
+        }
+      } else {
+        size_t nbytes = 0;
+        do {
+          // Per POSIX.1-2008, interrupted reads can return a length smaller
+          // than the given one instead of failing with errno EINTR.
+          ret = read(read_fd, raw_ehdr + nbytes, sizeof(raw_ehdr) - nbytes);
+          if (ret > 0)
+              nbytes += ret;
+        } while ((nbytes != sizeof(raw_ehdr) && ret > 0) ||
+                 (ret == -1 && errno == EINTR));
+        if (nbytes != sizeof(raw_ehdr)) {
+          valid_pipe = false;
+        }
+      }
+    }
+
+    if (valid_pipe && can_read) {
       const Elf::Ehdr *ehdr = Elf::Ehdr::validate(it->l_addr);
       if (ehdr) {
         info.dlpi_phdr = reinterpret_cast<const Elf::Phdr *>(
                          reinterpret_cast<const char *>(ehdr) + ehdr->e_phoff);
         info.dlpi_phnum = ehdr->e_phnum;
       }
     }