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