ipc: Add an option to require file seals when mapping shm.

In preparation for producer-provided SMBs, this patch makes
PosixSharedMemory::AttachToFd optionally ignore FDs that aren't sealed.

This only takes effect on systems where file seals (memfd) are
supported. Iff require_seals is set, and a non-sealed or non-memfd FD
is provided on a system that supports sealed memfds, the mapping is
aborted.

Bug: 147742905
Change-Id: I80cb2ed90804a43f27e3c8e715ca22f1c73d5acf
diff --git a/Android.bp b/Android.bp
index c83a8cb..a6c268c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6607,6 +6607,7 @@
   name: "perfetto_src_tracing_ipc_common",
   srcs: [
     "src/tracing/ipc/default_socket.cc",
+    "src/tracing/ipc/memfd.cc",
     "src/tracing/ipc/posix_shared_memory.cc",
   ],
 }
diff --git a/BUILD b/BUILD
index e7a59ac..2c0458e 100644
--- a/BUILD
+++ b/BUILD
@@ -1215,6 +1215,8 @@
     name = "src_tracing_ipc_common",
     srcs = [
         "src/tracing/ipc/default_socket.cc",
+        "src/tracing/ipc/memfd.cc",
+        "src/tracing/ipc/memfd.h",
         "src/tracing/ipc/posix_shared_memory.cc",
         "src/tracing/ipc/posix_shared_memory.h",
     ],
diff --git a/src/tracing/ipc/BUILD.gn b/src/tracing/ipc/BUILD.gn
index 0f2bcb6..93c97a6 100644
--- a/src/tracing/ipc/BUILD.gn
+++ b/src/tracing/ipc/BUILD.gn
@@ -28,6 +28,8 @@
   ]
   sources = [
     "default_socket.cc",
+    "memfd.cc",
+    "memfd.h",
     "posix_shared_memory.cc",
     "posix_shared_memory.h",
   ]
