Coverage Report

Created: 2025-10-10 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fuzz_crypto_ext.cpp
Line
Count
Source
1
// Copyright 2025 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
//
15
////////////////////////////////////////////////////////////////////////////////
16
#include <stddef.h>
17
#include <stdint.h>
18
#include <string.h>
19
#include <vector>
20
#include <algorithm>
21
22
#include <fuzzer/FuzzedDataProvider.h>
23
extern "C" {
24
  #include "md5_ext.h"
25
  #include "sha256_ext.h"
26
}
27
28
// Fuzzing target function pointer types for the enternal hash APIs
29
template <typename HashType> using InitOnceFn = void (*)(HashType*);
30
template <typename HashType> using UpdateFn   = void (*)(HashType*, size_t, const uint8_t*);
31
template <typename HashType> using FinishFn   = void (*)(HashType*, uint8_t*);
32
template <typename HashType> using DeinitFn   = void (*)(HashType*);
33
34
// Generic hashing flow that fuzz same hashing procedure for different algorithm
35
template <typename HashType>
36
static void fuzz_hash_ext_multi(FuzzedDataProvider &fdp,
37
                                size_t block_size,
38
                                InitOnceFn<HashType> init_once,
39
                                UpdateFn<HashType> update_fn,
40
                                FinishFn<HashType> finish_fn,
41
                                DeinitFn<HashType> deinit_fn,
42
2.45k
                                size_t digest_size) {
43
2.45k
  if (!fdp.remaining_bytes()) {
44
24
    return;
45
24
  }
46
47
  // Pull a random slice of data for fuzzing
48
2.42k
  size_t take_len = fdp.ConsumeIntegralInRange<size_t>(0, fdp.remaining_bytes());
49
2.42k
  std::vector<uint8_t> input_bytes = fdp.ConsumeBytes<uint8_t>(take_len);
50
51
  // Create 1 to 4 independent hashing contexts with it own digest buffer
52
2.42k
  const unsigned num_contexts = fdp.ConsumeIntegralInRange<unsigned>(1, 4);
53
2.42k
  std::vector<HashType> contexts(num_contexts);
54
2.42k
  std::vector<std::vector<uint8_t>> digests(num_contexts, std::vector<uint8_t>(digest_size));
55
7.90k
  for (unsigned i = 0; i < num_contexts; i++) {
56
5.48k
    init_once(&contexts[i]);
57
5.48k
  }
58
59
  // Intentionally misalign the data pointer to stress alignment sensitive paths
60
2.42k
  const size_t misalign_pad = fdp.ConsumeIntegralInRange<size_t>(0, 64);
61
2.42k
  std::vector<uint8_t> scratch_buf(misalign_pad + input_bytes.size());
62
2.42k
  if (!input_bytes.empty()) {
63
1.77k
    memcpy(scratch_buf.data() + misalign_pad, input_bytes.data(), input_bytes.size());
64
1.77k
  }
65
66
  // Define cursor and remaining bytes counter to keep track of the multiple hash update iterations
67
2.42k
  const uint8_t *cursor = scratch_buf.data() + misalign_pad;
68
2.42k
  size_t remaining = input_bytes.size();
69
70
  // Perform multiple hash update iterations on the raw data
71
2.42k
  unsigned num_iterations = fdp.ConsumeIntegralInRange<unsigned>(1, 4);
72
6.31k
  while (num_iterations-- && remaining > 0) {
73
    // Pick which context to feed this iteration
74
3.89k
    const unsigned ctx_index = (num_contexts == 1) ? 0 : fdp.ConsumeIntegralInRange<unsigned>(0, num_contexts - 1);
75
76
    // Choose a chunking pattern relative to block size.
77
3.89k
    enum Pattern { LESS1, EQ, PLUS1, SMALL, RANDOM, TAIL, HALT };
78
3.89k
    Pattern pattern = fdp.PickValueInArray<Pattern>({LESS1, EQ, PLUS1, SMALL, RANDOM, TAIL, HALT});
79
80
3.89k
    size_t chunk_len = 0;
81
3.89k
    switch (pattern) {
82
899
      case LESS1: {
83
        // Consume 1 byte less from block size from the raw data for this iteration
84
899
        if (block_size > 1) {
85
899
          chunk_len = std::min(remaining, block_size - 1);
86
899
        }
87
899
        break;
88
0
      }
89
215
      case EQ: {
90
        // Consume block size bytes from the raw data for this iteration
91
215
        chunk_len = std::min(remaining, block_size);
92
215
        break;
93
0
      }
94
250
      case PLUS1: {
95
        // Consume 1 byte more from block size from the raw data for this iteration
96
250
        chunk_len = std::min(remaining, block_size + 1);
97
250
        break;
98
0
      }
99
765
      case SMALL: {
100
        // Consume 1~32 bytes from the raw data for this iteration
101
765
        size_t small_len = (size_t)fdp.ConsumeIntegralInRange<int>(1, 32);
102
765
        chunk_len = std::min(remaining, small_len);
103
765
        break;
104
0
      }
105
638
      case RANDOM: {
106
        // Consume random bytes from the raw data for this iteration
107
638
        chunk_len = (remaining >= 1) ? (size_t)fdp.ConsumeIntegralInRange<size_t>(1, remaining) : 0;
108
638
        break;
109
0
      }
110
100
      case TAIL: {
111
        // Consume all remaining bytes from the raw data for this iteration
112
100
        chunk_len = remaining;
113
100
        break;
114
0
      }
115
1.02k
      case HALT: {
116
        // Consume small chunk and consider reinitialisation or early halt of the hash iteration
117
1.02k
        size_t step  = std::max<size_t>(1, fdp.ConsumeIntegralInRange<size_t>(1, block_size));
118
1.02k
        size_t loops = fdp.ConsumeIntegralInRange<size_t>(1, 4);
119
2.86k
        for (size_t j = 0; j < loops && remaining > 0; j++) {
120
1.84k
          size_t w = std::min(remaining, step);
121
1.84k
          update_fn(&contexts[ctx_index], w, cursor);
122
1.84k
          cursor += w;
123
1.84k
          remaining -= w;
124
1.84k
        }
125
126
        // Randomly reinitialise the hash stream
127
1.02k
        if (fdp.ConsumeBool()) {
128
439
          finish_fn(&contexts[ctx_index], digests[ctx_index].data());
129
439
        }
130
1.02k
        continue;
131
0
      }
132
3.89k
    }
133
134
2.86k
    if (chunk_len == 0 || chunk_len > remaining) {
135
0
      continue;
136
0
    }
137
138
    // Fuzz the update function
139
2.86k
    update_fn(&contexts[ctx_index], chunk_len, cursor);
140
2.86k
    cursor += chunk_len;
141
2.86k
    remaining -= chunk_len;
142
2.86k
  }
143
144
  // Finalize all active contexts (finish_reset).
145
7.90k
  for (unsigned i = 0; i < num_contexts; i++) {
146
5.48k
    finish_fn(&contexts[i], digests[i].data());
147
5.48k
  }
148
149
  // Additional fuzzing on special context chaining approach.
150
2.42k
  if (num_contexts >= 2 && digest_size && fdp.ConsumeBool()) {
151
752
    unsigned src_idx = fdp.ConsumeIntegralInRange<unsigned>(0, num_contexts - 1);
152
752
    unsigned dst_idx = fdp.ConsumeIntegralInRange<unsigned>(0, num_contexts - 1);
153
752
    if (src_idx != dst_idx) {
154
422
      size_t offset = fdp.ConsumeIntegralInRange<size_t>(0, digest_size - 1);
155
422
      size_t max_avail = digest_size - offset; // >= 1
156
422
      size_t feed_len = fdp.ConsumeIntegralInRange<size_t>(1, max_avail);
157
422
      update_fn(&contexts[dst_idx], feed_len, digests[src_idx].data() + offset);
158
422
      finish_fn(&contexts[dst_idx], digests[dst_idx].data());
159
422
    }
160
752
  }
161
162
  // Deinitialise all contexts after this iteration
163
7.90k
  for (unsigned i = 0; i < num_contexts; i++) {
164
5.48k
    deinit_fn(&contexts[i]);
165
5.48k
  }
166
2.42k
}
fuzz_crypto_ext.cpp:void fuzz_hash_ext_multi<mhd_Md5CtxExt>(FuzzedDataProvider&, unsigned long, void (*)(mhd_Md5CtxExt*), void (*)(mhd_Md5CtxExt*, unsigned long, unsigned char const*), void (*)(mhd_Md5CtxExt*, unsigned char*), void (*)(mhd_Md5CtxExt*), unsigned long)
Line
Count
Source
42
1.30k
                                size_t digest_size) {
43
1.30k
  if (!fdp.remaining_bytes()) {
44
10
    return;
45
10
  }
46
47
  // Pull a random slice of data for fuzzing
48
1.29k
  size_t take_len = fdp.ConsumeIntegralInRange<size_t>(0, fdp.remaining_bytes());
49
1.29k
  std::vector<uint8_t> input_bytes = fdp.ConsumeBytes<uint8_t>(take_len);
50
51
  // Create 1 to 4 independent hashing contexts with it own digest buffer
52
1.29k
  const unsigned num_contexts = fdp.ConsumeIntegralInRange<unsigned>(1, 4);
53
1.29k
  std::vector<HashType> contexts(num_contexts);
54
1.29k
  std::vector<std::vector<uint8_t>> digests(num_contexts, std::vector<uint8_t>(digest_size));
55
4.39k
  for (unsigned i = 0; i < num_contexts; i++) {
56
3.09k
    init_once(&contexts[i]);
57
3.09k
  }
58
59
  // Intentionally misalign the data pointer to stress alignment sensitive paths
60
1.29k
  const size_t misalign_pad = fdp.ConsumeIntegralInRange<size_t>(0, 64);
61
1.29k
  std::vector<uint8_t> scratch_buf(misalign_pad + input_bytes.size());
62
1.29k
  if (!input_bytes.empty()) {
63
991
    memcpy(scratch_buf.data() + misalign_pad, input_bytes.data(), input_bytes.size());
64
991
  }
65
66
  // Define cursor and remaining bytes counter to keep track of the multiple hash update iterations
67
1.29k
  const uint8_t *cursor = scratch_buf.data() + misalign_pad;
68
1.29k
  size_t remaining = input_bytes.size();
69
70
  // Perform multiple hash update iterations on the raw data
71
1.29k
  unsigned num_iterations = fdp.ConsumeIntegralInRange<unsigned>(1, 4);
72
3.49k
  while (num_iterations-- && remaining > 0) {
73
    // Pick which context to feed this iteration
74
2.20k
    const unsigned ctx_index = (num_contexts == 1) ? 0 : fdp.ConsumeIntegralInRange<unsigned>(0, num_contexts - 1);
75
76
    // Choose a chunking pattern relative to block size.
77
2.20k
    enum Pattern { LESS1, EQ, PLUS1, SMALL, RANDOM, TAIL, HALT };
78
2.20k
    Pattern pattern = fdp.PickValueInArray<Pattern>({LESS1, EQ, PLUS1, SMALL, RANDOM, TAIL, HALT});
79
80
2.20k
    size_t chunk_len = 0;
81
2.20k
    switch (pattern) {
82
476
      case LESS1: {
83
        // Consume 1 byte less from block size from the raw data for this iteration
84
476
        if (block_size > 1) {
85
476
          chunk_len = std::min(remaining, block_size - 1);
86
476
        }
87
476
        break;
88
0
      }
89
144
      case EQ: {
90
        // Consume block size bytes from the raw data for this iteration
91
144
        chunk_len = std::min(remaining, block_size);
92
144
        break;
93
0
      }
94
143
      case PLUS1: {
95
        // Consume 1 byte more from block size from the raw data for this iteration
96
143
        chunk_len = std::min(remaining, block_size + 1);
97
143
        break;
98
0
      }
99
450
      case SMALL: {
100
        // Consume 1~32 bytes from the raw data for this iteration
101
450
        size_t small_len = (size_t)fdp.ConsumeIntegralInRange<int>(1, 32);
102
450
        chunk_len = std::min(remaining, small_len);
103
450
        break;
104
0
      }
105
363
      case RANDOM: {
106
        // Consume random bytes from the raw data for this iteration
107
363
        chunk_len = (remaining >= 1) ? (size_t)fdp.ConsumeIntegralInRange<size_t>(1, remaining) : 0;
108
363
        break;
109
0
      }
110
61
      case TAIL: {
111
        // Consume all remaining bytes from the raw data for this iteration
112
61
        chunk_len = remaining;
113
61
        break;
114
0
      }
115
566
      case HALT: {
116
        // Consume small chunk and consider reinitialisation or early halt of the hash iteration
117
566
        size_t step  = std::max<size_t>(1, fdp.ConsumeIntegralInRange<size_t>(1, block_size));
118
566
        size_t loops = fdp.ConsumeIntegralInRange<size_t>(1, 4);
119
1.61k
        for (size_t j = 0; j < loops && remaining > 0; j++) {
120
1.04k
          size_t w = std::min(remaining, step);
121
1.04k
          update_fn(&contexts[ctx_index], w, cursor);
122
1.04k
          cursor += w;
123
1.04k
          remaining -= w;
124
1.04k
        }
125
126
        // Randomly reinitialise the hash stream
127
566
        if (fdp.ConsumeBool()) {
128
244
          finish_fn(&contexts[ctx_index], digests[ctx_index].data());
129
244
        }
130
566
        continue;
131
0
      }
132
2.20k
    }
133
134
1.63k
    if (chunk_len == 0 || chunk_len > remaining) {
135
0
      continue;
136
0
    }
137
138
    // Fuzz the update function
139
1.63k
    update_fn(&contexts[ctx_index], chunk_len, cursor);
140
1.63k
    cursor += chunk_len;
141
1.63k
    remaining -= chunk_len;
142
1.63k
  }
143
144
  // Finalize all active contexts (finish_reset).
145
4.39k
  for (unsigned i = 0; i < num_contexts; i++) {
146
3.09k
    finish_fn(&contexts[i], digests[i].data());
147
3.09k
  }
148
149
  // Additional fuzzing on special context chaining approach.
150
1.29k
  if (num_contexts >= 2 && digest_size && fdp.ConsumeBool()) {
151
446
    unsigned src_idx = fdp.ConsumeIntegralInRange<unsigned>(0, num_contexts - 1);
152
446
    unsigned dst_idx = fdp.ConsumeIntegralInRange<unsigned>(0, num_contexts - 1);
153
446
    if (src_idx != dst_idx) {
154
209
      size_t offset = fdp.ConsumeIntegralInRange<size_t>(0, digest_size - 1);
155
209
      size_t max_avail = digest_size - offset; // >= 1
156
209
      size_t feed_len = fdp.ConsumeIntegralInRange<size_t>(1, max_avail);
157
209
      update_fn(&contexts[dst_idx], feed_len, digests[src_idx].data() + offset);
158
209
      finish_fn(&contexts[dst_idx], digests[dst_idx].data());
159
209
    }
160
446
  }
161
162
  // Deinitialise all contexts after this iteration
163
4.39k
  for (unsigned i = 0; i < num_contexts; i++) {
164
3.09k
    deinit_fn(&contexts[i]);
165
3.09k
  }
166
1.29k
}
fuzz_crypto_ext.cpp:void fuzz_hash_ext_multi<mhd_Sha256CtxExt>(FuzzedDataProvider&, unsigned long, void (*)(mhd_Sha256CtxExt*), void (*)(mhd_Sha256CtxExt*, unsigned long, unsigned char const*), void (*)(mhd_Sha256CtxExt*, unsigned char*), void (*)(mhd_Sha256CtxExt*), unsigned long)
Line
Count
Source
42
1.14k
                                size_t digest_size) {
43
1.14k
  if (!fdp.remaining_bytes()) {
44
14
    return;
45
14
  }
46
47
  // Pull a random slice of data for fuzzing
48
1.13k
  size_t take_len = fdp.ConsumeIntegralInRange<size_t>(0, fdp.remaining_bytes());
49
1.13k
  std::vector<uint8_t> input_bytes = fdp.ConsumeBytes<uint8_t>(take_len);
50
51
  // Create 1 to 4 independent hashing contexts with it own digest buffer
52
1.13k
  const unsigned num_contexts = fdp.ConsumeIntegralInRange<unsigned>(1, 4);
53
1.13k
  std::vector<HashType> contexts(num_contexts);
54
1.13k
  std::vector<std::vector<uint8_t>> digests(num_contexts, std::vector<uint8_t>(digest_size));
55
3.51k
  for (unsigned i = 0; i < num_contexts; i++) {
56
2.38k
    init_once(&contexts[i]);
57
2.38k
  }
58
59
  // Intentionally misalign the data pointer to stress alignment sensitive paths
60
1.13k
  const size_t misalign_pad = fdp.ConsumeIntegralInRange<size_t>(0, 64);
61
1.13k
  std::vector<uint8_t> scratch_buf(misalign_pad + input_bytes.size());
62
1.13k
  if (!input_bytes.empty()) {
63
783
    memcpy(scratch_buf.data() + misalign_pad, input_bytes.data(), input_bytes.size());
64
783
  }
65
66
  // Define cursor and remaining bytes counter to keep track of the multiple hash update iterations
67
1.13k
  const uint8_t *cursor = scratch_buf.data() + misalign_pad;
68
1.13k
  size_t remaining = input_bytes.size();
69
70
  // Perform multiple hash update iterations on the raw data
71
1.13k
  unsigned num_iterations = fdp.ConsumeIntegralInRange<unsigned>(1, 4);
72
2.81k
  while (num_iterations-- && remaining > 0) {
73
    // Pick which context to feed this iteration
74
1.68k
    const unsigned ctx_index = (num_contexts == 1) ? 0 : fdp.ConsumeIntegralInRange<unsigned>(0, num_contexts - 1);
75
76
    // Choose a chunking pattern relative to block size.
77
1.68k
    enum Pattern { LESS1, EQ, PLUS1, SMALL, RANDOM, TAIL, HALT };
78
1.68k
    Pattern pattern = fdp.PickValueInArray<Pattern>({LESS1, EQ, PLUS1, SMALL, RANDOM, TAIL, HALT});
79
80
1.68k
    size_t chunk_len = 0;
81
1.68k
    switch (pattern) {
82
423
      case LESS1: {
83
        // Consume 1 byte less from block size from the raw data for this iteration
84
423
        if (block_size > 1) {
85
423
          chunk_len = std::min(remaining, block_size - 1);
86
423
        }
87
423
        break;
88
0
      }
89
71
      case EQ: {
90
        // Consume block size bytes from the raw data for this iteration
91
71
        chunk_len = std::min(remaining, block_size);
92
71
        break;
93
0
      }
94
107
      case PLUS1: {
95
        // Consume 1 byte more from block size from the raw data for this iteration
96
107
        chunk_len = std::min(remaining, block_size + 1);
97
107
        break;
98
0
      }
99
315
      case SMALL: {
100
        // Consume 1~32 bytes from the raw data for this iteration
101
315
        size_t small_len = (size_t)fdp.ConsumeIntegralInRange<int>(1, 32);
102
315
        chunk_len = std::min(remaining, small_len);
103
315
        break;
104
0
      }
105
275
      case RANDOM: {
106
        // Consume random bytes from the raw data for this iteration
107
275
        chunk_len = (remaining >= 1) ? (size_t)fdp.ConsumeIntegralInRange<size_t>(1, remaining) : 0;
108
275
        break;
109
0
      }
110
39
      case TAIL: {
111
        // Consume all remaining bytes from the raw data for this iteration
112
39
        chunk_len = remaining;
113
39
        break;
114
0
      }
115
457
      case HALT: {
116
        // Consume small chunk and consider reinitialisation or early halt of the hash iteration
117
457
        size_t step  = std::max<size_t>(1, fdp.ConsumeIntegralInRange<size_t>(1, block_size));
118
457
        size_t loops = fdp.ConsumeIntegralInRange<size_t>(1, 4);
119
1.25k
        for (size_t j = 0; j < loops && remaining > 0; j++) {
120
798
          size_t w = std::min(remaining, step);
121
798
          update_fn(&contexts[ctx_index], w, cursor);
122
798
          cursor += w;
123
798
          remaining -= w;
124
798
        }
125
126
        // Randomly reinitialise the hash stream
127
457
        if (fdp.ConsumeBool()) {
128
195
          finish_fn(&contexts[ctx_index], digests[ctx_index].data());
129
195
        }
130
457
        continue;
131
0
      }
132
1.68k
    }
133
134
1.23k
    if (chunk_len == 0 || chunk_len > remaining) {
135
0
      continue;
136
0
    }
137
138
    // Fuzz the update function
139
1.23k
    update_fn(&contexts[ctx_index], chunk_len, cursor);
140
1.23k
    cursor += chunk_len;
141
1.23k
    remaining -= chunk_len;
142
1.23k
  }
143
144
  // Finalize all active contexts (finish_reset).
145
3.51k
  for (unsigned i = 0; i < num_contexts; i++) {
146
2.38k
    finish_fn(&contexts[i], digests[i].data());
147
2.38k
  }
148
149
  // Additional fuzzing on special context chaining approach.
150
1.13k
  if (num_contexts >= 2 && digest_size && fdp.ConsumeBool()) {
151
306
    unsigned src_idx = fdp.ConsumeIntegralInRange<unsigned>(0, num_contexts - 1);
152
306
    unsigned dst_idx = fdp.ConsumeIntegralInRange<unsigned>(0, num_contexts - 1);
153
306
    if (src_idx != dst_idx) {
154
213
      size_t offset = fdp.ConsumeIntegralInRange<size_t>(0, digest_size - 1);
155
213
      size_t max_avail = digest_size - offset; // >= 1
156
213
      size_t feed_len = fdp.ConsumeIntegralInRange<size_t>(1, max_avail);
157
213
      update_fn(&contexts[dst_idx], feed_len, digests[src_idx].data() + offset);
158
213
      finish_fn(&contexts[dst_idx], digests[dst_idx].data());
159
213
    }
160
306
  }
161
162
  // Deinitialise all contexts after this iteration
163
3.51k
  for (unsigned i = 0; i < num_contexts; i++) {
164
2.38k
    deinit_fn(&contexts[i]);
165
2.38k
  }
166
1.13k
}
167
168
1.32k
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
169
1.32k
  FuzzedDataProvider fdp(data, size);
170
171
3.77k
  for (unsigned i = 0; i < fdp.ConsumeIntegralInRange<unsigned>(1, 4); i++) {
172
2.45k
    if (fdp.ConsumeBool()) {
173
1.30k
      fuzz_hash_ext_multi<struct mhd_Md5CtxExt>(
174
1.30k
        fdp, 64,
175
1.30k
        mhd_MD5_init_one_time, mhd_MD5_update, mhd_MD5_finish_reset, mhd_MD5_deinit,
176
1.30k
        mhd_MD5_DIGEST_SIZE);
177
1.30k
    } else {
178
1.14k
      fuzz_hash_ext_multi<struct mhd_Sha256CtxExt>(
179
1.14k
        fdp, 64,
180
1.14k
        mhd_SHA256_init_one_time, mhd_SHA256_update, mhd_SHA256_finish_reset, mhd_SHA256_deinit,
181
1.14k
        mhd_SHA256_DIGEST_SIZE);
182
1.14k
    }
183
2.45k
  }
184
1.32k
  return 0;
185
1.32k
}