mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-08-04 04:45:44 +02:00
Falcon: move round-trip fuzzer out of the wolfSSL tree
The keygen/sign/verify fuzzer now lives in the testing repo (fuzzers/falcon); it is not part of the library or its CI. Removing it here also drops tests/falcon/run.sh from the PR's changed-shell set.
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
/falcon_fuzz
|
||||
*.repro
|
||||
@@ -1,45 +0,0 @@
|
||||
# tests/falcon/Makefile -- builds the Falcon keygen/sign/verify fuzzer.
|
||||
#
|
||||
# Standalone: cd tests/falcon && make (links the in-tree library)
|
||||
# cd tests/falcon && make run (build + short smoke run)
|
||||
#
|
||||
# Assumes the parent wolfSSL tree has been configured and built with
|
||||
# ./configure --enable-experimental --enable-falcon && make
|
||||
# so that src/.libs/libwolfssl.so and wolfssl/options.h exist.
|
||||
|
||||
SRCDIR ?= $(CURDIR)
|
||||
WOLFROOT ?= $(abspath $(SRCDIR)/../..)
|
||||
|
||||
CC ?= cc
|
||||
CFLAGS ?= -Wall -Wextra -O2
|
||||
# The fuzzer includes <wolfssl/options.h> directly (autotools build), so do
|
||||
# NOT define WOLFSSL_USER_SETTINGS here.
|
||||
CPPFLAGS += -I$(WOLFROOT)
|
||||
# Link against the freshly built in-tree library, not any system copy.
|
||||
LDFLAGS += -L$(WOLFROOT)/src/.libs
|
||||
LDLIBS += -lwolfssl
|
||||
|
||||
# Let the loader find the in-tree shared object at run time without an install.
|
||||
export LD_LIBRARY_PATH := $(WOLFROOT)/src/.libs:$(LD_LIBRARY_PATH)
|
||||
|
||||
BIN = falcon_fuzz
|
||||
|
||||
.PHONY: all run clean
|
||||
|
||||
all: $(BIN)
|
||||
|
||||
$(BIN): falcon_fuzz.c $(WOLFROOT)/wolfssl/options.h
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
$(WOLFROOT)/wolfssl/options.h:
|
||||
@echo "error: $(WOLFROOT) is not configured/built." >&2
|
||||
@echo " run: cd $(WOLFROOT) && ./configure --enable-experimental \
|
||||
--enable-falcon && make" >&2
|
||||
@false
|
||||
|
||||
# Short run suitable for a quick check; the CI wrapper (run.sh) uses a longer one.
|
||||
run: $(BIN)
|
||||
./$(BIN) --iters 200 --msgs 4
|
||||
|
||||
clean:
|
||||
rm -f $(BIN) *.repro
|
||||
@@ -1,69 +0,0 @@
|
||||
# Falcon keygen/sign/verify fuzzer
|
||||
|
||||
`falcon_fuzz` hunts for a specific, intermittently reproducing fault in the
|
||||
native Falcon implementation: **a freshly generated key produces a signature
|
||||
that then fails to verify against its own public key.**
|
||||
|
||||
Because the fault is probabilistic — it depends on the Gaussian sampler and the
|
||||
signing-restart path, and on the particular key — a single
|
||||
`make_key`/`sign`/`verify` pass rarely trips it. This driver hammers the loop
|
||||
across many keys and many messages per key (varying message length, including
|
||||
the zero-length edge case) so a "fairly regular" fault surfaces quickly.
|
||||
|
||||
## What it checks per key
|
||||
|
||||
1. `wc_falcon_make_key` succeeds and `wc_falcon_check_key` passes.
|
||||
2. Each of `--msgs` random messages signs, and the signature **verifies**
|
||||
against the key's own public key (`res == 1`). This is the primary target.
|
||||
3. A single-bit-flipped signature is **rejected** — guards against a verifier
|
||||
that trivially accepts everything (which would otherwise mask the real bug).
|
||||
|
||||
## Reproducing a failure
|
||||
|
||||
Verification is a pure function of `(public key, message, signature)`, so on the
|
||||
first mismatch the driver writes a self-contained `*.repro` artifact (public
|
||||
key, raw private key, message, and the non-verifying signature). Replay it
|
||||
deterministically — no need to reconstruct the RNG stream:
|
||||
|
||||
```
|
||||
./falcon_fuzz --replay falcon_fail_L1_1234.repro
|
||||
```
|
||||
|
||||
Replay exits 1 (and prints "DOES NOT VERIFY") when the artifact reproduces the
|
||||
fault, 0 when it verifies cleanly.
|
||||
|
||||
## Building and running
|
||||
|
||||
Requires the parent tree configured/built with Falcon:
|
||||
|
||||
```
|
||||
./configure --enable-experimental --enable-falcon && make
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
sh tests/falcon/run.sh [iters] [msgs] # build + run, CI-friendly exit codes
|
||||
# or
|
||||
cd tests/falcon && make && ./falcon_fuzz --help
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--level 1|5|both security level(s) to fuzz (default: both)
|
||||
--iters N keygen iterations per level, 0 = forever (default: 5000)
|
||||
--msgs M messages signed per key (default: 8)
|
||||
--seed S seed the C RNG that chooses message lengths (repeatable schedule)
|
||||
--stop-on-fail stop at the first detected failure
|
||||
--dump-dir DIR directory for *.repro artifacts (default: .)
|
||||
--quiet suppress periodic progress output
|
||||
--replay FILE re-verify a dumped repro artifact and exit
|
||||
```
|
||||
|
||||
### Exit codes
|
||||
|
||||
- `0` — no failures detected
|
||||
- `1` — a signature did not verify (a `*.repro` artifact was written)
|
||||
- `2` — build/setup error
|
||||
- `77` — Falcon (or native signing) not compiled in — treated as "skip"
|
||||
@@ -1,529 +0,0 @@
|
||||
/* falcon_fuzz.c
|
||||
*
|
||||
* Copyright (C) 2006-2026 wolfSSL Inc.
|
||||
*
|
||||
* This file is part of wolfSSL.
|
||||
*
|
||||
* wolfSSL is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wolfSSL is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
||||
*/
|
||||
|
||||
/* Falcon keygen/sign/verify round-trip fuzzer.
|
||||
*
|
||||
* Targets a specific, intermittently reproducing failure reported against the
|
||||
* native Falcon implementation: a freshly generated key produces a signature
|
||||
* that then fails to verify against its own public key. Because the fault is
|
||||
* probabilistic (it depends on the Gaussian sampler / signing-restart path and
|
||||
* on the particular key), a single make_key/sign/verify pass rarely trips it;
|
||||
* this driver hammers the loop across many keys and many messages per key so a
|
||||
* "fairly regular" fault surfaces quickly.
|
||||
*
|
||||
* On the first mismatch the driver dumps a self-contained repro artifact
|
||||
* (public key, raw private key, message, and the signature that did not
|
||||
* verify) so the failure can be replayed deterministically -- verification is a
|
||||
* pure function of (public key, message, signature), so the dumped triple
|
||||
* reproduces the fault without needing to reconstruct the RNG stream.
|
||||
*
|
||||
* Usage:
|
||||
* falcon_fuzz [--level 1|5|both] [--iters N] [--msgs M] [--seed S]
|
||||
* [--stop-on-fail] [--dump-dir DIR] [--quiet]
|
||||
* falcon_fuzz --replay FILE
|
||||
*
|
||||
* Exit status is non-zero if any verify mismatch (or unexpected API error) was
|
||||
* observed, so it drops straight into CI / a nightly loop.
|
||||
*/
|
||||
|
||||
#include <wolfssl/options.h>
|
||||
#include <wolfssl/wolfcrypt/settings.h>
|
||||
#include <wolfssl/wolfcrypt/falcon.h>
|
||||
#include <wolfssl/wolfcrypt/random.h>
|
||||
#include <wolfssl/wolfcrypt/error-crypt.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
|
||||
#if !defined(HAVE_FALCON)
|
||||
int main(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"falcon_fuzz: wolfSSL was built without Falcon support.\n"
|
||||
"Reconfigure with --enable-experimental --enable-falcon.\n");
|
||||
return 77; /* automake "skip" convention */
|
||||
}
|
||||
#elif !defined(WC_FALCON_HAVE_NATIVE_SIGN)
|
||||
int main(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"falcon_fuzz: this build is verify-only (no native signing), so the\n"
|
||||
"keygen/sign/verify round trip cannot be exercised. Rebuild without\n"
|
||||
"WOLFSSL_FALCON_VERIFY_ONLY / WOLF_CRYPTO_CB_ONLY_FALCON.\n");
|
||||
return 77; /* automake "skip" convention */
|
||||
}
|
||||
#else
|
||||
|
||||
/* Largest message we sign; deliberately spans several hash-to-point blocks. */
|
||||
#define FUZZ_MAX_MSG_LEN 512
|
||||
|
||||
/* Extra headroom added to the signature buffer beyond any level's maximum, so
|
||||
* the signer must enforce the level budget itself instead of being clamped by
|
||||
* our buffer. Must be > 0 to exercise the over-length path on every level. */
|
||||
#define FALCON_SLACK 64
|
||||
|
||||
static volatile sig_atomic_t g_stop = 0;
|
||||
|
||||
static void on_signal(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
g_stop = 1;
|
||||
}
|
||||
|
||||
static void print_hex(FILE* f, const char* tag, const byte* buf, word32 len)
|
||||
{
|
||||
word32 i;
|
||||
fprintf(f, "%s=", tag);
|
||||
for (i = 0; i < len; i++)
|
||||
fprintf(f, "%02x", buf[i]);
|
||||
fprintf(f, "\n");
|
||||
}
|
||||
|
||||
/* Dump the failing artifact so it can be replayed with --replay. Returns 0 on
|
||||
* success. */
|
||||
static int dump_repro(const char* dir, int level, unsigned long iter,
|
||||
const byte* pub, word32 pubLen,
|
||||
const byte* prv, word32 prvLen,
|
||||
const byte* msg, word32 msgLen,
|
||||
const byte* sig, word32 sigLen)
|
||||
{
|
||||
char path[512];
|
||||
FILE* f;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/falcon_fail_L%d_%lu.repro",
|
||||
dir ? dir : ".", level, iter);
|
||||
f = fopen(path, "w");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, " (could not open repro file %s)\n", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fprintf(f, "# Falcon keygen/sign/verify failure artifact.\n");
|
||||
fprintf(f, "# Replay with: falcon_fuzz --replay %s\n", path);
|
||||
fprintf(f, "level=%d\n", level);
|
||||
print_hex(f, "pub", pub, pubLen);
|
||||
print_hex(f, "prv", prv, prvLen);
|
||||
print_hex(f, "msg", msg, msgLen);
|
||||
print_hex(f, "sig", sig, sigLen);
|
||||
fclose(f);
|
||||
|
||||
fprintf(stderr, " repro written to %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* One keygen/sign/verify episode. Returns 0 on success, 1 on a detected
|
||||
* failure (already reported + dumped), <0 on an unexpected API error that
|
||||
* prevents the test from running. */
|
||||
static int run_episode(WC_RNG* rng, int level, unsigned long iter,
|
||||
int msgsPerKey, const char* dumpDir, int quiet)
|
||||
{
|
||||
falcon_key key;
|
||||
byte pub[FALCON_MAX_PUB_KEY_SIZE];
|
||||
byte prv[FALCON_MAX_KEY_SIZE];
|
||||
byte msg[FUZZ_MAX_MSG_LEN];
|
||||
/* Deliberately over-sized: FALCON_SLACK bytes larger than any level's
|
||||
* signature. We hand this whole length to wc_falcon_sign_msg so the signer
|
||||
* is forced to bound the encoding by the *level's* fixed maximum rather
|
||||
* than coincidentally by our buffer size -- this is what exposes an
|
||||
* over-length (buffer-bounded) signature bug on BOTH levels, not just when
|
||||
* the buffer happens to exceed the level max. */
|
||||
byte sig[FALCON_MAX_SIG_SIZE + FALCON_SLACK];
|
||||
word32 sigMax = (word32)((level == FALCON_LEVEL5) ? FALCON_LEVEL5_SIG_SIZE
|
||||
: FALCON_LEVEL1_SIG_SIZE);
|
||||
word32 pubLen, prvLen, msgLen, sigLen;
|
||||
int ret;
|
||||
int m;
|
||||
int failures = 0;
|
||||
|
||||
ret = wc_falcon_init(&key);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "wc_falcon_init failed: %d\n", ret);
|
||||
return -1;
|
||||
}
|
||||
ret = wc_falcon_set_level(&key, (byte)level);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "wc_falcon_set_level(%d) failed: %d\n", level, ret);
|
||||
wc_falcon_free(&key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = wc_falcon_make_key(&key, rng);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "[L%d iter %lu] wc_falcon_make_key failed: %d\n",
|
||||
level, iter, ret);
|
||||
wc_falcon_free(&key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* A key that cannot self-check is itself a keygen bug worth catching. */
|
||||
ret = wc_falcon_check_key(&key);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "[L%d iter %lu] wc_falcon_check_key failed: %d\n",
|
||||
level, iter, ret);
|
||||
failures++;
|
||||
}
|
||||
|
||||
/* Export the key material up front so any failing message can be dumped
|
||||
* against the exact key that produced it. */
|
||||
pubLen = sizeof(pub);
|
||||
ret = wc_falcon_export_public(&key, pub, &pubLen);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "[L%d iter %lu] export_public failed: %d\n",
|
||||
level, iter, ret);
|
||||
wc_falcon_free(&key);
|
||||
return -1;
|
||||
}
|
||||
prvLen = sizeof(prv);
|
||||
ret = wc_falcon_export_private_only(&key, prv, &prvLen);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "[L%d iter %lu] export_private_only failed: %d\n",
|
||||
level, iter, ret);
|
||||
wc_falcon_free(&key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (m = 0; m < msgsPerKey; m++) {
|
||||
int res = 0;
|
||||
|
||||
/* Vary the message length, including the zero-length edge case, so we
|
||||
* cover different hash-to-point block counts. */
|
||||
if (m == 0) {
|
||||
msgLen = 0;
|
||||
}
|
||||
else {
|
||||
msgLen = (word32)(rand() % FUZZ_MAX_MSG_LEN) + 1;
|
||||
}
|
||||
if (msgLen > 0)
|
||||
(void)wc_RNG_GenerateBlock(rng, msg, msgLen);
|
||||
|
||||
sigLen = sizeof(sig);
|
||||
ret = wc_falcon_sign_msg(msg, msgLen, sig, &sigLen, &key, rng);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr,
|
||||
"[L%d iter %lu msg %d] wc_falcon_sign_msg failed: %d "
|
||||
"(msgLen=%u)\n", level, iter, m, ret, msgLen);
|
||||
failures++;
|
||||
/* Nothing to verify without a signature; capture the key anyway. */
|
||||
dump_repro(dumpDir, level, iter, pub, pubLen, prv, prvLen,
|
||||
msg, msgLen, sig, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* An emitted signature longer than the level's fixed maximum is itself
|
||||
* the bug (a buffer-bounded rather than budget-bounded encoding): it is
|
||||
* out of spec and no verifier will accept it. Catch it directly, even
|
||||
* before verify, since it is the precise fault we are hunting. */
|
||||
if (sigLen > sigMax) {
|
||||
fprintf(stderr,
|
||||
"[L%d iter %lu msg %d] OVER-LENGTH SIGNATURE: sigLen=%u > "
|
||||
"level max %u (msgLen=%u)\n",
|
||||
level, iter, m, sigLen, sigMax, msgLen);
|
||||
dump_repro(dumpDir, level, iter, pub, pubLen, prv, prvLen,
|
||||
msg, msgLen, sig, sigLen);
|
||||
failures++;
|
||||
continue;
|
||||
}
|
||||
|
||||
res = 0;
|
||||
ret = wc_falcon_verify_msg(sig, sigLen, msg, msgLen, &res, &key);
|
||||
if (ret != 0 || res != 1) {
|
||||
/* THE bug we are hunting: signed by a fresh key, will not verify. */
|
||||
fprintf(stderr,
|
||||
"[L%d iter %lu msg %d] SIGN/VERIFY MISMATCH: verify ret=%d "
|
||||
"res=%d (msgLen=%u sigLen=%u)\n",
|
||||
level, iter, m, ret, res, msgLen, sigLen);
|
||||
dump_repro(dumpDir, level, iter, pub, pubLen, prv, prvLen,
|
||||
msg, msgLen, sig, sigLen);
|
||||
failures++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Sanity guard against a verifier that trivially accepts everything:
|
||||
* a single flipped signature bit must NOT verify. */
|
||||
if (sigLen > 0) {
|
||||
byte saved = sig[sigLen / 2];
|
||||
sig[sigLen / 2] ^= 0x01;
|
||||
res = 1;
|
||||
ret = wc_falcon_verify_msg(sig, sigLen, msg, msgLen, &res, &key);
|
||||
if (ret == 0 && res == 1) {
|
||||
fprintf(stderr,
|
||||
"[L%d iter %lu msg %d] tampered signature verified as "
|
||||
"valid -- verifier is not rejecting corruption\n",
|
||||
level, iter, m);
|
||||
failures++;
|
||||
}
|
||||
sig[sigLen / 2] = saved;
|
||||
}
|
||||
}
|
||||
|
||||
wc_falcon_free(&key);
|
||||
|
||||
if (!quiet && failures == 0 && (iter % 500 == 0)) {
|
||||
fprintf(stdout, "[L%d] %lu keys ok\n", level, iter);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
|
||||
static int fuzz_level(WC_RNG* rng, int level, unsigned long iters,
|
||||
int msgsPerKey, const char* dumpDir, int stopOnFail, int quiet,
|
||||
unsigned long* keysDone, unsigned long* failsSeen)
|
||||
{
|
||||
unsigned long i;
|
||||
int hardError = 0;
|
||||
|
||||
for (i = 1; !g_stop && (iters == 0 || i <= iters); i++) {
|
||||
int r = run_episode(rng, level, i, msgsPerKey, dumpDir, quiet);
|
||||
(*keysDone)++;
|
||||
if (r < 0) {
|
||||
hardError = 1;
|
||||
break;
|
||||
}
|
||||
if (r > 0) {
|
||||
(*failsSeen)++;
|
||||
if (stopOnFail) {
|
||||
g_stop = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hardError;
|
||||
}
|
||||
|
||||
/* ---------------------------- replay support ---------------------------- */
|
||||
|
||||
static long read_hex_field(const char* line, const char* tag, byte* out,
|
||||
long outMax)
|
||||
{
|
||||
size_t taglen = strlen(tag);
|
||||
const char* p;
|
||||
long n = 0;
|
||||
|
||||
if (strncmp(line, tag, taglen) != 0 || line[taglen] != '=')
|
||||
return -1;
|
||||
p = line + taglen + 1;
|
||||
|
||||
while (p[0] && p[0] != '\n' && p[0] != '\r') {
|
||||
unsigned v;
|
||||
if (p[1] == '\0' || p[1] == '\n' || p[1] == '\r')
|
||||
break;
|
||||
if (sscanf(p, "%2x", &v) != 1)
|
||||
break;
|
||||
if (n >= outMax)
|
||||
return -2;
|
||||
out[n++] = (byte)v;
|
||||
p += 2;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static int replay(const char* file)
|
||||
{
|
||||
FILE* f = fopen(file, "r");
|
||||
char line[4096];
|
||||
int level = 0;
|
||||
byte pub[FALCON_MAX_PUB_KEY_SIZE];
|
||||
byte msg[FUZZ_MAX_MSG_LEN * 4];
|
||||
byte sig[FALCON_MAX_SIG_SIZE];
|
||||
long pubLen = -1, msgLen = -1, sigLen = -1;
|
||||
falcon_key key;
|
||||
int ret, res = 0;
|
||||
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "cannot open repro file %s\n", file);
|
||||
return 2;
|
||||
}
|
||||
|
||||
while (fgets(line, sizeof(line), f) != NULL) {
|
||||
if (line[0] == '#')
|
||||
continue;
|
||||
if (strncmp(line, "level=", 6) == 0) {
|
||||
level = atoi(line + 6);
|
||||
}
|
||||
else if (strncmp(line, "pub=", 4) == 0) {
|
||||
pubLen = read_hex_field(line, "pub", pub, (long)sizeof(pub));
|
||||
}
|
||||
else if (strncmp(line, "msg=", 4) == 0) {
|
||||
msgLen = read_hex_field(line, "msg", msg, (long)sizeof(msg));
|
||||
}
|
||||
else if (strncmp(line, "sig=", 4) == 0) {
|
||||
sigLen = read_hex_field(line, "sig", sig, (long)sizeof(sig));
|
||||
}
|
||||
/* prv= is captured for investigation but not needed to replay verify */
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
if (level == 0 || pubLen <= 0 || msgLen < 0 || sigLen <= 0) {
|
||||
fprintf(stderr, "malformed repro file (level=%d pubLen=%ld msgLen=%ld "
|
||||
"sigLen=%ld)\n", level, pubLen, msgLen, sigLen);
|
||||
return 2;
|
||||
}
|
||||
|
||||
ret = wc_falcon_init(&key);
|
||||
if (ret == 0)
|
||||
ret = wc_falcon_set_level(&key, (byte)level);
|
||||
if (ret == 0)
|
||||
ret = wc_falcon_import_public(pub, (word32)pubLen, &key);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "replay: importing public key failed: %d\n", ret);
|
||||
wc_falcon_free(&key);
|
||||
return 2;
|
||||
}
|
||||
|
||||
ret = wc_falcon_verify_msg(sig, (word32)sigLen, msg, (word32)msgLen,
|
||||
&res, &key);
|
||||
wc_falcon_free(&key);
|
||||
|
||||
printf("replay %s: level=%d msgLen=%ld sigLen=%ld -> verify ret=%d res=%d\n",
|
||||
file, level, msgLen, sigLen, ret, res);
|
||||
if (ret == 0 && res == 1) {
|
||||
printf(" VERIFIES OK (bug not reproduced with this artifact)\n");
|
||||
return 0;
|
||||
}
|
||||
printf(" DOES NOT VERIFY (bug reproduced)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ------------------------------- driver -------------------------------- */
|
||||
|
||||
static void usage(const char* argv0)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"usage: %s [options]\n"
|
||||
" --level 1|5|both security level(s) to fuzz (default: both)\n"
|
||||
" --iters N keygen iterations per level, 0 = forever "
|
||||
"(default: 5000)\n"
|
||||
" --msgs M messages signed per key (default: 8)\n"
|
||||
" --seed S seed the C RNG used for message lengths\n"
|
||||
" --stop-on-fail stop at the first detected failure\n"
|
||||
" --dump-dir DIR directory for repro artifacts (default: .)\n"
|
||||
" --quiet suppress periodic progress output\n"
|
||||
" --replay FILE re-verify a dumped repro artifact and exit\n",
|
||||
argv0);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
WC_RNG rng;
|
||||
int ret;
|
||||
int doL1 = 1, doL5 = 1;
|
||||
unsigned long iters = 5000;
|
||||
int msgsPerKey = 8;
|
||||
int stopOnFail = 0;
|
||||
int quiet = 0;
|
||||
unsigned int seed = 0;
|
||||
int haveSeed = 0;
|
||||
const char* dumpDir = ".";
|
||||
const char* replayFile = NULL;
|
||||
unsigned long keysDone = 0, failsSeen = 0;
|
||||
int i;
|
||||
int hardError = 0;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--level") == 0 && i + 1 < argc) {
|
||||
const char* v = argv[++i];
|
||||
if (strcmp(v, "1") == 0) { doL1 = 1; doL5 = 0; }
|
||||
else if (strcmp(v, "5") == 0) { doL1 = 0; doL5 = 1; }
|
||||
else if (strcmp(v, "both") == 0) { doL1 = 1; doL5 = 1; }
|
||||
else { usage(argv[0]); return 2; }
|
||||
}
|
||||
else if (strcmp(argv[i], "--iters") == 0 && i + 1 < argc) {
|
||||
iters = strtoul(argv[++i], NULL, 10);
|
||||
}
|
||||
else if (strcmp(argv[i], "--msgs") == 0 && i + 1 < argc) {
|
||||
msgsPerKey = atoi(argv[++i]);
|
||||
if (msgsPerKey < 1) msgsPerKey = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--seed") == 0 && i + 1 < argc) {
|
||||
seed = (unsigned int)strtoul(argv[++i], NULL, 10);
|
||||
haveSeed = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--stop-on-fail") == 0) {
|
||||
stopOnFail = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--dump-dir") == 0 && i + 1 < argc) {
|
||||
dumpDir = argv[++i];
|
||||
}
|
||||
else if (strcmp(argv[i], "--quiet") == 0) {
|
||||
quiet = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--replay") == 0 && i + 1 < argc) {
|
||||
replayFile = argv[++i];
|
||||
}
|
||||
else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "unknown / incomplete option: %s\n", argv[i]);
|
||||
usage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (replayFile != NULL)
|
||||
return replay(replayFile);
|
||||
|
||||
signal(SIGINT, on_signal);
|
||||
signal(SIGTERM, on_signal);
|
||||
|
||||
/* The C library RNG only chooses message lengths; key/signature entropy all
|
||||
* comes from the wolfCrypt WC_RNG below. Seeding it just makes the length
|
||||
* schedule repeatable across runs. */
|
||||
if (!haveSeed)
|
||||
seed = (unsigned int)time(NULL);
|
||||
srand(seed);
|
||||
|
||||
ret = wc_InitRng(&rng);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "wc_InitRng failed: %d\n", ret);
|
||||
return 2;
|
||||
}
|
||||
|
||||
printf("falcon_fuzz: levels=%s%s iters=%lu msgs/key=%d seed=%u\n",
|
||||
doL1 ? "1" : "", doL5 ? (doL1 ? ",5" : "5") : "",
|
||||
iters, msgsPerKey, seed);
|
||||
fflush(stdout);
|
||||
|
||||
if (doL1 && !g_stop)
|
||||
hardError |= fuzz_level(&rng, FALCON_LEVEL1, iters, msgsPerKey,
|
||||
dumpDir, stopOnFail, quiet,
|
||||
&keysDone, &failsSeen);
|
||||
if (doL5 && !g_stop)
|
||||
hardError |= fuzz_level(&rng, FALCON_LEVEL5, iters, msgsPerKey,
|
||||
dumpDir, stopOnFail, quiet,
|
||||
&keysDone, &failsSeen);
|
||||
|
||||
wc_FreeRng(&rng);
|
||||
|
||||
printf("\nfalcon_fuzz: %lu keys exercised, %lu failure(s) detected%s\n",
|
||||
keysDone, failsSeen, g_stop ? " (interrupted)" : "");
|
||||
|
||||
if (hardError)
|
||||
return 2;
|
||||
return failsSeen ? 1 : 0;
|
||||
}
|
||||
|
||||
#endif /* HAVE_FALCON && WC_FALCON_HAVE_NATIVE_SIGN */
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/bin/sh
|
||||
# run.sh
|
||||
#
|
||||
# Copyright (C) 2006-2026 wolfSSL Inc.
|
||||
#
|
||||
# This file is part of wolfSSL.
|
||||
#
|
||||
# wolfSSL is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# wolfSSL is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
||||
|
||||
# Build and run the Falcon keygen/sign/verify fuzzer against the in-tree
|
||||
# library. Hunts for the intermittent "freshly signed message fails to verify"
|
||||
# fault. Exit 0 = no failures, 1 = a mismatch was found (a *.repro artifact is
|
||||
# left behind for replay), 2 = build/setup error, 77 = Falcon not built in.
|
||||
#
|
||||
# Run from the wolfssl repo root: sh tests/falcon/run.sh [iters] [msgs]
|
||||
set -u
|
||||
|
||||
ITERS="${1:-2000}"
|
||||
MSGS="${2:-8}"
|
||||
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
WOLFROOT=$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)
|
||||
|
||||
have_lib=no
|
||||
[ -f "$WOLFROOT/src/.libs/libwolfssl.so" ] && have_lib=yes
|
||||
[ -f "$WOLFROOT/src/.libs/libwolfssl.a" ] && have_lib=yes
|
||||
if [ ! -f "$WOLFROOT/wolfssl/options.h" ] || [ "$have_lib" = no ]; then
|
||||
echo "error: $WOLFROOT is not configured/built with Falcon." >&2
|
||||
echo " run: ./configure --enable-experimental --enable-falcon && make" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
make -C "$SCRIPT_DIR" || exit 2
|
||||
|
||||
LD_LIBRARY_PATH="$WOLFROOT/src/.libs:${LD_LIBRARY_PATH:-}"
|
||||
export LD_LIBRARY_PATH
|
||||
|
||||
echo "Running Falcon fuzzer: $ITERS keys/level, $MSGS msgs/key ..."
|
||||
"$SCRIPT_DIR/falcon_fuzz" --level both --iters "$ITERS" --msgs "$MSGS" \
|
||||
--dump-dir "$SCRIPT_DIR"
|
||||
status=$?
|
||||
|
||||
if [ "$status" -eq 0 ]; then
|
||||
echo "OK: no keygen/sign/verify failures detected."
|
||||
elif [ "$status" -eq 1 ]; then
|
||||
echo "FAIL: a signature did not verify. See *.repro in $SCRIPT_DIR;" >&2
|
||||
echo " replay with: $SCRIPT_DIR/falcon_fuzz --replay <file>" >&2
|
||||
fi
|
||||
exit $status
|
||||
Reference in New Issue
Block a user