Skip to content

Commit 1ee0f91

Browse files
author
Chandra Pratap
committed
fuzz-tests: Add a test for codex32 operations
Changelog-None: Add a test for `codex32_encode()` and `codex32_secret_decode()` defined in `common/codex32.{c, h}`.
1 parent c50bd38 commit 1ee0f91

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed

tests/fuzz/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ FUZZ_COMMON_OBJS := \
2929
common/close_tx.o \
3030
common/configdir.o \
3131
common/configvar.o \
32+
common/codex32.o \
3233
common/channel_id.o \
3334
common/channel_type.o \
3435
common/cryptomsg.o \

tests/fuzz/fuzz-codex32.c

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
#include "config.h"
2+
#include <assert.h>
3+
#include <ccan/ccan/array_size/array_size.h>
4+
#include <ccan/ccan/tal/str/str.h>
5+
#include <common/setup.h>
6+
#include <common/bech32.h>
7+
#include <common/utils.h>
8+
#include <common/codex32.h>
9+
#include <tests/fuzz/libfuzz.h>
10+
11+
/* Default mutator defined by libFuzzer */
12+
size_t LLVMFuzzerMutate(uint8_t *data, size_t size, size_t max_size);
13+
size_t LLVMFuzzerCustomMutator(uint8_t *fuzz_data, size_t size, size_t max_size,
14+
unsigned int seed);
15+
size_t LLVMFuzzerCustomCrossOver(const u8 *in1, size_t in1_size, const u8 *in2,
16+
size_t in2_size, u8 *out, size_t max_out_size,
17+
unsigned seed);
18+
19+
/* Duplicate codex32 structure */
20+
static struct codex32 *codex32_dup(const tal_t *ctx, const struct codex32 *src)
21+
{
22+
struct codex32 *dup = tal(ctx, struct codex32);
23+
dup->hrp = tal_strdup(dup, src->hrp);
24+
dup->threshold = src->threshold;
25+
memcpy(dup->id, src->id, sizeof(dup->id));
26+
dup->share_idx = src->share_idx;
27+
dup->payload = tal_dup_arr(dup, u8, src->payload,
28+
tal_bytelen(src->payload), 0);
29+
dup->type = src->type;
30+
return dup;
31+
}
32+
33+
static bool codex32_fields_valid_for_encoding(const struct codex32 *parts)
34+
{
35+
/* Check threshold */
36+
if (parts->threshold > 9 || parts->threshold == 1)
37+
return false;
38+
39+
/* Check id: must be 4 characters, each in bech32 charset and ASCII */
40+
if (strlen(parts->id) != 4)
41+
return false;
42+
43+
for (int i = 0; i < 4; i++) {
44+
unsigned char c = parts->id[i];
45+
if (c == 0 || c >= 128)
46+
return false;
47+
if (bech32_charset_rev[c] == -1)
48+
return false;
49+
}
50+
51+
/* Check HRP: must be 2 characters, no embedded zeros, and ASCII */
52+
if (strlen(parts->hrp) != 2)
53+
return false;
54+
55+
if (parts->hrp[0] == 0)
56+
return false;
57+
58+
for (size_t i = 0; i < strlen(parts->hrp); i++) {
59+
unsigned char c = parts->hrp[i];
60+
if (c == 0 || c >= 128)
61+
return false;
62+
}
63+
64+
/* Check share index */
65+
unsigned char si = parts->share_idx;
66+
if (si == 0 || si >= 128)
67+
return false;
68+
if (bech32_charset_rev[si] == -1)
69+
return false;
70+
71+
return true;
72+
}
73+
74+
void init(int *argc, char ***argv)
75+
{
76+
common_setup("fuzzer");
77+
}
78+
79+
/* Custom mutator with structure-aware and byte-level mutations */
80+
size_t LLVMFuzzerCustomMutator(uint8_t *fuzz_data, size_t size,
81+
size_t max_size, unsigned int seed)
82+
{
83+
srand(seed);
84+
char *str = to_string(tmpctx, fuzz_data, size);
85+
char *fail;
86+
struct codex32 *parts = codex32_decode(tmpctx, NULL, str, &fail);
87+
88+
/* If valid, try structure-aware mutation */
89+
if (parts) {
90+
/* Make mutable copy */
91+
struct codex32 *mutable_parts = codex32_dup(tmpctx, parts);
92+
tal_free(parts);
93+
parts = mutable_parts;
94+
95+
/* Mutate a random component */
96+
switch(rand() % 4) {
97+
case 0: /* Mutate HRP arbitrarily */
98+
{
99+
size_t hrp_len = strlen(parts->hrp);
100+
size_t max_hrp_len = 10; /* Reasonable max */
101+
char *new_hrp = tal_arr(parts, char, max_hrp_len + 1);
102+
size_t new_hrp_len = LLVMFuzzerMutate((u8 *)new_hrp,
103+
hrp_len, max_hrp_len);
104+
new_hrp[new_hrp_len] = '\0';
105+
parts->hrp = new_hrp;
106+
}
107+
break;
108+
109+
case 1: /* Mutate threshold to any value */
110+
parts->threshold = rand();
111+
break;
112+
113+
case 2: /* Mutate ID arbitrarily */
114+
for (int i = 0; i < 4; i++)
115+
parts->id[i] = rand();
116+
parts->id[4] = '\0';
117+
break;
118+
119+
case 3: /* Mutate payload */
120+
{
121+
size_t cur_size = tal_bytelen(parts->payload);
122+
size_t max_payload_size = max_size;
123+
u8 *new_payload = tal_arr(parts, u8, max_payload_size);
124+
125+
/* Copy existing data */
126+
if (cur_size > 0 && cur_size <= max_payload_size)
127+
memcpy(new_payload, parts->payload, cur_size);
128+
129+
/* Apply mutation */
130+
size_t new_size = LLVMFuzzerMutate(new_payload,
131+
cur_size < max_payload_size ?
132+
cur_size : max_payload_size,
133+
max_payload_size);
134+
tal_free(parts->payload);
135+
parts->payload = new_payload;
136+
tal_resize(&parts->payload, new_size);
137+
}
138+
break;
139+
}
140+
141+
/* Only try to re-encode if parts are valid */
142+
if (codex32_fields_valid_for_encoding(parts)) {
143+
char *reencoded;
144+
const char *err = codex32_secret_encode(tmpctx, parts->hrp, parts->id,
145+
parts->threshold, parts->payload,
146+
tal_bytelen(parts->payload), &reencoded);
147+
if (!err) {
148+
size_t len = strlen(reencoded);
149+
if (len <= max_size) {
150+
memcpy(fuzz_data, reencoded, len);
151+
return len;
152+
}
153+
}
154+
}
155+
}
156+
157+
/* Fallback: byte-level mutation */
158+
return LLVMFuzzerMutate(fuzz_data, size, max_size);
159+
}
160+
161+
/* Custom crossover with structure-aware recombination */
162+
size_t LLVMFuzzerCustomCrossOver(const u8 *in1, size_t in1_size, const u8 *in2, size_t in2_size,
163+
u8 *out, size_t max_out_size, unsigned seed)
164+
{
165+
srand(seed);
166+
char *str1 = to_string(tmpctx, in1, in1_size);
167+
char *str2 = to_string(tmpctx, in2, in2_size);
168+
char *fail;
169+
170+
/* Decode both inputs */
171+
struct codex32 *p1 = codex32_decode(tmpctx, NULL, str1, &fail);
172+
struct codex32 *p2 = codex32_decode(tmpctx, NULL, str2, &fail);
173+
174+
/* If both valid, try structure-aware crossover */
175+
if (p1 && p2) {
176+
/* Create child by combining parts */
177+
struct codex32 *child = codex32_dup(tmpctx, p1);
178+
179+
/* Choose crossover method */
180+
switch(rand() % 5) {
181+
case 0: /* Crossover HRP */
182+
{
183+
size_t hrp1_len = strlen(p1->hrp);
184+
size_t hrp2_len = strlen(p2->hrp);
185+
char *new_hrp = tal_arr(child, char, max_out_size);
186+
size_t new_hrp_len = cross_over((const u8 *)p1->hrp, hrp1_len,
187+
(const u8 *)p2->hrp, hrp2_len,
188+
(u8 *)new_hrp, max_out_size, rand());
189+
new_hrp[new_hrp_len] = '\0';
190+
child->hrp = new_hrp;
191+
}
192+
break;
193+
194+
case 1: /* Crossover threshold */
195+
child->threshold = p2->threshold;
196+
break;
197+
198+
case 2: /* Crossover ID */
199+
{
200+
u8 new_id[5];
201+
cross_over((const u8 *)p1->id, 4, (const u8 *)p2->id, 4,
202+
new_id, 4, rand());
203+
new_id[4] = '\0';
204+
memcpy(child->id, new_id, 4);
205+
}
206+
break;
207+
208+
case 3: /* Crossover payload */
209+
{
210+
size_t p1_len = tal_bytelen(p1->payload);
211+
size_t p2_len = tal_bytelen(p2->payload);
212+
u8 *new_payload = tal_arr(child, u8, max_out_size);
213+
size_t new_payload_len = cross_over(p1->payload, p1_len,
214+
p2->payload, p2_len,
215+
new_payload, max_out_size, rand());
216+
tal_free(child->payload);
217+
child->payload = new_payload;
218+
tal_resize(&child->payload, new_payload_len);
219+
}
220+
break;
221+
222+
case 4: /* Random combination */
223+
if (rand() % 2)
224+
child->hrp = tal_strdup(child, p2->hrp);
225+
if (rand() % 2)
226+
child->threshold = p2->threshold;
227+
if (rand() % 2)
228+
memcpy(child->id, p2->id, 5);
229+
if (rand() % 2) {
230+
tal_free(child->payload);
231+
child->payload = tal_dup_arr(child, u8, p2->payload,
232+
tal_bytelen(p2->payload), 0);
233+
}
234+
break;
235+
}
236+
237+
/* Only try to re-encode if child valid */
238+
if (codex32_fields_valid_for_encoding(child)) {
239+
char *reencoded;
240+
const char *err = codex32_secret_encode(tmpctx, child->hrp, child->id,
241+
child->threshold, child->payload,
242+
tal_bytelen(child->payload), &reencoded);
243+
if (!err) {
244+
size_t len = strlen(reencoded);
245+
if (len <= max_out_size) {
246+
memcpy(out, reencoded, len);
247+
return len;
248+
}
249+
}
250+
}
251+
}
252+
253+
/* Fallback: byte-level crossover */
254+
return cross_over(in1, in1_size, in2, in2_size, out, max_out_size, seed);
255+
}
256+
257+
void run(const uint8_t *data, size_t size)
258+
{
259+
struct codex32 *c32;
260+
char *str, *fail, *bip93;
261+
262+
str = to_string(tmpctx, data, size);
263+
264+
c32 = codex32_decode(tmpctx, NULL, str, &fail);
265+
if (c32) {
266+
const char *ret = codex32_secret_encode(tmpctx, c32->hrp, c32->id, c32->threshold,
267+
c32->payload, tal_bytelen(c32->payload), &bip93);
268+
assert(!ret && bip93);
269+
} else
270+
assert(fail);
271+
272+
clean_tmpctx();
273+
}

0 commit comments

Comments
 (0)