Skip to content

Commit 0e4f878

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 0e4f878

File tree

2 files changed

+192
-0
lines changed

2 files changed

+192
-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: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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+
static const char valid_hrp_chars[] = "acdefghjklmnpqrstuvwxyz";
20+
21+
/* Duplicate codex32 structure */
22+
static struct codex32 *codex32_dup(const tal_t *ctx, const struct codex32 *src)
23+
{
24+
struct codex32 *dup = tal(ctx, struct codex32);
25+
dup->hrp = tal_strdup(dup, src->hrp);
26+
dup->threshold = src->threshold;
27+
memcpy(dup->id, src->id, sizeof(dup->id));
28+
dup->share_idx = src->share_idx;
29+
dup->payload = tal_dup_arr(dup, u8, src->payload,
30+
tal_bytelen(src->payload), 0);
31+
dup->type = src->type;
32+
return dup;
33+
}
34+
35+
void init(int *argc, char ***argv)
36+
{
37+
common_setup("fuzzer");
38+
}
39+
40+
/* Custom mutator with structure-aware and byte-level mutations */
41+
size_t LLVMFuzzerCustomMutator(uint8_t *fuzz_data, size_t size,
42+
size_t max_size, unsigned int seed)
43+
{
44+
srand(seed);
45+
char *str = to_string(tmpctx, fuzz_data, size);
46+
char *fail;
47+
struct codex32 *parts = codex32_decode(tmpctx, NULL, str, &fail);
48+
49+
/* If valid, try structure-aware mutation */
50+
if (parts) {
51+
/* Mutate a random component */
52+
switch(rand() % 4) {
53+
case 0: /* Mutate HRP within valid constraints */
54+
{
55+
char *new_hrp = tal_arr(parts, char, 3);
56+
new_hrp[0] = valid_hrp_chars[rand() % strlen(valid_hrp_chars)];
57+
new_hrp[1] = valid_hrp_chars[rand() % strlen(valid_hrp_chars)];
58+
new_hrp[2] = '\0';
59+
parts->hrp = new_hrp;
60+
}
61+
break;
62+
63+
case 1: /* Mutate threshold to any value */
64+
parts->threshold = rand();
65+
break;
66+
67+
case 2: /* Mutate ID arbitrarily */
68+
{
69+
size_t id_len = sizeof(parts->id) - 1;
70+
LLVMFuzzerMutate((u8 *)parts->id, id_len, id_len);
71+
parts->id[id_len] = '\0';
72+
}
73+
break;
74+
75+
case 3: /* Mutate payload */
76+
{
77+
size_t old_size = tal_bytelen(parts->payload);
78+
tal_resize(&parts->payload, max_size);
79+
size_t new_size = LLVMFuzzerMutate((u8 *)parts->payload, old_size, max_size);
80+
tal_resize(&parts->payload, new_size);
81+
}
82+
break;
83+
}
84+
85+
char *reencoded;
86+
const char *err = codex32_secret_encode(tmpctx, parts->hrp, parts->id,
87+
parts->threshold, parts->payload,
88+
tal_bytelen(parts->payload), &reencoded);
89+
if (!err) {
90+
size_t len = tal_bytelen(reencoded) - 1;
91+
if (len <= max_size) {
92+
memcpy(fuzz_data, reencoded, len);
93+
return len;
94+
}
95+
}
96+
}
97+
98+
/* Fallback: byte-level mutation */
99+
return LLVMFuzzerMutate(fuzz_data, size, max_size);
100+
}
101+
102+
/* Custom crossover with structure-aware recombination */
103+
size_t LLVMFuzzerCustomCrossOver(const u8 *in1, size_t in1_size, const u8 *in2, size_t in2_size,
104+
u8 *out, size_t max_out_size, unsigned seed)
105+
{
106+
srand(seed);
107+
char *str1 = to_string(tmpctx, in1, in1_size);
108+
char *str2 = to_string(tmpctx, in2, in2_size);
109+
char *fail;
110+
111+
/* Decode both inputs */
112+
struct codex32 *p1 = codex32_decode(tmpctx, NULL, str1, &fail);
113+
struct codex32 *p2 = codex32_decode(tmpctx, NULL, str2, &fail);
114+
115+
/* If both valid, try structure-aware crossover */
116+
if (p1 && p2) {
117+
/* Create child by combining parts */
118+
struct codex32 *child = codex32_dup(tmpctx, p1);
119+
120+
/* Choose crossover method */
121+
switch(rand() % 4) {
122+
case 0: /* Crossover HRP within valid constraints */
123+
{
124+
char *new_hrp = tal_arr(child, char, 3);
125+
new_hrp[0] = valid_hrp_chars[rand() % strlen(valid_hrp_chars)];
126+
new_hrp[1] = valid_hrp_chars[rand() % strlen(valid_hrp_chars)];
127+
new_hrp[2] = '\0';
128+
child->hrp = new_hrp;
129+
}
130+
break;
131+
132+
case 1: /* Crossover threshold */
133+
child->threshold = p2->threshold;
134+
break;
135+
136+
case 2: /* Crossover ID */
137+
{
138+
size_t id_len = sizeof(p1->id) - 1;
139+
cross_over((const u8 *)p1->id, id_len, (const u8 *)p2->id, id_len,
140+
(u8 *)child->id, id_len, rand());
141+
child->id[id_len] = '\0';
142+
}
143+
break;
144+
145+
case 3: /* Crossover payload */
146+
{
147+
size_t p1_len = tal_bytelen(p1->payload);
148+
size_t p2_len = tal_bytelen(p2->payload);
149+
tal_resize(&child->payload, max_out_size);
150+
size_t new_payload_len = cross_over(p1->payload, p1_len,
151+
p2->payload, p2_len,
152+
(u8 *)child->payload, max_out_size, rand());
153+
tal_resize(&child->payload, new_payload_len);
154+
}
155+
break;
156+
}
157+
158+
char *reencoded;
159+
const char *err = codex32_secret_encode(tmpctx, child->hrp, child->id,
160+
child->threshold, child->payload,
161+
tal_bytelen(child->payload), &reencoded);
162+
if (!err) {
163+
size_t len = strlen(reencoded);
164+
if (len <= max_out_size) {
165+
memcpy(out, reencoded, len);
166+
return len;
167+
}
168+
}
169+
}
170+
171+
/* Fallback: byte-level crossover */
172+
return cross_over(in1, in1_size, in2, in2_size, out, max_out_size, seed);
173+
}
174+
175+
void run(const uint8_t *data, size_t size)
176+
{
177+
struct codex32 *c32;
178+
char *str, *fail, *bip93;
179+
180+
str = to_string(tmpctx, data, size);
181+
182+
c32 = codex32_decode(tmpctx, NULL, str, &fail);
183+
if (c32) {
184+
const char *ret = codex32_secret_encode(tmpctx, c32->hrp, c32->id, c32->threshold,
185+
c32->payload, tal_bytelen(c32->payload), &bip93);
186+
assert(!ret && bip93);
187+
} else
188+
assert(fail);
189+
190+
clean_tmpctx();
191+
}

0 commit comments

Comments
 (0)