From 029d85869f9fdf4cb9fe5c7586d77ed9402440f3 Mon Sep 17 00:00:00 2001 From: Andrew Dinh Date: Wed, 22 Jan 2025 20:45:00 +0700 Subject: [PATCH] Add quic-server fuzz test. Also update fuzz corpora submodule. Reviewed-by: Tomas Mraz Reviewed-by: Neil Horman (Merged from https://github.com/openssl/openssl/pull/26514) --- fuzz/build.info | 12 +- fuzz/quic-server.c | 258 ++++++++++++++++++++++++ test/recipes/99-test_fuzz_quic_server.t | 25 +++ 3 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 fuzz/quic-server.c create mode 100644 test/recipes/99-test_fuzz_quic_server.t diff --git a/fuzz/build.info b/fuzz/build.info index af0a62a3678..87ac0c56703 100644 --- a/fuzz/build.info +++ b/fuzz/build.info @@ -39,7 +39,7 @@ IF[{- !$disabled{"fuzz-afl"} || !$disabled{"fuzz-libfuzzer"} -}] ENDIF IF[{- !$disabled{"quic"} -}] - PROGRAMS{noinst}=quic-client quic-srtm quic-lcidm quic-rcidm + PROGRAMS{noinst}=quic-client quic-server quic-srtm quic-lcidm quic-rcidm ENDIF IF[{- !$disabled{"dtls"} -}] @@ -126,6 +126,10 @@ IF[{- !$disabled{"fuzz-afl"} || !$disabled{"fuzz-libfuzzer"} -}] INCLUDE[quic-client]=../include {- $ex_inc -} DEPEND[quic-client]=../libcrypto.a ../libssl.a {- $ex_lib -} + SOURCE[quic-server]=quic-server.c driver.c fuzz_rand.c + INCLUDE[quic-server]=../include {- $ex_inc -} + DEPEND[quic-server]=../libcrypto.a ../libssl.a {- $ex_lib -} + SOURCE[quic-srtm]=quic-srtm.c driver.c fuzz_rand.c INCLUDE[quic-srtm]=../include {- $ex_inc -} DEPEND[quic-srtm]=../libcrypto.a ../libssl.a {- $ex_lib -} @@ -190,7 +194,7 @@ IF[{- !$disabled{tests} -}] ENDIF IF[{- !$disabled{"quic"} -}] - PROGRAMS{noinst}=quic-client-test quic-srtm-test quic-lcidm-test + PROGRAMS{noinst}=quic-client-test quic-server-test quic-srtm-test quic-lcidm-test PROGRAMS{noinst}=quic-rcidm-test ENDIF @@ -288,6 +292,10 @@ IF[{- !$disabled{tests} -}] INCLUDE[quic-client-test]=../include DEPEND[quic-client-test]=../libcrypto.a ../libssl.a + SOURCE[quic-server-test]=quic-server.c test-corpus.c fuzz_rand.c + INCLUDE[quic-server-test]=../include + DEPEND[quic-server-test]=../libcrypto.a ../libssl.a + SOURCE[quic-srtm-test]=quic-srtm.c test-corpus.c fuzz_rand.c INCLUDE[quic-srtm-test]=../include DEPEND[quic-srtm-test]=../libcrypto.a ../libssl.a diff --git a/fuzz/quic-server.c b/fuzz/quic-server.c new file mode 100644 index 00000000000..881f4585a3a --- /dev/null +++ b/fuzz/quic-server.c @@ -0,0 +1,258 @@ +/* + * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.openssl.org/source/license.html + * or in the file LICENSE in the source distribution. + */ + +#include +#include +#include +#include "fuzzer.h" +#include "internal/sockets.h" +#include "internal/time.h" +#include "internal/quic_ssl.h" + +/* unused, to avoid warning. */ +static int idx; + +static OSSL_TIME fake_now; + +static OSSL_TIME fake_now_cb(void *arg) +{ + return fake_now; +} + +int FuzzerInitialize(int *argc, char ***argv) +{ + STACK_OF(SSL_COMP) *comp_methods; + + FuzzerSetRand(); + OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS | OPENSSL_INIT_ASYNC, NULL); + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL); + ERR_clear_error(); + CRYPTO_free_ex_index(0, -1); + idx = SSL_get_ex_data_X509_STORE_CTX_idx(); + comp_methods = SSL_COMP_get_compression_methods(); + if (comp_methods != NULL) + sk_SSL_COMP_sort(comp_methods); + + return 1; +} + +#define HANDSHAKING 0 +#define READING 1 +#define WRITING 2 +#define ACCEPTING_STREAM 3 +#define CREATING_STREAM 4 +#define SWAPPING_STREAM 5 + +/* + * This callback validates and negotiates the desired ALPN on the server side. + * Accept any ALPN. + */ +static int select_alpn(SSL *ssl, const unsigned char **out, + unsigned char *out_len, const unsigned char *in, + unsigned int in_len, void *arg) +{ + return SSL_TLSEXT_ERR_OK; +} + +int FuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + SSL *server = NULL, *stream = NULL; + SSL *allstreams[] = {NULL, NULL, NULL, NULL}; + size_t i, thisstream = 0, numstreams = 1; + BIO *in; + BIO *out; + SSL_CTX *ctx; + struct timeval tv; + int state = HANDSHAKING; + uint8_t tmp[1024]; + int writelen = 0; + + if (len == 0) + return 0; + + ctx = SSL_CTX_new(OSSL_QUIC_server_method()); + if (ctx == NULL) + goto end; + + SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL); + + server = SSL_new_listener(ctx, 0); + allstreams[0] = stream = server; + if (server == NULL) + goto end; + + fake_now = ossl_ms2time(1); + if (!ossl_quic_set_override_now_cb(server, fake_now_cb, NULL)) + goto end; + + in = BIO_new(BIO_s_dgram_mem()); + if (in == NULL) + goto end; + out = BIO_new(BIO_s_dgram_mem()); + if (out == NULL) { + BIO_free(in); + goto end; + } + if (!BIO_dgram_set_caps(out, BIO_DGRAM_CAP_HANDLES_DST_ADDR)) { + BIO_free(in); + BIO_free(out); + goto end; + } + SSL_set_bio(server, in, out); + SSL_set_accept_state(server); + + for (;;) { + size_t size; + uint64_t nxtpktms = 0; + OSSL_TIME nxtpkt = ossl_time_zero(), nxttimeout; + int isinf, ret = 0; + + if (len >= 2) { + if (len >= 5 && buf[0] == 0xff && buf[1] == 0xff) { + switch (buf[2]) { + case 0x00: + if (state == READING) + state = ACCEPTING_STREAM; + break; + case 0x01: + if (state == READING) + state = CREATING_STREAM; + break; + case 0x02: + if (state == READING) + state = SWAPPING_STREAM; + break; + default: + /* ignore */ + break; + } + len -= 3; + buf += 3; + } + nxtpktms = buf[0] + (buf[1] << 8); + nxtpkt = ossl_time_add(fake_now, ossl_ms2time(nxtpktms)); + len -= 2; + buf += 2; + } + + for (;;) { + switch (state) { + case HANDSHAKING: + ret = SSL_accept_connection(stream, 0) != NULL; + if (ret == 1) + state = READING; + break; + + case READING: + ret = SSL_read(stream, tmp, sizeof(tmp)); + if (ret > 0) { + state = WRITING; + writelen = ret; + assert(writelen <= (int)sizeof(tmp)); + } + break; + + case WRITING: + ret = SSL_write(stream, tmp, writelen); + if (ret > 0) + state = READING; + break; + + case ACCEPTING_STREAM: + state = READING; + ret = 1; + if (numstreams == OSSL_NELEM(allstreams) + || SSL_get_accept_stream_queue_len(server) == 0) + break; + thisstream = numstreams; + stream = allstreams[numstreams++] = SSL_accept_stream(server, 0); + if (stream == NULL) + goto end; + break; + + case CREATING_STREAM: + state = READING; + ret = 1; + if (numstreams == OSSL_NELEM(allstreams)) + break; + stream = SSL_new_stream(server, 0); + if (stream == NULL) { + /* Ignore, and go back to the previous stream */ + stream = allstreams[thisstream]; + break; + } + thisstream = numstreams; + allstreams[numstreams++] = stream; + break; + + case SWAPPING_STREAM: + state = READING; + ret = 1; + if (numstreams == 1) + break; + if (++thisstream == numstreams) + thisstream = 0; + stream = allstreams[thisstream]; + break; + } + assert(stream != NULL); + assert(thisstream < numstreams); + if (ret <= 0) { + switch (SSL_get_error(stream, ret)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + default: + goto end; + } + } + + if (!SSL_get_event_timeout(server, &tv, &isinf)) + goto end; + + if (isinf) { + fake_now = nxtpkt; + break; + } else { + nxttimeout = ossl_time_add(fake_now, + ossl_time_from_timeval(tv)); + if (len > 3 && ossl_time_compare(nxttimeout, nxtpkt) >= 0) { + fake_now = nxtpkt; + break; + } + fake_now = nxttimeout; + } + } + + if (len <= 3) + break; + + size = buf[0] + (buf[1] << 8); + if (size > len - 2) + break; + + if (size > 0) + BIO_write(in, buf + 2, size); + len -= size + 2; + buf += size + 2; + } + end: + for (i = 0; i < numstreams; i++) + SSL_free(allstreams[i]); + ERR_clear_error(); + SSL_CTX_free(ctx); + + return 0; +} + +void FuzzerCleanup(void) +{ + FuzzerClearRand(); +} diff --git a/test/recipes/99-test_fuzz_quic_server.t b/test/recipes/99-test_fuzz_quic_server.t new file mode 100644 index 00000000000..b09fdf7852b --- /dev/null +++ b/test/recipes/99-test_fuzz_quic_server.t @@ -0,0 +1,25 @@ +#!/usr/bin/env perl +# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +use strict; +use warnings; + +use OpenSSL::Test qw/:DEFAULT srctop_file/; +use OpenSSL::Test::Utils; + +my $fuzzer = "quic-server"; +setup("test_fuzz_${fuzzer}"); + +plan skip_all => "This test requires quic support" + if disabled("quic"); + +plan tests => 2; # one more due to below require_ok(...) + +require_ok(srctop_file('test','recipes','fuzz.pl')); + +fuzz_ok($fuzzer); -- 2.47.2