diff --git a/ipc/glue/SharedMemory.h b/ipc/glue/SharedMemory.h index 0fa3e3912e38..5f180f67be58 100644 --- a/ipc/glue/SharedMemory.h +++ b/ipc/glue/SharedMemory.h @@ -83,6 +83,8 @@ class SharedMemory { 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); diff --git a/ipc/glue/SharedMemory_posix.cpp b/ipc/glue/SharedMemory_posix.cpp index 62b5a9a3c541..b540d987f304 100644 --- a/ipc/glue/SharedMemory_posix.cpp +++ b/ipc/glue/SharedMemory_posix.cpp @@ -13,12 +13,19 @@ 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); } diff --git a/ipc/glue/SharedMemory_windows.cpp b/ipc/glue/SharedMemory_windows.cpp index 00cc3b09d2ab..1cc93687af38 100644 --- a/ipc/glue/SharedMemory_windows.cpp +++ b/ipc/glue/SharedMemory_windows.cpp @@ -12,6 +12,13 @@ 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; @@ -21,8 +28,7 @@ void SharedMemory::SystemProtect(char* aAddr, size_t aSize, int aRights) { 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() { diff --git a/ipc/gtest/TestSharedMemory.cpp b/ipc/gtest/TestSharedMemory.cpp new file mode 100644 index 000000000000..fce72aa032a6 --- /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(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(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(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(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(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 diff --git a/ipc/gtest/moz.build b/ipc/gtest/moz.build new file mode 100644 index 000000000000..af9ef5ca3925 --- /dev/null +++ b/ipc/gtest/moz.build @@ -0,0 +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/. + +Library('ipctest') + +SOURCES += [ + 'TestSharedMemory.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul-gtest' diff --git a/ipc/moz.build b/ipc/moz.build index ac5a0554ad1e..1afc94f22eda 100644 --- a/ipc/moz.build +++ b/ipc/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DIRS += [ + 'app', 'chromium', 'glue', 'ipdl', @@ -17,7 +18,9 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android': if CONFIG['OS_ARCH'] == 'WINNT': DIRS += ['mscom'] -DIRS += ['app'] +TEST_DIRS += [ + 'gtest', +] with Files("**"): BUG_COMPONENT = ("Core", "IPC")