Skip to content

Commit bcfaf70

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 bcfaf70

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-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: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
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+
/* Local copy of bech32 charset reverse mapping */
34+
static const s8 charset_rev[128] = {
35+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
36+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
37+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
38+
-1, 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1,
39+
-1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19,
40+
-1, 1, 0, 3, 16, 11, 28, 12, 14, 6, -1, 4, -1, -1, -1, -1,
41+
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
42+
1, 0, 3, 16, 11, 28, 12, 14, 6, -1, 4, -1, -1, -1, -1, -1
43+
};
44+
45+
/* Validation to prevent checksum crash */
46+
static bool hrp_valid_for_encoding(const struct codex32 *parts)
47+
{
48+
if (strlen(parts->hrp) != 2)
49+
return false;
50+
51+
for (int i = 0; i < strlen(parts->hrp); i++) {
52+
int c = parts->hrp[i];
53+
if (c < 0 || c >= 128 || charset_rev[c] == -1)
54+
return false;
55+
}
56+
return true;
57+
}
58+
59+
void init(int *argc, char ***argv)
60+
{
61+
common_setup("fuzzer");
62+
}
63+
64+
/* Custom mutator with structure-aware and byte-level mutations */
65+
size_t LLVMFuzzerCustomMutator(uint8_t *fuzz_data, size_t size,
66+
size_t max_size, unsigned int seed)
67+
{
68+
srand(seed);
69+
char *str = to_string(tmpctx, fuzz_data, size);
70+
char *fail;
71+
struct codex32 *parts = codex32_decode(tmpctx, NULL, str, &fail);
72+
73+
/* If valid, try structure-aware mutation */
74+
if (parts) {
75+
/* Mutate a random component */
76+
switch(rand() % 4) {
77+
case 0: /* Mutate HRP arbitrarily */
78+
{
79+
size_t hrp_len = strlen(parts->hrp);
80+
size_t max_hrp_len = 2;
81+
char *new_hrp = tal_arr(parts, char, max_hrp_len + 1);
82+
size_t new_hrp_len = LLVMFuzzerMutate((u8 *)new_hrp,
83+
hrp_len, max_hrp_len);
84+
new_hrp[new_hrp_len] = '\0';
85+
parts->hrp = new_hrp;
86+
}
87+
break;
88+
89+
case 1: /* Mutate threshold to any value */
90+
parts->threshold = rand();
91+
break;
92+
93+
case 2: /* Mutate ID arbitrarily */
94+
{
95+
size_t id_len = sizeof(parts->id) - 1;
96+
LLVMFuzzerMutate((u8 *)parts->id, id_len, id_len);
97+
parts->id[id_len] = '\0';
98+
}
99+
break;
100+
101+
case 3: /* Mutate payload */
102+
{
103+
size_t old_size = tal_bytelen(parts->payload);
104+
tal_resize(&parts->payload, max_size);
105+
size_t new_size = LLVMFuzzerMutate((u8 *)parts->payload, old_size, max_size);
106+
tal_resize(&parts->payload, new_size);
107+
}
108+
break;
109+
}
110+
111+
if (hrp_valid_for_encoding(parts)) {
112+
char *reencoded;
113+
const char *err = codex32_secret_encode(tmpctx, parts->hrp, parts->id,
114+
parts->threshold, parts->payload,
115+
tal_bytelen(parts->payload), &reencoded);
116+
if (!err) {
117+
size_t len = tal_bytelen(reencoded) - 1;
118+
if (len <= max_size) {
119+
memcpy(fuzz_data, reencoded, len);
120+
return len;
121+
}
122+
}
123+
}
124+
}
125+
126+
/* Fallback: byte-level mutation */
127+
return LLVMFuzzerMutate(fuzz_data, size, max_size);
128+
}
129+
130+
/* Custom crossover with structure-aware recombination */
131+
size_t LLVMFuzzerCustomCrossOver(const u8 *in1, size_t in1_size, const u8 *in2, size_t in2_size,
132+
u8 *out, size_t max_out_size, unsigned seed)
133+
{
134+
srand(seed);
135+
char *str1 = to_string(tmpctx, in1, in1_size);
136+
char *str2 = to_string(tmpctx, in2, in2_size);
137+
char *fail;
138+
139+
/* Decode both inputs */
140+
struct codex32 *p1 = codex32_decode(tmpctx, NULL, str1, &fail);
141+
struct codex32 *p2 = codex32_decode(tmpctx, NULL, str2, &fail);
142+
143+
/* If both valid, try structure-aware crossover */
144+
if (p1 && p2) {
145+
/* Create child by combining parts */
146+
struct codex32 *child = codex32_dup(tmpctx, p1);
147+
148+
/* Choose crossover method */
149+
switch(rand() % 4) {
150+
case 0: /* Crossover HRP */
151+
{
152+
size_t hrp1_len = strlen(p1->hrp);
153+
size_t hrp2_len = strlen(p2->hrp);
154+
char *new_hrp = tal_arr(child, char, max_out_size);
155+
size_t new_hrp_len = cross_over((const u8 *)p1->hrp, hrp1_len,
156+
(const u8 *)p2->hrp, hrp2_len,
157+
(u8 *)new_hrp, max_out_size, rand());
158+
new_hrp[new_hrp_len] = '\0';
159+
child->hrp = new_hrp;
160+
}
161+
break;
162+
163+
case 1: /* Crossover threshold */
164+
child->threshold = p2->threshold;
165+
break;
166+
167+
case 2: /* Crossover ID */
168+
{
169+
size_t id_len = sizeof(p1->id) - 1;
170+
cross_over((const u8 *)p1->id, id_len, (const u8 *)p2->id, id_len,
171+
(u8 *)child->id, id_len, rand());
172+
child->id[id_len] = '\0';
173+
}
174+
break;
175+
176+
case 3: /* Crossover payload */
177+
{
178+
size_t p1_len = tal_bytelen(p1->payload);
179+
size_t p2_len = tal_bytelen(p2->payload);
180+
tal_resize(&child->payload, max_out_size);
181+
size_t new_payload_len = cross_over(p1->payload, p1_len,
182+
p2->payload, p2_len,
183+
(u8 *)child->payload, max_out_size, rand());
184+
tal_resize(&child->payload, new_payload_len);
185+
}
186+
break;
187+
}
188+
189+
if (hrp_valid_for_encoding(child)) {
190+
char *reencoded;
191+
const char *err = codex32_secret_encode(tmpctx, child->hrp, child->id,
192+
child->threshold, child->payload,
193+
tal_bytelen(child->payload), &reencoded);
194+
if (!err) {
195+
size_t len = strlen(reencoded);
196+
if (len <= max_out_size) {
197+
memcpy(out, reencoded, len);
198+
return len;
199+
}
200+
}
201+
}
202+
}
203+
204+
/* Fallback: byte-level crossover */
205+
return cross_over(in1, in1_size, in2, in2_size, out, max_out_size, seed);
206+
}
207+
208+
void run(const uint8_t *data, size_t size)
209+
{
210+
struct codex32 *c32;
211+
char *str, *fail, *bip93;
212+
213+
str = to_string(tmpctx, data, size);
214+
215+
c32 = codex32_decode(tmpctx, NULL, str, &fail);
216+
if (c32) {
217+
const char *ret = codex32_secret_encode(tmpctx, c32->hrp, c32->id, c32->threshold,
218+
c32->payload, tal_bytelen(c32->payload), &bip93);
219+
assert(!ret && bip93);
220+
} else
221+
assert(fail);
222+
223+
clean_tmpctx();
224+
}

0 commit comments

Comments
 (0)