Coverage Report

Created: 2026-10-18 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/fuzzer/fuzzer_der.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/**
18
 * @file src/fuzzer/fuzzer_der.c
19
 * @brief Fuzz the DER (ASN.1) decoder against an explicit list of root attributes.
20
 *
21
 * The DER decoder is unusual among FreeRADIUS protocol decoders in that
22
 * it cannot meaningfully run with the dictionary root as its starting
23
 * attribute - fr_der_decode_proto() explicitly rejects that. Each useful
24
 * DER decode begins at a top-level ASN.1 structure such as Certificate
25
 * (RFC 5280) or CertificationRequest (RFC 2986). This harness keeps an
26
 * explicit list of those roots and selects one per input so a single
27
 * binary exercises all DER entry points instead of being pinned to one
28
 * via the FR_FUZZER_ROOT_ATTR environment variable.
29
 *
30
 * Input layout:
31
 *   byte[0]      - selects which root attribute to decode against,
32
 *                  modulo the size of the root table
33
 *   byte[1..]    - DER-encoded payload passed to fr_der_decode_proto()
34
 */
35
RCSID("$Id: af9119b609ad976548702b3cdd7a56ec9e599026 $")
36
37
#include <freeradius-devel/fuzzer/common.h>
38
39
/*
40
 *  The set of DER root attributes this harness fuzzes against.
41
 *  These names must exist as top-level attributes (DEFINE) in the
42
 *  DER dictionary - share/dictionary/der/. Adding a new root here
43
 *  is sufficient to extend coverage; no other change is required.
44
 */
45
static char const *der_root_names[] = {
46
  "Certificate",      /* RFC 5280 - X.509 */
47
  "CertificateRequest",   /* RFC 2986 - PKCS#10 CSR */
48
};
49
50
1.28k
#define NUM_DER_ROOTS (sizeof(der_root_names) / sizeof(der_root_names[0]))
51
52
static fr_dict_attr_t const *der_roots[NUM_DER_ROOTS];
53
54
extern fr_test_point_proto_decode_t der_tp_decode_proto;
55
56
int LLVMFuzzerInitialize(int *argc, char ***argv);
57
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len);
58
59
int LLVMFuzzerInitialize(int *argc, char ***argv)
60
4
{
61
4
  size_t i;
62
4
  fr_dict_t const *dict_der;
63
64
4
  if (fuzzer_common_init(argc, argv, true) < 0) fr_exit_now(EXIT_FAILURE);
65
66
  /*
67
   *  The DER protocol dictionary is loaded by fuzzer_common_init()
68
   *  via libfreeradius_der_dict_protocol's autoload table. The
69
   *  global `dict` in common.c is the internal dictionary; the
70
   *  per-protocol dict (which holds Certificate, CertificateRequest
71
   *  etc.) must be looked up by protocol name.
72
   */
73
4
  dict_der = fr_dict_by_protocol_name("der");
74
4
  if (!dict_der) {
75
0
    fr_perror("fuzzer_der: DER protocol dictionary is not loaded");
76
0
    fr_exit_now(EXIT_FAILURE);
77
0
  }
78
79
  /*
80
   *  Resolve each root attribute once. A missing root is a
81
   *  hard failure: it means the harness is out of sync with
82
   *  the DER dictionary and the next time CI runs the fuzzer
83
   *  would silently skip that path.
84
   */
85
8
  for (i = 0; i < NUM_DER_ROOTS; i++) {
86
4
    der_roots[i] = fr_dict_attr_by_name(NULL, fr_dict_root(dict_der), der_root_names[i]);
87
4
    if (!der_roots[i]) {
88
0
      fr_perror("fuzzer_der: failed to find DER root attribute '%s'", der_root_names[i]);
89
0
      fr_exit_now(EXIT_FAILURE);
90
0
    }
91
4
  }
92
93
4
  return 1;
94
4
}
95
96
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
97
1.27k
{
98
1.27k
  TALLOC_CTX      *ctx;
99
1.27k
  fr_pair_list_t      vps;
100
1.27k
  void        *decode_ctx = NULL;
101
1.27k
  fr_dict_attr_t const    *root;
102
1.27k
  fr_test_point_proto_decode_t  *tp = &der_tp_decode_proto;
103
104
1.27k
  if (!dict) LLVMFuzzerInitialize(NULL, NULL);
105
106
  /*
107
   *  Need at least one selector byte plus one DER byte to
108
   *  have a non-trivial payload.
109
   */
110
1.27k
  if (len < 2) return 0;
111
112
1.27k
  root = der_roots[buf[0] % NUM_DER_ROOTS];
113
1.27k
  buf++;
114
1.27k
  len--;
115
116
1.27k
  ctx = talloc_init_const("fuzzer_der");
117
1.27k
  fr_pair_list_init(&vps);
118
119
1.27k
  if (tp->test_ctx && (tp->test_ctx(&decode_ctx, NULL, dict, root) < 0)) {
120
0
    fr_perror("fuzzer_der: failed initializing decode_ctx");
121
0
    fr_exit_now(EXIT_FAILURE);
122
0
  }
123
124
1.27k
  if (tp->func(ctx, &vps, buf, len, decode_ctx) > 0) {
125
463
    PAIR_LIST_VERIFY_WITH_CTX(ctx, &vps);
126
463
    if (fr_debug_lvl > 3) fr_pair_list_debug(stderr, &vps);
127
463
  }
128
129
1.27k
  talloc_free(decode_ctx);
130
1.27k
  talloc_free(ctx);
131
132
  /*
133
   *  Drop accumulated strerror messages so libFuzzer's leak
134
   *  heuristics don't see growing allocations.
135
   */
136
1.27k
  fr_strerror_clear();
137
138
1.27k
  return 0;
139
1.27k
}