diff options
Diffstat (limited to 'ipc/gtest')
-rw-r--r-- | ipc/gtest/TestBigBuffer.cpp | 86 | ||||
-rw-r--r-- | ipc/gtest/TestDataPipe.cpp | 374 | ||||
-rw-r--r-- | ipc/gtest/TestLogging.cpp | 65 | ||||
-rw-r--r-- | ipc/gtest/TestRandomAccessStreamUtils.cpp | 216 | ||||
-rw-r--r-- | ipc/gtest/TestSharedMemory.cpp | 338 | ||||
-rw-r--r-- | ipc/gtest/TestUnsafeSharedMemoryHandle.cpp | 58 | ||||
-rw-r--r-- | ipc/gtest/moz.build | 20 |
7 files changed, 1157 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..52a8a8ead6 --- /dev/null +++ b/ipc/gtest/TestLogging.cpp @@ -0,0 +1,65 @@ +/* -*- 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("PContent", ParentSide, emptyFilter)); + EXPECT_FALSE(LoggingEnabledFor("PContent", ChildSide, emptyFilter)); +} + +TEST(IPCLogging, SingleProtocolFilter) +{ + const char* contentParentFilter = "PContentParent"; + EXPECT_TRUE(LoggingEnabledFor("PContent", ParentSide, contentParentFilter)); + EXPECT_FALSE(LoggingEnabledFor("PContent", ChildSide, contentParentFilter)); +} + +TEST(IPCLogging, CommaDelimitedProtocolsFilter) +{ + const char* gmpContentFilter = "PGMPContentChild,PGMPContentParent"; + EXPECT_TRUE(LoggingEnabledFor("PGMPContent", ChildSide, gmpContentFilter)); + EXPECT_TRUE(LoggingEnabledFor("PGMPContent", ParentSide, gmpContentFilter)); + EXPECT_FALSE(LoggingEnabledFor("PContent", ParentSide, gmpContentFilter)); + EXPECT_FALSE(LoggingEnabledFor("PContent", ChildSide, gmpContentFilter)); +} + +TEST(IPCLogging, SpaceDelimitedProtocolsFilter) +{ + const char* gmpContentFilter = "PGMPContentChild PGMPContentParent"; + EXPECT_TRUE(LoggingEnabledFor("PGMPContent", ChildSide, gmpContentFilter)); + EXPECT_TRUE(LoggingEnabledFor("PGMPContent", ParentSide, gmpContentFilter)); + EXPECT_FALSE(LoggingEnabledFor("PContent", ParentSide, gmpContentFilter)); + EXPECT_FALSE(LoggingEnabledFor("PContent", ChildSide, gmpContentFilter)); +} + +TEST(IPCLogging, CatchAllFilter) +{ + const char* catchAllFilter = "1"; + EXPECT_TRUE(LoggingEnabledFor("PGMPContent", ChildSide, catchAllFilter)); + EXPECT_TRUE(LoggingEnabledFor("PGMPContent", ParentSide, catchAllFilter)); + EXPECT_TRUE(LoggingEnabledFor("PContent", ParentSide, catchAllFilter)); + EXPECT_TRUE(LoggingEnabledFor("PContent", ChildSide, catchAllFilter)); +} + +TEST(IPCLogging, BothSidesFilter) +{ + const char* gmpContentFilter = "PGMPContent,PContentParent"; + EXPECT_TRUE(LoggingEnabledFor("PGMPContent", ChildSide, gmpContentFilter)); + EXPECT_TRUE(LoggingEnabledFor("PGMPContent", ParentSide, gmpContentFilter)); + EXPECT_TRUE(LoggingEnabledFor("PContent", ParentSide, gmpContentFilter)); + EXPECT_FALSE(LoggingEnabledFor("PContent", ChildSide, gmpContentFilter)); +} +#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" |