Skip to content

Commit f7488a4

Browse files
DavidKorczynskialandekok
authored andcommitted
fuzzer: add der targeted fuzzer
Signed-off-by: David Korczynski <david@adalogics.com>
1 parent c8f3dc9 commit f7488a4

3 files changed

Lines changed: 151 additions & 3 deletions

File tree

src/fuzzer/all.mk

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# source / makefile are generated from src/fuzzer/fuzzer.c and
66
# src/fuzzer/fuzzer.mk via FUZZ_PROTOCOL below.
77
#
8-
FUZZER_PROTOCOLS = radius dhcpv4 dhcpv6 dns tacacs vmps tftp bfd cbor der arp
8+
FUZZER_PROTOCOLS = radius dhcpv4 dhcpv6 dns tacacs vmps tftp bfd cbor arp
99

1010
#
1111
# Standalone fuzzer targets - each has a hand-written fuzzer_<name>.c
@@ -15,7 +15,7 @@ FUZZER_PROTOCOLS = radius dhcpv4 dhcpv6 dns tacacs vmps tftp bfd cbor der arp
1515
# as a wire protocol (see src/lib/util/fuzzer.c) but isn't itself
1616
# a network protocol, so it lives here too.
1717
#
18-
FUZZER_NON_PROTOCOL_TARGETS = util json value cf xlat base16_32_64 tmpl
18+
FUZZER_NON_PROTOCOL_TARGETS = util json value cf xlat base16_32_64 tmpl der
1919

2020
#
2121
# Build these fuzzers, but skip them in CI.
@@ -30,6 +30,7 @@ FUZZER_NO_TEST = cf xlat tmpl
3030
FUZZER_util_ARGS := -D share/dictionary
3131
FUZZER_tmpl_ARGS := -D share/dictionary
3232
FUZZER_xlat_ARGS := -D share/dictionary
33+
FUZZER_der_ARGS := -D share/dictionary
3334

3435
#
3536
# Add the fuzzer only if everything was built with the fuzzing flags.
@@ -70,7 +71,7 @@ clean: clean.fuzzer
7071
#
7172
# Standalone fuzzers' build mks
7273
#
73-
SUBMAKEFILES += fuzzer_json.mk fuzzer_value.mk fuzzer_xlat.mk fuzzer_cf.mk fuzzer_base16_32_64.mk fuzzer_tmpl.mk
74+
SUBMAKEFILES += fuzzer_json.mk fuzzer_value.mk fuzzer_xlat.mk fuzzer_cf.mk fuzzer_base16_32_64.mk fuzzer_tmpl.mk fuzzer_der.mk
7475

7576
$(foreach X,${FUZZER_PROTOCOLS},$(eval $(call FUZZ_PROTOCOL,${X})))
7677

src/fuzzer/fuzzer_der.c

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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$")
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+
#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+
{
61+
size_t i;
62+
fr_dict_t const *dict_der;
63+
64+
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+
dict_der = fr_dict_by_protocol_name("der");
74+
if (!dict_der) {
75+
fr_perror("fuzzer_der: DER protocol dictionary is not loaded");
76+
fr_exit_now(EXIT_FAILURE);
77+
}
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+
for (i = 0; i < NUM_DER_ROOTS; i++) {
86+
der_roots[i] = fr_dict_attr_by_name(NULL, fr_dict_root(dict_der), der_root_names[i]);
87+
if (!der_roots[i]) {
88+
fr_perror("fuzzer_der: failed to find DER root attribute '%s'", der_root_names[i]);
89+
fr_exit_now(EXIT_FAILURE);
90+
}
91+
}
92+
93+
return 1;
94+
}
95+
96+
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
97+
{
98+
TALLOC_CTX *ctx;
99+
fr_pair_list_t vps;
100+
void *decode_ctx = NULL;
101+
fr_dict_attr_t const *root;
102+
fr_test_point_proto_decode_t *tp = &der_tp_decode_proto;
103+
104+
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+
if (len < 2) return 0;
111+
112+
root = der_roots[buf[0] % NUM_DER_ROOTS];
113+
buf++;
114+
len--;
115+
116+
ctx = talloc_init_const("fuzzer_der");
117+
fr_pair_list_init(&vps);
118+
119+
if (tp->test_ctx && (tp->test_ctx(&decode_ctx, NULL, dict, root) < 0)) {
120+
fr_perror("fuzzer_der: failed initializing decode_ctx");
121+
fr_exit_now(EXIT_FAILURE);
122+
}
123+
124+
if (tp->func(ctx, &vps, buf, len, decode_ctx) > 0) {
125+
PAIR_LIST_VERIFY_WITH_CTX(ctx, &vps);
126+
if (fr_debug_lvl > 3) fr_pair_list_debug(stderr, &vps);
127+
}
128+
129+
talloc_free(decode_ctx);
130+
talloc_free(ctx);
131+
132+
/*
133+
* Drop accumulated strerror messages so libFuzzer's leak
134+
* heuristics don't see growing allocations.
135+
*/
136+
fr_strerror_clear();
137+
138+
return 0;
139+
}

src/fuzzer/fuzzer_der.mk

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
TARGET := fuzzer_der$(E)
2+
SOURCES := fuzzer_der.c common.c
3+
4+
TGT_PREREQS := libfreeradius-der$(L)
5+
6+
SRC_CFLAGS := -fsanitize=fuzzer
7+
TGT_LDFLAGS := -fsanitize=fuzzer
8+
TGT_LDLIBS := $(LIBS)

0 commit comments

Comments
 (0)