Bug 1479960 - Add unit tests for shared memory freezing. r=froydnj
authorJed Davis <jld@mozilla.com>
Wed, 14 Aug 2019 22:48:52 +0000
changeset 488054 d8ac382b5f1790a5da7e2da12e4e1c92c39bb22b
parent 488053 86cb672b7000844c4802bc890e7b759e42e0e722
child 488055 ed066ea4b64cf7ec8253027ad11bf0f90d12c27b
push id36434
push usercbrindusan@mozilla.com
push dateThu, 15 Aug 2019 09:44:30 +0000
treeherdermozilla-central@144fbfb409b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1479960
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1479960 - Add unit tests for shared memory freezing. r=froydnj Also refactor SharedMemoryBasic::SystemProtect to allow testing cases that are expected to fail. Depends on D26748 Differential Revision: https://phabricator.services.mozilla.com/D26749
ipc/glue/SharedMemory.h
ipc/glue/SharedMemory_posix.cpp
ipc/glue/SharedMemory_windows.cpp
ipc/gtest/TestSharedMemory.cpp
ipc/gtest/moz.build
ipc/moz.build
--- a/ipc/glue/SharedMemory.h
+++ b/ipc/glue/SharedMemory.h
@@ -78,16 +78,18 @@ class SharedMemory {
   }
 
   // bug 1168843, compositor thread may create shared memory instances that are
   // destroyed by main thread on shutdown, so this must use thread-safe RC to
   // avoid hitting assertion
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedMemory)
 
   static void SystemProtect(char* aAddr, size_t aSize, int aRights);
+  static MOZ_MUST_USE bool SystemProtectFallible(char* aAddr, size_t aSize,
+                                                 int aRights);
   static size_t SystemPageSize();
   static size_t PageAlignedSize(size_t aSize);
 
  protected:
   SharedMemory();
 
   // Implementations should call these methods on shmem usage changes,
   // but *only if* the OS-specific calls are known to have succeeded.
--- a/ipc/glue/SharedMemory_posix.cpp
+++ b/ipc/glue/SharedMemory_posix.cpp
@@ -8,20 +8,27 @@
 #include <unistd.h>    // sysconf
 
 #include "mozilla/ipc/SharedMemory.h"
 
 namespace mozilla {
 namespace ipc {
 
 void SharedMemory::SystemProtect(char* aAddr, size_t aSize, int aRights) {
+  if (!SystemProtectFallible(aAddr, aSize, aRights)) {
+    MOZ_CRASH("can't mprotect()");
+  }
+}
+
+bool SharedMemory::SystemProtectFallible(char* aAddr, size_t aSize,
+                                         int aRights) {
   int flags = 0;
   if (aRights & RightsRead) flags |= PROT_READ;
   if (aRights & RightsWrite) flags |= PROT_WRITE;
   if (RightsNone == aRights) flags = PROT_NONE;
 
-  if (0 < mprotect(aAddr, aSize, flags)) MOZ_CRASH("can't mprotect()");
+  return 0 == mprotect(aAddr, aSize, flags);
 }
 
 size_t SharedMemory::SystemPageSize() { return sysconf(_SC_PAGESIZE); }
 
 }  // namespace ipc
 }  // namespace mozilla
