1 /*
2 ** Copyright 2023, The Android Open Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 ** http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15 */
16
17 #include "MultifileBlobCache.h"
18
19 #include <android-base/test_utils.h>
20 #include <fcntl.h>
21 #include <fuzzer/FuzzedDataProvider.h>
22 #include <stddef.h>
23 #include <stdint.h>
24 #include <stdio.h>
25
26 namespace android {
27
28 constexpr size_t kMaxKeySize = 2 * 1024;
29 constexpr size_t kMaxValueSize = 6 * 1024;
30 constexpr size_t kMaxTotalSize = 32 * 1024;
31 constexpr size_t kMaxTotalEntries = 64;
32
LLVMFuzzerTestOneInput(const uint8_t * data,size_t size)33 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
34 // To fuzz this, we're going to create a key/value pair from data
35 // and use them with MultifileBlobCache in a random order
36 // - Use the first entry in data to determine keySize
37 // - Use the second entry in data to determine valueSize
38 // - Mod each of them against half the remaining size, ensuring both fit
39 // - Create key and value using sizes from data
40 // - Use remaining data to switch between GET and SET while
41 // tweaking the keys slightly
42 // - Ensure two cache cleaning scenarios are hit at the end
43
44 // Ensure we have enough data to create interesting key/value pairs
45 size_t kMinInputLength = 128;
46 if (size < kMinInputLength) {
47 return 0;
48 }
49
50 // Need non-zero sizes for interesting results
51 if (data[0] == 0 || data[1] == 0) {
52 return 0;
53 }
54
55 // We need to divide the data up into buffers and sizes
56 FuzzedDataProvider fdp(data, size);
57
58 // Pull two values from data for key and value size
59 EGLsizeiANDROID keySize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
60 EGLsizeiANDROID valueSize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
61 size -= 2 * sizeof(uint8_t);
62
63 // Ensure key and value fit in the remaining space (cap them at half data size)
64 keySize = keySize % (size >> 1);
65 valueSize = valueSize % (size >> 1);
66
67 // If either size ended up zero, just move on to save time
68 if (keySize == 0 || valueSize == 0) {
69 return 0;
70 }
71
72 // Create key and value from remaining data
73 std::vector<uint8_t> key;
74 std::vector<uint8_t> value;
75 key = fdp.ConsumeBytes<uint8_t>(keySize);
76 value = fdp.ConsumeBytes<uint8_t>(valueSize);
77
78 // Create a tempfile and a cache
79 std::unique_ptr<TemporaryFile> tempFile;
80 std::unique_ptr<MultifileBlobCache> mbc;
81
82 tempFile.reset(new TemporaryFile());
83 mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
84 &tempFile->path[0]));
85 // With remaining data, select different paths below
86 int loopCount = 1;
87 uint8_t bumpCount = 0;
88 while (fdp.remaining_bytes() > 0) {
89 // Bounce back and forth between gets and sets
90 if (fdp.ConsumeBool()) {
91 mbc->set(key.data(), keySize, value.data(), valueSize);
92 } else {
93 uint8_t* buffer = new uint8_t[valueSize];
94 mbc->get(key.data(), keySize, buffer, valueSize);
95 delete[] buffer;
96 }
97
98 // Bump the key and values periodically, causing different hits/misses
99 if (fdp.ConsumeBool()) {
100 key[0]++;
101 value[0]++;
102 bumpCount++;
103 }
104
105 // Reset the key and value periodically to hit old entries
106 if (fdp.ConsumeBool()) {
107 key[0] -= bumpCount;
108 value[0] -= bumpCount;
109 bumpCount = 0;
110 }
111
112 loopCount++;
113 }
114 mbc->finish();
115
116 // Fill 2 keys and 2 values to max size with unique values
117 std::vector<uint8_t> maxKey1, maxKey2, maxValue1, maxValue2;
118 maxKey1.resize(kMaxKeySize, 0);
119 maxKey2.resize(kMaxKeySize, 0);
120 maxValue1.resize(kMaxValueSize, 0);
121 maxValue2.resize(kMaxValueSize, 0);
122 for (int i = 0; i < keySize && i < kMaxKeySize; ++i) {
123 maxKey1[i] = key[i];
124 maxKey2[i] = key[i] - 1;
125 }
126 for (int i = 0; i < valueSize && i < kMaxValueSize; ++i) {
127 maxValue1[i] = value[i];
128 maxValue2[i] = value[i] - 1;
129 }
130
131 // Trigger hot cache trimming
132 // Place the maxKey/maxValue twice
133 // The first will fit, the second will trigger hot cache trimming
134 tempFile.reset(new TemporaryFile());
135 mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
136 &tempFile->path[0]));
137 uint8_t* buffer = new uint8_t[kMaxValueSize];
138 mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
139 mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
140 mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
141 mbc->finish();
142
143 // Trigger cold cache trimming
144 // Create a total size small enough only one entry fits
145 // Since the cache will add a header, 2 * key + value will only hold one value, the second will
146 // overflow
147 tempFile.reset(new TemporaryFile());
148 mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize),
149 kMaxTotalEntries, &tempFile->path[0]));
150 mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
151 mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
152 mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
153 mbc->finish();
154
155 delete[] buffer;
156 return 0;
157 }
158
159 } // namespace android
160