Bug 1440203 - Use memfd_create for shared memory on Linux where available. r?froydnj draft
authorJed Davis <jld@mozilla.com>
Fri, 13 Apr 2018 21:33:14 -0600
changeset 785766 7539d790d50d84a51400683deecb2cec476b186a
parent 785765 d85d9a8464f597e4e21a785191e1f7c92ad5bc37
push id107310
push userbmo:jld@mozilla.com
push dateFri, 20 Apr 2018 16:54:54 +0000
reviewersfroydnj
bugs1440203, 1439057
milestone61.0a1
Bug 1440203 - Use memfd_create for shared memory on Linux where available. r?froydnj memfd_create is roughly similar to FreeBSD's SHM_ANON, but it was introduced in kernel 3.17 and didn't get a C wrapper in glibc until 2.27. Because we need to be able to build (runtime-detected) support for it on much older host systems, the syscalls numbers for several common architectures are hard-coded and syscall() is used directly. When memfd_create support is detected, the content sandbox policy will not allow access to /dev/shm; see bug 1439057. MozReview-Commit-ID: Lv5515Hn8Xq
ipc/chromium/src/base/shared_memory_posix.cc
--- a/ipc/chromium/src/base/shared_memory_posix.cc
+++ b/ipc/chromium/src/base/shared_memory_posix.cc
@@ -11,22 +11,48 @@
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #ifdef ANDROID
 #include <linux/ashmem.h>
 #endif
 
+#ifdef OS_LINUX
+#include <sys/syscall.h>
+#endif
+
 #include "base/eintr_wrapper.h"
 #include "base/logging.h"
 #include "base/string_util.h"
 #include "mozilla/Atomics.h"
 #include "prenv.h"
 