--- a/ipc/glue/SharedMemory_windows.cpp
+++ b/ipc/glue/SharedMemory_windows.cpp
@@ -7,27 +7,33 @@
 #include <windows.h>
 
 #include "mozilla/ipc/SharedMemory.h"
 
 namespace mozilla {
 namespace ipc {
 
 void SharedMemory::SystemProtect(char* aAddr, size_t aSize, int aRights) {
+  if (!SystemProtectFallible(aAddr, aSize, aRights)) {
+    MOZ_CRASH("can't VirtualProtect()");
+  }
+}
+
+bool SharedMemory::SystemProtectFallible(char* aAddr, size_t aSize,
+                                         int aRights) {
   DWORD flags;
   if ((aRights & RightsRead) && (aRights & RightsWrite))
     flags = PAGE_READWRITE;
   else if (aRights & RightsRead)
     flags = PAGE_READONLY;
   else
     flags = PAGE_NOACCESS;
 
   DWORD oldflags;
-  if (!VirtualProtect(aAddr, aSize, flags, &oldflags))
-    MOZ_CRASH("can't VirtualProtect()");
+  return VirtualProtect(aAddr, aSize, flags, &oldflags);
 }
 
 size_t SharedMemory::SystemPageSize() {
   SYSTEM_INFO si;
   GetSystemInfo(&si);
   return si.dwPageSize;
 }
 
new file mode 100644
--- /dev/null
+++ b/ipc/gtest/TestSharedMemory.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "base/shared_memory.h"
+
+#include "base/process_util.h"
+#include "mozilla/ipc/SharedMemory.h"
+
+namespace mozilla {
+
+// Try to map a frozen shm for writing.  Threat model: the process is
+// compromised and then receives a frozen handle.
+TEST(IPCSharedMemory, FreezeAndMapRW)
+{
+  base::SharedMemory shm;
+
+  // Create and initialize
+  ASSERT_TRUE(shm.CreateFreezeable(1));
+  ASSERT_TRUE(shm.Map(1));
+  auto mem = reinterpret_cast<char*>(shm.memory());
+  ASSERT_TRUE(mem);
+  *mem = 'A';
+
+  // Freeze
+  ASSERT_TRUE(shm.Freeze());
+  ASSERT_FALSE(shm.memory());
+
+  // Re-create as writeable
+  auto handle = base::SharedMemory::NULLHandle();
+  ASSERT_TRUE(shm.GiveToProcess(base::GetCurrentProcId(), &handle));
+  ASSERT_TRUE(shm.IsHandleValid(handle));
+  ASSERT_FALSE(shm.IsValid());
+  ASSERT_TRUE(shm.SetHandle(handle, /* read-only */ false));
+  ASSERT_TRUE(shm.IsValid());
+
+  // This should fail
+  EXPECT_FALSE(shm.Map(1));
+}
+
+// Try to restore write permissions to a frozen mapping.  Threat
+// model: the process has mapped frozen shm normally and then is
+// compromised, or as for FreezeAndMapRW (see also the
+// proof-of-concept at https://crbug.com/project-zero/1671 ).
+TEST(IPCSharedMemory, FreezeAndReprotect)
+{
+  base::SharedMemory shm;
+
+  // Create and initialize
+  ASSERT_TRUE(shm.CreateFreezeable(1));
+  ASSERT_TRUE(shm.Map(1));
+  auto mem = reinterpret_cast<char*>(shm.memory());
+  ASSERT_TRUE(mem);
+  *mem = 'A';
+
+  // Freeze
+  ASSERT_TRUE(shm.Freeze());
+  ASSERT_FALSE(shm.memory());
+
+  // Re-map
+  ASSERT_TRUE(shm.Map(1));
+  mem = reinterpret_cast<char*>(shm.memory());
+  ASSERT_EQ(*mem, 'A');
+
+  // Try to alter protection; should fail
+  EXPECT_FALSE(ipc::SharedMemory::SystemProtectFallible(
+      mem, 1, ipc::SharedMemory::RightsReadWrite));
+}
+
+#ifndef XP_WIN
+// This essentially tests whether FreezeAndReprotect would have failed
+// without the freeze.  It doesn't work on Windows: VirtualProtect
+// can't exceed the permissions set in MapViewOfFile regardless of the
+// security status of the original handle.
+TEST(IPCSharedMemory, Reprotect)
+{
+  base::SharedMemory shm;
+
+  // Create and initialize
+  ASSERT_TRUE(shm.CreateFreezeable(1));
+  ASSERT_TRUE(shm.Map(1));
+  auto mem = reinterpret_cast<char*>(shm.memory());
+  ASSERT_TRUE(mem);
+  *mem = 'A';
+
+  // Re-create as read-only
+  auto handle = base::SharedMemory::NULLHandle();
+  ASSERT_TRUE(shm.GiveToProcess(base::GetCurrentProcId(), &handle));
+  ASSERT_TRUE(shm.IsHandleValid(handle));
+  ASSERT_FALSE(shm.IsValid());
+  ASSERT_TRUE(shm.SetHandle(handle, /* read-only */ true));
+  ASSERT_TRUE(shm.IsValid());
+
+  // Re-map
+  ASSERT_TRUE(shm.Map(1));
+  mem = reinterpret_cast<char*>(shm.memory());
+  ASSERT_EQ(*mem, 'A');
+
+  // Try to alter protection; should succeed, because not frozen
+  EXPECT_TRUE(ipc::SharedMemory::SystemProtectFallible(
+      mem, 1, ipc::SharedMemory::RightsReadWrite));
+}
+#endif
+
+}  // namespace mozilla
copy from ipc/moz.build
copy to ipc/gtest/moz.build
--- a/ipc/moz.build
+++ b/ipc/gtest/moz.build
@@ -1,23 +1,15 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-DIRS += [
-    'chromium',
-    'glue',
-    'ipdl',
-    'testshell',
+Library('ipctest')
+
+SOURCES += [
+    'TestSharedMemory.cpp',
 ]
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
-    DIRS += ['contentproc']
+include('/ipc/chromium/chromium-config.mozbuild')
 
-if CONFIG['OS_ARCH'] == 'WINNT':
-    DIRS += ['mscom']
-
-DIRS += ['app']
-
-with Files("**"):
-    BUG_COMPONENT = ("Core", "IPC")
+FINAL_LIBRARY = 'xul-gtest'
--- a/ipc/moz.build
+++ b/ipc/moz.build
@@ -1,23 +1,26 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
+    'app',
     'chromium',
     'glue',
     'ipdl',
     'testshell',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     DIRS += ['contentproc']
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['mscom']
 
-DIRS += ['app']
+TEST_DIRS += [
+    'gtest',
+]
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "IPC")