1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "gtest/gtest.h"
9 #include "base/shared_memory.h"
11 #include "mozilla/RefPtr.h"
12 #include "mozilla/ipc/SharedMemory.h"
13 #include "mozilla/ipc/SharedMemoryBasic.h"
17 # include <linux/magic.h>
20 # include <sys/statfs.h>
21 # include <sys/utsname.h>
30 // Try to map a frozen shm for writing. Threat model: the process is
31 // compromised and then receives a frozen handle.
32 TEST(IPCSharedMemory
, FreezeAndMapRW
)
34 base::SharedMemory shm
;
36 // Create and initialize
37 ASSERT_TRUE(shm
.CreateFreezeable(1));
38 ASSERT_TRUE(shm
.Map(1));
39 auto mem
= reinterpret_cast<char*>(shm
.memory());
44 ASSERT_TRUE(shm
.Freeze());
45 ASSERT_FALSE(shm
.memory());
47 // Re-create as writeable
48 auto handle
= shm
.TakeHandle();
49 ASSERT_TRUE(shm
.IsHandleValid(handle
));
50 ASSERT_FALSE(shm
.IsValid());
51 ASSERT_TRUE(shm
.SetHandle(std::move(handle
), /* read-only */ false));
52 ASSERT_TRUE(shm
.IsValid());
55 EXPECT_FALSE(shm
.Map(1));
58 // Try to restore write permissions to a frozen mapping. Threat
59 // model: the process has mapped frozen shm normally and then is
60 // compromised, or as for FreezeAndMapRW (see also the
61 // proof-of-concept at https://crbug.com/project-zero/1671 ).
62 TEST(IPCSharedMemory
, FreezeAndReprotect
)
64 base::SharedMemory shm
;
66 // Create and initialize
67 ASSERT_TRUE(shm
.CreateFreezeable(1));
68 ASSERT_TRUE(shm
.Map(1));
69 auto mem
= reinterpret_cast<char*>(shm
.memory());
74 ASSERT_TRUE(shm
.Freeze());
75 ASSERT_FALSE(shm
.memory());
78 ASSERT_TRUE(shm
.Map(1));
79 mem
= reinterpret_cast<char*>(shm
.memory());
82 // Try to alter protection; should fail
83 EXPECT_FALSE(ipc::SharedMemory::SystemProtectFallible(
84 mem
, 1, ipc::SharedMemory::RightsReadWrite
));
88 // This essentially tests whether FreezeAndReprotect would have failed
89 // without the freeze. It doesn't work on Windows: VirtualProtect
90 // can't exceed the permissions set in MapViewOfFile regardless of the
91 // security status of the original handle.
92 TEST(IPCSharedMemory
, Reprotect
)
94 base::SharedMemory shm
;
96 // Create and initialize
97 ASSERT_TRUE(shm
.CreateFreezeable(1));
98 ASSERT_TRUE(shm
.Map(1));
99 auto mem
= reinterpret_cast<char*>(shm
.memory());
103 // Re-create as read-only
104 auto handle
= shm
.TakeHandle();
105 ASSERT_TRUE(shm
.IsHandleValid(handle
));
106 ASSERT_FALSE(shm
.IsValid());
107 ASSERT_TRUE(shm
.SetHandle(std::move(handle
), /* read-only */ true));
108 ASSERT_TRUE(shm
.IsValid());
111 ASSERT_TRUE(shm
.Map(1));
112 mem
= reinterpret_cast<char*>(shm
.memory());
113 ASSERT_EQ(*mem
, 'A');
115 // Try to alter protection; should succeed, because not frozen
116 EXPECT_TRUE(ipc::SharedMemory::SystemProtectFallible(
117 mem
, 1, ipc::SharedMemory::RightsReadWrite
));
122 // Try to regain write permissions on a read-only handle using
123 // DuplicateHandle; this will succeed if the object has no DACL.
124 // See also https://crbug.com/338538
125 TEST(IPCSharedMemory
, WinUnfreeze
)
127 base::SharedMemory shm
;
129 // Create and initialize
130 ASSERT_TRUE(shm
.CreateFreezeable(1));
131 ASSERT_TRUE(shm
.Map(1));
132 auto mem
= reinterpret_cast<char*>(shm
.memory());
137 ASSERT_TRUE(shm
.Freeze());
138 ASSERT_FALSE(shm
.memory());
141 auto handle
= shm
.TakeHandle();
142 ASSERT_TRUE(shm
.IsHandleValid(handle
));
143 ASSERT_FALSE(shm
.IsValid());
146 HANDLE newHandle
= INVALID_HANDLE_VALUE
;
147 bool unfroze
= ::DuplicateHandle(
148 GetCurrentProcess(), handle
.release(), GetCurrentProcess(), &newHandle
,
149 FILE_MAP_ALL_ACCESS
, false, DUPLICATE_CLOSE_SOURCE
);
150 ASSERT_FALSE(unfroze
);
154 // Test that a read-only copy sees changes made to the writeable
155 // mapping in the case that the page wasn't accessed before the copy.
156 TEST(IPCSharedMemory
, ROCopyAndWrite
)
158 base::SharedMemory shmRW
, shmRO
;
160 // Create and initialize
161 ASSERT_TRUE(shmRW
.CreateFreezeable(1));
162 ASSERT_TRUE(shmRW
.Map(1));
163 auto memRW
= reinterpret_cast<char*>(shmRW
.memory());
166 // Create read-only copy
167 ASSERT_TRUE(shmRW
.ReadOnlyCopy(&shmRO
));
168 EXPECT_FALSE(shmRW
.IsValid());
169 ASSERT_EQ(shmRW
.memory(), memRW
);
170 ASSERT_EQ(shmRO
.max_size(), size_t(1));
173 ASSERT_TRUE(shmRO
.IsValid());
174 ASSERT_TRUE(shmRO
.Map(1));
175 auto memRO
= reinterpret_cast<const char*>(shmRO
.memory());
177 ASSERT_NE(memRW
, memRO
);
181 EXPECT_EQ(*memRO
, 'A');
184 // Test that a read-only copy sees changes made to the writeable
185 // mapping in the case that the page was accessed before the copy
186 // (and, before that, sees the state as of when the copy was made).
187 TEST(IPCSharedMemory
, ROCopyAndRewrite
)
189 base::SharedMemory shmRW
, shmRO
;
191 // Create and initialize
192 ASSERT_TRUE(shmRW
.CreateFreezeable(1));
193 ASSERT_TRUE(shmRW
.Map(1));
194 auto memRW
= reinterpret_cast<char*>(shmRW
.memory());
198 // Create read-only copy
199 ASSERT_TRUE(shmRW
.ReadOnlyCopy(&shmRO
));
200 EXPECT_FALSE(shmRW
.IsValid());
201 ASSERT_EQ(shmRW
.memory(), memRW
);
202 ASSERT_EQ(shmRO
.max_size(), size_t(1));
205 ASSERT_TRUE(shmRO
.IsValid());
206 ASSERT_TRUE(shmRO
.Map(1));
207 auto memRO
= reinterpret_cast<const char*>(shmRO
.memory());
209 ASSERT_NE(memRW
, memRO
);
212 ASSERT_EQ(*memRW
, 'A');
213 EXPECT_EQ(*memRO
, 'A');
215 EXPECT_EQ(*memRO
, 'X');
218 // See FreezeAndMapRW.
219 TEST(IPCSharedMemory
, ROCopyAndMapRW
)
221 base::SharedMemory shmRW
, shmRO
;
223 // Create and initialize
224 ASSERT_TRUE(shmRW
.CreateFreezeable(1));
225 ASSERT_TRUE(shmRW
.Map(1));
226 auto memRW
= reinterpret_cast<char*>(shmRW
.memory());
230 // Create read-only copy
231 ASSERT_TRUE(shmRW
.ReadOnlyCopy(&shmRO
));
232 ASSERT_TRUE(shmRO
.IsValid());
234 // Re-create as writeable
235 auto handle
= shmRO
.TakeHandle();
236 ASSERT_TRUE(shmRO
.IsHandleValid(handle
));
237 ASSERT_FALSE(shmRO
.IsValid());
238 ASSERT_TRUE(shmRO
.SetHandle(std::move(handle
), /* read-only */ false));
239 ASSERT_TRUE(shmRO
.IsValid());
242 EXPECT_FALSE(shmRO
.Map(1));
245 // See FreezeAndReprotect
246 TEST(IPCSharedMemory
, ROCopyAndReprotect
)
248 base::SharedMemory shmRW
, shmRO
;
250 // Create and initialize
251 ASSERT_TRUE(shmRW
.CreateFreezeable(1));
252 ASSERT_TRUE(shmRW
.Map(1));
253 auto memRW
= reinterpret_cast<char*>(shmRW
.memory());
257 // Create read-only copy
258 ASSERT_TRUE(shmRW
.ReadOnlyCopy(&shmRO
));
259 ASSERT_TRUE(shmRO
.IsValid());
262 ASSERT_TRUE(shmRO
.Map(1));
263 auto memRO
= reinterpret_cast<char*>(shmRO
.memory());
264 ASSERT_EQ(*memRO
, 'A');
266 // Try to alter protection; should fail
267 EXPECT_FALSE(ipc::SharedMemory::SystemProtectFallible(
268 memRO
, 1, ipc::SharedMemory::RightsReadWrite
));
271 TEST(IPCSharedMemory
, IsZero
)
273 base::SharedMemory shm
;
275 static constexpr size_t kSize
= 65536;
276 ASSERT_TRUE(shm
.Create(kSize
));
277 ASSERT_TRUE(shm
.Map(kSize
));
279 auto* mem
= reinterpret_cast<char*>(shm
.memory());
280 for (size_t i
= 0; i
< kSize
; ++i
) {
281 ASSERT_EQ(mem
[i
], 0) << "offset " << i
;
286 TEST(IPCSharedMemory
, BasicIsZero
)
288 auto shm
= MakeRefPtr
<ipc::SharedMemoryBasic
>();
290 static constexpr size_t kSize
= 65536;
291 ASSERT_TRUE(shm
->Create(kSize
));
292 ASSERT_TRUE(shm
->Map(kSize
));
294 auto* mem
= reinterpret_cast<char*>(shm
->memory());
295 for (size_t i
= 0; i
< kSize
; ++i
) {
296 ASSERT_EQ(mem
[i
], 0) << "offset " << i
;
301 #if defined(XP_LINUX) && !defined(ANDROID)
302 // Test that memfd_create is used where expected.
304 // More precisely: if memfd_create support is expected, verify that
305 // shared memory isn't subject to a filesystem size limit.
306 TEST(IPCSharedMemory
, IsMemfd
)
308 static constexpr int kMajor
= 3;
309 static constexpr int kMinor
= 17;
312 ASSERT_EQ(uname(&uts
), 0) << strerror(errno
);
313 ASSERT_STREQ(uts
.sysname
, "Linux");
315 ASSERT_EQ(sscanf(uts
.release
, "%d.%d", &major
, &minor
), 2);
316 bool expectMemfd
= major
> kMajor
|| (major
== kMajor
&& minor
>= kMinor
);
318 base::SharedMemory shm
;
319 ASSERT_TRUE(shm
.Create(1));
320 UniqueFileHandle fd
= shm
.TakeHandle();
324 ASSERT_EQ(fstatfs(fd
.get(), &fs
), 0) << strerror(errno
);
325 EXPECT_EQ(fs
.f_type
, TMPFS_MAGIC
);
326 static constexpr decltype(fs
.f_blocks
) kNoLimit
= 0;
328 EXPECT_EQ(fs
.f_blocks
, kNoLimit
);
330 // On older kernels, we expect the memfd / no-limit test to fail.
331 // (In theory it could succeed if backported memfd support exists;
332 // if that ever happens, this check can be removed.)
333 EXPECT_NE(fs
.f_blocks
, kNoLimit
);
338 } // namespace mozilla