Files
url.c/tests/fuzz.c
T
Claude bd0a0f0c26 Add comprehensive fuzzing infrastructure for URL parser
This commit adds AFL++ (American Fuzzy Lop) fuzzing support to help find bugs,
crashes, and security vulnerabilities in the URL parser.

Changes:
- Add fuzz_afl.c: AFL++ fuzz target that tests all parser functions
  - url_parse() with different flag combinations
  - url_parse_ipv4() and url_parse_ipv6()
  - url_serialize(), url_percent_decode(), url_remove_white_space()

- Add build_afl.sh: Build script with AFL++ compiler detection
- Add run_afl.sh: Automated fuzzing script with result reporting
- Add url.dict: Dictionary file to guide AFL++ mutations

- Add corpus/: Seed inputs covering various URL patterns
  - Basic URLs, IPv4/IPv6, percent-encoding, relative paths
  - Edge cases and special schemes (file://, mailto:, etc.)

- Add FUZZING.md: Comprehensive documentation
  - Installation instructions
  - Quick start guide
  - Advanced usage (parallel fuzzing, sanitizers)
  - Troubleshooting tips

- Add fuzz.c: Standalone mutation-based fuzzer (no AFL++ required)
  - Useful for quick testing without installing AFL++
  - Implements various mutation strategies
2025-12-06 15:23:32 +00:00

364 lines
9.4 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <stdbool.h>
#include "../url.h"
// PRNG state
static uint64_t rng_state = 0;
// Simple xorshift64 RNG
static uint64_t rand64(void)
{
rng_state ^= rng_state << 13;
rng_state ^= rng_state >> 7;
rng_state ^= rng_state << 17;
return rng_state;
}
// Generate random byte
static uint8_t rand_byte(void)
{
return (uint8_t)(rand64() & 0xFF);
}
// Generate random integer in range [0, max)
static int rand_range(int max)
{
if (max <= 0) return 0;
return (int)(rand64() % max);
}
// Seed URLs for mutation-based fuzzing
static const char *seed_urls[] = {
"http://example.com",
"https://user:pass@example.com:8080/path?query=value#fragment",
"ftp://192.168.1.1/file.txt",
"http://[2001:db8::1]/",
"mailto:user@example.com",
"file:///path/to/file",
"http://example.com:80/path",
"https://example.com/path%20with%20spaces",
"http://localhost:3000",
"http://127.0.0.1:8080/api/v1/resource",
"//example.com/path",
"/path/to/resource",
"../relative/path",
"?query=only",
"#fragment-only",
"http://example.com:99999",
"http://256.256.256.256",
"http://example..com",
"http://:password@example.com",
"http://user:@example.com",
"http://example.com/path//double//slash",
"http://example.com/path/../../../etc/passwd",
"http://example.com/%2e%2e%2f",
"http://example.com/\x00null",
"http://example.com/\x0a\x0d",
"http://\t\n\rexample.com",
"http://example.com:0",
"http://example.com:-1",
"http://example.com:abc",
"http://[::1]",
"http://[::ffff:192.0.2.1]",
"http://[2001:db8::1]:8080",
"http://[::1",
"http://::1]",
"data:text/plain;base64,SGVsbG8=",
"javascript:alert(1)",
"about:blank",
};
#define NUM_SEEDS (sizeof(seed_urls) / sizeof(seed_urls[0]))
// Character classes for generation
static const char *char_classes[] = {
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
"://",
"@.:/?#[]",
"%",
"\t\n\r ",
"\x00\x01\x02\x03\x04\x05\x06\x07\x08",
};
#define NUM_CHAR_CLASSES (sizeof(char_classes) / sizeof(char_classes[0]))
// Mutation strategies
typedef enum {
MUT_BIT_FLIP,
MUT_BYTE_FLIP,
MUT_BYTE_INSERT,
MUT_BYTE_DELETE,
MUT_BYTE_REPLACE,
MUT_CHUNK_INSERT,
MUT_CHUNK_DELETE,
MUT_CHUNK_DUPLICATE,
MUT_INTERESTING_VALUE,
MUT_COUNT
} MutationType;
// Interesting byte values
static const uint8_t interesting_bytes[] = {
0x00, 0x01, 0xFF, 0x7F, 0x80,
'/', '?', '#', ':', '@', '[', ']',
'%', '.', '-', '_', '~',
};
// Mutate a buffer
static int mutate_buffer(char *dst, int dst_cap, const char *src, int src_len)
{
if (src_len <= 0 || src_len >= dst_cap) {
// Can't mutate empty or too large buffer
return src_len;
}
// Copy source to destination
memcpy(dst, src, src_len);
int len = src_len;
// Apply 1-5 random mutations
int num_mutations = 1 + rand_range(5);
for (int i = 0; i < num_mutations && len > 0 && len < dst_cap - 1; i++) {
MutationType mut = rand_range(MUT_COUNT);
int pos = rand_range(len);
switch (mut) {
case MUT_BIT_FLIP:
if (pos < len) {
dst[pos] ^= (1 << rand_range(8));
}
break;
case MUT_BYTE_FLIP:
if (pos < len) {
dst[pos] ^= 0xFF;
}
break;
case MUT_BYTE_INSERT:
if (len < dst_cap - 1) {
memmove(dst + pos + 1, dst + pos, len - pos);
dst[pos] = rand_byte();
len++;
}
break;
case MUT_BYTE_DELETE:
if (pos < len && len > 1) {
memmove(dst + pos, dst + pos + 1, len - pos - 1);
len--;
}
break;
case MUT_BYTE_REPLACE:
if (pos < len) {
dst[pos] = rand_byte();
}
break;
case MUT_CHUNK_INSERT: {
int chunk_len = 1 + rand_range(16);
if (len + chunk_len < dst_cap) {
memmove(dst + pos + chunk_len, dst + pos, len - pos);
for (int j = 0; j < chunk_len; j++) {
dst[pos + j] = rand_byte();
}
len += chunk_len;
}
break;
}
case MUT_CHUNK_DELETE: {
int chunk_len = 1 + rand_range(16);
if (pos + chunk_len <= len) {
memmove(dst + pos, dst + pos + chunk_len, len - pos - chunk_len);
len -= chunk_len;
}
break;
}
case MUT_CHUNK_DUPLICATE: {
int chunk_len = 1 + rand_range(16);
if (pos + chunk_len <= len && len + chunk_len < dst_cap) {
memmove(dst + pos + chunk_len, dst + pos, len - pos);
memcpy(dst + pos + chunk_len, dst + pos, chunk_len);
len += chunk_len;
}
break;
}
case MUT_INTERESTING_VALUE:
if (pos < len) {
dst[pos] = interesting_bytes[rand_range(sizeof(interesting_bytes))];
}
break;
default:
break;
}
}
return len;
}
// Generate a random URL-like string
static int generate_random_url(char *buf, int cap)
{
int strategy = rand_range(3);
if (strategy == 0) {
// Fully random generation
int len = rand_range(cap - 1);
for (int i = 0; i < len; i++) {
buf[i] = rand_byte();
}
return len;
} else if (strategy == 1) {
// Generate from character classes
int len = rand_range(cap - 1);
for (int i = 0; i < len; i++) {
const char *class = char_classes[rand_range(NUM_CHAR_CLASSES)];
int class_len = strlen(class);
buf[i] = class[rand_range(class_len)];
}
return len;
} else {
// Mutation-based: pick a seed and mutate it
const char *seed = seed_urls[rand_range(NUM_SEEDS)];
int seed_len = strlen(seed);
return mutate_buffer(buf, cap, seed, seed_len);
}
}
// Fuzz url_parse
static void fuzz_url_parse(const char *input, int len)
{
URL url;
int flags_options[] = { 0, URL_FLAG_ALLOWREF, URL_FLAG_RFC3986, URL_FLAG_ALLOWREF | URL_FLAG_RFC3986 };
int flags = flags_options[rand_range(4)];
// Test with NULL cursor
url_parse((char *)input, len, NULL, &url, flags);
// Test with cursor
int cursor = 0;
url_parse((char *)input, len, &cursor, &url, flags);
// Test serialization if parse succeeded
if (url_parse((char *)input, len, NULL, &url, flags) == 0) {
char output[4096];
url_serialize(url, NULL, output, sizeof(output));
}
// Test percent decode on random substrings
if (len > 0) {
int start = rand_range(len);
int end = start + rand_range(len - start);
URL_String str = { (char *)input + start, end - start };
char decoded[4096];
url_percent_decode(str, decoded, sizeof(decoded));
}
// Test whitespace removal
char tmp[4096];
url_remove_white_space((char *)input, len, tmp, sizeof(tmp));
}
// Fuzz url_parse_ipv4
static void fuzz_ipv4_parse(const char *input, int len)
{
URL_IPv4 ipv4;
// Test with NULL cursor
url_parse_ipv4((char *)input, len, NULL, &ipv4);
// Test with cursor
int cursor = 0;
url_parse_ipv4((char *)input, len, &cursor, &ipv4);
}
// Fuzz url_parse_ipv6
static void fuzz_ipv6_parse(const char *input, int len)
{
URL_IPv6 ipv6;
// Test with NULL cursor
url_parse_ipv6((char *)input, len, NULL, &ipv6);
// Test with cursor
int cursor = 0;
url_parse_ipv6((char *)input, len, &cursor, &ipv6);
}
// Run one fuzz iteration
static void fuzz_iteration(int iteration)
{
char input[4096];
int len = generate_random_url(input, sizeof(input));
// Ensure null termination for safety (though our API doesn't require it)
if (len < (int)sizeof(input)) {
input[len] = '\0';
}
// Fuzz different functions
int target = rand_range(3);
switch (target) {
case 0:
fuzz_url_parse(input, len);
break;
case 1:
fuzz_ipv4_parse(input, len);
break;
case 2:
fuzz_ipv6_parse(input, len);
break;
}
}
int main(int argc, char **argv)
{
// Initialize RNG
if (argc > 1) {
rng_state = strtoull(argv[1], NULL, 10);
} else {
rng_state = time(NULL);
}
printf("URL Parser Fuzzer\n");
printf("Seed: %llu\n", (unsigned long long)rng_state);
printf("Running fuzz tests...\n\n");
int num_iterations = 100000;
if (argc > 2) {
num_iterations = atoi(argv[2]);
}
clock_t start = clock();
for (int i = 0; i < num_iterations; i++) {
fuzz_iteration(i);
// Progress update every 10000 iterations
if (i > 0 && i % 10000 == 0) {
double elapsed = (double)(clock() - start) / CLOCKS_PER_SEC;
double rate = i / elapsed;
printf("\rIteration %d/%d (%.0f iter/sec)", i, num_iterations, rate);
fflush(stdout);
}
}
double elapsed = (double)(clock() - start) / CLOCKS_PER_SEC;
printf("\n\nCompleted %d iterations in %.2f seconds\n", num_iterations, elapsed);
printf("Average rate: %.0f iterations/second\n", num_iterations / elapsed);
printf("\nNo crashes detected!\n");
return 0;
}