Commit af3173bd11 for openssl.org
commit af3173bd11a83a18bc32ffe11f809c5d4a34336a
Author: Andrew Dinh <andrewd@openssl.org>
Date: Mon Aug 3 12:38:10 2026 +0700
Port script_54
Assisted-by: Claude:claude-sonnet-5
Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
Reviewed-by: Norbert Pocs <norbertp@openssl.org>
Merge-date: Tue Sep 15 08:52:57 2026
Merged-from: https://github.com/openssl/openssl/pull/32469
diff --git a/test/quic_multistream_test.c b/test/quic_multistream_test.c
index f407bf669b..20d4554970 100644
--- a/test/quic_multistream_test.c
+++ b/test/quic_multistream_test.c
@@ -2583,24 +2583,8 @@ static const struct script_op script_53[] = {
};
/* 54. Fault injection - corrupted crypto stream data */
-static int script_54_inject_handshake(struct helper *h,
- unsigned char *buf, size_t buf_len)
-{
- size_t i;
-
- for (i = 0; i < buf_len; ++i)
- buf[i] ^= 0xff;
-
- return 1;
-}
-
static const struct script_op script_54[] = {
- OP_S_SET_INJECT_HANDSHAKE(script_54_inject_handshake),
- OP_C_SET_ALPN("ossltest"),
- OP_C_CONNECT_WAIT_OR_FAIL(),
-
- OP_C_EXPECT_CONN_CLOSE_INFO(OSSL_QUIC_ERR_CRYPTO_UNEXPECTED_MESSAGE, 0, 0),
-
+ /* test moved to test/radix/quic_tests.c */
OP_END
};
diff --git a/test/radix/quic_ops.c b/test/radix/quic_ops.c
index e5f314c1b3..b4bd59e9ec 100644
--- a/test/radix/quic_ops.c
+++ b/test/radix/quic_ops.c
@@ -7,6 +7,7 @@
* https://www.openssl.org/source/license.html
*/
#include "internal/sockets.h"
+#include "internal/statem.h"
#include <openssl/rand.h>
static const unsigned char alpn_ossltest[] = {
@@ -728,11 +729,19 @@ err:
return ok;
}
+/*
+ * If tolerate_failure is set, tolerates the connection attempt itself failing
+ * (rather than the connection later closing after a successful handshake),
+ * for scripts where fault injection is expected to prevent the handshake
+ * from completing at all.
+ */
DEF_FUNC(hf_connect_wait)
{
int ok = 0, ret;
SSL *ssl;
+ uint64_t tolerate_failure;
+ F_POP(tolerate_failure);
REQUIRE_SSL(ssl);
/* if not started */
@@ -756,7 +765,7 @@ DEF_FUNC(hf_connect_wait)
if (is_want(ssl, ret))
F_SPIN_AGAIN();
- if (!TEST_int_eq(ret, 1))
+ if (!tolerate_failure && !TEST_int_eq(ret, 1))
goto err;
}
@@ -1325,6 +1334,38 @@ static ossl_inline radix_fault_plain_cb radix_fault_ptr_to_plain_cb(void *ptr)
return u.cb;
}
+/*
+ * Handshake (crypto stream) message mutator, used to tamper with a QUIC
+ * connection's outgoing TLS handshake messages before they are queued for
+ * transmission.
+ */
+typedef int (*radix_fault_handshake_cb)(RADIX_FAULT *fault,
+ unsigned char *buf, size_t len);
+
+static ossl_inline void *
+radix_fault_handshake_cb_to_ptr(radix_fault_handshake_cb cb)
+{
+ union {
+ radix_fault_handshake_cb cb;
+ void *ptr;
+ } u;
+
+ u.cb = cb;
+ return u.ptr;
+}
+
+static ossl_inline radix_fault_handshake_cb
+radix_fault_ptr_to_handshake_cb(void *ptr)
+{
+ union {
+ radix_fault_handshake_cb cb;
+ void *ptr;
+ } u;
+
+ u.ptr = ptr;
+ return u.cb;
+}
+
struct radix_fault_st {
QUIC_PKT_HDR hdr;
OSSL_QTX_IOVEC io;
@@ -1332,6 +1373,10 @@ struct radix_fault_st {
radix_fault_plain_cb cb;
QUIC_CHANNEL *ch;
uint64_t word0, word1;
+ /* Handshake message mutator state. */
+ unsigned char *handbuf;
+ size_t handbuflen;
+ radix_fault_handshake_cb hcb;
};
/* Fault injection against one channel at a time. */
@@ -1488,6 +1533,71 @@ err:
return ok;
}
+/*
+ * ossl_statem_mutate_handshake_cb: intercepts an outgoing TLS handshake
+ * message on the crypto stream before it is queued for transmission.
+ */
+static int radix_fault_handshake_mutate(const unsigned char *msgin,
+ size_t msginlen,
+ unsigned char **msgout,
+ size_t *msgoutlen,
+ void *arg)
+{
+ RADIX_FAULT *fault = arg;
+ unsigned char *buf;
+
+ buf = OPENSSL_malloc(msginlen);
+ if (buf == NULL)
+ return 0;
+
+ OPENSSL_free(fault->handbuf);
+ fault->handbuf = buf;
+ fault->handbuflen = msginlen;
+ memcpy(buf, msgin, msginlen);
+
+ if (fault->hcb != NULL && !fault->hcb(fault, buf, fault->handbuflen))
+ return 0;
+
+ *msgout = buf;
+ *msgoutlen = fault->handbuflen;
+
+ return 1;
+}
+
+static void radix_fault_handshake_finish(void *arg)
+{
+ RADIX_FAULT *fault = arg;
+
+ OPENSSL_free(fault->handbuf);
+ fault->handbuf = NULL;
+}
+
+/*
+ * Arms the handshake mutator callback without attaching it to any
+ * connection yet. Used by scripts that need to intercept a server's very
+ * first handshake flight: the corresponding connection does not exist until
+ * a client's Initial packet is accepted, by which point that first flight
+ * has typically already been generated, so the mutator must instead be
+ * installed from a SSL_CTX client_hello callback (see script_54 for an
+ * example) armed with this callback beforehand.
+ */
+DEF_FUNC(hf_set_inject_handshake_cb)
+{
+ int ok = 0;
+ void *cbptr;
+
+ F_POP(cbptr);
+
+ OPENSSL_free(radix_fault.handbuf);
+ radix_fault.handbuf = NULL;
+ radix_fault.handbuflen = 0;
+ radix_fault.hcb = radix_fault_ptr_to_handshake_cb(cbptr);
+
+ ok = 1;
+err:
+ return ok;
+}
+
DEF_FUNC(hf_push_stream_id_plus_one)
{
int ok = 0;
@@ -1557,6 +1667,12 @@ err:
#define OP_CONNECT_WAIT(name) \
(OP_SELECT_SSL(0, name), \
+ OP_PUSH_U64(0), \
+ OP_FUNC(hf_connect_wait))
+
+#define OP_CONNECT_WAIT_OR_FAIL(name) \
+ (OP_SELECT_SSL(0, name), \
+ OP_PUSH_U64(1), \
OP_FUNC(hf_connect_wait))
#define OP_LISTEN(name) \
@@ -1858,6 +1974,10 @@ err:
OP_PUSH_U64(word1), \
OP_FUNC(hf_set_inject_word))
+#define OP_SET_INJECT_HANDSHAKE_CB(cb) \
+ (OP_PUSH_P(radix_fault_handshake_cb_to_ptr(cb)), \
+ OP_FUNC(hf_set_inject_handshake_cb))
+
#define OP_PUSH_STREAM_ID_PLUS_ONE(name) \
(OP_SELECT_SSL(0, name), \
OP_FUNC(hf_push_stream_id_plus_one))
diff --git a/test/radix/quic_tests.c b/test/radix/quic_tests.c
index a8190f813f..bf03a08929 100644
--- a/test/radix/quic_tests.c
+++ b/test/radix/quic_tests.c
@@ -3293,8 +3293,83 @@ DEF_SCRIPT(script_53, "Fault injection - excess CRYPTO buffer size")
OP_EXPECT_CONN_CLOSE_INFO(C, OSSL_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED, 0, 0);
}
-DEF_SCRIPT(script_54, "place holder for multistrem script_54")
+/* 54. Fault injection - corrupted crypto stream data */
+static int script_54_inject_handshake(RADIX_FAULT *fault, unsigned char *buf,
+ size_t buf_len)
{
+ size_t i;
+
+ for (i = 0; i < buf_len; ++i)
+ buf[i] ^= 0xff;
+
+ return 1;
+}
+
+/*
+ * The corrupted connection's channel does not exist until the client's
+ * Initial packet is accepted, by which point the server's first handshake
+ * flight has already been generated. So instead of arming the handshake
+ * mutator on an already-accepted connection (too late for this test), a
+ * client_hello callback is used to install it as soon as the ClientHello is
+ * processed, before any response is generated.
+ */
+static int script_54_client_hello_cb(SSL *s, int *al, void *arg)
+{
+ return ossl_statem_set_mutator(s, radix_fault_handshake_mutate,
+ radix_fault_handshake_finish, &radix_fault);
+}
+
+DEF_FUNC(new_listener_54)
+{
+ int ok = 0;
+ SSL_CTX *ctx = NULL;
+ SSL *listener = NULL;
+ 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_client_hello_cb(ctx, script_54_client_hello_cb, NULL);
+
+ if (!TEST_ptr(listener = SSL_new_listener(ctx, 0))
+ || !TEST_true(ssl_attach_bio_dgram(listener, 0, NULL))
+ || !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(script_54, "Fault injection - corrupted crypto stream data")
+{
+ OP_SET_INJECT_HANDSHAKE_CB(script_54_inject_handshake);
+
+ OP_PUSH_PZ("L");
+ OP_FUNC(new_listener_54);
+ OP_LISTEN(L);
+ OP_NEW_SSL_C(C);
+ OP_SET_PEER_ADDR_FROM(C, L);
+
+ OP_CONNECT_WAIT_OR_FAIL(C);
+
+ OP_EXPECT_CONN_CLOSE_INFO(C, OSSL_QUIC_ERR_CRYPTO_UNEXPECTED_MESSAGE, 0, 0);
}
DEF_SCRIPT(script_55, "place holder for multistrem script_55")