From bc75bd470140c4915a998ac7a1b552e0b6a0a57e Mon Sep 17 00:00:00 2001 From: Valentin Gosu Date: Thu, 10 Aug 2023 12:42:00 +0000 Subject: [PATCH] Bug 1798631 - Add zip reader fuzzer r=decoder,necko-reviewers,kershaw Add back nsIZipReader.openMemory - Backed out changeset 1b442368d567 Differential Revision: https://phabricator.services.mozilla.com/D162239 --- modules/libjar/moz.build | 1 + modules/libjar/nsIZipReader.idl | 10 ++ modules/libjar/nsJAR.cpp | 15 +++ modules/libjar/nsZipArchive.cpp | 14 ++ modules/libjar/nsZipArchive.h | 1 + netwerk/test/fuzz/TestJARFuzzing.cpp | 187 +++++++++++++++++++++++++++ netwerk/test/fuzz/moz.build | 1 + 7 files changed, 229 insertions(+) create mode 100644 netwerk/test/fuzz/TestJARFuzzing.cpp diff --git a/modules/libjar/moz.build b/modules/libjar/moz.build index ccd2e2f4228a..abd7c0b9b502 100644 --- a/modules/libjar/moz.build +++ b/modules/libjar/moz.build @@ -43,5 +43,6 @@ XPCOM_MANIFESTS += [ ] include("/ipc/chromium/chromium-config.mozbuild") +include("/tools/fuzzing/libfuzzer-config.mozbuild") FINAL_LIBRARY = "xul" diff --git a/modules/libjar/nsIZipReader.idl b/modules/libjar/nsIZipReader.idl index 7c00a7074e36..7b3bf2bf9ee7 100644 --- a/modules/libjar/nsIZipReader.idl +++ b/modules/libjar/nsIZipReader.idl @@ -77,6 +77,16 @@ interface nsIZipReader : nsISupports */ void openInner(in nsIZipReader zipReader, in AUTF8String zipEntry); + /** + * Opens a zip file stored in memory; the file attribute will be null. + * + * The ZipReader does not copy or take ownership of this memory; the + * caller must ensure that it is valid and unmodified until the + * ZipReader is closed or destroyed, and must free the memory as + * appropriate afterwards. + */ + void openMemory(in voidPtr aData, in unsigned long aLength); + /** * The file that represents the zip with which this zip reader was * initialized. This will be null if there is no underlying file. diff --git a/modules/libjar/nsJAR.cpp b/modules/libjar/nsJAR.cpp index e61066c69d70..4dbef243a761 100644 --- a/modules/libjar/nsJAR.cpp +++ b/modules/libjar/nsJAR.cpp @@ -149,6 +149,21 @@ nsJAR::OpenInner(nsIZipReader* aZipReader, const nsACString& aZipEntry) { return mZip ? NS_OK : NS_ERROR_FAILURE; } +NS_IMETHODIMP +nsJAR::OpenMemory(void* aData, uint32_t aLength) { + NS_ENSURE_ARG_POINTER(aData); + RecursiveMutexAutoLock lock(mLock); + if (mZip) return NS_ERROR_FAILURE; // Already open! + + RefPtr handle; + nsresult rv = nsZipHandle::Init(static_cast(aData), aLength, + getter_AddRefs(handle)); + if (NS_FAILED(rv)) return rv; + + mZip = nsZipArchive::OpenArchive(handle); + return mZip ? NS_OK : NS_ERROR_FAILURE; +} + NS_IMETHODIMP nsJAR::GetFile(nsIFile** result) { RecursiveMutexAutoLock lock(mLock); diff --git a/modules/libjar/nsZipArchive.cpp b/modules/libjar/nsZipArchive.cpp index 1f43a0d6259c..c53375302f03 100644 --- a/modules/libjar/nsZipArchive.cpp +++ b/modules/libjar/nsZipArchive.cpp @@ -259,6 +259,20 @@ nsresult nsZipHandle::Init(nsZipArchive* zip, const char* entry, return NS_OK; } +nsresult nsZipHandle::Init(const uint8_t* aData, uint32_t aLen, + nsZipHandle** aRet) { + RefPtr handle = new nsZipHandle(); + + handle->mFileStart = aData; + handle->mTotalLen = aLen; + nsresult rv = handle->findDataStart(); + if (NS_FAILED(rv)) { + return rv; + } + handle.forget(aRet); + return NS_OK; +} + // This function finds the start of the ZIP data. If the file is a regular ZIP, // this is just the start of the file. If the file is a CRX file, the start of // the data is after the CRX header. diff --git a/modules/libjar/nsZipArchive.h b/modules/libjar/nsZipArchive.h index e38445ea8799..12e4b7ea0024 100644 --- a/modules/libjar/nsZipArchive.h +++ b/modules/libjar/nsZipArchive.h @@ -364,6 +364,7 @@ class nsZipHandle final { static nsresult Init(nsIFile* file, nsZipHandle** ret, PRFileDesc** aFd = nullptr); static nsresult Init(nsZipArchive* zip, const char* entry, nsZipHandle** ret); + static nsresult Init(const uint8_t* aData, uint32_t aLen, nsZipHandle** aRet); NS_METHOD_(MozExternalRefCountType) AddRef(void); NS_METHOD_(MozExternalRefCountType) Release(void); diff --git a/netwerk/test/fuzz/TestJARFuzzing.cpp b/netwerk/test/fuzz/TestJARFuzzing.cpp new file mode 100644 index 000000000000..3d27a5bf7d3b --- /dev/null +++ b/netwerk/test/fuzz/TestJARFuzzing.cpp @@ -0,0 +1,187 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include + +#include "FuzzingInterface.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIZipReader.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "mozilla/Span.h" +#include "mozilla/Unused.h" +#include "nsIInputStream.h" +#include "nsIStringEnumerator.h" + +enum FuzzMethodType { + eTest = 0, + eGetEntry, + eHasEntry, + eFindEntries, + eInputStream, + eOpenInner, + eLastMethod, +}; +static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); + +template +T jar_get_num(char** buf, size_t* size) { + if (sizeof(T) > *size) { + return 0; + } + + T* iptr = reinterpret_cast(*buf); + *buf += sizeof(T); + *size -= sizeof(T); + return *iptr; +} + +nsAutoCString jar_get_string(char** buf, size_t* size) { + uint8_t len = jar_get_num(buf, size); + if (len > *size) { + len = static_cast(*size); + } + nsAutoCString str(*buf, len); + + *buf += len; + *size -= len; + return str; +} + +nsresult FuzzEntries(char** buf, size_t* size, nsIZipReader* aReader, + const nsACString& aName) { + uint8_t iters = jar_get_num(buf, size); + nsresult rv; + for (uint8_t i = 0; i < iters; ++i) { + nsAutoCString out; + uint64_t written; + nsCOMPtr entry; + nsCOMPtr stream; + + switch (jar_get_num(buf, size) % eLastMethod) { + case eTest: { + rv = aReader->Test(aName); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + case eGetEntry: { + rv = aReader->GetEntry(aName, getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + case eHasEntry: { + bool has = false; + rv = aReader->HasEntry(aName, &has); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + case eInputStream: + rv = aReader->GetInputStream(aName, getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + break; + } + rv = NS_ReadInputStreamToString(stream, out, -1, &written); + NS_ENSURE_SUCCESS(rv, rv); + break; + default: + break; + } + } + return NS_OK; +} + +nsresult FuzzReader(char** buf, size_t* size, nsIZipReader* aReader) { + nsresult rv; + nsAutoCString name; + nsCOMPtr entry; + bool has = false; + nsAutoCString pattern; + nsCOMPtr enumerator; + nsCOMPtr stream; + bool hasMore; + nsAutoCString out; + uint64_t written; + nsCOMPtr newReader = do_CreateInstance(kZipReaderCID, &rv); + switch (jar_get_num(buf, size) % eLastMethod) { + case eTest: + rv = aReader->Test(""_ns); + NS_ENSURE_SUCCESS(rv, rv); + break; + case eGetEntry: + name = jar_get_string(buf, size); + rv = aReader->GetEntry(name, getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + break; + case eHasEntry: + name = jar_get_string(buf, size); + rv = aReader->HasEntry(name, &has); + NS_ENSURE_SUCCESS(rv, rv); + break; + case eFindEntries: + pattern = jar_get_string(buf, size); + rv = aReader->FindEntries(pattern, getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + while (NS_SUCCEEDED(enumerator->HasMore(&hasMore)) && hasMore) { + if (NS_FAILED(enumerator->GetNext(name))) { + break; + } + rv = FuzzEntries(buf, size, aReader, name); + NS_ENSURE_SUCCESS(rv, rv); + } + + break; + case eInputStream: + name = jar_get_string(buf, size); + rv = aReader->GetInputStream(name, getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_ReadInputStreamToString(stream, out, -1, &written); + NS_ENSURE_SUCCESS(rv, rv); + break; + case eOpenInner: + name = jar_get_string(buf, size); + rv = newReader->OpenInner(aReader, name); + NS_ENSURE_SUCCESS(rv, rv); + rv = FuzzReader(buf, size, newReader); + NS_ENSURE_SUCCESS(rv, rv); + break; + default: + break; + } + return rv; +} + +static int FuzzingRunJARParser(const uint8_t* data, size_t size) { + char* buf = (char*)data; + nsresult rv; + + nsCOMPtr uri; + nsAutoCString jardata = jar_get_string(&buf, &size); + + nsCOMPtr reader = do_CreateInstance(kZipReaderCID, &rv); + rv = reader->OpenMemory((void*)jardata.get(), jardata.Length()); + NS_ENSURE_SUCCESS(rv, 0); + +#if 0 + // For easily exporting the last test case that triggered a crash. + FILE * f = fopen("/tmp/input.jar", "wb"); + fwrite((void*)jardata.get(), 1, jardata.Length(), f); + fclose(f); +#endif + + uint8_t iters = jar_get_num(&buf, &size); + for (uint8_t i = 0; i < iters; ++i) { + rv = FuzzReader(&buf, &size, reader); + NS_ENSURE_SUCCESS(rv, 0); + } + + return 0; +} + +MOZ_FUZZING_INTERFACE_RAW(nullptr, FuzzingRunJARParser, JARParser); diff --git a/netwerk/test/fuzz/moz.build b/netwerk/test/fuzz/moz.build index 9ff7eab92315..969b9b0b8278 100644 --- a/netwerk/test/fuzz/moz.build +++ b/netwerk/test/fuzz/moz.build @@ -7,6 +7,7 @@ UNIFIED_SOURCES += [ "FuzzingStreamListener.cpp", "TestHttpFuzzing.cpp", + "TestJARFuzzing.cpp", "TestURIFuzzing.cpp", "TestWebsocketFuzzing.cpp", ]