+#ifdef OS_LINUX
+// memfd_create ABI.
+// Need to build with old headers, so can't require <sys/memfd.h>...
+# ifndef MFD_CLOEXEC
+#  define MFD_CLOEXEC 0x0001U
+# endif
+// ...or the syscall number definition.
+# ifndef __NR_memfd_create
+#  if defined(__x86_64__)
+#   define __NR_memfd_create 319
+#  elif defined(__i386__)
+#   define __NR_memfd_create 356
+#  elif defined(__aarch64__)
+#   define __NR_memfd_create 279
+#  elif defined(__arm__)
+#   define __NR_memfd_create 385
+#  else
+#   warning "memfd_create syscall number unknown"
+#  endif // arch
+# endif // ifndef __NR_memfd_create
+#endif // OS_LINUX
+
 namespace base {
 
 SharedMemory::SharedMemory()
     : mapped_file_(-1),
       memory_(NULL),
       read_only_(false),
       max_size_(0) {
 }
@@ -48,22 +74,64 @@ bool SharedMemory::IsHandleValid(const S
   return handle.fd >= 0;
 }
 
 // static
 SharedMemoryHandle SharedMemory::NULLHandle() {
   return SharedMemoryHandle();
 }
 
+#ifdef __NR_memfd_create
+static int
+MemFdCreate()
+{
+  return syscall(__NR_memfd_create, "mozilla-ipc", MFD_CLOEXEC);
+}
+
+static bool
+HaveMemFdCreate()
+{
+  static const bool kValue = []{
+    int fd = MemFdCreate();
+    if (fd >= 0) {
+      close(fd);
+      return true;
+    }
+    DCHECK(errno == ENOSYS);
+    return false;
+  }();
+  return kValue;
+}
+#else
+// No memfd_create; use stub definitions to reduce ifdef clutter.
+static int
+MemFdCreate()
+{
+  CHROMIUM_LOG(FATAL) << "unreachable";
+  // Return something so the compiler is happy.
+  return -1;
+}
+
+static bool
+HaveMemFdCreate()
+{
+  return false;
+}
+#endif
+
 // static
 bool SharedMemory::AppendPosixShmPrefix(std::string* str, pid_t pid)
 {
 #if defined(ANDROID) || defined(SHM_ANON)
   return false;
 #else
+  if (HaveMemFdCreate()) {
+    return false;
+  }
+
   *str += '/';
 #ifdef OS_LINUX
   // The Snap package environment doesn't provide a private /dev/shm
   // (it's used for communication with services like PulseAudio);
   // instead AppArmor is used to restrict access to it.  Anything with
   // this prefix is allowed:
   static const char* const kSnap = PR_GetEnv("SNAP_NAME");
   if (kSnap) {
@@ -81,54 +149,64 @@ bool SharedMemory::Create(size_t size) {
   read_only_ = false;
 
   DCHECK(size > 0);
   DCHECK(mapped_file_ == -1);
 
   int fd;
   bool needs_truncate = true;
 
+  if (HaveMemFdCreate()) {
+    fd = MemFdCreate();
+    // If this fails, fallback to named shm_open won't work:
+    // AppendPosixShmPrefix will return false, and that also means
+    // that the sandbox policy wasn't set up to allow it (if this is a
+    // content process).  ashmem would work in theory, but if
+    // memfd_create fails transiently it's probably something like
+    // EMFILE that would also affect ashmem.
+  } else {
 #ifdef ANDROID
-  // Android has its own shared memory facility:
-  fd = open("/" ASHMEM_NAME_DEF, O_RDWR, 0600);
-  if (fd < 0) {
-    CHROMIUM_LOG(WARNING) << "failed to open shm: " << strerror(errno);
-    return false;
-  }
-  if (ioctl(fd, ASHMEM_SET_SIZE, size) != 0) {
-    CHROMIUM_LOG(WARNING) << "failed to set shm size: " << strerror(errno);
-    close(fd);
-    return false;
-  }
-  needs_truncate = false;
+    // Android has its own shared memory facility:
+    fd = open("/" ASHMEM_NAME_DEF, O_RDWR, 0600);
+    if (fd < 0) {
+      CHROMIUM_LOG(WARNING) << "failed to open shm: " << strerror(errno);
+      return false;
+    }
+    if (ioctl(fd, ASHMEM_SET_SIZE, size) != 0) {
+      CHROMIUM_LOG(WARNING) << "failed to set shm size: " << strerror(errno);
+      close(fd);
+      return false;
+    }
+    needs_truncate = false;
 #elif defined(SHM_ANON)
-  // FreeBSD (or any other Unix that might decide to implement this
-  // nice, simple API):
-  fd = shm_open(SHM_ANON, O_RDWR, 0600);
+    // FreeBSD (or any other Unix that might decide to implement this
+    // nice, simple API):
+    fd = shm_open(SHM_ANON, O_RDWR, 0600);
 #else
-  // Generic Unix: shm_open + shm_unlink
-  do {
-    // The names don't need to be unique, but it saves time if they
-    // usually are.
-    static mozilla::Atomic<size_t> sNameCounter;
-    std::string name;
-    CHECK(AppendPosixShmPrefix(&name, getpid()));
-    StringAppendF(&name, "%zu", sNameCounter++);
-    // O_EXCL means the names being predictable shouldn't be a problem.
-    fd = HANDLE_EINTR(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
-    if (fd >= 0) {
-      if (shm_unlink(name.c_str()) != 0) {
-        // This shouldn't happen, but if it does: assume the file is
-        // in fact leaked, and bail out now while it's still 0-length.
-        DLOG(FATAL) << "failed to unlink shm: " << strerror(errno);
-        return false;
+    // Generic Unix: shm_open + shm_unlink
+    do {
+      // The names don't need to be unique, but it saves time if they
+      // usually are.
+      static mozilla::Atomic<size_t> sNameCounter;
+      std::string name;
+      CHECK(AppendPosixShmPrefix(&name, getpid()));
+      StringAppendF(&name, "%zu", sNameCounter++);
+      // O_EXCL means the names being predictable shouldn't be a problem.
+      fd = HANDLE_EINTR(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
+      if (fd >= 0) {
+        if (shm_unlink(name.c_str()) != 0) {
+          // This shouldn't happen, but if it does: assume the file is
+          // in fact leaked, and bail out now while it's still 0-length.
+          DLOG(FATAL) << "failed to unlink shm: " << strerror(errno);
+          return false;
+        }
       }
-    }
-  } while (fd < 0 && errno == EEXIST);
+    } while (fd < 0 && errno == EEXIST);
 #endif
+  }
 
   if (fd < 0) {
     CHROMIUM_LOG(WARNING) << "failed to open shm: " << strerror(errno);
     return false;
   }
 
   if (needs_truncate) {
     if (HANDLE_EINTR(ftruncate(fd, static_cast<off_t>(size))) != 0) {