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
LLVMFuzzerTestOneInput(const uint8_t * data,size_t size)32 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
33 // To fuzz this, we're going to create a key/value pair from data
34 // and use them with MultifileBlobCache in a random order
35 // - Use the first entry in data to determine keySize
36 // - Use the second entry in data to determine valueSize
37 // - Mod each of them against half the remaining size, ensuring both fit
38 // - Create key and value using sizes from data
39 // - Use remaining data to switch between GET and SET while
40 // tweaking the keys slightly
41 // - Ensure two cache cleaning scenarios are hit at the end
42
43 // Ensure we have enough data to create interesting key/value pairs
44 size_t kMinInputLength = 128;
45 if (size < kMinInputLength) {
46 return 0;
47 }
48
49 // Need non-zero sizes for interesting results
50 if (data[0] == 0 || data[1] == 0) {
51 return 0;
52 }
53
54 // We need to divide the data up into buffers and sizes
55 FuzzedDataProvider fdp(data, size);
56
57 // Pull two values from data for key and value size
58 EGLsizeiANDROID keySize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
59 EGLsizeiANDROID valueSize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
60 size -= 2 * sizeof(uint8_t);
61
62 // Ensure key and value fit in the remaining space (cap them at half data size)
63 keySize = keySize % (size >> 1);
64 valueSize = valueSize % (size >> 1);
65
66 // If either size ended up zero, just move on to save time
67 if (keySize == 0 || valueSize == 0) {
68 return 0;
69 }
70
71 // Create key and value from remaining data
72 std::vector<uint8_t> key;
73 std::vector<uint8_t> value;
74 key = fdp.ConsumeBytes<uint8_t>(keySize);
75 value = fdp.ConsumeBytes<uint8_t>(valueSize);
76
77 // Create a tempfile and a cache
78 std::unique_ptr<TemporaryFile> tempFile;
79 std::unique_ptr<MultifileBlobCache> mbc;
80
81 tempFile.reset(new TemporaryFile());
82 mbc.reset(
83 new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
84 // With remaining data, select different paths below
85 int loopCount = 1;
86 uint8_t bumpCount = 0;
87 while (fdp.remaining_bytes() > 0) {
88 // Bounce back and forth between gets and sets
89 if (fdp.ConsumeBool()) {
90 mbc->set(key.data(), keySize, value.data(), valueSize);
91 } else {
92 uint8_t* buffer = new uint8_t[valueSize];
93 mbc->get(key.data(), keySize, buffer, valueSize);
94 delete[] buffer;
95 }
96
97 // Bump the key and values periodically, causing different hits/misses
98 if (fdp.ConsumeBool()) {
99 key[0]++;
100 value[0]++;
101 bumpCount++;
102 }
103
104 // Reset the key and value periodically to hit old entries
105 if (fdp.ConsumeBool()) {
106 key[0] -= bumpCount;
107 value[0] -= bumpCount;
108 bumpCount = 0;
109 }
110
111 loopCount++;
112 }
113 mbc->finish();
114
115 // Fill 2 keys and 2 values to max size with unique values
116 std::vector<uint8_t> maxKey1, maxKey2, maxValue1, maxValue2;
117 maxKey1.resize(kMaxKeySize, 0);
118 maxKey2.resize(kMaxKeySize, 0);
119 maxValue1.resize(kMaxValueSize, 0);
120 maxValue2.resize(kMaxValueSize, 0);
121 for (int i = 0; i < keySize && i < kMaxKeySize; ++i) {
122 maxKey1[i] = key[i];
123 maxKey2[i] = key[i] - 1;
124 }
125 for (int i = 0; i < valueSize && i < kMaxValueSize; ++i) {
126 maxValue1[i] = value[i];
127 maxValue2[i] = value[i] - 1;
128 }
129
130 // Trigger hot cache trimming
131 // Place the maxKey/maxValue twice
132 // The first will fit, the second will trigger hot cache trimming
133 tempFile.reset(new TemporaryFile());
134 mbc.reset(
135 new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
136 uint8_t* buffer = new uint8_t[kMaxValueSize];
137 mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
138 mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
139 mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
140 mbc->finish();
141
142 // Trigger cold cache trimming
143 // Create a total size small enough only one entry fits
144 // Since the cache will add a header, 2 * key + value will only hold one value, the second will
145 // overflow
146 tempFile.reset(new TemporaryFile());
147 mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize),
148 &tempFile->path[0]));
149 mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
150 mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
151 mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
152 mbc->finish();
153
154 delete[] buffer;
155 return 0;
156 }
157
158 } // namespace android
159