Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 45ce680a authored by Cody Northrop's avatar Cody Northrop
Browse files

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
parent 5dbcfa78
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -276,11 +276,10 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi

    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 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi
    // 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;
    }

+42 −0
Original line number Diff line number Diff line
// 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",
    ],
}
+158 −0
Original line number Diff line number Diff line
/*
 ** 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