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