v2.2.2
This commit is contained in:
parent
f3e39fb8e6
commit
e5f5d32aab
124 changed files with 11892 additions and 2461 deletions
157
tests/fuzz_format.c
Normal file
157
tests/fuzz_format.c
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/* zupt format parser fuzz harness
|
||||
*
|
||||
* Mutates valid archives and feeds them to the listing/extract path
|
||||
* under ASAN/UBSAN. Any crash, leak, or sanitizer error is a finding.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
#define _DEFAULT_SOURCE 1
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
|
||||
/* Simple xorshift64 PRNG — deterministic, fast */
|
||||
static uint64_t rng_state = 0xc0ffeebabe5050ULL;
|
||||
static uint64_t rng(void) {
|
||||
uint64_t x = rng_state;
|
||||
x ^= x << 13; x ^= x >> 7; x ^= x << 17;
|
||||
return rng_state = x;
|
||||
}
|
||||
|
||||
static void mutate(uint8_t *buf, size_t n) {
|
||||
if (n == 0) return;
|
||||
int op = rng() % 5;
|
||||
switch (op) {
|
||||
case 0: { /* byte flip */
|
||||
size_t i = rng() % n;
|
||||
buf[i] ^= 1u << (rng() % 8);
|
||||
break;
|
||||
}
|
||||
case 1: { /* random byte set */
|
||||
size_t i = rng() % n;
|
||||
buf[i] = (uint8_t)(rng() & 0xFF);
|
||||
break;
|
||||
}
|
||||
case 2: { /* zero a 16-byte run */
|
||||
if (n < 16) return;
|
||||
size_t off = rng() % (n - 16);
|
||||
memset(buf + off, 0, 16);
|
||||
break;
|
||||
}
|
||||
case 3: { /* set 0xFF run */
|
||||
if (n < 16) return;
|
||||
size_t off = rng() % (n - 16);
|
||||
memset(buf + off, 0xFF, 16);
|
||||
break;
|
||||
}
|
||||
case 4: { /* swap two bytes */
|
||||
if (n < 2) return;
|
||||
size_t i = rng() % n;
|
||||
size_t j = rng() % n;
|
||||
uint8_t t = buf[i]; buf[i] = buf[j]; buf[j] = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Run the zupt binary on a given archive file. We use fork+exec because
|
||||
* any crash in zupt would otherwise take down the harness; the parent
|
||||
* just records the exit status. ASAN/UBSAN errors return non-zero. */
|
||||
static int run_zupt(const char *zupt_path, const char *archive,
|
||||
const char **flag, int with_pq) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) return -1;
|
||||
if (pid == 0) {
|
||||
/* child */
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
if (devnull >= 0) {
|
||||
dup2(devnull, 1); dup2(devnull, 2);
|
||||
close(devnull);
|
||||
}
|
||||
if (with_pq) {
|
||||
execl(zupt_path, "zupt", "list", "--pq-sdk", "/tmp/_fuzz.priv",
|
||||
archive, (char*)NULL);
|
||||
} else {
|
||||
execl(zupt_path, "zupt", "list", archive, (char*)NULL);
|
||||
}
|
||||
_exit(127);
|
||||
(void)flag;
|
||||
}
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFSIGNALED(status)) return -2; /* crash! */
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int n_iters = argc > 1 ? atoi(argv[1]) : 1000;
|
||||
const char *zupt_path = argc > 2 ? argv[2] : "./zupt";
|
||||
const char *seed = argc > 3 ? argv[3] : "/tmp/_fuzz_seed.zupt";
|
||||
|
||||
if (access(seed, R_OK) != 0) {
|
||||
fprintf(stderr, "Seed archive not found at %s\n", seed);
|
||||
fprintf(stderr, "Build seed first: ./zupt c %s some_file.txt\n", seed);
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE *f = fopen(seed, "rb");
|
||||
if (!f) return 1;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long sz = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
uint8_t *seed_data = malloc((size_t)sz);
|
||||
if (!seed_data || fread(seed_data, 1, (size_t)sz, f) != (size_t)sz) {
|
||||
fclose(f); free(seed_data); return 1;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
fprintf(stderr, "Fuzzing zupt format parser: %d iters, seed=%ld bytes\n",
|
||||
n_iters, sz);
|
||||
|
||||
int crashes = 0, errors = 0, accepts = 0;
|
||||
char fuzzfile[64] = "/tmp/_fuzz_archive.zupt";
|
||||
double t0 = (double)clock() / CLOCKS_PER_SEC;
|
||||
|
||||
for (int i = 0; i < n_iters; i++) {
|
||||
/* Copy seed, apply 1-5 random mutations */
|
||||
uint8_t *buf = malloc((size_t)sz);
|
||||
if (!buf) break;
|
||||
memcpy(buf, seed_data, (size_t)sz);
|
||||
int n_mut = 1 + (rng() % 5);
|
||||
for (int m = 0; m < n_mut; m++) mutate(buf, (size_t)sz);
|
||||
|
||||
FILE *out = fopen(fuzzfile, "wb");
|
||||
if (!out) { free(buf); continue; }
|
||||
fwrite(buf, 1, (size_t)sz, out);
|
||||
fclose(out);
|
||||
free(buf);
|
||||
|
||||
int rc = run_zupt(zupt_path, fuzzfile, NULL, 0);
|
||||
if (rc == -2) crashes++;
|
||||
else if (rc != 0) errors++;
|
||||
else accepts++;
|
||||
|
||||
if (i > 0 && i % 100 == 0)
|
||||
fprintf(stderr, " [%d/%d] crashes=%d errors=%d accepts=%d\r",
|
||||
i, n_iters, crashes, errors, accepts);
|
||||
}
|
||||
double dt = (double)clock() / CLOCKS_PER_SEC - t0;
|
||||
|
||||
unlink(fuzzfile);
|
||||
free(seed_data);
|
||||
|
||||
fprintf(stderr, "\n\nFuzz results (%d iterations, %.1fs):\n", n_iters, dt);
|
||||
fprintf(stderr, " CRASHES (signal): %d <-- bugs if non-zero\n", crashes);
|
||||
fprintf(stderr, " errors (rejected): %d (expected, parser working)\n", errors);
|
||||
fprintf(stderr, " accepts: %d (mutations that didn't break magic/size)\n", accepts);
|
||||
|
||||
return crashes > 0 ? 1 : 0;
|
||||
}
|
||||
Loading…
Reference in a new issue