Coverage Report

Created: 2026-10-28 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}