commit f31c5d7aa74d144e6a6aba9844b592505e1074d0
parent 27fdfda522bbf9b237630f4a4f517540b6826f68
Author: Tom Ritter <tom@mozilla.com>
Date: Mon, 6 Oct 2025 02:09:45 +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.