diff --git a/src/tracing/ipc/memfd.cc b/src/tracing/ipc/memfd.cc
new file mode 100644
index 0000000..64025bf
--- /dev/null
+++ b/src/tracing/ipc/memfd.cc
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/tracing/ipc/memfd.h"
+
+#include <errno.h>
+
+#define PERFETTO_MEMFD_ENABLED()             \
+  PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+      PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX)
+
+#if PERFETTO_MEMFD_ENABLED()
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/syscall.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+// Some android build bots use a sysroot that doesn't support memfd when
+// compiling for the host, so we redefine it if necessary.
+#if !defined(__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
+#error "unsupported sysroot without memfd support"
+#endif
+#endif  // !defined(__NR_memfd_create)
+
+namespace perfetto {
+bool HasMemfdSupport() {
+  static bool kSupportsMemfd = [] {
+    // Check kernel version supports memfd_create(). Some older kernels segfault
+    // executing memfd_create() rather than returning ENOSYS (b/116769556).
+    static constexpr int kRequiredMajor = 3;
+    static constexpr int kRequiredMinor = 17;
+    struct utsname uts;
+    int major, minor;
+    if (uname(&uts) == 0 && strcmp(uts.sysname, "Linux") == 0 &&
+        sscanf(uts.release, "%d.%d", &major, &minor) == 2 &&
+        ((major < kRequiredMajor ||
+          (major == kRequiredMajor && minor < kRequiredMinor)))) {
+      return false;
+    }
+
+    base::ScopedFile fd;
+    fd.reset(static_cast<int>(syscall(__NR_memfd_create, "perfetto_shmem",
+                                      MFD_CLOEXEC | MFD_ALLOW_SEALING)));
+    return !!fd;
+  }();
+  return kSupportsMemfd;
+}
+
+base::ScopedFile CreateMemfd(const char* name, unsigned int flags) {
+  if (!HasMemfdSupport()) {
+    errno = ENOSYS;
+    return base::ScopedFile();
+  }
+  return base::ScopedFile(
+      static_cast<int>(syscall(__NR_memfd_create, name, flags)));
+}
+}  // namespace perfetto
+
+#else  // PERFETTO_MEMFD_ENABLED()
+
+namespace perfetto {
+bool HasMemfdSupport() {
+  return false;
+}
+base::ScopedFile CreateMemfd(const char*, unsigned int) {
+  errno = ENOSYS;
+  return base::ScopedFile();
+}
+}  // namespace perfetto
+
+#endif  // PERFETTO_MEMFD_ENABLED()
diff --git a/src/tracing/ipc/memfd.h b/src/tracing/ipc/memfd.h
new file mode 100644
index 0000000..8cf4b2a
--- /dev/null
+++ b/src/tracing/ipc/memfd.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACING_IPC_MEMFD_H_
+#define SRC_TRACING_IPC_MEMFD_H_
+
+#include "perfetto/base/build_config.h"
+
+#include "perfetto/ext/base/scoped_file.h"
+
+// Some android build bots use a sysroot that doesn't support memfd when
+// compiling for the host, so we define the flags we need ourselves.
+
+// from memfd.h
+#ifndef MFD_CLOEXEC
+#define MFD_CLOEXEC 0x0001U
+#define MFD_ALLOW_SEALING 0x0002U
+#endif
+
+// from fcntl.h
+#ifndef F_ADD_SEALS
+#define F_ADD_SEALS 1033
+#define F_GET_SEALS 1034
+#define F_SEAL_SEAL 0x0001
+#define F_SEAL_SHRINK 0x0002
+#define F_SEAL_GROW 0x0004
+#define F_SEAL_WRITE 0x0008
+#endif
+
+namespace perfetto {
+
+// Whether the operating system supports memfd.
+bool HasMemfdSupport();
+
+// Call memfd(2) if available on platform and return the fd as result. This call
+// also makes a kernel version check for safety on older kernels (b/116769556).
+// Returns an invalid ScopedFile on failure.
+base::ScopedFile CreateMemfd(const char* name, unsigned int flags);
+
+}  // namespace perfetto
+
+#endif  // SRC_TRACING_IPC_MEMFD_H_
diff --git a/src/tracing/ipc/posix_shared_memory.cc b/src/tracing/ipc/posix_shared_memory.cc
index d7f0904..4d5191d 100644
--- a/src/tracing/ipc/posix_shared_memory.cc
+++ b/src/tracing/ipc/posix_shared_memory.cc
@@ -27,50 +27,55 @@
 #include <memory>
 #include <utility>
 
