821 lines
22 KiB
C
821 lines
22 KiB
C
/*
|
|
* Copyright 2024-2026 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
|
|
*/
|
|
|
|
#include "internal/quic_reactor.h"
|
|
#include "../../ssl/rio/poll_builder.h"
|
|
|
|
#if defined(_AIX)
|
|
/*
|
|
* Some versions of AIX define macros for events and revents for use when
|
|
* accessing pollfd structures (see Github issue #24236). That interferes
|
|
* with our use of these names here. We simply undef them.
|
|
*/
|
|
#undef revents
|
|
#undef events
|
|
#endif
|
|
|
|
/*
|
|
* Test Scripts
|
|
* ============================================================================
|
|
*/
|
|
|
|
/*
|
|
* Test: simple_conn
|
|
* -----------------
|
|
*/
|
|
DEF_SCRIPT(simple_conn, "simple connection to server")
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < 2; ++i) {
|
|
if (i == 0) {
|
|
OP_SIMPLE_PAIR_CONN_D();
|
|
} else {
|
|
OP_CLEAR();
|
|
OP_SIMPLE_PAIR_CONN();
|
|
}
|
|
|
|
OP_WRITE_B(C, "apple");
|
|
|
|
OP_ACCEPT_CONN_WAIT(L, La, 0);
|
|
OP_ACCEPT_CONN_NONE(L);
|
|
|
|
OP_READ_EXPECT_B(La, "apple");
|
|
OP_WRITE_B(La, "orange");
|
|
OP_READ_EXPECT_B(C, "orange");
|
|
}
|
|
}
|
|
|
|
DEF_SCRIPT(simple_thread_child,
|
|
"test that RADIX multithreading is working (child)")
|
|
{
|
|
}
|
|
|
|
/*
|
|
* Test: simple_thread
|
|
* -------------------
|
|
*/
|
|
DEF_SCRIPT(simple_thread,
|
|
"test that RADIX multithreading is working")
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < 2; ++i)
|
|
OP_SPAWN_THREAD(simple_thread_child);
|
|
}
|
|
|
|
/*
|
|
* Test: ssl_poll
|
|
* --------------
|
|
*/
|
|
DEF_SCRIPT(ssl_poll_child,
|
|
"test that SSL_poll is working (child)")
|
|
{
|
|
OP_SLEEP(100);
|
|
OP_WRITE_B(C0, "extra");
|
|
}
|
|
|
|
DEF_FUNC(ssl_poll_check)
|
|
{
|
|
int ok = 0;
|
|
SSL *La, *Lax[4];
|
|
SSL_POLL_ITEM items[6] = { 0 }, expected_items[6] = { 0 };
|
|
size_t result_count = 0, i;
|
|
const struct timeval z_timeout = { 0 }, *p_timeout = &z_timeout;
|
|
struct timeval timeout = { 0 };
|
|
uint64_t mode;
|
|
size_t expected_result_count;
|
|
OSSL_TIME time_before, time_after;
|
|
|
|
F_POP(mode);
|
|
REQUIRE_SSL_5(La, Lax[0], Lax[1], Lax[2], Lax[3]);
|
|
|
|
items[0].desc = SSL_as_poll_descriptor(La);
|
|
items[0].events = 0;
|
|
items[0].revents = 0;
|
|
|
|
for (i = 0; i < 4; ++i) {
|
|
items[i + 1].desc = SSL_as_poll_descriptor(Lax[i]);
|
|
items[i + 1].events = SSL_POLL_EVENT_R | SSL_POLL_EVENT_I;
|
|
items[i + 1].revents = 0;
|
|
}
|
|
|
|
items[5].desc = SSL_as_poll_descriptor(SSL_get0_listener(La));
|
|
|
|
switch (mode) {
|
|
case 0: /* Nothing ready */
|
|
case 2:
|
|
expected_result_count = 0;
|
|
break;
|
|
case 1: /* Various events reported correctly */
|
|
expected_result_count = 5;
|
|
items[0].events = SSL_POLL_EVENT_OS;
|
|
expected_items[0].revents = SSL_POLL_EVENT_OS;
|
|
|
|
expected_items[1].revents = SSL_POLL_EVENT_R;
|
|
|
|
for (i = 0; i < 4; ++i) {
|
|
items[i + 1].events |= SSL_POLL_EVENT_W;
|
|
expected_items[i + 1].revents |= SSL_POLL_EVENT_W;
|
|
}
|
|
|
|
break;
|
|
case 3: /* Blocking test */
|
|
expected_result_count = 1;
|
|
expected_items[1].revents = SSL_POLL_EVENT_R;
|
|
|
|
p_timeout = &timeout;
|
|
timeout.tv_sec = 10;
|
|
timeout.tv_usec = 0;
|
|
break;
|
|
case 4: /* Listener test */
|
|
expected_result_count = 1;
|
|
items[5].events = SSL_POLL_EVENT_IC;
|
|
expected_items[5].revents = SSL_POLL_EVENT_IC;
|
|
break;
|
|
default:
|
|
goto err;
|
|
}
|
|
|
|
/* Zero-timeout call. */
|
|
result_count = SIZE_MAX;
|
|
time_before = ossl_time_now();
|
|
if (!TEST_true(SSL_poll(items, OSSL_NELEM(items), sizeof(SSL_POLL_ITEM),
|
|
p_timeout, 0, &result_count)))
|
|
goto err;
|
|
|
|
time_after = ossl_time_now();
|
|
if (!TEST_size_t_eq(result_count, expected_result_count))
|
|
goto err;
|
|
|
|
for (i = 0; i < OSSL_NELEM(items); ++i)
|
|
if (!TEST_uint64_t_eq(items[i].revents, expected_items[i].revents))
|
|
goto err;
|
|
|
|
/*
|
|
* The SSL_poll call for the blocking test definitely shouldn't have
|
|
* returned sooner than in 100ms.
|
|
*/
|
|
if (i == 3 && !TEST_uint64_t_ge(ossl_time2ms(ossl_time_subtract(time_after, time_before)), 100))
|
|
goto err;
|
|
|
|
ok = 1;
|
|
err:
|
|
return ok;
|
|
}
|
|
|
|
DEF_SCRIPT(ssl_poll,
|
|
"test that SSL_poll is working")
|
|
{
|
|
size_t i;
|
|
|
|
OP_SIMPLE_PAIR_CONN_ND();
|
|
|
|
/* Setup streams */
|
|
OP_NEW_STREAM(C, C0, 0);
|
|
OP_WRITE_B(C0, "apple");
|
|
|
|
OP_NEW_STREAM(C, C1, 0);
|
|
OP_WRITE_B(C1, "orange");
|
|
|
|
OP_NEW_STREAM(C, C2, 0);
|
|
OP_WRITE_B(C2, "Strawberry");
|
|
|
|
OP_NEW_STREAM(C, C3, 0);
|
|
OP_WRITE_B(C3, "sync");
|
|
|
|
OP_ACCEPT_CONN_WAIT1_ND(L, La, 0);
|
|
|
|
OP_ACCEPT_STREAM_WAIT(La, La0, 0);
|
|
OP_READ_EXPECT_B(La0, "apple");
|
|
|
|
OP_ACCEPT_STREAM_WAIT(La, La1, 0);
|
|
OP_READ_EXPECT_B(La1, "orange");
|
|
|
|
OP_ACCEPT_STREAM_WAIT(La, La2, 0);
|
|
OP_READ_EXPECT_B(La2, "Strawberry");
|
|
|
|
OP_ACCEPT_STREAM_WAIT(La, La3, 0);
|
|
OP_READ_EXPECT_B(La3, "sync");
|
|
|
|
for (i = 0; i <= 4; ++i) {
|
|
/* 0: Check nothing ready */
|
|
/* 1: Check that various events are reported correctly */
|
|
/* 2: Check nothing ready */
|
|
/* 3: Blocking call unblocked from child thread */
|
|
/* 4: Listener test */
|
|
|
|
if (i == 1) {
|
|
OP_WRITE_B(C0, "orange");
|
|
OP_WRITE_B(C3, "sync");
|
|
OP_READ_EXPECT_B(La3, "sync");
|
|
} else if (i == 2) {
|
|
OP_READ_EXPECT_B(La0, "orange");
|
|
} else if (i == 3) {
|
|
OP_SPAWN_THREAD(ssl_poll_child);
|
|
} else if (i == 4) {
|
|
OP_NEW_SSL_C(Cb);
|
|
OP_SET_PEER_ADDR_FROM(Cb, L);
|
|
OP_CONNECT_WAIT(Cb);
|
|
}
|
|
|
|
OP_SELECT_SSL(0, La);
|
|
OP_SELECT_SSL(1, La0);
|
|
OP_SELECT_SSL(2, La1);
|
|
OP_SELECT_SSL(3, La2);
|
|
OP_SELECT_SSL(4, La3);
|
|
OP_PUSH_U64(i);
|
|
OP_FUNC(ssl_poll_check);
|
|
|
|
if (i == 3)
|
|
OP_READ_EXPECT_B(La0, "extra");
|
|
|
|
if (i == 4) {
|
|
OP_ACCEPT_CONN_WAIT1_ND(L, Lb, 0);
|
|
OP_NEW_STREAM(Lb, Lb0, 0);
|
|
OP_WRITE_B(Lb0, "foo");
|
|
OP_READ_EXPECT_B(Cb, "foo");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test: poll_abort_blocking
|
|
* -------------------------
|
|
*
|
|
* SSL_poll(), when it has to block, registers each item's QUIC connection
|
|
* for cross-thread notification one item at a time (poll_translate() in
|
|
* ssl/rio/poll_immediate.c). If an item turns out to already be ready right
|
|
* as it is being registered, translation is aborted so the readout loop can
|
|
* retry instead of actually blocking. This exercises that abort path and
|
|
* checks that:
|
|
*
|
|
* - SSL_poll() reports success rather than spuriously failing, and
|
|
* - any items already registered before the abort have their blocking
|
|
* section correctly left (i.e. no leak in the QUIC reactor's blocking
|
|
* waiter count).
|
|
*
|
|
* The race between an item being registered and becoming ready is normally
|
|
* vanishingly narrow, so we use ossl_quic_poll_translate_test_step_cb (test
|
|
* instrumentation only, see ssl/rio/poll_builder.h) to deterministically
|
|
* make the second item ready immediately before poll_translate() processes
|
|
* it, while the first item is still mid-registration.
|
|
*/
|
|
struct poll_abort_test_ctx {
|
|
SSL *peer_writer; /* write here to make target ready */
|
|
SSL *target;
|
|
uint64_t target_events;
|
|
size_t trigger_idx;
|
|
int made_ready; /* set by poll_abort_test_step_cb() on success */
|
|
};
|
|
|
|
static void poll_abort_test_step_cb(size_t idx, void *arg)
|
|
{
|
|
struct poll_abort_test_ctx *ctx = arg;
|
|
uint64_t revents = 0;
|
|
int i;
|
|
|
|
if (idx != ctx->trigger_idx)
|
|
return;
|
|
|
|
if (SSL_write(ctx->peer_writer, "x", 1) != 1)
|
|
return;
|
|
|
|
/* Force the data through synchronously so target is ready by the time we return. */
|
|
for (i = 0; i < 1000; ++i) {
|
|
if (!ossl_quic_conn_poll_events(ctx->target, ctx->target_events,
|
|
/* do_tick = */ 1, &revents))
|
|
return;
|
|
|
|
if (revents != 0) {
|
|
ctx->made_ready = 1;
|
|
return;
|
|
}
|
|
|
|
OSSL_sleep(1);
|
|
}
|
|
}
|
|
|
|
DEF_FUNC(check_poll_abort_blocking)
|
|
{
|
|
int ok = 0;
|
|
SSL *C, *C0, *Cb0, *Lb0;
|
|
QUIC_CHANNEL *ch0;
|
|
QUIC_REACTOR *rtor0;
|
|
SSL_POLL_ITEM items[2] = { 0 };
|
|
size_t result_count = SIZE_MAX, waiters_before, waiters_after;
|
|
struct poll_abort_test_ctx ctx;
|
|
const struct timeval z_timeout = { 0 };
|
|
|
|
/*
|
|
* C0 and Cb0 are streams of two independent client connections, and so
|
|
* belong to two independent QUIC_REACTORs. The bug being tested for does
|
|
* not actually require this: it reproduces just as well if all items
|
|
* share one reactor. What needs two reactors is poll_abort_test_step_cb()
|
|
* below, which forces Cb0 ready by ticking its reactor directly, on this
|
|
* thread, while C0's blocking section is still open. Doing that on C0's
|
|
* own (shared) reactor would deadlock: ossl_quic_reactor_tick() would see
|
|
* a nonzero cur_blocking_waiters left over from C0 and call
|
|
* rtor_notify_other_threads(), which waits on a condvar for some *other*
|
|
* thread to clear the notifier signal - a thread that doesn't exist here.
|
|
* Using Cb0's own, still-untouched reactor keeps that tick a no-op.
|
|
*/
|
|
REQUIRE_SSL_4(C, C0, Cb0, Lb0);
|
|
|
|
items[0].desc = SSL_as_poll_descriptor(C0);
|
|
items[0].events = SSL_POLL_EVENT_R;
|
|
items[1].desc = SSL_as_poll_descriptor(Cb0);
|
|
items[1].events = SSL_POLL_EVENT_R;
|
|
|
|
/* Sanity check: nothing ready yet, so SSL_poll() will need to block. */
|
|
if (!TEST_true(SSL_poll(items, OSSL_NELEM(items), sizeof(SSL_POLL_ITEM),
|
|
&z_timeout, 0, &result_count))
|
|
|| !TEST_size_t_eq(result_count, 0))
|
|
goto err;
|
|
|
|
if (!TEST_ptr(ch0 = ossl_quic_conn_get_channel(C)))
|
|
goto err;
|
|
rtor0 = ossl_quic_channel_get_reactor(ch0);
|
|
waiters_before = rtor0->cur_blocking_waiters;
|
|
|
|
ctx.peer_writer = Lb0;
|
|
ctx.target = Cb0;
|
|
ctx.target_events = items[1].events;
|
|
ctx.trigger_idx = 1;
|
|
ctx.made_ready = 0;
|
|
|
|
ossl_quic_poll_translate_test_step_cb_arg = &ctx;
|
|
ossl_quic_poll_translate_test_step_cb = poll_abort_test_step_cb;
|
|
|
|
result_count = SIZE_MAX;
|
|
/*
|
|
* No timeout: if the abort_blocking case were instead to actually block,
|
|
* this call would hang forever rather than fail fast.
|
|
*/
|
|
ok = TEST_true(SSL_poll(items, OSSL_NELEM(items), sizeof(SSL_POLL_ITEM),
|
|
NULL, 0, &result_count));
|
|
|
|
ossl_quic_poll_translate_test_step_cb = NULL;
|
|
ossl_quic_poll_translate_test_step_cb_arg = NULL;
|
|
|
|
if (!ok)
|
|
goto err;
|
|
|
|
ok = 0;
|
|
if (!TEST_true(ctx.made_ready)
|
|
|| !TEST_size_t_ge(result_count, 1)
|
|
|| !TEST_true((items[1].revents & SSL_POLL_EVENT_R) != 0))
|
|
goto err;
|
|
|
|
/* The first item's blocking-section entry must have been balanced. */
|
|
waiters_after = rtor0->cur_blocking_waiters;
|
|
if (!TEST_size_t_eq(waiters_after, waiters_before))
|
|
goto err;
|
|
|
|
ok = 1;
|
|
err:
|
|
ossl_quic_poll_translate_test_step_cb = NULL;
|
|
ossl_quic_poll_translate_test_step_cb_arg = NULL;
|
|
return ok;
|
|
}
|
|
|
|
DEF_SCRIPT(poll_abort_blocking,
|
|
"test that SSL_poll() correctly handles an item becoming ready while blocking is being set up")
|
|
{
|
|
OP_SIMPLE_PAIR_CONN_ND();
|
|
|
|
OP_NEW_STREAM(C, C0, 0);
|
|
OP_WRITE_B(C0, "probe0");
|
|
|
|
OP_ACCEPT_CONN_WAIT1_ND(L, La, 0);
|
|
OP_ACCEPT_STREAM_WAIT(La, La0, 0);
|
|
OP_READ_EXPECT_B(La0, "probe0");
|
|
|
|
/* A second, independent client connection to the same listener. */
|
|
OP_NEW_SSL_C(Cb);
|
|
OP_SET_PEER_ADDR_FROM(Cb, L);
|
|
OP_CONNECT_WAIT(Cb);
|
|
OP_SET_DEFAULT_STREAM_MODE(Cb, SSL_DEFAULT_STREAM_MODE_NONE);
|
|
|
|
OP_NEW_STREAM(Cb, Cb0, 0);
|
|
OP_WRITE_B(Cb0, "probe1");
|
|
|
|
OP_ACCEPT_CONN_WAIT1_ND(L, Lb, 0);
|
|
OP_ACCEPT_STREAM_WAIT(Lb, Lb0, 0);
|
|
OP_READ_EXPECT_B(Lb0, "probe1");
|
|
|
|
OP_SELECT_SSL(0, C);
|
|
OP_SELECT_SSL(1, C0);
|
|
OP_SELECT_SSL(2, Cb0);
|
|
OP_SELECT_SSL(3, Lb0);
|
|
OP_FUNC(check_poll_abort_blocking);
|
|
}
|
|
|
|
DEF_FUNC(check_writeable)
|
|
{
|
|
int ok = 0;
|
|
SSL *ssl;
|
|
SSL_POLL_ITEM item;
|
|
size_t result_count = 0;
|
|
uint64_t expect;
|
|
const struct timeval z_timeout = { 0 }, *p_timeout = &z_timeout;
|
|
|
|
F_POP(expect);
|
|
REQUIRE_SSL(ssl);
|
|
|
|
item.desc = SSL_as_poll_descriptor(ssl);
|
|
item.events = SSL_POLL_EVENT_W;
|
|
item.revents = 0;
|
|
|
|
/* Zero-timeout call. */
|
|
result_count = SIZE_MAX;
|
|
if (!TEST_true(SSL_poll(&item, 1, sizeof(SSL_POLL_ITEM),
|
|
p_timeout, 0, &result_count)))
|
|
goto err;
|
|
|
|
ok = (!!(item.revents & SSL_POLL_EVENT_W) == expect);
|
|
|
|
err:
|
|
return ok;
|
|
}
|
|
|
|
DEF_SCRIPT(check_cwm, "check stream obeys cwm")
|
|
{
|
|
OP_SIMPLE_PAIR_CONN();
|
|
|
|
/* Create the initial stream by writing some data */
|
|
OP_WRITE_RAND(C, 1024);
|
|
|
|
/* We should be writeable at the start */
|
|
OP_PUSH_U64(1);
|
|
OP_SELECT_SSL(0, C);
|
|
OP_FUNC(check_writeable);
|
|
|
|
/* Default stream cwm is 512k (we already sent 1k). Consume all the rest */
|
|
OP_WRITE_RAND(C, 511 * 1024);
|
|
|
|
/* Confirm we are no longer writeable */
|
|
OP_PUSH_U64(0);
|
|
OP_SELECT_SSL(0, C);
|
|
OP_FUNC(check_writeable);
|
|
|
|
/* We now expect writes to fail */
|
|
OP_WRITE_FAIL(C);
|
|
}
|
|
|
|
struct mutcbk_ctx {
|
|
QUIC_PKT_HDR mutctx_qhdrin;
|
|
OSSL_QTX_IOVEC mutctx_iov;
|
|
const unsigned char *mutctx_inject;
|
|
size_t mutctx_inject_sz;
|
|
int mutctx_done;
|
|
};
|
|
|
|
static int mutcbk_inject_frames(const QUIC_PKT_HDR *hdrin,
|
|
const OSSL_QTX_IOVEC *iovecin, size_t numin, QUIC_PKT_HDR **hdrout,
|
|
const OSSL_QTX_IOVEC **iovecout, size_t *numout, void *arg)
|
|
{
|
|
struct mutcbk_ctx *mutctx = (struct mutcbk_ctx *)arg;
|
|
size_t i;
|
|
size_t grow_allowance = 1200; /* QUIC_MIN_INITIAL_DGRAM_LEN */
|
|
size_t bufsz = 0;
|
|
char *buf;
|
|
|
|
/*
|
|
* make injection callback a one shot event,
|
|
* callback is invoked for every packet we
|
|
* want to modify only one packet here. Returning 0 tells the QTX the
|
|
* packet send itself failed (tearing down the connection), so once
|
|
* we're done mutating we must pass subsequent packets through
|
|
* unmodified instead.
|
|
*/
|
|
if (mutctx->mutctx_done) {
|
|
*hdrout = (QUIC_PKT_HDR *)hdrin;
|
|
*iovecout = iovecin;
|
|
*numout = numin;
|
|
return 1;
|
|
}
|
|
|
|
mutctx->mutctx_done = 1;
|
|
|
|
for (i = 0; i < numin; i++)
|
|
bufsz += iovecin[i].buf_len;
|
|
|
|
mutctx->mutctx_iov.buf_len = bufsz; /* keeps old size */
|
|
grow_allowance -= (bufsz < grow_allowance) ? bufsz : grow_allowance;
|
|
/* AEAD tag (16 bytes) + long header (14 bytes) */
|
|
grow_allowance -= (30 < grow_allowance) ? 30 : grow_allowance;
|
|
|
|
grow_allowance -= (hdrin->dst_conn_id.id_len < grow_allowance) ? hdrin->dst_conn_id.id_len : grow_allowance;
|
|
grow_allowance -= (hdrin->src_conn_id.id_len < grow_allowance) ? hdrin->src_conn_id.id_len : grow_allowance;
|
|
|
|
if (grow_allowance == 0) {
|
|
TEST_info("mutcbk_inject_frames() not enough space to inject");
|
|
return 0;
|
|
}
|
|
bufsz += grow_allowance;
|
|
|
|
/* discard const */
|
|
OPENSSL_free((char *)mutctx->mutctx_iov.buf);
|
|
mutctx->mutctx_iov.buf = OPENSSL_malloc(bufsz);
|
|
/* discard const */
|
|
buf = (char *)mutctx->mutctx_iov.buf;
|
|
if (buf == NULL) {
|
|
TEST_info("mutcbk_inject_frames() OPENSSL_malloc() failed");
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < numin; i++) {
|
|
memcpy(buf, iovecin[i].buf, iovecin[i].buf_len);
|
|
buf += iovecin[i].buf_len;
|
|
}
|
|
|
|
/* discard const */
|
|
buf = (char *)mutctx->mutctx_iov.buf;
|
|
if (mutctx->mutctx_inject != NULL) {
|
|
memmove(buf + mutctx->mutctx_inject_sz, buf,
|
|
mutctx->mutctx_iov.buf_len);
|
|
memcpy(buf, mutctx->mutctx_inject, mutctx->mutctx_inject_sz);
|
|
}
|
|
/*
|
|
* perhaps needed to have not looked at yet
|
|
*/
|
|
mutctx->mutctx_qhdrin = *hdrin;
|
|
*hdrout = &mutctx->mutctx_qhdrin;
|
|
mutctx->mutctx_iov.buf_len += mutctx->mutctx_inject_sz;
|
|
*iovecout = &mutctx->mutctx_iov;
|
|
*numout = 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void mutcbk_finish_injecct_frames(void *arg)
|
|
{
|
|
struct mutcbk_ctx *mutctx = (struct mutcbk_ctx *)arg;
|
|
|
|
OPENSSL_free((char *)mutctx->mutctx_iov.buf);
|
|
mutctx->mutctx_iov.buf = NULL;
|
|
}
|
|
|
|
/* 16 path challenge frames */
|
|
#define PATH_CHALLENGE_FRAMES \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH" \
|
|
"\x1a" \
|
|
"ABCDEFGH"
|
|
|
|
DEF_FUNC(mount_flood)
|
|
{
|
|
int ok = 0;
|
|
SSL *ssl;
|
|
QUIC_CHANNEL *ch;
|
|
static struct mutcbk_ctx mutctx = { 0 };
|
|
static const unsigned char *inject_frames = (const unsigned char *)PATH_CHALLENGE_FRAMES;
|
|
|
|
mutctx.mutctx_inject = inject_frames;
|
|
mutctx.mutctx_inject_sz = sizeof(PATH_CHALLENGE_FRAMES) - 1;
|
|
REQUIRE_SSL(ssl);
|
|
ch = ossl_quic_conn_get_channel(ssl);
|
|
if (!TEST_ptr(ch))
|
|
goto err;
|
|
|
|
if (!TEST_true(ossl_quic_channel_set_mutator(ch, mutcbk_inject_frames,
|
|
mutcbk_finish_injecct_frames, &mutctx)))
|
|
goto err;
|
|
ok = 1;
|
|
err:
|
|
return ok;
|
|
}
|
|
|
|
DEF_FUNC(check_flood_stats)
|
|
{
|
|
int ok = 0;
|
|
SSL *ssl;
|
|
QUIC_CHANNEL *ch;
|
|
uint64_t path_response_count;
|
|
uint64_t path_challenge_count;
|
|
|
|
REQUIRE_SSL(ssl);
|
|
ch = ossl_quic_conn_get_channel(ssl);
|
|
if (!TEST_ptr(ch))
|
|
goto err;
|
|
|
|
path_challenge_count = ossl_quic_channel_get_path_challenge_count(ch);
|
|
path_response_count = ossl_quic_channel_get_path_response_count(ch);
|
|
|
|
/*
|
|
* The flood is delivered over a real socket and processed by the
|
|
* connection's assist thread asynchronously, so give it a chance to
|
|
* catch up rather than failing on the first observation.
|
|
*/
|
|
if (path_challenge_count < 16 || path_response_count < 1)
|
|
F_SPIN_AGAIN();
|
|
|
|
if (!TEST_uint64_t_eq(path_challenge_count, 16))
|
|
goto err;
|
|
if (!TEST_uint64_t_eq(path_response_count, 1))
|
|
goto err;
|
|
|
|
ok = 1;
|
|
err:
|
|
return ok;
|
|
}
|
|
|
|
DEF_SCRIPT(check_pc_flood, "check path challenge flood")
|
|
{
|
|
OP_SIMPLE_PAIR_CONN();
|
|
OP_SELECT_SSL(0, C);
|
|
OP_FUNC(mount_flood);
|
|
OP_ACCEPT_CONN_WAIT(L, S, 0);
|
|
OP_WRITE_B(C, "attack");
|
|
OP_SELECT_SSL(0, S);
|
|
OP_FUNC(check_flood_stats);
|
|
}
|
|
|
|
/*
|
|
* Test to make sure that SSL_accept_connection returns the same ssl object
|
|
* that is used in the various TLS callbacks
|
|
*
|
|
* Unlike TCP, QUIC processes new connections independently from their
|
|
* acceptance, and so we need to pre-allocate tls objects to return during
|
|
* connection acceptance via the user_ssl. This is just a quic test to validate
|
|
* that:
|
|
* 1) The new callback to inform the user of a new pending ssl acceptance works
|
|
* properly
|
|
* 2) That the object returned from SSL_accept_connection matches the one passed
|
|
* to various callbacks
|
|
*
|
|
* It would be better as its own test, but currently the tserver used in the
|
|
* other quic_tests doesn't actually accept connections (it pre-creates them
|
|
* and fixes them up in place), so testing there is not feasible at the moment
|
|
*
|
|
* For details on this issue see:
|
|
* https://github.com/openssl/project/issues/918
|
|
*/
|
|
static SSL *pending_ssl_obj = NULL;
|
|
static SSL *client_hello_ssl_obj = NULL;
|
|
static int check_pending_match = 0;
|
|
static int pending_cb_called = 0;
|
|
static int hello_cb_called = 0;
|
|
|
|
static int new_pending_cb(SSL_CTX *ctx, SSL *new_ssl, void *arg)
|
|
{
|
|
pending_ssl_obj = new_ssl;
|
|
pending_cb_called = 1;
|
|
return 1;
|
|
}
|
|
|
|
static int client_hello_cb(SSL *s, int *al, void *arg)
|
|
{
|
|
client_hello_ssl_obj = s;
|
|
hello_cb_called = 1;
|
|
return 1;
|
|
}
|
|
|
|
DEF_FUNC(init_pending_test)
|
|
{
|
|
pending_ssl_obj = NULL;
|
|
client_hello_ssl_obj = NULL;
|
|
check_pending_match = 0;
|
|
pending_cb_called = 0;
|
|
hello_cb_called = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
DEF_FUNC(check_pending)
|
|
{
|
|
int ok = 0;
|
|
SSL *conn;
|
|
|
|
REQUIRE_SSL(conn);
|
|
|
|
if (check_pending_match) {
|
|
if (!TEST_true(pending_cb_called))
|
|
goto err;
|
|
|
|
if (!TEST_true(hello_cb_called))
|
|
goto err;
|
|
|
|
if (!TEST_ptr_eq(pending_ssl_obj, client_hello_ssl_obj))
|
|
goto err;
|
|
|
|
if (!TEST_ptr_eq(pending_ssl_obj, conn))
|
|
goto err;
|
|
|
|
pending_ssl_obj = client_hello_ssl_obj = NULL;
|
|
check_pending_match = 0;
|
|
pending_cb_called = hello_cb_called = 0;
|
|
}
|
|
|
|
ok = 1;
|
|
err:
|
|
return ok;
|
|
}
|
|
|
|
DEF_FUNC(new_listener)
|
|
{
|
|
int ok = 0;
|
|
SSL_CTX *ctx = NULL;
|
|
SSL *listener;
|
|
const char *name;
|
|
|
|
F_POP(name);
|
|
|
|
if (!TEST_ptr(ctx = SSL_CTX_new(OSSL_QUIC_server_method())))
|
|
goto err;
|
|
|
|
#if defined(OPENSSL_THREADS)
|
|
if (!TEST_true(SSL_CTX_set_domain_flags(ctx,
|
|
SSL_DOMAIN_FLAG_MULTI_THREAD
|
|
| SSL_DOMAIN_FLAG_BLOCKING)))
|
|
goto err;
|
|
#endif
|
|
|
|
if (!TEST_true(ssl_ctx_configure(ctx, 1)))
|
|
goto err;
|
|
|
|
SSL_CTX_set_new_pending_conn_cb(ctx, new_pending_cb, NULL);
|
|
SSL_CTX_set_client_hello_cb(ctx, client_hello_cb, NULL);
|
|
check_pending_match = 1;
|
|
if (!TEST_ptr(listener = SSL_new_listener(ctx, 0)))
|
|
goto err;
|
|
|
|
if (!TEST_true(ssl_attach_bio_dgram(listener, 0, NULL))) {
|
|
SSL_free(listener);
|
|
goto err;
|
|
}
|
|
|
|
if (!TEST_true(RADIX_PROCESS_set_ssl(RP(), name, listener))) {
|
|
SSL_free(listener);
|
|
goto err;
|
|
}
|
|
|
|
ok = 1;
|
|
err:
|
|
/* SSL object will hold ref, we don't need it */
|
|
SSL_CTX_free(ctx);
|
|
return ok;
|
|
}
|
|
|
|
DEF_SCRIPT(check_ctx_cbks, "Check new_pending and client_hello callbacks")
|
|
{
|
|
OP_FUNC(init_pending_test);
|
|
OP_PUSH_PZ("L");
|
|
OP_FUNC(new_listener);
|
|
OP_LISTEN(L);
|
|
OP_NEW_SSL_C(C);
|
|
OP_SET_PEER_ADDR_FROM(C, L);
|
|
OP_CONNECT_WAIT(C);
|
|
OP_ACCEPT_CONN_WAIT(L, S, 0);
|
|
OP_SELECT_SSL(0, S);
|
|
OP_FUNC(check_pending);
|
|
}
|
|
|
|
/*
|
|
* List of Test Scripts
|
|
* ============================================================================
|
|
*/
|
|
static SCRIPT_INFO *const scripts[] = {
|
|
USE(simple_conn),
|
|
USE(simple_thread),
|
|
USE(ssl_poll),
|
|
USE(poll_abort_blocking),
|
|
USE(check_cwm),
|
|
USE(check_pc_flood),
|
|
USE(check_ctx_cbks),
|
|
};
|