BloomFilterReadFromFuzzer.java
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
////////////////////////////////////////////////////////////////////////////////
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class BloomFilterReadFromFuzzer {
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
byte[] input = data.consumeRemainingAsBytes();
/*
* BloomFilter serialized format:
*
* byte strategyOrdinal
* byte numHashFunctions
* int dataLength
* long[dataLength] data
*/
if (input.length < 6) {
return;
}
/*
* Extract attacker-controlled allocation metadata for reporting only.
*
* The real allocation must still occur inside Guava to prove impact.
*/
int dataLength =
((input[2] & 0xFF) << 24)
| ((input[3] & 0xFF) << 16)
| ((input[4] & 0xFF) << 8)
| (input[5] & 0xFF);
long requestedBytes = -1L;
double amplification = -1.0;
if (dataLength >= 0) {
try {
requestedBytes =
Math.multiplyExact((long) dataLength,
(long) Long.BYTES);
amplification =
(double) requestedBytes / (double) input.length;
} catch (ArithmeticException ignored) {
/*
* Overflow itself is interesting but we still want Guava's
* real behavior.
*/
}
}
try (InputStream is = new ByteArrayInputStream(input)) {
BloomFilter<String> filter =
BloomFilter.readFrom(
is,
Funnels.stringFunnel(StandardCharsets.UTF_8));
filter.mightContain("probe");
} catch (OutOfMemoryError e) {
StringBuilder message = new StringBuilder();
message.append(
"BloomFilter.readFrom() performs attacker-controlled " +
"heap allocation from serialized metadata without " +
"application-level bounds checking.\n\n");
message.append("Input size: ")
.append(input.length)
.append(" bytes\n");
message.append("dataLength: ")
.append(dataLength)
.append("\n");
if (requestedBytes >= 0) {
message.append("Requested allocation: ")
.append(requestedBytes)
.append(" bytes\n");
message.append("Amplification factor: ")
.append(String.format("%.2f", amplification))
.append("x\n");
}
message.append("\nRoot cause:\n")
.append("BloomFilter.readFrom() trusts serialized ")
.append("dataLength metadata and propagates it into ")
.append("LockFreeBitArray allocation logic without ")
.append("validating whether the requested allocation ")
.append("size is reasonable for untrusted input.\n\n");
message.append("Underlying JVM error: ")
.append(e.getMessage());
throw new FuzzerSecurityIssueCritical(message.toString());
} catch (IOException
| IllegalArgumentException
| ArithmeticException e) {
/*
* Expected for malformed inputs.
*/
}
}
}