Commit 77511e1baa for openssl.org
commit 77511e1baa038c2faf0c10d930c08ea87305dbec
Author: Andrew Dinh <andrewd@openssl.org>
Date: Wed Jul 8 11:10:18 2026 +0700
Port script_20
Assisted-by: Claude:claude-sonnet-5
Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
Reviewed-by: Bob Beck <beck@openssl.org>
MergeDate: Thu Jul 30 06:12:39 2026
(Merged from https://github.com/openssl/openssl/pull/32003)
diff --git a/test/quic_multistream_test.c b/test/quic_multistream_test.c
index a921f13610..8e74e83a70 100644
--- a/test/quic_multistream_test.c
+++ b/test/quic_multistream_test.c
@@ -2068,98 +2068,8 @@ static const struct script_op script_19[] = {
};
/* 20. Multiple threads accept stream with socket forcibly closed (error test) */
-static int script_20_trigger(struct helper *h, volatile uint64_t *counter)
-{
-#if defined(OPENSSL_THREADS)
- ossl_crypto_mutex_lock(h->misc_m);
- ++*counter;
- ossl_crypto_condvar_broadcast(h->misc_cv);
- ossl_crypto_mutex_unlock(h->misc_m);
-#endif
- return 1;
-}
-
-static int script_20_wait(struct helper *h, volatile uint64_t *counter, uint64_t threshold)
-{
-#if defined(OPENSSL_THREADS)
- int stop = 0;
-
- ossl_crypto_mutex_lock(h->misc_m);
- while (!stop) {
- stop = (*counter >= threshold);
- if (stop)
- break;
-
- ossl_crypto_condvar_wait(h->misc_cv, h->misc_m);
- }
-
- ossl_crypto_mutex_unlock(h->misc_m);
-#endif
- return 1;
-}
-
-static int script_20_trigger1(struct helper *h, struct helper_local *hl)
-{
- return script_20_trigger(h, &h->scratch0);
-}
-
-static int script_20_wait1(struct helper *h, struct helper_local *hl)
-{
- return script_20_wait(h, &h->scratch0, hl->check_op->arg2);
-}
-
-static int script_20_trigger2(struct helper *h, struct helper_local *hl)
-{
- return script_20_trigger(h, &h->scratch1);
-}
-
-static int script_20_wait2(struct helper *h, struct helper_local *hl)
-{
- return script_20_wait(h, &h->scratch1, hl->check_op->arg2);
-}
-
-static const struct script_op script_20_child[] = {
- OP_C_ACCEPT_STREAM_WAIT(a),
- OP_C_READ_EXPECT(a, "foo", 3),
-
- OP_CHECK(script_20_trigger1, 0),
- OP_CHECK(script_20_wait2, 1),
-
- OP_C_READ_FAIL_WAIT(a),
- OP_C_EXPECT_SSL_ERR(a, SSL_ERROR_SYSCALL),
-
- OP_EXPECT_ERR_LIB(ERR_LIB_SSL),
- OP_EXPECT_ERR_REASON(SSL_R_PROTOCOL_IS_SHUTDOWN),
-
- OP_POP_ERR(),
- OP_EXPECT_ERR_LIB(ERR_LIB_SSL),
- OP_EXPECT_ERR_REASON(SSL_R_QUIC_NETWORK_ERROR),
-
- OP_C_FREE_STREAM(a),
-
- OP_END
-};
-
static const struct script_op script_20[] = {
- OP_C_SET_ALPN("ossltest"),
- OP_C_CONNECT_WAIT(),
- OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE),
-
- OP_NEW_THREAD(5, script_20_child),
-
- OP_BEGIN_REPEAT(5),
-
- OP_S_NEW_STREAM_BIDI(a, ANY_ID),
- OP_S_WRITE(a, "foo", 3),
- OP_S_UNBIND_STREAM_ID(a),
-
- OP_END_REPEAT(),
-
- OP_CHECK(script_20_wait1, 5),
-
- OP_C_CLOSE_SOCKET(),
- OP_CHECK(script_20_trigger2, 0),
-
+ /* test moved to test/radix/quic_tests.c */
OP_END
};
diff --git a/test/radix/quic_bindings.c b/test/radix/quic_bindings.c
index 63bcecad99..fb87c2245d 100644
--- a/test/radix/quic_bindings.c
+++ b/test/radix/quic_bindings.c
@@ -68,6 +68,7 @@ typedef struct radix_process_st {
CRYPTO_MUTEX *gm; /* global mutex */
LHASH_OF(RADIX_OBJ) *objs; /* protected by gm */
BIO *keylog_out; /* protected by gm */
+ uint64_t counter[2]; /* protected by gm */
CRYPTO_MUTEX *time_m;
OSSL_TIME base_time; /* set once at init, constant thereafter */
@@ -107,7 +108,7 @@ typedef struct radix_thread_st {
DEFINE_STACK_OF(RADIX_THREAD)
-/* ssl reference is transferred. name is copied and is required. */
+/* name is copied and is required. Created with no SSL object bound yet. */
static RADIX_OBJ *RADIX_OBJ_new_empty(const char *name)
{
RADIX_OBJ *obj;
@@ -135,6 +136,7 @@ static RADIX_OBJ *RADIX_OBJ_new_empty(const char *name)
return obj;
}
+/* ssl reference is transferred. name is copied and is required. */
static RADIX_OBJ *RADIX_OBJ_new(const char *name, SSL *ssl)
{
RADIX_OBJ *obj;
@@ -142,8 +144,7 @@ static RADIX_OBJ *RADIX_OBJ_new(const char *name, SSL *ssl)
if (!TEST_ptr(ssl))
return NULL;
- obj = RADIX_OBJ_new_empty(name);
- if (!TEST_ptr(obj))
+ if (!TEST_ptr(obj = RADIX_OBJ_new_empty(name)))
return NULL;
obj->ssl = ssl;
@@ -712,7 +713,7 @@ static void radix_skip_time(OSSL_TIME t)
static void per_op_tick_obj(RADIX_OBJ *obj)
{
ossl_crypto_mutex_lock(obj->mx);
- if (obj->active && obj->ssl)
+ if (obj->active && obj->ssl != NULL)
SSL_handle_events(obj->ssl);
ossl_crypto_mutex_unlock(obj->mx);
}
diff --git a/test/radix/quic_ops.c b/test/radix/quic_ops.c
index ed129d0ff7..6d7daa0148 100644
--- a/test/radix/quic_ops.c
+++ b/test/radix/quic_ops.c
@@ -29,20 +29,21 @@ err:
DEF_FUNC(hf_bind)
{
+ int ok = 0;
const char *name;
RADIX_OBJ *empty_obj;
F_POP(name);
- empty_obj = RADIX_OBJ_new_empty(name);
- if (empty_obj == NULL)
- return 0;
-
- RADIX_PROCESS_set_obj(RP(), name, empty_obj);
+ if (!TEST_ptr(empty_obj = RADIX_OBJ_new_empty(name))
+ || !TEST_true(RADIX_PROCESS_set_obj(RP(), name, empty_obj))) {
+ RADIX_OBJ_free(empty_obj);
+ goto err;
+ }
- return 1;
+ ok = 1;
err:
- return 0;
+ return ok;
}
static int ssl_ctx_select_alpn(SSL *ssl,
@@ -292,8 +293,9 @@ DEF_FUNC(hf_new_stream)
if (replace == 0) {
if (!TEST_ptr_null(stream_obj))
goto err;
- } else if (TEST_ptr_null(stream_obj))
+ } else if (TEST_ptr_null(stream_obj)) {
goto err;
+ }
if (do_accept) {
stream = SSL_accept_stream(conn, flags & OP_F_MASK);
@@ -1049,6 +1051,62 @@ err:
return ok;
}
+DEF_FUNC(hf_close_socket)
+{
+ int ok = 0;
+ SSL *ssl;
+ BIO *bio;
+ int fd = -1;
+
+ REQUIRE_SSL(ssl);
+
+ bio = SSL_get_rbio(ssl);
+ if (!TEST_ptr(bio) || !TEST_true(BIO_get_fd(bio, &fd)) || !TEST_int_ge(fd, 0))
+ goto err;
+
+ BIO_closesocket(fd);
+
+ ok = 1;
+err:
+ return ok;
+}
+
+/* Process-global counters (RP()->counter[idx]), used for cross-thread sync. */
+DEF_FUNC(hf_trigger_counter)
+{
+ int ok = 0;
+ uint64_t idx;
+
+ F_POP(idx);
+
+ ossl_crypto_mutex_lock(RP()->gm);
+ ++RP()->counter[idx];
+ ossl_crypto_mutex_unlock(RP()->gm);
+
+ ok = 1;
+err:
+ return ok;
+}
+
+DEF_FUNC(hf_wait_counter)
+{
+ int ok = 0;
+ uint64_t idx, threshold, current;
+
+ F_POP2(idx, threshold);
+
+ ossl_crypto_mutex_lock(RP()->gm);
+ current = RP()->counter[idx];
+ ossl_crypto_mutex_unlock(RP()->gm);
+
+ if (current < threshold)
+ F_SPIN_AGAIN();
+
+ ok = 1;
+err:
+ return ok;
+}
+
#define OP_UNBIND(name) \
(OP_PUSH_PZ(#name), \
OP_FUNC(hf_unbind))
@@ -1295,3 +1353,16 @@ err:
(OP_SELECT_SSL(0, name), \
OP_PUSH_U64(update_type), \
OP_FUNC(hf_trigger_key_update))
+
+#define OP_CLOSE_SOCKET(name) \
+ (OP_SELECT_SSL(0, name), \
+ OP_FUNC(hf_close_socket))
+
+#define OP_TRIGGER_COUNTER(idx) \
+ (OP_PUSH_U64(idx), \
+ OP_FUNC(hf_trigger_counter))
+
+#define OP_WAIT_COUNTER(idx, threshold) \
+ (OP_PUSH_U64(idx), \
+ OP_PUSH_U64(threshold), \
+ OP_FUNC(hf_wait_counter))
diff --git a/test/radix/quic_tests.c b/test/radix/quic_tests.c
index f1abcce118..40d3290adc 100644
--- a/test/radix/quic_tests.c
+++ b/test/radix/quic_tests.c
@@ -1630,8 +1630,119 @@ DEF_SCRIPT(script_19, "Key update test - artificially triggered")
OP_CHECK_KEY_UPDATE_GE(C, 1);
}
-DEF_SCRIPT(script_20, "place holder for multistrem script_20")
+/* 20. Multiple threads accept stream with socket forcibly closed (error test) */
+DEF_SCRIPT(script_20_child_0,
+ "child: accept stream, read, signal ready, wait, expect read failure")
{
+ OP_ACCEPT_STREAM_WAIT(C, Ca, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_READ_EXPECT_B(Ca, "foo");
+
+ OP_TRIGGER_COUNTER(0);
+ OP_WAIT_COUNTER(1, 1);
+
+ OP_READ_FAIL_WAIT(Ca);
+ OP_EXPECT_SSL_ERR(Ca, SSL_ERROR_SYSCALL);
+}
+
+DEF_SCRIPT(script_20_child_1,
+ "child: accept stream, read, signal ready, wait, expect read failure")
+{
+ OP_ACCEPT_STREAM_WAIT(C, Cb, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_READ_EXPECT_B(Cb, "foo");
+
+ OP_TRIGGER_COUNTER(0);
+ OP_WAIT_COUNTER(1, 1);
+
+ OP_READ_FAIL_WAIT(Cb);
+ OP_EXPECT_SSL_ERR(Cb, SSL_ERROR_SYSCALL);
+}
+
+DEF_SCRIPT(script_20_child_2,
+ "child: accept stream, read, signal ready, wait, expect read failure")
+{
+ OP_ACCEPT_STREAM_WAIT(C, Cc, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_READ_EXPECT_B(Cc, "foo");
+
+ OP_TRIGGER_COUNTER(0);
+ OP_WAIT_COUNTER(1, 1);
+
+ OP_READ_FAIL_WAIT(Cc);
+ OP_EXPECT_SSL_ERR(Cc, SSL_ERROR_SYSCALL);
+}
+
+DEF_SCRIPT(script_20_child_3,
+ "child: accept stream, read, signal ready, wait, expect read failure")
+{
+ OP_ACCEPT_STREAM_WAIT(C, Cd, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_READ_EXPECT_B(Cd, "foo");
+
+ OP_TRIGGER_COUNTER(0);
+ OP_WAIT_COUNTER(1, 1);
+
+ OP_READ_FAIL_WAIT(Cd);
+ OP_EXPECT_SSL_ERR(Cd, SSL_ERROR_SYSCALL);
+}
+
+DEF_SCRIPT(script_20_child_4,
+ "child: accept stream, read, signal ready, wait, expect read failure")
+{
+ OP_ACCEPT_STREAM_WAIT(C, Ce, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_READ_EXPECT_B(Ce, "foo");
+
+ OP_TRIGGER_COUNTER(0);
+ OP_WAIT_COUNTER(1, 1);
+
+ OP_READ_FAIL_WAIT(Ce);
+ OP_EXPECT_SSL_ERR(Ce, SSL_ERROR_SYSCALL);
+}
+
+DEF_SCRIPT(script_20, "Multiple threads accept stream with socket forcibly closed (error test)")
+{
+ OP_SIMPLE_PAIR_CONN_ND();
+ OP_ACCEPT_CONN_WAIT_ND(L, S, 0);
+
+ OP_BIND(Ca);
+ OP_BIND(Cb);
+ OP_BIND(Cc);
+ OP_BIND(Cd);
+ OP_BIND(Ce);
+ OP_BIND(Sa);
+ OP_BIND(Sb);
+ OP_BIND(Sc);
+ OP_BIND(Sd);
+ OP_BIND(Se);
+
+ OP_SPAWN_THREAD(script_20_child_0);
+ OP_SPAWN_THREAD(script_20_child_1);
+ OP_SPAWN_THREAD(script_20_child_2);
+ OP_SPAWN_THREAD(script_20_child_3);
+ OP_SPAWN_THREAD(script_20_child_4);
+
+ OP_NEW_STREAM(S, Sa, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_WRITE_B(Sa, "foo");
+ OP_CONCLUDE(Sa);
+
+ OP_NEW_STREAM(S, Sb, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_WRITE_B(Sb, "foo");
+ OP_CONCLUDE(Sb);
+
+ OP_NEW_STREAM(S, Sc, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_WRITE_B(Sc, "foo");
+ OP_CONCLUDE(Sc);
+
+ OP_NEW_STREAM(S, Sd, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_WRITE_B(Sd, "foo");
+ OP_CONCLUDE(Sd);
+
+ OP_NEW_STREAM(S, Se, OP_F_REPLACE_STREAM /* bidirectional */);
+ OP_WRITE_B(Se, "foo");
+ OP_CONCLUDE(Se);
+
+ OP_WAIT_COUNTER(0, 5);
+
+ OP_CLOSE_SOCKET(C);
+
+ OP_TRIGGER_COUNTER(1);
}
DEF_SCRIPT(script_21, "place holder for multistrem script_21")