Skip to content

Commit 4927a23

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 4927a23

File tree

2 files changed

+229
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)