-
Notifications
You must be signed in to change notification settings - Fork 945
fuzz-tests: Add a test for codex32
operations
#8390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
tests/fuzz/fuzz-codex32.c
Outdated
static const char *valid_vectors[] = { | ||
"ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw", | ||
"MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM", | ||
"ms13cashsllhdmn9m42vcsamx24zrxgs3qqjzqud4m0d6nln", | ||
"ms10leetsllhdmn9m42vcsamx24zrxgs3qqjzqud4m0d6nlnve25gvezzyqqtum9pgv99ycma", | ||
"MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD6AN074RXV" | ||
"CEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK" | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think it's cleaner to put seeds in the corpus directory instead of hard-coding them into the fuzz target.
I'd prefer to just put one seed here, or even get rid of initial_input
entirely and just return 0 instead for simplicity.
tests/fuzz/fuzz-codex32.c
Outdated
if (streq(parts->hrp, "ms")) | ||
parts->hrp = "MS"; | ||
else | ||
parts->hrp = "ms"; | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to limit the HRP to "MS" or "ms"? Allowing other mutations may trigger new coverage.
tests/fuzz/fuzz-codex32.c
Outdated
case 1: /* Mutate threshold (0-9) */ | ||
parts->threshold = rand() % 10; | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to limit threshold
here? Allowing other values may trigger new coverage.
tests/fuzz/fuzz-codex32.c
Outdated
for (int i = 0; i < 4; i++) | ||
parts->id[i] = 'A' + (rand() % 26); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid IDs have lowercase bech32 characters only. If the goal is to make a valid ID, we should be using those. Otherwise maybe we should leave it unconstrained?
tests/fuzz/fuzz-codex32.c
Outdated
parts->id[4] = '\0'; | ||
break; | ||
case 3: /* Mutate share index (valid char) */ | ||
parts->share_idx = "abcdefghijklmnopqrstuvwxyzABCDEF"[rand() % 32]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems codex32_secret_encode
overwrites the share_idx
with s
anyway, so this mutation is currently pointless.
tests/fuzz/fuzz-codex32.c
Outdated
if (tal_bytelen(parts->payload) > 0) { | ||
size_t mutate_len = 1 + rand() % tal_bytelen(parts->payload); | ||
LLVMFuzzerMutate((u8 *) parts->payload, mutate_len, | ||
tal_bytelen(parts->payload)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should let LLVMFuzzerMutate
decide how many bytes to mutate, and we should allow it to increase or decrease the payload size.
if (tal_bytelen(parts->payload) > 0) { | |
size_t mutate_len = 1 + rand() % tal_bytelen(parts->payload); | |
LLVMFuzzerMutate((u8 *) parts->payload, mutate_len, | |
tal_bytelen(parts->payload)); | |
} | |
size_t cur_size = tal_bytelen(parts->payload); | |
tal_resize(parts->payload, max_size); | |
size_t new_size = LLVMFuzzerMutate((u8 *) parts->payload, cur_size, max_size); | |
tal_resize(parse->payload, new_size); |
tests/fuzz/fuzz-codex32.c
Outdated
switch(rand() % 5) { | ||
case 0: | ||
child->hrp = p2->hrp; | ||
break; | ||
case 1: | ||
child->threshold = p2->threshold; | ||
break; | ||
case 2: | ||
memcpy(child->id, p2->id, 5); | ||
break; | ||
case 3: | ||
child->share_idx = p2->share_idx; | ||
break; | ||
case 4: /* Payload crossover */ | ||
child->payload = tal_arr(child, u8, tal_bytelen(p2->payload)); | ||
memcpy((u8 *) child->payload, p2->payload, tal_bytelen(p2->payload)); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea behind the crossover mutator is to combine pieces of each input into a new input (like genetic crossover). Using the cross_over
helper function from libfuzz.h
here would greatly strengthen this mutator.
Hey @morehouse, I was working on incorporating the feedback above but the resulting target fails with an out-of-bounds error when executed on the current corpus. Executing the resulting crash file however, fails to reproduce the crash:
Is this an actual bug or something wrong with the target? Any ideas on how I can investigate this further? |
Since it doesn't reproduce, it's likely a bug in one of your custom mutators. Check the line of code that UBSan is pointing to and go from there. |
The test works without breaking now. |
tests/fuzz/fuzz-codex32.c
Outdated
struct codex32 *mutable_parts = codex32_dup(tmpctx, parts); | ||
tal_free(parts); | ||
parts = mutable_parts; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the copy needed, especially since we just discard the original anyway?
tests/fuzz/fuzz-codex32.c
Outdated
for (int i = 0; i < 4; i++) | ||
parts->id[i] = rand(); | ||
parts->id[4] = '\0'; | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should prefer LLVMFuzzerMutate
here.
for (int i = 0; i < 4; i++) | |
parts->id[i] = rand(); | |
parts->id[4] = '\0'; | |
break; | |
size_t id_len = sizeof(parts->id) - 1; | |
LLVMFuzzerMutate(parts->id, id_len, id_len); | |
parts->id[id_len] = '\0'; | |
break; |
tests/fuzz/fuzz-codex32.c
Outdated
case 3: /* Mutate payload */ | ||
{ | ||
size_t cur_size = tal_bytelen(parts->payload); | ||
size_t max_payload_size = max_size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
max_payload_size
is unnecessary; we can just use max_size
directly.
tests/fuzz/fuzz-codex32.c
Outdated
{ | ||
size_t cur_size = tal_bytelen(parts->payload); | ||
size_t max_payload_size = max_size; | ||
u8 *new_payload = tal_arr(parts, u8, max_payload_size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we just resize the original parts->payload
, rather than managing an extra pointer?
tests/fuzz/fuzz-codex32.c
Outdated
parts->threshold, parts->payload, | ||
tal_bytelen(parts->payload), &reencoded); | ||
if (!err) { | ||
size_t len = strlen(reencoded); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can use tal_bytelen
here instead, that should be more efficient.
tests/fuzz/fuzz-codex32.c
Outdated
/* Apply mutation */ | ||
size_t new_size = LLVMFuzzerMutate(new_payload, | ||
cur_size < max_payload_size ? | ||
cur_size : max_payload_size, | ||
max_payload_size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why we would need to take the min here.
/* Apply mutation */ | |
size_t new_size = LLVMFuzzerMutate(new_payload, | |
cur_size < max_payload_size ? | |
cur_size : max_payload_size, | |
max_payload_size); | |
/* Apply mutation */ | |
size_t new_size = LLVMFuzzerMutate(new_payload, | |
cur_size, | |
max_payload_size); |
tests/fuzz/fuzz-codex32.c
Outdated
u8 new_id[5]; | ||
cross_over((const u8 *)p1->id, 4, (const u8 *)p2->id, 4, | ||
new_id, 4, rand()); | ||
new_id[4] = '\0'; | ||
memcpy(child->id, new_id, 4); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few issues with this code.
- we fail to copy the null byte
- we can make it simpler by mutating
child->id
directly.
u8 new_id[5]; | |
cross_over((const u8 *)p1->id, 4, (const u8 *)p2->id, 4, | |
new_id, 4, rand()); | |
new_id[4] = '\0'; | |
memcpy(child->id, new_id, 4); | |
size_t id_len = sizeof(p1->id) - 1; | |
cross_over((const u8 *)p1->id, id_len, (const u8 *)p2->id, id_len, | |
child->id, id_len, rand()); | |
child->id[id_len] = '\0'; |
tests/fuzz/fuzz-codex32.c
Outdated
u8 *new_payload = tal_arr(child, u8, max_out_size); | ||
size_t new_payload_len = cross_over(p1->payload, p1_len, | ||
p2->payload, p2_len, | ||
new_payload, max_out_size, rand()); | ||
tal_free(child->payload); | ||
child->payload = new_payload; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not resize child->payload
for simplicity?
u8 *new_payload = tal_arr(child, u8, max_out_size); | |
size_t new_payload_len = cross_over(p1->payload, p1_len, | |
p2->payload, p2_len, | |
new_payload, max_out_size, rand()); | |
tal_free(child->payload); | |
child->payload = new_payload; | |
tal_resize(&child->payload, u8, max_out_size); | |
size_t new_payload_len = cross_over(p1->payload, p1_len, | |
p2->payload, p2_len, | |
child->payload, max_out_size, rand()); |
tests/fuzz/fuzz-codex32.c
Outdated
if (rand() % 2) | ||
child->threshold = p2->threshold; | ||
if (rand() % 2) | ||
memcpy(child->id, p2->id, 5); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit
memcpy(child->id, p2->id, 5); | |
memcpy(child->id, p2->id, sizeof(p2->id)); |
} | ||
break; | ||
|
||
case 4: /* Random combination */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to do a random combination, perhaps we should create functions for the above crossover cases, so we can reuse them here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would make the test more complicated without the added benefit to match, perhaps we should get rid of this case altogether?
Changelog-None: Add a test for `codex32_encode()` and `codex32_secret_decode()` defined in `common/codex32.{c, h}`.
Add a minimal input set as a seed corpus for the newly introduced test. This leads to discovery of interesting code paths faster.
Add a test for
codex32_encode()
andcodex32_secret_decode()
defined incommon/codex32.{c, h}
.Checklist
Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:
CC: @morehouse