/src/libmongocrypt/test/fuzz_mongocrypt.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2024-present MongoDB, Inc. |
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 | | /* |
18 | | * OSS-Fuzz fuzzing entry point for libmongocrypt. |
19 | | * |
20 | | * Exercises the libmongocrypt state machine by initializing contexts for |
21 | | * various operations and feeding fuzzed data through each state transition. |
22 | | */ |
23 | | |
24 | | #include "mongocrypt.h" |
25 | | #include <bson/bson.h> |
26 | | #include <stdbool.h> |
27 | | #include <stddef.h> |
28 | | #include <stdint.h> |
29 | | #include <stdlib.h> |
30 | | #include <string.h> |
31 | | |
32 | | /* MONGOCRYPT_KEY_LEN is 96 bytes (defined in mongocrypt-crypto-private.h). */ |
33 | 20.5k | #define FUZZ_KEY_LEN 96 |
34 | | |
35 | | /* Maximum number of state transitions to prevent infinite loops. */ |
36 | 8.20k | #define MAX_STATE_TRANSITIONS 32 |
37 | | |
38 | | /* Operation types selected by the first byte of fuzz input. */ |
39 | | enum { |
40 | | OP_ENCRYPT = 0, |
41 | | OP_DECRYPT = 1, |
42 | | OP_EXPLICIT_ENCRYPT = 2, |
43 | | OP_EXPLICIT_DECRYPT = 3, |
44 | | OP_DATAKEY = 4, |
45 | | OP_REWRAP_MANY_DATAKEY = 5, |
46 | | OP_EXPLICIT_ENCRYPT_EXPRESSION = 6, |
47 | | OP_DATAKEY_AWS = 7, /* exercises NEED_KMS → KMS response parser (HTTP chunked) */ |
48 | | OP_DATAKEY_KMIP = 8, /* exercises NEED_KMS → KMIP response parser */ |
49 | | OP_COUNT = 9, |
50 | | }; |
51 | | |
52 | | /* Helper: consume bytes from the fuzz input. */ |
53 | 16.5k | static const uint8_t *fuzz_consume(const uint8_t **data, size_t *remaining, size_t n) { |
54 | 16.5k | if (*remaining < n) { |
55 | 0 | return NULL; |
56 | 0 | } |
57 | 16.5k | const uint8_t *ptr = *data; |
58 | 16.5k | *data += n; |
59 | 16.5k | *remaining -= n; |
60 | 16.5k | return ptr; |
61 | 16.5k | } |
62 | | |
63 | | /* Helper: consume one byte. */ |
64 | 10.2k | static int fuzz_consume_byte(const uint8_t **data, size_t *remaining) { |
65 | 10.2k | const uint8_t *p = fuzz_consume(data, remaining, 1); |
66 | 10.2k | return p ? *p : -1; |
67 | 10.2k | } |
68 | | |
69 | | /* |
70 | | * Drive the context state machine, feeding fuzzed data at each state. |
71 | | * Returns when the context reaches DONE, ERROR, or we run out of input. |
72 | | */ |
73 | 5.67k | static void drive_ctx(mongocrypt_ctx_t *ctx, const uint8_t **data, size_t *remaining) { |
74 | 5.67k | mongocrypt_ctx_state_t state; |
75 | 5.67k | int transitions = 0; |
76 | | |
77 | 8.20k | while (transitions++ < MAX_STATE_TRANSITIONS) { |
78 | 8.20k | state = mongocrypt_ctx_state(ctx); |
79 | | |
80 | 8.20k | switch (state) { |
81 | 0 | case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB: |
82 | 10 | case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: |
83 | 11 | case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: |
84 | 1.22k | case MONGOCRYPT_CTX_NEED_MONGO_KEYS: { |
85 | | /* Feed fuzzed BSON-like data as a mongo response. */ |
86 | 1.22k | size_t feed_len = 0; |
87 | 1.22k | const uint8_t *len_bytes = fuzz_consume(data, remaining, 2); |
88 | 1.22k | if (!len_bytes) { |
89 | 0 | return; |
90 | 0 | } |
91 | 1.22k | feed_len = (size_t)len_bytes[0] | ((size_t)len_bytes[1] << 8); |
92 | 1.22k | if (feed_len > *remaining) { |
93 | 1.08k | feed_len = *remaining; |
94 | 1.08k | } |
95 | 1.22k | if (feed_len > 0) { |
96 | 1.22k | const uint8_t *feed_data = fuzz_consume(data, remaining, feed_len); |
97 | 1.22k | if (feed_data) { |
98 | 1.22k | mongocrypt_binary_t *bin = |
99 | 1.22k | mongocrypt_binary_new_from_data((uint8_t *)feed_data, (uint32_t)feed_len); |
100 | 1.22k | mongocrypt_ctx_mongo_feed(ctx, bin); |
101 | 1.22k | mongocrypt_binary_destroy(bin); |
102 | 1.22k | } |
103 | 1.22k | } |
104 | 1.22k | mongocrypt_ctx_mongo_done(ctx); |
105 | 1.22k | break; |
106 | 1.22k | } |
107 | | |
108 | 0 | case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS: { |
109 | | /* Feed fuzzed KMS provider credentials. */ |
110 | 0 | size_t feed_len = 0; |
111 | 0 | const uint8_t *len_bytes = fuzz_consume(data, remaining, 2); |
112 | 0 | if (!len_bytes) { |
113 | 0 | return; |
114 | 0 | } |
115 | 0 | feed_len = (size_t)len_bytes[0] | ((size_t)len_bytes[1] << 8); |
116 | 0 | if (feed_len > *remaining) { |
117 | 0 | feed_len = *remaining; |
118 | 0 | } |
119 | 0 | if (feed_len > 0) { |
120 | 0 | const uint8_t *feed_data = fuzz_consume(data, remaining, feed_len); |
121 | 0 | if (feed_data) { |
122 | 0 | mongocrypt_binary_t *bin = |
123 | 0 | mongocrypt_binary_new_from_data((uint8_t *)feed_data, (uint32_t)feed_len); |
124 | 0 | mongocrypt_ctx_provide_kms_providers(ctx, bin); |
125 | 0 | mongocrypt_binary_destroy(bin); |
126 | 0 | } |
127 | 0 | } else { |
128 | 0 | bson_t empty = BSON_INITIALIZER; |
129 | 0 | mongocrypt_binary_t *bin = |
130 | 0 | mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(&empty), (uint32_t)empty.len); |
131 | 0 | mongocrypt_ctx_provide_kms_providers(ctx, bin); |
132 | 0 | mongocrypt_binary_destroy(bin); |
133 | 0 | } |
134 | 0 | break; |
135 | 0 | } |
136 | | |
137 | 3.85k | case MONGOCRYPT_CTX_NEED_KMS: { |
138 | | /* Iterate KMS contexts and feed fuzzed response data. */ |
139 | 3.85k | mongocrypt_kms_ctx_t *kms_ctx; |
140 | 4.57k | while ((kms_ctx = mongocrypt_ctx_next_kms_ctx(ctx)) != NULL) { |
141 | 3.85k | uint32_t bytes_needed = mongocrypt_kms_ctx_bytes_needed(kms_ctx); |
142 | 3.85k | if (bytes_needed == 0) { |
143 | 0 | continue; |
144 | 0 | } |
145 | 3.85k | size_t feed_len = bytes_needed; |
146 | 3.85k | if (feed_len > *remaining) { |
147 | 3.68k | feed_len = *remaining; |
148 | 3.68k | } |
149 | 3.85k | if (feed_len == 0) { |
150 | 0 | return; |
151 | 0 | } |
152 | 3.85k | const uint8_t *feed_data = fuzz_consume(data, remaining, feed_len); |
153 | 3.85k | if (feed_data) { |
154 | 3.85k | mongocrypt_binary_t *bin = |
155 | 3.85k | mongocrypt_binary_new_from_data((uint8_t *)feed_data, (uint32_t)feed_len); |
156 | 3.85k | bool ok = mongocrypt_kms_ctx_feed(kms_ctx, bin); |
157 | 3.85k | mongocrypt_binary_destroy(bin); |
158 | 3.85k | if (!ok) { |
159 | 3.14k | return; |
160 | 3.14k | } |
161 | 3.85k | } |
162 | 3.85k | } |
163 | 715 | mongocrypt_ctx_kms_done(ctx); |
164 | 715 | break; |
165 | 3.85k | } |
166 | | |
167 | 590 | case MONGOCRYPT_CTX_READY: { |
168 | 590 | mongocrypt_binary_t *out = mongocrypt_binary_new(); |
169 | 590 | if (out) { |
170 | 590 | mongocrypt_ctx_finalize(ctx, out); |
171 | 590 | mongocrypt_binary_destroy(out); |
172 | 590 | } |
173 | 590 | break; |
174 | 3.85k | } |
175 | | |
176 | 593 | case MONGOCRYPT_CTX_DONE: |
177 | 2.53k | case MONGOCRYPT_CTX_ERROR: |
178 | 2.53k | default: return; |
179 | 8.20k | } |
180 | 8.20k | } |
181 | 5.67k | } |
182 | | |
183 | 10.2k | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
184 | | /* |
185 | | * Need at least 1 byte for op selection + some data to feed. |
186 | | * Trivially small inputs won't exercise meaningful code paths. |
187 | | */ |
188 | 10.2k | if (size < 5) { |
189 | 4 | return 0; |
190 | 4 | } |
191 | | |
192 | 10.2k | const uint8_t *cursor = data; |
193 | 10.2k | size_t remaining = size; |
194 | | |
195 | | /* Consume first byte to select operation. */ |
196 | 10.2k | int op = fuzz_consume_byte(&cursor, &remaining) % OP_COUNT; |
197 | | |
198 | | /* Set up mongocrypt_t with a local KMS provider. */ |
199 | 10.2k | mongocrypt_t *crypt = mongocrypt_new(); |
200 | 10.2k | if (!crypt) { |
201 | 0 | return 0; |
202 | 0 | } |
203 | | |
204 | | /* Configure local KMS provider with a fixed 96-byte key. */ |
205 | 10.2k | uint8_t local_key[FUZZ_KEY_LEN]; |
206 | 10.2k | memset(local_key, 0xAB, FUZZ_KEY_LEN); |
207 | 10.2k | mongocrypt_binary_t *key_bin = mongocrypt_binary_new_from_data(local_key, FUZZ_KEY_LEN); |
208 | 10.2k | mongocrypt_setopt_kms_provider_local(crypt, key_bin); |
209 | 10.2k | mongocrypt_binary_destroy(key_bin); |
210 | | |
211 | | /* Configure AWS KMS provider so OP_DATAKEY_AWS can reach NEED_KMS state |
212 | | * and exercise the KMS HTTP response parser with fuzz-controlled bytes. */ |
213 | 10.2k | mongocrypt_setopt_kms_provider_aws(crypt, |
214 | 10.2k | "AKIAIOSFODNN7EXAMPLE", |
215 | 10.2k | -1, |
216 | 10.2k | "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", |
217 | 10.2k | -1); |
218 | | |
219 | | /* Configure KMIP KMS provider so OP_DATAKEY_KMIP can reach NEED_KMS state |
220 | | * and exercise the KMIP response parser with fuzz-controlled bytes. */ |
221 | 10.2k | { |
222 | 10.2k | bson_t *kmip_doc = BCON_NEW("kmip", "{", "endpoint", "localhost:5696", "}"); |
223 | 10.2k | mongocrypt_binary_t *kmip_bin = |
224 | 10.2k | mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(kmip_doc), (uint32_t)kmip_doc->len); |
225 | 10.2k | mongocrypt_setopt_kms_providers(crypt, kmip_bin); |
226 | 10.2k | mongocrypt_binary_destroy(kmip_bin); |
227 | 10.2k | bson_destroy(kmip_doc); |
228 | 10.2k | } |
229 | | |
230 | 10.2k | if (!mongocrypt_init(crypt)) { |
231 | 0 | mongocrypt_destroy(crypt); |
232 | 0 | return 0; |
233 | 0 | } |
234 | | |
235 | 10.2k | mongocrypt_ctx_t *ctx = mongocrypt_ctx_new(crypt); |
236 | 10.2k | if (!ctx) { |
237 | 0 | mongocrypt_destroy(crypt); |
238 | 0 | return 0; |
239 | 0 | } |
240 | | |
241 | | /* Create a binary view of remaining fuzz data for init calls. */ |
242 | 10.2k | mongocrypt_binary_t *input_bin = NULL; |
243 | 10.2k | if (remaining > 0) { |
244 | 10.2k | input_bin = mongocrypt_binary_new_from_data((uint8_t *)cursor, (uint32_t)remaining); |
245 | 10.2k | } |
246 | | |
247 | 10.2k | bool init_ok = false; |
248 | | |
249 | 10.2k | switch (op) { |
250 | 3.63k | case OP_ENCRYPT: |
251 | 3.63k | if (input_bin) { |
252 | 3.63k | init_ok = mongocrypt_ctx_encrypt_init(ctx, "test", -1, input_bin); |
253 | 3.63k | } |
254 | 3.63k | break; |
255 | | |
256 | 1.09k | case OP_DECRYPT: |
257 | 1.09k | if (input_bin) { |
258 | 1.09k | init_ok = mongocrypt_ctx_decrypt_init(ctx, input_bin); |
259 | 1.09k | } |
260 | 1.09k | break; |
261 | | |
262 | 166 | case OP_EXPLICIT_ENCRYPT: { |
263 | | /* Set a fixed key id (16-byte UUID). */ |
264 | 166 | uint8_t key_id[16]; |
265 | 166 | memset(key_id, 0x61, sizeof(key_id)); /* matches "YWFhYWFhYWFhYWFhYWFhYQ==" */ |
266 | 166 | mongocrypt_binary_t *kid_bin = mongocrypt_binary_new_from_data(key_id, sizeof(key_id)); |
267 | 166 | mongocrypt_ctx_setopt_key_id(ctx, kid_bin); |
268 | 166 | mongocrypt_binary_destroy(kid_bin); |
269 | 166 | mongocrypt_ctx_setopt_algorithm(ctx, "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", -1); |
270 | 166 | if (input_bin) { |
271 | 166 | init_ok = mongocrypt_ctx_explicit_encrypt_init(ctx, input_bin); |
272 | 166 | } |
273 | 166 | break; |
274 | 0 | } |
275 | | |
276 | 541 | case OP_EXPLICIT_DECRYPT: |
277 | 541 | if (input_bin) { |
278 | 541 | init_ok = mongocrypt_ctx_explicit_decrypt_init(ctx, input_bin); |
279 | 541 | } |
280 | 541 | break; |
281 | | |
282 | 1 | case OP_DATAKEY: { |
283 | 1 | bson_t *kek = BCON_NEW("provider", "local"); |
284 | 1 | mongocrypt_binary_t *kek_bin = |
285 | 1 | mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(kek), (uint32_t)kek->len); |
286 | 1 | mongocrypt_ctx_setopt_key_encryption_key(ctx, kek_bin); |
287 | 1 | mongocrypt_binary_destroy(kek_bin); |
288 | 1 | bson_destroy(kek); |
289 | 1 | init_ok = mongocrypt_ctx_datakey_init(ctx); |
290 | 1 | break; |
291 | 0 | } |
292 | | |
293 | 1.00k | case OP_REWRAP_MANY_DATAKEY: |
294 | 1.00k | if (input_bin) { |
295 | 1.00k | init_ok = mongocrypt_ctx_rewrap_many_datakey_init(ctx, input_bin); |
296 | 1.00k | } |
297 | 1.00k | break; |
298 | | |
299 | 1 | case OP_EXPLICIT_ENCRYPT_EXPRESSION: { |
300 | 1 | uint8_t key_id[16]; |
301 | 1 | memset(key_id, 0x61, sizeof(key_id)); |
302 | 1 | mongocrypt_binary_t *kid_bin = mongocrypt_binary_new_from_data(key_id, sizeof(key_id)); |
303 | 1 | mongocrypt_ctx_setopt_key_id(ctx, kid_bin); |
304 | 1 | mongocrypt_binary_destroy(kid_bin); |
305 | 1 | mongocrypt_ctx_setopt_query_type(ctx, MONGOCRYPT_QUERY_TYPE_RANGE_STR, -1); |
306 | 1 | if (input_bin) { |
307 | 1 | init_ok = mongocrypt_ctx_explicit_encrypt_expression_init(ctx, input_bin); |
308 | 1 | } |
309 | 1 | break; |
310 | 0 | } |
311 | | |
312 | 3.68k | case OP_DATAKEY_AWS: { |
313 | | /* Create a datakey encrypted with AWS KMS. The context transitions |
314 | | * directly to NEED_KMS, so drive_ctx feeds fuzz bytes as the raw |
315 | | * HTTP response to the KMS response parser. */ |
316 | 3.68k | bson_t *kek = BCON_NEW("provider", "aws", "region", "us-east-1", "key", "arn:aws:kms:us-east-1:0:key/fuzz"); |
317 | 3.68k | mongocrypt_binary_t *kek_bin = |
318 | 3.68k | mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(kek), (uint32_t)kek->len); |
319 | 3.68k | mongocrypt_ctx_setopt_key_encryption_key(ctx, kek_bin); |
320 | 3.68k | mongocrypt_binary_destroy(kek_bin); |
321 | 3.68k | bson_destroy(kek); |
322 | 3.68k | init_ok = mongocrypt_ctx_datakey_init(ctx); |
323 | 3.68k | break; |
324 | 0 | } |
325 | | |
326 | 166 | case OP_DATAKEY_KMIP: { |
327 | | /* Create a datakey encrypted with KMIP. The context transitions to |
328 | | * NEED_KMS (KMIP Register request), so drive_ctx feeds fuzz bytes as |
329 | | * the raw KMIP/TTLV response to the KMIP response parser. */ |
330 | 166 | bson_t *kek = BCON_NEW("provider", "kmip"); |
331 | 166 | mongocrypt_binary_t *kek_bin = |
332 | 166 | mongocrypt_binary_new_from_data((uint8_t *)bson_get_data(kek), (uint32_t)kek->len); |
333 | 166 | mongocrypt_ctx_setopt_key_encryption_key(ctx, kek_bin); |
334 | 166 | mongocrypt_binary_destroy(kek_bin); |
335 | 166 | bson_destroy(kek); |
336 | 166 | init_ok = mongocrypt_ctx_datakey_init(ctx); |
337 | 166 | break; |
338 | 0 | } |
339 | | |
340 | 0 | default: break; |
341 | 10.2k | } |
342 | | |
343 | 10.2k | if (input_bin) { |
344 | 10.2k | mongocrypt_binary_destroy(input_bin); |
345 | 10.2k | } |
346 | | |
347 | | /* If initialization succeeded, drive the state machine with fuzzed data. */ |
348 | 10.2k | if (init_ok) { |
349 | 5.67k | drive_ctx(ctx, &cursor, &remaining); |
350 | 5.67k | } |
351 | | |
352 | 10.2k | mongocrypt_ctx_destroy(ctx); |
353 | 10.2k | mongocrypt_destroy(crypt); |
354 | | |
355 | 10.2k | return 0; |
356 | 10.2k | } |