summaryrefslogtreecommitdiffstats
path: root/ipc/gtest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /ipc/gtest
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/gtest')
-rw-r--r--ipc/gtest/TestBigBuffer.cpp86
-rw-r--r--ipc/gtest/TestDataPipe.cpp374
-rw-r--r--ipc/gtest/TestLogging.cpp56
-rw-r--r--ipc/gtest/TestRandomAccessStreamUtils.cpp216
-rw-r--r--ipc/gtest/TestSharedMemory.cpp338
-rw-r--r--ipc/gtest/TestUnsafeSharedMemoryHandle.cpp58
-rw-r--r--ipc/gtest/moz.build20
7 files changed, 1148 insertions, 0 deletions
diff --git a/ipc/gtest/TestBigBuffer.cpp b/ipc/gtest/TestBigBuffer.cpp
new file mode 100644
index 0000000000..88c353d53d
--- /dev/null
+++ b/ipc/gtest/TestBigBuffer.cpp
@@ -0,0 +1,86 @@
+/* -*- 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 "chrome/common/ipc_message.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "gtest/gtest.h"
+
+#include "mozilla/RandomNum.h"
+#include "mozilla/ipc/BigBuffer.h"
+
+namespace mozilla::ipc {
+
+static bool SerializeAndDeserialize(BigBuffer&& aIn, BigBuffer* aOut) {
+ IPC::Message msg(MSG_ROUTING_NONE, 0);
+ {
+ IPC::MessageWriter writer(msg);
+ IPC::WriteParam(&writer, std::move(aIn));
+ }
+ EXPECT_EQ(aIn.Size(), 0u);
+
+ IPC::MessageReader reader(msg);
+ return IPC::ReadParam(&reader, aOut);
+}
+
+TEST(BigBuffer, Empty)
+{
+ BigBuffer in(0);
+ EXPECT_EQ(in.GetSharedMemory(), nullptr);
+ EXPECT_EQ(in.Size(), 0u);
+
+ BigBuffer out;
+ ASSERT_TRUE(SerializeAndDeserialize(std::move(in), &out));
+
+ EXPECT_EQ(in.GetSharedMemory(), nullptr);
+ EXPECT_EQ(in.Size(), 0u);
+ EXPECT_EQ(out.GetSharedMemory(), nullptr);
+ EXPECT_EQ(out.Size(), 0u);
+}
+
+TEST(BigBuffer, SmallSize)
+{
+ uint8_t data[]{1, 2, 3};
+ BigBuffer in{Span(data)};
+ EXPECT_EQ(in.GetSharedMemory(), nullptr);
+ EXPECT_EQ(in.Size(), 3u);
+
+ BigBuffer out;
+ ASSERT_TRUE(SerializeAndDeserialize(std::move(in), &out));
+
+ EXPECT_EQ(in.GetSharedMemory(), nullptr);
+ EXPECT_EQ(in.Size(), 0u);
+ EXPECT_EQ(out.GetSharedMemory(), nullptr);
+ EXPECT_EQ(out.Size(), 3u);
+
+ EXPECT_TRUE(out.AsSpan() == Span(data));
+}
+
+TEST(BigBuffer, BigSize)
+{
+ size_t size = BigBuffer::kShmemThreshold * 2;
+ // Generate a large block of random data which will be serialized in a shmem.
+ nsTArray<uint8_t> data(size);
+ for (size_t i = 0; i < size; ++i) {
+ data.AppendElement(mozilla::RandomUint64OrDie());
+ }
+ BigBuffer in{Span(data)};
+ EXPECT_NE(in.GetSharedMemory(), nullptr);
+ EXPECT_EQ(in.Size(), size);
+ EXPECT_EQ(in.GetSharedMemory()->memory(), in.Data());
+
+ BigBuffer out;
+ ASSERT_TRUE(SerializeAndDeserialize(std::move(in), &out));
+
+ EXPECT_EQ(in.GetSharedMemory(), nullptr);
+ EXPECT_EQ(in.Size(), 0u);
+ EXPECT_NE(out.GetSharedMemory(), nullptr);
+ EXPECT_EQ(out.Size(), size);
+ EXPECT_EQ(out.GetSharedMemory()->memory(), out.Data());
+
+ EXPECT_TRUE(out.AsSpan() == Span(data));
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/gtest/TestDataPipe.cpp b/ipc/gtest/TestDataPipe.cpp
new file mode 100644
index 0000000000..340da7c218
--- /dev/null
+++ b/ipc/gtest/TestDataPipe.cpp
@@ -0,0 +1,374 @@
+/* -*- 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 "chrome/common/ipc_message.h"
+#include "gtest/gtest.h"
+
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/ipc/DataPipe.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla::ipc {
+
+namespace {
+
+struct InputStreamCallback : public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit InputStreamCallback(
+ std::function<nsresult(nsIAsyncInputStream*)> aFunc = nullptr)
+ : mFunc(std::move(aFunc)) {}
+
+ NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream* aStream) override {
+ MOZ_ALWAYS_FALSE(mCalled.exchange(true));
+ return mFunc ? mFunc(aStream) : NS_OK;
+ }
+
+ bool Called() const { return mCalled; }
+
+ private:
+ virtual ~InputStreamCallback() = default;
+
+ std::atomic<bool> mCalled = false;
+ std::function<nsresult(nsIAsyncInputStream*)> mFunc;
+};
+
+NS_IMPL_ISUPPORTS(InputStreamCallback, nsIInputStreamCallback)
+
+struct OutputStreamCallback : public nsIOutputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit OutputStreamCallback(
+ std::function<nsresult(nsIAsyncOutputStream*)> aFunc = nullptr)
+ : mFunc(std::move(aFunc)) {}
+
+ NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream* aStream) override {
+ MOZ_ALWAYS_FALSE(mCalled.exchange(true));
+ return mFunc ? mFunc(aStream) : NS_OK;
+ }
+
+ bool Called() const { return mCalled; }
+
+ private:
+ virtual ~OutputStreamCallback() = default;
+
+ std::atomic<bool> mCalled = false;
+ std::function<nsresult(nsIAsyncOutputStream*)> mFunc;
+};
+
+NS_IMPL_ISUPPORTS(OutputStreamCallback, nsIOutputStreamCallback)
+
+// Populate an array with the given number of bytes. Data is lorem ipsum
+// random text, but deterministic across multiple calls.
+void CreateData(uint32_t aNumBytes, nsCString& aDataOut) {
+ static const char data[] =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec egestas "
+ "purus eu condimentum iaculis. In accumsan leo eget odio porttitor, non "
+ "rhoncus nulla vestibulum. Etiam lacinia consectetur nisl nec "
+ "sollicitudin. Sed fringilla accumsan diam, pulvinar varius massa. Duis "
+ "mollis dignissim felis, eget tempus nisi tristique ut. Fusce euismod, "
+ "lectus non lacinia tempor, tellus diam suscipit quam, eget hendrerit "
+ "lacus nunc fringilla ante. Sed ultrices massa vitae risus molestie, ut "
+ "finibus quam laoreet nullam.";
+ static const uint32_t dataLength = sizeof(data) - 1;
+
+ aDataOut.SetCapacity(aNumBytes);
+
+ while (aNumBytes > 0) {
+ uint32_t amount = std::min(dataLength, aNumBytes);
+ aDataOut.Append(data, amount);
+ aNumBytes -= amount;
+ }
+}
+
+// Synchronously consume the given input stream and validate the resulting data
+// against the given string of expected values.
+void ConsumeAndValidateStream(nsIInputStream* aStream,
+ const nsACString& aExpectedData) {
+ nsAutoCString outputData;
+ nsresult rv = NS_ConsumeStream(aStream, UINT32_MAX, outputData);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(aExpectedData.Length(), outputData.Length());
+ ASSERT_TRUE(aExpectedData.Equals(outputData));
+}
+
+} // namespace
+
+TEST(DataPipe, SegmentedReadWrite)
+{
+ RefPtr<DataPipeReceiver> reader;
+ RefPtr<DataPipeSender> writer;
+
+ nsresult rv =
+ NewDataPipe(1024, getter_AddRefs(writer), getter_AddRefs(reader));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCString inputData1;
+ CreateData(512, inputData1);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData1.BeginReading(), inputData1.Length(),
+ &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(numWritten, 512u);
+
+ uint64_t available = 0;
+ rv = reader->Available(&available);
+ EXPECT_EQ(available, 512u);
+ ConsumeAndValidateStream(reader, inputData1);
+
+ nsCString inputData2;
+ CreateData(1024, inputData2);
+
+ rv = writer->Write(inputData2.BeginReading(), inputData2.Length(),
+ &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(numWritten, 1024u);
+
+ rv = reader->Available(&available);
+ EXPECT_EQ(available, 1024u);
+ ConsumeAndValidateStream(reader, inputData2);
+}
+
+TEST(DataPipe, SegmentedPartialRead)
+{
+ RefPtr<DataPipeReceiver> reader;
+ RefPtr<DataPipeSender> writer;
+
+ nsresult rv =
+ NewDataPipe(1024, getter_AddRefs(writer), getter_AddRefs(reader));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCString inputData1;
+ CreateData(512, inputData1);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData1.BeginReading(), inputData1.Length(),
+ &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(numWritten, 512u);
+
+ uint64_t available = 0;
+ rv = reader->Available(&available);
+ EXPECT_EQ(available, 512u);
+ ConsumeAndValidateStream(reader, inputData1);
+
+ nsCString inputData2;
+ CreateData(1024, inputData2);
+
+ rv = writer->Write(inputData2.BeginReading(), inputData2.Length(),
+ &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(numWritten, 1024u);
+
+ rv = reader->Available(&available);
+ EXPECT_EQ(available, 1024u);
+
+ nsAutoCString outputData;
+ rv = NS_ReadInputStreamToString(reader, outputData, 768);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(768u, outputData.Length());
+ ASSERT_TRUE(Substring(inputData2, 0, 768).Equals(outputData));
+
+ rv = reader->Available(&available);
+ EXPECT_EQ(available, 256u);
+
+ nsAutoCString outputData2;
+ rv = NS_ReadInputStreamToString(reader, outputData2, 256);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(256u, outputData2.Length());
+ ASSERT_TRUE(Substring(inputData2, 768).Equals(outputData2));
+}
+
+TEST(DataPipe, Write_AsyncWait)
+{
+ RefPtr<DataPipeReceiver> reader;
+ RefPtr<DataPipeSender> writer;
+
+ const uint32_t segmentSize = 1024;
+
+ nsresult rv =
+ NewDataPipe(segmentSize, getter_AddRefs(writer), getter_AddRefs(reader));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCString inputData;
+ CreateData(segmentSize, inputData);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.BeginReading(), inputData.Length(), &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(numWritten, segmentSize);
+
+ rv = writer->Write(inputData.BeginReading(), inputData.Length(), &numWritten);
+ ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv);
+
+ RefPtr<OutputStreamCallback> cb = new OutputStreamCallback();
+
+ rv = writer->AsyncWait(cb, 0, 0, GetCurrentSerialEventTarget());
+ ASSERT_NS_SUCCEEDED(rv);
+
+ NS_ProcessPendingEvents(nullptr);
+
+ ASSERT_FALSE(cb->Called());
+
+ ConsumeAndValidateStream(reader, inputData);
+
+ ASSERT_FALSE(cb->Called());
+
+ NS_ProcessPendingEvents(nullptr);
+
+ ASSERT_TRUE(cb->Called());
+}
+
+TEST(DataPipe, Read_AsyncWait)
+{
+ RefPtr<DataPipeReceiver> reader;
+ RefPtr<DataPipeSender> writer;
+
+ const uint32_t segmentSize = 1024;
+
+ nsresult rv =
+ NewDataPipe(segmentSize, getter_AddRefs(writer), getter_AddRefs(reader));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCString inputData;
+ CreateData(segmentSize, inputData);
+
+ RefPtr<InputStreamCallback> cb = new InputStreamCallback();
+
+ rv = reader->AsyncWait(cb, 0, 0, GetCurrentSerialEventTarget());
+ ASSERT_NS_SUCCEEDED(rv);
+
+ NS_ProcessPendingEvents(nullptr);
+
+ ASSERT_FALSE(cb->Called());
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.BeginReading(), inputData.Length(), &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ASSERT_FALSE(cb->Called());
+
+ NS_ProcessPendingEvents(nullptr);
+
+ ASSERT_TRUE(cb->Called());
+
+ ConsumeAndValidateStream(reader, inputData);
+}
+
+TEST(DataPipe, Write_AsyncWait_Cancel)
+{
+ RefPtr<DataPipeReceiver> reader;
+ RefPtr<DataPipeSender> writer;
+
+ const uint32_t segmentSize = 1024;
+
+ nsresult rv =
+ NewDataPipe(segmentSize, getter_AddRefs(writer), getter_AddRefs(reader));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCString inputData;
+ CreateData(segmentSize, inputData);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.BeginReading(), inputData.Length(), &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+ EXPECT_EQ(numWritten, segmentSize);
+
+ rv = writer->Write(inputData.BeginReading(), inputData.Length(), &numWritten);
+ ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv);
+
+ RefPtr<OutputStreamCallback> cb = new OutputStreamCallback();
+
+ // Register a callback and immediately cancel it.
+ rv = writer->AsyncWait(cb, 0, 0, GetCurrentSerialEventTarget());
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = writer->AsyncWait(nullptr, 0, 0, nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Even after consuming the stream and processing pending events, the callback
+ // shouldn't be called as it was cancelled.
+ ConsumeAndValidateStream(reader, inputData);
+ NS_ProcessPendingEvents(nullptr);
+ ASSERT_FALSE(cb->Called());
+}
+
+TEST(DataPipe, Read_AsyncWait_Cancel)
+{
+ RefPtr<DataPipeReceiver> reader;
+ RefPtr<DataPipeSender> writer;
+
+ const uint32_t segmentSize = 1024;
+
+ nsresult rv =
+ NewDataPipe(segmentSize, getter_AddRefs(writer), getter_AddRefs(reader));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCString inputData;
+ CreateData(segmentSize, inputData);
+
+ RefPtr<InputStreamCallback> cb = new InputStreamCallback();
+
+ // Register a callback and immediately cancel it.
+ rv = reader->AsyncWait(cb, 0, 0, GetCurrentSerialEventTarget());
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = reader->AsyncWait(nullptr, 0, 0, nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Write data into the pipe to make the callback become ready.
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.BeginReading(), inputData.Length(), &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Even after processing pending events, the callback shouldn't be called as
+ // it was cancelled.
+ NS_ProcessPendingEvents(nullptr);
+ ASSERT_FALSE(cb->Called());
+
+ ConsumeAndValidateStream(reader, inputData);
+}
+
+TEST(DataPipe, SerializeReader)
+{
+ RefPtr<DataPipeReceiver> reader;
+ RefPtr<DataPipeSender> writer;
+ nsresult rv =
+ NewDataPipe(1024, getter_AddRefs(writer), getter_AddRefs(reader));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ IPC::Message msg(MSG_ROUTING_NONE, 0);
+ IPC::MessageWriter msgWriter(msg);
+ IPC::WriteParam(&msgWriter, reader);
+
+ uint64_t available = 0;
+ rv = reader->Available(&available);
+ ASSERT_NS_FAILED(rv);
+
+ nsCString inputData;
+ CreateData(512, inputData);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.BeginReading(), inputData.Length(), &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ RefPtr<DataPipeReceiver> reader2;
+ IPC::MessageReader msgReader(msg);
+ ASSERT_TRUE(IPC::ReadParam(&msgReader, &reader2));
+ ASSERT_TRUE(reader2);
+
+ rv = reader2->Available(&available);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(available, 512u);
+ ConsumeAndValidateStream(reader2, inputData);
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/gtest/TestLogging.cpp b/ipc/gtest/TestLogging.cpp
new file mode 100644
index 0000000000..ef9c063096
--- /dev/null
+++ b/ipc/gtest/TestLogging.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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 "mozilla/ipc/ProtocolUtils.h"
+
+namespace mozilla::ipc {
+
+#if defined(DEBUG) || defined(FUZZING)
+TEST(IPCLogging, EmptyFilter)
+{
+ const char* emptyFilter = "";
+ EXPECT_FALSE(LoggingEnabledFor("PContentParent", emptyFilter));
+ EXPECT_FALSE(LoggingEnabledFor("PContentChild", emptyFilter));
+}
+
+TEST(IPCLogging, SingleProtocolFilter)
+{
+ const char* contentParentFilter = "PContentParent";
+ EXPECT_TRUE(LoggingEnabledFor("PContentParent", contentParentFilter));
+ EXPECT_FALSE(LoggingEnabledFor("PContentChild", contentParentFilter));
+}
+
+TEST(IPCLogging, CommaDelimitedProtocolsFilter)
+{
+ const char* gmpContentFilter = "PGMPContentChild,PGMPContentParent";
+ EXPECT_TRUE(LoggingEnabledFor("PGMPContentChild", gmpContentFilter));
+ EXPECT_TRUE(LoggingEnabledFor("PGMPContentParent", gmpContentFilter));
+ EXPECT_FALSE(LoggingEnabledFor("PContentParent", gmpContentFilter));
+ EXPECT_FALSE(LoggingEnabledFor("PContentChild", gmpContentFilter));
+}
+
+TEST(IPCLogging, SpaceDelimitedProtocolsFilter)
+{
+ const char* gmpContentFilter = "PGMPContentChild PGMPContentParent";
+ EXPECT_TRUE(LoggingEnabledFor("PGMPContentChild", gmpContentFilter));
+ EXPECT_TRUE(LoggingEnabledFor("PGMPContentParent", gmpContentFilter));
+ EXPECT_FALSE(LoggingEnabledFor("PContentParent", gmpContentFilter));
+ EXPECT_FALSE(LoggingEnabledFor("PContentChild", gmpContentFilter));
+}
+
+TEST(IPCLogging, CatchAllFilter)
+{
+ const char* catchAllFilter = "1";
+ EXPECT_TRUE(LoggingEnabledFor("PGMPContentChild", catchAllFilter));
+ EXPECT_TRUE(LoggingEnabledFor("PGMPContentParent", catchAllFilter));
+ EXPECT_TRUE(LoggingEnabledFor("PContentParent", catchAllFilter));
+ EXPECT_TRUE(LoggingEnabledFor("PContentChild", catchAllFilter));
+}
+#endif // defined(DEBUG) || defined(FUZZING)
+
+} // namespace mozilla::ipc
diff --git a/ipc/gtest/TestRandomAccessStreamUtils.cpp b/ipc/gtest/TestRandomAccessStreamUtils.cpp
new file mode 100644
index 0000000000..899c528a30
--- /dev/null
+++ b/ipc/gtest/TestRandomAccessStreamUtils.cpp
@@ -0,0 +1,216 @@
+/* -*- 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 "chrome/common/ipc_message.h"
+#include "gtest/gtest.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/ipc/RandomAccessStreamParams.h"
+#include "mozilla/ipc/RandomAccessStreamUtils.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsCOMPtr.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsIRandomAccessStream.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla::ipc {
+
+namespace {
+
+Result<nsCOMPtr<nsIRandomAccessStream>, nsresult> CreateFileStream() {
+ nsCOMPtr<nsIFile> dir;
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(dir));
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = dir->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ rv = file->Append(u"testfile"_ns);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ nsCOMPtr<nsIRandomAccessStream> stream;
+ rv = NS_NewLocalFileRandomAccessStream(getter_AddRefs(stream), file);
+ if (NS_FAILED(rv)) {
+ return Err(rv);
+ }
+
+ return stream;
+}
+
+// Populate an array with the given number of bytes. Data is lorem ipsum
+// random text, but deterministic across multiple calls.
+void CreateData(uint32_t aNumBytes, nsCString& aDataOut) {
+ static const char data[] =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec egestas "
+ "purus eu condimentum iaculis. In accumsan leo eget odio porttitor, non "
+ "rhoncus nulla vestibulum. Etiam lacinia consectetur nisl nec "
+ "sollicitudin. Sed fringilla accumsan diam, pulvinar varius massa. Duis "
+ "mollis dignissim felis, eget tempus nisi tristique ut. Fusce euismod, "
+ "lectus non lacinia tempor, tellus diam suscipit quam, eget hendrerit "
+ "lacus nunc fringilla ante. Sed ultrices massa vitae risus molestie, ut "
+ "finibus quam laoreet nullam.";
+ static const uint32_t dataLength = sizeof(data) - 1;
+
+ aDataOut.SetCapacity(aNumBytes);
+
+ while (aNumBytes > 0) {
+ uint32_t amount = std::min(dataLength, aNumBytes);
+ aDataOut.Append(data, amount);
+ aNumBytes -= amount;
+ }
+}
+
+// Synchronously consume the given input stream and validate the resulting data
+// against the given string of expected values.
+void ConsumeAndValidateStream(nsIInputStream* aStream,
+ const nsACString& aExpectedData) {
+ uint64_t available = 0;
+ nsresult rv = aStream->Available(&available);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(available, aExpectedData.Length());
+
+ nsAutoCString outputData;
+ rv = NS_ConsumeStream(aStream, UINT32_MAX, outputData);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ASSERT_EQ(aExpectedData.Length(), outputData.Length());
+ ASSERT_TRUE(aExpectedData.Equals(outputData));
+}
+
+} // namespace
+
+TEST(RandomAccessStreamUtils, NullRandomAccessStream_MaybeSerialize)
+{
+ nsCOMPtr<nsIRandomAccessStream> stream;
+
+ Maybe<RandomAccessStreamParams> streamParams =
+ SerializeRandomAccessStream(stream, nullptr);
+
+ ASSERT_TRUE(streamParams.isNothing());
+
+ auto res = DeserializeRandomAccessStream(streamParams);
+ ASSERT_TRUE(res.isOk());
+
+ nsCOMPtr<nsIRandomAccessStream> stream2 = res.unwrap();
+ ASSERT_EQ(stream2, nullptr);
+}
+
+TEST(RandomAccessStreamUtils, FileRandomAccessStream_Serialize)
+{
+ const uint32_t dataSize = 256;
+
+ auto res = CreateFileStream();
+ ASSERT_TRUE(res.isOk());
+
+ auto stream = res.unwrap();
+ ASSERT_TRUE(stream);
+
+ nsCOMPtr<nsIFileRandomAccessStream> fileStream = do_QueryInterface(stream);
+ ASSERT_TRUE(fileStream);
+
+ nsCString inputData;
+ CreateData(dataSize, inputData);
+
+ uint32_t numWritten = 0;
+ nsresult rv = stream->OutputStream()->Write(inputData.BeginReading(),
+ inputData.Length(), &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(numWritten, dataSize);
+
+ RandomAccessStreamParams streamParams = SerializeRandomAccessStream(
+ WrapMovingNotNullUnchecked(std::move(stream)), nullptr);
+
+ ASSERT_EQ(streamParams.type(),
+ RandomAccessStreamParams::TFileRandomAccessStreamParams);
+
+ auto res2 = DeserializeRandomAccessStream(streamParams);
+ ASSERT_TRUE(res2.isOk());
+
+ NotNull<nsCOMPtr<nsIRandomAccessStream>> stream2 = res2.unwrap();
+
+ nsCOMPtr<nsIFileRandomAccessStream> fileStream2 =
+ do_QueryInterface(stream2.get());
+ ASSERT_TRUE(fileStream2);
+
+ int64_t offset;
+ rv = stream2->Tell(&offset);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(offset, dataSize);
+
+ rv = stream2->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ConsumeAndValidateStream(stream2->InputStream(), inputData);
+}
+
+TEST(RandomAccessStreamUtils, FileRandomAccessStream_MaybeSerialize)
+{
+ const uint32_t dataSize = 512;
+
+ auto res = CreateFileStream();
+ ASSERT_TRUE(res.isOk());
+
+ auto stream = res.unwrap();
+ ASSERT_TRUE(stream);
+
+ nsCOMPtr<nsIFileRandomAccessStream> fileStream = do_QueryInterface(stream);
+ ASSERT_TRUE(fileStream);
+
+ nsCString inputData;
+ CreateData(dataSize, inputData);
+
+ uint32_t numWritten = 0;
+ nsresult rv = stream->OutputStream()->Write(inputData.BeginReading(),
+ inputData.Length(), &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(numWritten, dataSize);
+
+ Maybe<RandomAccessStreamParams> streamParams =
+ SerializeRandomAccessStream(stream, nullptr);
+
+ ASSERT_TRUE(streamParams);
+ ASSERT_EQ(streamParams->type(),
+ RandomAccessStreamParams::TFileRandomAccessStreamParams);
+
+ auto res2 = DeserializeRandomAccessStream(streamParams);
+ ASSERT_TRUE(res2.isOk());
+
+ nsCOMPtr<nsIRandomAccessStream> stream2 = res2.unwrap();
+ ASSERT_TRUE(stream2);
+
+ nsCOMPtr<nsIFileRandomAccessStream> fileStream2 = do_QueryInterface(stream2);
+ ASSERT_TRUE(fileStream2);
+
+ int64_t offset;
+ rv = stream2->Tell(&offset);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(offset, dataSize);
+
+ rv = stream2->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ConsumeAndValidateStream(stream2->InputStream(), inputData);
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/gtest/TestSharedMemory.cpp b/ipc/gtest/TestSharedMemory.cpp
new file mode 100644
index 0000000000..5d27bd81d0
--- /dev/null
+++ b/ipc/gtest/TestSharedMemory.cpp
@@ -0,0 +1,338 @@
+/* -*- 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 "mozilla/RefPtr.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/ipc/SharedMemoryBasic.h"
+
+#ifdef XP_LINUX
+# include <errno.h>
+# include <linux/magic.h>
+# include <stdio.h>
+# include <string.h>
+# include <sys/statfs.h>
+# include <sys/utsname.h>
+#endif
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+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 = shm.TakeHandle();
+ ASSERT_TRUE(shm.IsHandleValid(handle));
+ ASSERT_FALSE(shm.IsValid());
+ ASSERT_TRUE(shm.SetHandle(std::move(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 = shm.TakeHandle();
+ ASSERT_TRUE(shm.IsHandleValid(handle));
+ ASSERT_FALSE(shm.IsValid());
+ ASSERT_TRUE(shm.SetHandle(std::move(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
+
+#ifdef XP_WIN
+// Try to regain write permissions on a read-only handle using
+// DuplicateHandle; this will succeed if the object has no DACL.
+// See also https://crbug.com/338538
+TEST(IPCSharedMemory, WinUnfreeze)
+{
+ 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());
+
+ // Extract handle.
+ auto handle = shm.TakeHandle();
+ ASSERT_TRUE(shm.IsHandleValid(handle));
+ ASSERT_FALSE(shm.IsValid());
+
+ // Unfreeze.
+ HANDLE newHandle = INVALID_HANDLE_VALUE;
+ bool unfroze = ::DuplicateHandle(
+ GetCurrentProcess(), handle.release(), GetCurrentProcess(), &newHandle,
+ FILE_MAP_ALL_ACCESS, false, DUPLICATE_CLOSE_SOURCE);
+ ASSERT_FALSE(unfroze);
+}
+#endif
+
+// Test that a read-only copy sees changes made to the writeable
+// mapping in the case that the page wasn't accessed before the copy.
+TEST(IPCSharedMemory, ROCopyAndWrite)
+{
+ base::SharedMemory shmRW, shmRO;
+
+ // Create and initialize
+ ASSERT_TRUE(shmRW.CreateFreezeable(1));
+ ASSERT_TRUE(shmRW.Map(1));
+ auto memRW = reinterpret_cast<char*>(shmRW.memory());
+ ASSERT_TRUE(memRW);
+
+ // Create read-only copy
+ ASSERT_TRUE(shmRW.ReadOnlyCopy(&shmRO));
+ EXPECT_FALSE(shmRW.IsValid());
+ ASSERT_EQ(shmRW.memory(), memRW);
+ ASSERT_EQ(shmRO.max_size(), size_t(1));
+
+ // Map read-only
+ ASSERT_TRUE(shmRO.IsValid());
+ ASSERT_TRUE(shmRO.Map(1));
+ auto memRO = reinterpret_cast<const char*>(shmRO.memory());
+ ASSERT_TRUE(memRO);
+ ASSERT_NE(memRW, memRO);
+
+ // Check
+ *memRW = 'A';
+ EXPECT_EQ(*memRO, 'A');
+}
+
+// Test that a read-only copy sees changes made to the writeable
+// mapping in the case that the page was accessed before the copy
+// (and, before that, sees the state as of when the copy was made).
+TEST(IPCSharedMemory, ROCopyAndRewrite)
+{
+ base::SharedMemory shmRW, shmRO;
+
+ // Create and initialize
+ ASSERT_TRUE(shmRW.CreateFreezeable(1));
+ ASSERT_TRUE(shmRW.Map(1));
+ auto memRW = reinterpret_cast<char*>(shmRW.memory());
+ ASSERT_TRUE(memRW);
+ *memRW = 'A';
+
+ // Create read-only copy
+ ASSERT_TRUE(shmRW.ReadOnlyCopy(&shmRO));
+ EXPECT_FALSE(shmRW.IsValid());
+ ASSERT_EQ(shmRW.memory(), memRW);
+ ASSERT_EQ(shmRO.max_size(), size_t(1));
+
+ // Map read-only
+ ASSERT_TRUE(shmRO.IsValid());
+ ASSERT_TRUE(shmRO.Map(1));
+ auto memRO = reinterpret_cast<const char*>(shmRO.memory());
+ ASSERT_TRUE(memRO);
+ ASSERT_NE(memRW, memRO);
+
+ // Check
+ ASSERT_EQ(*memRW, 'A');
+ EXPECT_EQ(*memRO, 'A');
+ *memRW = 'X';
+ EXPECT_EQ(*memRO, 'X');
+}
+
+// See FreezeAndMapRW.
+TEST(IPCSharedMemory, ROCopyAndMapRW)
+{
+ base::SharedMemory shmRW, shmRO;
+
+ // Create and initialize
+ ASSERT_TRUE(shmRW.CreateFreezeable(1));
+ ASSERT_TRUE(shmRW.Map(1));
+ auto memRW = reinterpret_cast<char*>(shmRW.memory());
+ ASSERT_TRUE(memRW);
+ *memRW = 'A';
+
+ // Create read-only copy
+ ASSERT_TRUE(shmRW.ReadOnlyCopy(&shmRO));
+ ASSERT_TRUE(shmRO.IsValid());
+
+ // Re-create as writeable
+ auto handle = shmRO.TakeHandle();
+ ASSERT_TRUE(shmRO.IsHandleValid(handle));
+ ASSERT_FALSE(shmRO.IsValid());
+ ASSERT_TRUE(shmRO.SetHandle(std::move(handle), /* read-only */ false));
+ ASSERT_TRUE(shmRO.IsValid());
+
+ // This should fail
+ EXPECT_FALSE(shmRO.Map(1));
+}
+
+// See FreezeAndReprotect
+TEST(IPCSharedMemory, ROCopyAndReprotect)
+{
+ base::SharedMemory shmRW, shmRO;
+
+ // Create and initialize
+ ASSERT_TRUE(shmRW.CreateFreezeable(1));
+ ASSERT_TRUE(shmRW.Map(1));
+ auto memRW = reinterpret_cast<char*>(shmRW.memory());
+ ASSERT_TRUE(memRW);
+ *memRW = 'A';
+
+ // Create read-only copy
+ ASSERT_TRUE(shmRW.ReadOnlyCopy(&shmRO));
+ ASSERT_TRUE(shmRO.IsValid());
+
+ // Re-map
+ ASSERT_TRUE(shmRO.Map(1));
+ auto memRO = reinterpret_cast<char*>(shmRO.memory());
+ ASSERT_EQ(*memRO, 'A');
+
+ // Try to alter protection; should fail
+ EXPECT_FALSE(ipc::SharedMemory::SystemProtectFallible(
+ memRO, 1, ipc::SharedMemory::RightsReadWrite));
+}
+
+TEST(IPCSharedMemory, IsZero)
+{
+ base::SharedMemory shm;
+
+ static constexpr size_t kSize = 65536;
+ ASSERT_TRUE(shm.Create(kSize));
+ ASSERT_TRUE(shm.Map(kSize));
+
+ auto* mem = reinterpret_cast<char*>(shm.memory());
+ for (size_t i = 0; i < kSize; ++i) {
+ ASSERT_EQ(mem[i], 0) << "offset " << i;
+ }
+}
+
+#ifndef FUZZING
+TEST(IPCSharedMemory, BasicIsZero)
+{
+ auto shm = MakeRefPtr<ipc::SharedMemoryBasic>();
+
+ static constexpr size_t kSize = 65536;
+ ASSERT_TRUE(shm->Create(kSize));
+ ASSERT_TRUE(shm->Map(kSize));
+
+ auto* mem = reinterpret_cast<char*>(shm->memory());
+ for (size_t i = 0; i < kSize; ++i) {
+ ASSERT_EQ(mem[i], 0) << "offset " << i;
+ }
+}
+#endif
+
+#if defined(XP_LINUX) && !defined(ANDROID)
+// Test that memfd_create is used where expected.
+//
+// More precisely: if memfd_create support is expected, verify that
+// shared memory isn't subject to a filesystem size limit.
+TEST(IPCSharedMemory, IsMemfd)
+{
+ static constexpr int kMajor = 3;
+ static constexpr int kMinor = 17;
+
+ struct utsname uts;
+ ASSERT_EQ(uname(&uts), 0) << strerror(errno);
+ ASSERT_STREQ(uts.sysname, "Linux");
+ int major, minor;
+ ASSERT_EQ(sscanf(uts.release, "%d.%d", &major, &minor), 2);
+ bool expectMemfd = major > kMajor || (major == kMajor && minor >= kMinor);
+
+ base::SharedMemory shm;
+ ASSERT_TRUE(shm.Create(1));
+ UniqueFileHandle fd = shm.TakeHandle();
+ ASSERT_TRUE(fd);
+
+ struct statfs fs;
+ ASSERT_EQ(fstatfs(fd.get(), &fs), 0) << strerror(errno);
+ EXPECT_EQ(fs.f_type, TMPFS_MAGIC);
+ static constexpr decltype(fs.f_blocks) kNoLimit = 0;
+ if (expectMemfd) {
+ EXPECT_EQ(fs.f_blocks, kNoLimit);
+ } else {
+ // On older kernels, we expect the memfd / no-limit test to fail.
+ // (In theory it could succeed if backported memfd support exists;
+ // if that ever happens, this check can be removed.)
+ EXPECT_NE(fs.f_blocks, kNoLimit);
+ }
+}
+#endif
+
+} // namespace mozilla
diff --git a/ipc/gtest/TestUnsafeSharedMemoryHandle.cpp b/ipc/gtest/TestUnsafeSharedMemoryHandle.cpp
new file mode 100644
index 0000000000..6934ae0d4c
--- /dev/null
+++ b/ipc/gtest/TestUnsafeSharedMemoryHandle.cpp
@@ -0,0 +1,58 @@
+/* -*- 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 "chrome/common/ipc_message.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "gtest/gtest.h"
+
+#include "mozilla/RandomNum.h"
+#include "mozilla/ipc/RawShmem.h"
+
+namespace mozilla::ipc {
+
+static bool SerializeAndDeserialize(UnsafeSharedMemoryHandle&& aIn,
+ UnsafeSharedMemoryHandle* aOut) {
+ IPC::Message msg(MSG_ROUTING_NONE, 0);
+ {
+ IPC::MessageWriter writer(msg);
+ IPC::WriteParam(&writer, std::move(aIn));
+ }
+
+ IPC::MessageReader reader(msg);
+ return IPC::ReadParam(&reader, aOut);
+}
+
+void TestUnsafeSharedMemoryHandle(size_t aSize) {
+ auto maybeHandle = UnsafeSharedMemoryHandle::CreateAndMap(aSize);
+ ASSERT_TRUE(maybeHandle);
+ auto [handle, mapping] = maybeHandle.extract();
+
+ EXPECT_EQ(mapping.Size(), aSize);
+
+ auto inBytes = mapping.Bytes();
+ for (size_t i = 0; i < aSize; ++i) {
+ inBytes[i] = static_cast<uint8_t>(i % 255);
+ }
+
+ UnsafeSharedMemoryHandle out;
+ ASSERT_TRUE(SerializeAndDeserialize(std::move(handle), &out));
+
+ auto mapping2 = WritableSharedMemoryMapping::Open(std::move(out)).value();
+
+ EXPECT_EQ(mapping2.Size(), aSize);
+ auto outBytes = mapping2.Bytes();
+ for (size_t i = 0; i < aSize; ++i) {
+ EXPECT_EQ(outBytes[i], static_cast<uint8_t>(i % 255));
+ }
+}
+
+TEST(UnsafeSharedMemoryHandle, Empty)
+{ TestUnsafeSharedMemoryHandle(0); }
+
+TEST(UnsafeSharedMemoryHandle, SmallSize)
+{ TestUnsafeSharedMemoryHandle(2048); }
+
+} // namespace mozilla::ipc
diff --git a/ipc/gtest/moz.build b/ipc/gtest/moz.build
new file mode 100644
index 0000000000..e56b0754ec
--- /dev/null
+++ b/ipc/gtest/moz.build
@@ -0,0 +1,20 @@
+# -*- 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 += [
+ "TestBigBuffer.cpp",
+ "TestDataPipe.cpp",
+ "TestLogging.cpp",
+ "TestRandomAccessStreamUtils.cpp",
+ "TestSharedMemory.cpp",
+ "TestUnsafeSharedMemoryHandle.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"