EGL BlobCache: Add fuzzer for multifile
This fuzzer breaks up the incoming data buffer into
a key and value, then invokes the cache in a few
different ways.
Also includes one small fix to delete a buffer,
discovered while writing the test.
Test: /data/fuzz/arm64/MultifileBlobCache_fuzzer/MultifileBlobCache_fuzzer
Bug: b/261868299
Change-Id: I724584ddb5a92b4b33e371f22f4511cdeb965775
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index b5ddd60..7ffdac7 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -276,11 +276,10 @@
uint8_t* buffer = new uint8_t[fileSize];
- // Write placeholders for magic and CRC until deferred thread complets the write
+ // Write placeholders for magic and CRC until deferred thread completes the write
android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
sizeof(android::MultifileHeader));
-
// Write the key and value after the header
memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
keySize);
@@ -301,6 +300,7 @@
// Sending -1 as the fd indicates we don't have an fd for this
if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
ALOGE("SET: Failed to add %u to hot cache", entryHash);
+ delete[] buffer;
return;
}
diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp
new file mode 100644
index 0000000..022a2a3
--- /dev/null
+++ b/opengl/libs/EGL/fuzzer/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_native_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_fuzz {
+ name: "MultifileBlobCache_fuzzer",
+
+ fuzz_config: {
+ cc: ["cnorthrop@google.com"],
+ libfuzzer_options: ["len_control=0"],
+ },
+
+ static_libs: [
+ "libbase",
+ "libEGL_blobCache",
+ "liblog",
+ "libutils",
+ ],
+
+ srcs: [
+ "MultifileBlobCache_fuzzer.cpp",
+ ],
+}
diff --git a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
new file mode 100644
index 0000000..633cc9c
--- /dev/null
+++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
@@ -0,0 +1,158 @@
+/*
+ ** Copyright 2023, The Android Open Source Project
+ **
+ ** 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
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include "MultifileBlobCache.h"
+
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+namespace android {
+
+constexpr size_t kMaxKeySize = 2 * 1024;
+constexpr size_t kMaxValueSize = 6 * 1024;
+constexpr size_t kMaxTotalSize = 32 * 1024;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ // To fuzz this, we're going to create a key/value pair from data
+ // and use them with MultifileBlobCache in a random order
+ // - Use the first entry in data to determine keySize
+ // - Use the second entry in data to determine valueSize
+ // - Mod each of them against half the remaining size, ensuring both fit
+ // - Create key and value using sizes from data
+ // - Use remaining data to switch between GET and SET while
+ // tweaking the keys slightly
+ // - Ensure two cache cleaning scenarios are hit at the end
+
+ // Ensure we have enough data to create interesting key/value pairs
+ size_t kMinInputLength = 128;
+ if (size < kMinInputLength) {
+ return 0;
+ }
+
+ // Need non-zero sizes for interesting results
+ if (data[0] == 0 || data[1] == 0) {
+ return 0;
+ }
+
+ // We need to divide the data up into buffers and sizes
+ FuzzedDataProvider fdp(data, size);
+
+ // Pull two values from data for key and value size
+ EGLsizeiANDROID keySize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
+ EGLsizeiANDROID valueSize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
+ size -= 2 * sizeof(uint8_t);
+
+ // Ensure key and value fit in the remaining space (cap them at half data size)
+ keySize = keySize % (size >> 1);
+ valueSize = valueSize % (size >> 1);
+
+ // If either size ended up zero, just move on to save time
+ if (keySize == 0 || valueSize == 0) {
+ return 0;
+ }
+
+ // Create key and value from remaining data
+ std::vector<uint8_t> key;
+ std::vector<uint8_t> value;
+ key = fdp.ConsumeBytes<uint8_t>(keySize);
+ value = fdp.ConsumeBytes<uint8_t>(valueSize);
+
+ // Create a tempfile and a cache
+ std::unique_ptr<TemporaryFile> tempFile;
+ std::unique_ptr<MultifileBlobCache> mbc;
+
+ tempFile.reset(new TemporaryFile());
+ mbc.reset(
+ new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+ // With remaining data, select different paths below
+ int loopCount = 1;
+ uint8_t bumpCount = 0;
+ while (fdp.remaining_bytes() > 0) {
+ // Bounce back and forth between gets and sets
+ if (fdp.ConsumeBool()) {
+ mbc->set(key.data(), keySize, value.data(), valueSize);
+ } else {
+ uint8_t* buffer = new uint8_t[valueSize];
+ mbc->get(key.data(), keySize, buffer, valueSize);
+ delete[] buffer;
+ }
+
+ // Bump the key and values periodically, causing different hits/misses
+ if (fdp.ConsumeBool()) {
+ key[0]++;
+ value[0]++;
+ bumpCount++;
+ }
+
+ // Reset the key and value periodically to hit old entries
+ if (fdp.ConsumeBool()) {
+ key[0] -= bumpCount;
+ value[0] -= bumpCount;
+ bumpCount = 0;
+ }
+
+ loopCount++;
+ }
+ mbc->finish();
+
+ // Fill 2 keys and 2 values to max size with unique values
+ std::vector<uint8_t> maxKey1, maxKey2, maxValue1, maxValue2;
+ maxKey1.resize(kMaxKeySize, 0);
+ maxKey2.resize(kMaxKeySize, 0);
+ maxValue1.resize(kMaxValueSize, 0);
+ maxValue2.resize(kMaxValueSize, 0);
+ for (int i = 0; i < keySize && i < kMaxKeySize; ++i) {
+ maxKey1[i] = key[i];
+ maxKey2[i] = key[i] - 1;
+ }
+ for (int i = 0; i < valueSize && i < kMaxValueSize; ++i) {
+ maxValue1[i] = value[i];
+ maxValue2[i] = value[i] - 1;
+ }
+
+ // Trigger hot cache trimming
+ // Place the maxKey/maxValue twice
+ // The first will fit, the second will trigger hot cache trimming
+ tempFile.reset(new TemporaryFile());
+ mbc.reset(
+ new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+ uint8_t* buffer = new uint8_t[kMaxValueSize];
+ mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
+ mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
+ mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
+ mbc->finish();
+
+ // Trigger cold cache trimming
+ // Create a total size small enough only one entry fits
+ // Since the cache will add a header, 2 * key + value will only hold one value, the second will
+ // overflow
+ tempFile.reset(new TemporaryFile());
+ mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize),
+ &tempFile->path[0]));
+ mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
+ mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
+ mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
+ mbc->finish();
+
+ delete[] buffer;
+ return 0;
+}
+
+} // namespace android