commit 5910c78f6181b12315ae4a359a5afc7b071171c7
parent a8a220b58adf1638f6deb05c3bc145510f70548f
Author: Tom Ritter <tom@mozilla.com>
Date: Fri, 3 Oct 2025 16:03:18 +0000
Bug 1980264: Implement a streaming randomization approach for PNG images r=tnikkel
As we read the resulting image, hash the bytes (eliminating the
need to read them twice), then modify the resulting image based
on the randomization key and the image hash.
Differential Revision: https://phabricator.services.mozilla.com/D267100
Diffstat:
5 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/image/encoders/png/nsPNGEncoder.cpp b/image/encoders/png/nsPNGEncoder.cpp
@@ -4,6 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ImageLogging.h"
+#include "mozilla/XorShift128PlusRNG.h"
#include "nsCRT.h"
#include "nsPNGEncoder.h"
#include "nsStreamUtils.h"
@@ -25,11 +26,13 @@ NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream,
nsPNGEncoder::nsPNGEncoder()
: mPNG(nullptr),
mPNGinfo(nullptr),
+ mAddCustomMetadata(false),
mIsAnimation(false),
mFinished(false),
mImageBuffer(nullptr),
mImageBufferSize(0),
mImageBufferUsed(0),
+ mImageBufferHash(0),
mImageBufferReadPoint(0),
mCallback(nullptr),
mCallbackTarget(nullptr),
@@ -65,6 +68,11 @@ nsPNGEncoder::InitFromData(const uint8_t* aData,
NS_ENSURE_ARG(aData);
nsresult rv;
+ MOZ_ASSERT_IF(aRandomizationKey.IsEmpty(), aRandomizationKey.IsVoid());
+ if (!aRandomizationKey.IsEmpty()) {
+ mAddCustomMetadata = true;
+ }
+
rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions);
if (!NS_SUCCEEDED(rv)) {
return rv;
@@ -76,6 +84,11 @@ nsPNGEncoder::InitFromData(const uint8_t* aData,
return rv;
}
+ rv = MaybeAddCustomMetadata(aRandomizationKey);
+ if (!NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
rv = EndImageEncode();
return rv;
@@ -353,6 +366,37 @@ nsPNGEncoder::EndImageEncode() {
return NS_OK;
}
+nsresult nsPNGEncoder::MaybeAddCustomMetadata(
+ const nsACString& aRandomizationKey) {
+ MOZ_ASSERT_IF(mAddCustomMetadata, !aRandomizationKey.IsEmpty());
+
+ if (!mAddCustomMetadata) {
+ return NS_OK;
+ }
+
+ nsCString hex;
+ nsresult rv = nsRFPService::GenerateRandomizationKeyFromHash(
+ aRandomizationKey, mImageBufferHash, hex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ png_size_t chunkLength = 16;
+ png_unknown_chunk chunk;
+ chunk.name[0] = 'd';
+ chunk.name[1] = 'e';
+ chunk.name[2] = 'B';
+ chunk.name[3] = 'G';
+ chunk.name[4] = '\0';
+
+ chunk.data = reinterpret_cast<png_byte*>(hex.BeginWriting());
+ chunk.size = chunkLength;
+ chunk.location = PNG_AFTER_IDAT;
+
+ png_set_unknown_chunks(mPNG, mPNGinfo, &chunk, 1);
+ png_set_unknown_chunk_location(mPNG, mPNGinfo, 0, PNG_AFTER_IDAT);
+
+ return NS_OK;
+}
+
nsresult nsPNGEncoder::ParseOptions(const nsAString& aOptions,
bool* useTransparency, bool* skipFirstFrame,
uint32_t* numFrames, uint32_t* numPlays,
@@ -777,6 +821,10 @@ nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, png_size_t size) {
}
}
+ if (that->mAddCustomMetadata) {
+ that->mImageBufferHash = HashBytes(data, size, that->mImageBufferHash);
+ }
+
memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size);
that->mImageBufferUsed += size;
that->NotifyListener();
diff --git a/image/encoders/png/nsPNGEncoder.h b/image/encoders/png/nsPNGEncoder.h
@@ -51,10 +51,12 @@ class nsPNGEncoder final : public imgIEncoder {
static void WriteCallback(png_structp png, png_bytep data, png_size_t size);
void NullOutImageBuffer();
void NotifyListener();
+ nsresult MaybeAddCustomMetadata(const nsACString& aRandomizationKey);
png_struct* mPNG;
png_info* mPNGinfo;
+ bool mAddCustomMetadata;
bool mIsAnimation;
bool mFinished;
@@ -62,6 +64,7 @@ class nsPNGEncoder final : public imgIEncoder {
uint8_t* mImageBuffer;
uint32_t mImageBufferSize;
uint32_t mImageBufferUsed;
+ uint32_t mImageBufferHash;
uint32_t mImageBufferReadPoint;
diff --git a/media/libpng/pnglibconf.h b/media/libpng/pnglibconf.h
@@ -148,6 +148,9 @@
#define PNG_STDIO_SUPPORTED
#define PNG_eXIf_SUPPORTED
#define PNG_READ_eXIf_SUPPORTED
+#define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED
+#define PNG_USER_CHUNKS_SUPPORTED
+#define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
#define PNG_CHECK_cHRM_SUPPORTED
#define PNG_ERROR_TEXT_SUPPORTED
diff --git a/mfbt/HashFunctions.cpp b/mfbt/HashFunctions.cpp
@@ -13,8 +13,9 @@
namespace mozilla {
-uint32_t HashBytes(const void* aBytes, size_t aLength) {
- uint32_t hash = 0;
+uint32_t HashBytes(const void* aBytes, size_t aLength,
+ HashNumber startingHash) {
+ uint32_t hash = startingHash;
const char* b = reinterpret_cast<const char*>(aBytes);
/* Walk word by word. */
diff --git a/mfbt/HashFunctions.h b/mfbt/HashFunctions.h
@@ -327,7 +327,8 @@ template <typename WCharT, typename = typename std::enable_if<
* same result out of HashBytes as you would out of HashString.
*/
[[nodiscard]] extern MFBT_API HashNumber HashBytes(const void* bytes,
- size_t aLength);
+ size_t aLength,
+ HashNumber startingHash = 0);
/**
* A pseudorandom function mapping 32-bit integers to 32-bit integers.