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(