1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/tracing/ipc/posix_shared_memory.h"
18 
19 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
20     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
21     PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
22 
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 
30 #include "perfetto/base/build_config.h"
31 #include "perfetto/ext/base/file_utils.h"
32 #include "perfetto/ext/base/scoped_file.h"
33 #include "perfetto/ext/base/temp_file.h"
34 #include "perfetto/ext/base/utils.h"
35 #include "src/base/test/test_task_runner.h"
36 #include "src/base/test/vm_test_utils.h"
37 #include "src/tracing/ipc/memfd.h"
38 #include "test/gtest_and_gmock.h"
39 
40 namespace perfetto {
41 namespace {
42 
IsFileDescriptorClosed(int fd)43 bool IsFileDescriptorClosed(int fd) {
44   return lseek(fd, 0, SEEK_CUR) == -1 && errno == EBADF;
45 }
46 
TEST(PosixSharedMemoryTest,DestructorUnmapsMemory)47 TEST(PosixSharedMemoryTest, DestructorUnmapsMemory) {
48   PosixSharedMemory::Factory factory;
49   std::unique_ptr<SharedMemory> shm =
50       factory.CreateSharedMemory(base::GetSysPageSize());
51   ASSERT_NE(shm.get(), nullptr);
52   void* const shm_start = shm->start();
53   const size_t shm_size = shm->size();
54   ASSERT_NE(nullptr, shm_start);
55   ASSERT_EQ(base::GetSysPageSize(), shm_size);
56 
57   memcpy(shm_start, "test", 5);
58   ASSERT_TRUE(base::vm_test_utils::IsMapped(shm_start, shm_size));
59 
60   shm.reset();
61   ASSERT_FALSE(base::vm_test_utils::IsMapped(shm_start, shm_size));
62 }
63 
TEST(PosixSharedMemoryTest,DestructorClosesFD)64 TEST(PosixSharedMemoryTest, DestructorClosesFD) {
65   std::unique_ptr<PosixSharedMemory> shm =
66       PosixSharedMemory::Create(base::GetSysPageSize());
67   ASSERT_NE(shm.get(), nullptr);
68   int fd = shm->fd();
69   ASSERT_GE(fd, 0);
70   ASSERT_EQ(static_cast<off_t>(base::GetSysPageSize()), lseek(fd, 0, SEEK_END));
71 
72   shm.reset();
73   ASSERT_TRUE(IsFileDescriptorClosed(fd));
74 }
75 
TEST(PosixSharedMemoryTest,AttachToFdWithoutSeals)76 TEST(PosixSharedMemoryTest, AttachToFdWithoutSeals) {
77   base::TempFile tmp_file = base::TempFile::CreateUnlinked();
78   const int fd_num = tmp_file.fd();
79   ASSERT_EQ(0, ftruncate(fd_num, static_cast<off_t>(base::GetSysPageSize())));
80   ASSERT_EQ(7, base::WriteAll(fd_num, "foobar", 7));
81 
82   std::unique_ptr<PosixSharedMemory> shm = PosixSharedMemory::AttachToFd(
83       tmp_file.ReleaseFD(), /*require_seals_if_supported=*/false);
84   ASSERT_NE(shm.get(), nullptr);
85   void* const shm_start = shm->start();
86   const size_t shm_size = shm->size();
87   ASSERT_NE(nullptr, shm_start);
88   ASSERT_EQ(base::GetSysPageSize(), shm_size);
89   ASSERT_EQ(0, memcmp("foobar", shm_start, 7));
90 
91   ASSERT_FALSE(IsFileDescriptorClosed(fd_num));
92 
93   shm.reset();
94   ASSERT_TRUE(IsFileDescriptorClosed(fd_num));
95   ASSERT_FALSE(base::vm_test_utils::IsMapped(shm_start, shm_size));
96 }
97 
TEST(PosixSharedMemoryTest,AttachToFdRequiresSeals)98 TEST(PosixSharedMemoryTest, AttachToFdRequiresSeals) {
99   base::TempFile tmp_file = base::TempFile::CreateUnlinked();
100   const int fd_num = tmp_file.fd();
101   ASSERT_EQ(0, ftruncate(fd_num, static_cast<off_t>(base::GetSysPageSize())));
102 
103   std::unique_ptr<PosixSharedMemory> shm =
104       PosixSharedMemory::AttachToFd(tmp_file.ReleaseFD());
105 
106   if (HasMemfdSupport()) {
107     EXPECT_EQ(shm.get(), nullptr);
108   } else {
109     ASSERT_NE(shm.get(), nullptr);
110     EXPECT_NE(shm->start(), nullptr);
111   }
112 }
113 
TEST(PosixSharedMemoryTest,CreateAndMap)114 TEST(PosixSharedMemoryTest, CreateAndMap) {
115   // Deliberately trying to cover cases where the shm size is smaller than the
116   // system page size (crbug.com/1116576).
117   const size_t kLessThanAPage = 2048;
118   std::unique_ptr<PosixSharedMemory> shm =
119       PosixSharedMemory::Create(kLessThanAPage);
120   void* const shm_start = shm->start();
121   const size_t shm_size = shm->size();
122   ASSERT_NE(shm_start, nullptr);
123   ASSERT_EQ(shm_size, kLessThanAPage);
124 
125   memcpy(shm_start, "test", 5);
126   ASSERT_TRUE(base::vm_test_utils::IsMapped(shm_start, shm_size));
127 
128   base::ScopedFile shm_fd2(dup(shm->fd()));
129   std::unique_ptr<PosixSharedMemory> shm2 =
130       PosixSharedMemory::AttachToFd(std::move(shm_fd2));
131   ASSERT_NE(shm2.get(), nullptr);
132   void* const shm2_start = shm2->start();
133   const size_t shm2_size = shm2->size();
134   ASSERT_NE(shm2_start, nullptr);
135   ASSERT_EQ(shm2_size, shm_size);
136 
137   ASSERT_EQ(0, memcmp("test", shm2->start(), 5));
138   ASSERT_TRUE(base::vm_test_utils::IsMapped(shm2_start, shm2_size));
139 
140   shm2.reset();
141   ASSERT_FALSE(base::vm_test_utils::IsMapped(shm2_start, shm2_size));
142   ASSERT_TRUE(base::vm_test_utils::IsMapped(shm_start, shm_size));
143 
144   shm.reset();
145   ASSERT_FALSE(base::vm_test_utils::IsMapped(shm2_start, shm2_size));
146   ASSERT_FALSE(base::vm_test_utils::IsMapped(shm_start, shm_size));
147 }
148 
149 }  // namespace
150 }  // namespace perfetto
151 #endif  // OS_LINUX || OS_ANDROID || OS_APPLE
152