-#include "perfetto/base/build_config.h"
+#include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/temp_file.h"
-
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-#include <linux/memfd.h>
-#include <sys/syscall.h>
-#endif
+#include "src/tracing/ipc/memfd.h"
 
 namespace perfetto {
 
+namespace {
+int kFileSeals = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL;
+}  // namespace
+
 // static
 std::unique_ptr<PosixSharedMemory> PosixSharedMemory::Create(size_t size) {
-  base::ScopedFile fd;
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-  bool is_memfd = false;
-  fd.reset(static_cast<int>(syscall(__NR_memfd_create, "perfetto_shmem",
-                                    MFD_CLOEXEC | MFD_ALLOW_SEALING)));
-  is_memfd = !!fd;
+  base::ScopedFile fd =
+      CreateMemfd("perfetto_shmem", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+  bool is_memfd = !!fd;
 
   if (!fd) {
     // TODO: if this fails on Android we should fall back on ashmem.
     PERFETTO_DPLOG("memfd_create() failed");
-  }
-#endif
-
-  if (!fd)
     fd = base::TempFile::CreateUnlinked().ReleaseFD();
+  }
 
   PERFETTO_CHECK(fd);
   int res = ftruncate(fd.get(), static_cast<off_t>(size));
   PERFETTO_CHECK(res == 0);
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+
   if (is_memfd) {
-    res = fcntl(*fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL);
+    // When memfd is supported, file seals should be, too.
+    res = fcntl(*fd, F_ADD_SEALS, kFileSeals);
     PERFETTO_DCHECK(res == 0);
   }
-#endif
+
   return MapFD(std::move(fd), size);
 }
 
 // static
 std::unique_ptr<PosixSharedMemory> PosixSharedMemory::AttachToFd(
-    base::ScopedFile fd) {
+    base::ScopedFile fd,
+    bool require_seals_if_supported) {
+  if (require_seals_if_supported && HasMemfdSupport()) {
+    // If the system supports memfd, we require a sealed memfd.
+    int res = fcntl(*fd, F_GET_SEALS);
+    if (res == -1 || (res & kFileSeals) != kFileSeals) {
+      PERFETTO_PLOG("Couldn't verify file seals on shmem FD");
+      return nullptr;
+    }
+  }
+
   struct stat stat_buf = {};
   int res = fstat(fd.get(), &stat_buf);
   PERFETTO_CHECK(res == 0 && stat_buf.st_size > 0);
diff --git a/src/tracing/ipc/posix_shared_memory.h b/src/tracing/ipc/posix_shared_memory.h
index 4bb3baf..0a0c39e 100644
--- a/src/tracing/ipc/posix_shared_memory.h
+++ b/src/tracing/ipc/posix_shared_memory.h
@@ -21,6 +21,7 @@
 
 #include <memory>
 
+#include "perfetto/base/build_config.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/tracing/core/shared_memory.h"
 
@@ -35,11 +36,17 @@
     std::unique_ptr<SharedMemory> CreateSharedMemory(size_t) override;
   };
 
-  // Create a brand new SHM region (the service uses this).
+  // Create a brand new SHM region.
   static std::unique_ptr<PosixSharedMemory> Create(size_t size);
 
-  // Mmaps a file descriptor to an existing SHM region (the producer uses this).
-  static std::unique_ptr<PosixSharedMemory> AttachToFd(base::ScopedFile);
+  // Mmaps a file descriptor to an existing SHM region. If
+  // |require_seals_if_supported| is true and the system supports
+  // memfd_create(), the FD is required to be a sealed memfd with F_SEAL_SEAL,
+  // F_SEAL_GROW, and F_SEAL_SHRINK seals set (otherwise, nullptr is returned).
+  // May also return nullptr if mapping fails for another reason (e.g. OOM).
+  static std::unique_ptr<PosixSharedMemory> AttachToFd(
+      base::ScopedFile,
+      bool require_seals_if_supported = true);
 
   ~PosixSharedMemory() override;
 
diff --git a/src/tracing/ipc/posix_shared_memory_unittest.cc b/src/tracing/ipc/posix_shared_memory_unittest.cc
index 1156556..e5589d0 100644
--- a/src/tracing/ipc/posix_shared_memory_unittest.cc
+++ b/src/tracing/ipc/posix_shared_memory_unittest.cc
@@ -30,6 +30,7 @@
 #include "perfetto/ext/base/utils.h"
 #include "src/base/test/test_task_runner.h"
 #include "src/base/test/vm_test_utils.h"
+#include "src/tracing/ipc/memfd.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
@@ -43,6 +44,7 @@
   PosixSharedMemory::Factory factory;
   std::unique_ptr<SharedMemory> shm =
       factory.CreateSharedMemory(base::kPageSize);
+  ASSERT_NE(shm.get(), nullptr);
   void* const shm_start = shm->start();
   const size_t shm_size = shm->size();
   ASSERT_NE(nullptr, shm_start);
@@ -58,6 +60,7 @@
 TEST(PosixSharedMemoryTest, DestructorClosesFD) {
   std::unique_ptr<PosixSharedMemory> shm =
       PosixSharedMemory::Create(base::kPageSize);
+  ASSERT_NE(shm.get(), nullptr);
   int fd = shm->fd();
   ASSERT_GE(fd, 0);
   ASSERT_EQ(static_cast<off_t>(base::kPageSize), lseek(fd, 0, SEEK_END));
@@ -66,14 +69,15 @@
   ASSERT_TRUE(IsFileDescriptorClosed(fd));
 }
 
-TEST(PosixSharedMemoryTest, AttachToFd) {
+TEST(PosixSharedMemoryTest, AttachToFdWithoutSeals) {
   base::TempFile tmp_file = base::TempFile::CreateUnlinked();
   const int fd_num = tmp_file.fd();
   ASSERT_EQ(0, ftruncate(fd_num, base::kPageSize));
   ASSERT_EQ(7, base::WriteAll(fd_num, "foobar", 7));
 
-  std::unique_ptr<PosixSharedMemory> shm =
-      PosixSharedMemory::AttachToFd(tmp_file.ReleaseFD());
+  std::unique_ptr<PosixSharedMemory> shm = PosixSharedMemory::AttachToFd(
+      tmp_file.ReleaseFD(), /*require_seals_if_supported=*/false);
+  ASSERT_NE(shm.get(), nullptr);
   void* const shm_start = shm->start();
   const size_t shm_size = shm->size();
   ASSERT_NE(nullptr, shm_start);
@@ -87,5 +91,53 @@
   ASSERT_FALSE(base::vm_test_utils::IsMapped(shm_start, shm_size));
 }
 
+TEST(PosixSharedMemoryTest, AttachToFdRequiresSeals) {
+  base::TempFile tmp_file = base::TempFile::CreateUnlinked();
+  const int fd_num = tmp_file.fd();
+  ASSERT_EQ(0, ftruncate(fd_num, base::kPageSize));
+
+  std::unique_ptr<PosixSharedMemory> shm =
+      PosixSharedMemory::AttachToFd(tmp_file.ReleaseFD());
+
+  if (HasMemfdSupport()) {
+    EXPECT_EQ(shm.get(), nullptr);
+  } else {
+    ASSERT_NE(shm.get(), nullptr);
+    EXPECT_NE(shm->start(), nullptr);
+  }
+}
+
+TEST(PosixSharedMemoryTest, CreateAndMap) {
+  std::unique_ptr<PosixSharedMemory> shm =
+      PosixSharedMemory::Create(base::kPageSize);
+  void* const shm_start = shm->start();
+  const size_t shm_size = shm->size();
+  ASSERT_NE(shm_start, nullptr);
+  ASSERT_EQ(shm_size, base::kPageSize);
+
+  memcpy(shm_start, "test", 5);
+  ASSERT_TRUE(base::vm_test_utils::IsMapped(shm_start, shm_size));
+
+  base::ScopedFile shm_fd2(dup(shm->fd()));
+  std::unique_ptr<PosixSharedMemory> shm2 =
+      PosixSharedMemory::AttachToFd(std::move(shm_fd2));
+  ASSERT_NE(shm2.get(), nullptr);
+  void* const shm2_start = shm2->start();
+  const size_t shm2_size = shm2->size();
+  ASSERT_NE(shm2_start, nullptr);
+  ASSERT_EQ(shm2_size, shm_size);
+
+  ASSERT_EQ(0, memcmp("test", shm2->start(), 5));
+  ASSERT_TRUE(base::vm_test_utils::IsMapped(shm2_start, shm2_size));
+
+  shm2.reset();
+  ASSERT_FALSE(base::vm_test_utils::IsMapped(shm2_start, shm2_size));
+  ASSERT_TRUE(base::vm_test_utils::IsMapped(shm_start, shm_size));
+
+  shm.reset();
+  ASSERT_FALSE(base::vm_test_utils::IsMapped(shm2_start, shm2_size));
+  ASSERT_FALSE(base::vm_test_utils::IsMapped(shm_start, shm_size));
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.cc b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
index f06801a..e828a12 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.cc
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
@@ -186,7 +186,9 @@
     PERFETTO_CHECK(shmem_fd);
 
     // TODO(primiano): handle mmap failure in case of OOM.
-    shared_memory_ = PosixSharedMemory::AttachToFd(std::move(shmem_fd));
+    shared_memory_ =
+        PosixSharedMemory::AttachToFd(std::move(shmem_fd),
+                                      /*require_seals_if_supported=*/false);
     shared_buffer_page_size_kb_ =
         cmd.setup_tracing().shared_buffer_page_size_kb();
     shared_memory_arbiter_ = SharedMemoryArbiter::CreateInstance(