@@ -34824,6 +34824,188 @@ static int test_write_dup_want_write_simul(void)
3482434824 return EXPECT_RESULT();
3482534825}
3482634826
34827+ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(HAVE_WRITE_DUP) && \
34828+ defined(HAVE_CHACHA) && defined(HAVE_POLY1305) && !defined(NO_SHA256) && \
34829+ (defined(HAVE_ECC) || defined(HAVE_CURVE25519) || \
34830+ defined(HAVE_CURVE448)) && \
34831+ !defined(NO_RSA) && defined(USE_WOLFSSL_MEMORY) && \
34832+ !defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_STATIC_MEMORY) && \
34833+ !defined(WOLFSSL_KERNEL_MODE) && !defined(WOLFSSL_NO_TLS12) && \
34834+ !defined(NO_TLS)
34835+ #include <wolfssl/wolfcrypt/poly1305.h>
34836+
34837+ /* Custom allocator state for test_write_dup_oom.
34838+ * The allocator counts allocations of size oom_match_size (set to
34839+ * sizeof(Poly1305) by the test). The Nth such allocation is forced to
34840+ * fail if oom_fail_at_match == N; oom_failed is then set so callers can
34841+ * confirm the fault was actually injected. The test runs in two phases:
34842+ * 1) Measure: oom_fail_at_match = 0 (no failures), record oom_match_count
34843+ * after a successful wolfSSL_write_dup(). The Poly1305 alloc in
34844+ * DupSSL() is the LAST sizeof(Poly1305) allocation in the call path,
34845+ * so that count is the index that targets it.
34846+ * 2) Trigger: oom_fail_at_match = recorded count, run write_dup again on
34847+ * a fresh handshake. The Nth match is the Poly1305 alloc, so the
34848+ * failure exercises exactly the bug under test. */
34849+ static size_t oom_match_size = 0;
34850+ static int oom_match_count = 0;
34851+ static int oom_fail_at_match = 0;
34852+ static int oom_failed = 0;
34853+
34854+ #ifdef WOLFSSL_DEBUG_MEMORY
34855+ static void* oom_malloc_cb(size_t size, const char* func, unsigned int line)
34856+ {
34857+ (void)func;
34858+ (void)line;
34859+ #else
34860+ static void* oom_malloc_cb(size_t size)
34861+ {
34862+ #endif
34863+ if (!oom_failed && oom_match_size != 0 && size == oom_match_size) {
34864+ oom_match_count++;
34865+ if (oom_fail_at_match != 0 && oom_match_count == oom_fail_at_match) {
34866+ oom_failed = 1;
34867+ return NULL;
34868+ }
34869+ }
34870+ return malloc(size);
34871+ }
34872+
34873+ #ifdef WOLFSSL_DEBUG_MEMORY
34874+ static void oom_free_cb(void* ptr, const char* func, unsigned int line)
34875+ {
34876+ (void)func;
34877+ (void)line;
34878+ #else
34879+ static void oom_free_cb(void* ptr)
34880+ {
34881+ #endif
34882+ free(ptr);
34883+ }
34884+
34885+ #ifdef WOLFSSL_DEBUG_MEMORY
34886+ static void* oom_realloc_cb(void* ptr, size_t size, const char* func,
34887+ unsigned int line)
34888+ {
34889+ (void)func;
34890+ (void)line;
34891+ #else
34892+ static void* oom_realloc_cb(void* ptr, size_t size)
34893+ {
34894+ #endif
34895+ return realloc(ptr, size);
34896+ }
34897+ #endif
34898+
34899+ /* Regression test for the DupSSL() error-path bug: an allocation failure on
34900+ * the Poly1305 alloc must not leave the original ssl object with a zeroed
34901+ * encrypt context or a non-NULL ssl->dupWrite.
34902+ *
34903+ * Pass 0 measures the count of sizeof(Poly1305) allocations during a
34904+ * successful wolfSSL_write_dup(); pass 1 fails the same-indexed allocation
34905+ * on a fresh handshake to deterministically target the DupSSL() Poly1305
34906+ * alloc regardless of any incidental size collisions. */
34907+ static int test_write_dup_oom(void)
34908+ {
34909+ EXPECT_DECLS;
34910+ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(HAVE_WRITE_DUP) && \
34911+ defined(HAVE_CHACHA) && defined(HAVE_POLY1305) && !defined(NO_SHA256) && \
34912+ (defined(HAVE_ECC) || defined(HAVE_CURVE25519) || \
34913+ defined(HAVE_CURVE448)) && \
34914+ !defined(NO_RSA) && defined(USE_WOLFSSL_MEMORY) && \
34915+ !defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_STATIC_MEMORY) && \
34916+ !defined(WOLFSSL_KERNEL_MODE) && !defined(WOLFSSL_NO_TLS12) && \
34917+ !defined(NO_TLS)
34918+ char hiWorld[] = "dup message";
34919+ char readData[sizeof(hiWorld) + 5];
34920+ wolfSSL_Malloc_cb prev_mc = NULL;
34921+ wolfSSL_Free_cb prev_fc = NULL;
34922+ wolfSSL_Realloc_cb prev_rc = NULL;
34923+ int allocators_set = 0;
34924+ int target_match = 0;
34925+ int pass;
34926+
34927+ ExpectIntEQ(wolfSSL_GetAllocators(&prev_mc, &prev_fc, &prev_rc), 0);
34928+ ExpectIntEQ(wolfSSL_SetAllocators(oom_malloc_cb, oom_free_cb,
34929+ oom_realloc_cb), 0);
34930+ if (EXPECT_SUCCESS())
34931+ allocators_set = 1;
34932+
34933+ for (pass = 0; pass < 2 && EXPECT_SUCCESS(); pass++) {
34934+ struct test_memio_ctx test_ctx;
34935+ WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
34936+ WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
34937+ WOLFSSL *ssl_c2 = NULL;
34938+
34939+ XMEMSET(&test_ctx, 0, sizeof(test_ctx));
34940+ test_ctx.c_ciphers = test_ctx.s_ciphers =
34941+ "ECDHE-RSA-CHACHA20-POLY1305";
34942+
34943+ ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
34944+ wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0);
34945+ ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
34946+
34947+ /* Start counting sizeof(Poly1305) allocations only now, after the
34948+ * handshake, so the count reflects only allocations on the
34949+ * wolfSSL_write_dup() code path. */
34950+ oom_match_size = sizeof(Poly1305);
34951+ oom_match_count = 0;
34952+ oom_failed = 0;
34953+ oom_fail_at_match = (pass == 0) ? 0 : target_match;
34954+
34955+ if (pass == 0) {
34956+ ExpectNotNull(ssl_c2 = wolfSSL_write_dup(ssl_c));
34957+ target_match = oom_match_count;
34958+ /* Sanity: at least one Poly1305 alloc must occur on this path,
34959+ * otherwise the test setup was wrong (feature compiled out, or
34960+ * negotiated cipher does not use Poly1305). */
34961+ ExpectIntGE(target_match, 1);
34962+ }
34963+ else {
34964+ ExpectNull(wolfSSL_write_dup(ssl_c));
34965+ /* Confirm the targeted Poly1305 allocation actually failed. */
34966+ ExpectIntEQ(oom_failed, 1);
34967+ /* Stop further failures so the recovery path can run. */
34968+ oom_fail_at_match = 0;
34969+
34970+ /* The original ssl_c must NOT be poisoned: the WriteDup must have
34971+ * been cleaned up, the encrypt cipher context must still be
34972+ * intact, and a fresh wolfSSL_write_dup() must succeed. */
34973+ ExpectNull(ssl_c->dupWrite);
34974+ ExpectNotNull(ssl_c2 = wolfSSL_write_dup(ssl_c));
34975+
34976+ /* Round-trip data both directions to confirm both sides still
34977+ * encrypt with the original ssl_c context preserved. */
34978+ ExpectIntEQ(wolfSSL_write(ssl_s, hiWorld, sizeof(hiWorld)),
34979+ sizeof(hiWorld));
34980+ ExpectIntEQ(wolfSSL_read(ssl_c, readData, sizeof(readData)),
34981+ sizeof(hiWorld));
34982+ ExpectIntEQ(wolfSSL_write(ssl_c2, hiWorld, sizeof(hiWorld)),
34983+ sizeof(hiWorld));
34984+ ExpectIntEQ(wolfSSL_read(ssl_s, readData, sizeof(readData)),
34985+ sizeof(hiWorld));
34986+ }
34987+
34988+ /* Disable matching/failure during teardown so frees and any internal
34989+ * allocations of the same size during cleanup are unaffected. */
34990+ oom_match_size = 0;
34991+ oom_fail_at_match = 0;
34992+
34993+ wolfSSL_free(ssl_c);
34994+ wolfSSL_free(ssl_c2);
34995+ wolfSSL_free(ssl_s);
34996+ wolfSSL_CTX_free(ctx_c);
34997+ wolfSSL_CTX_free(ctx_s);
34998+ }
34999+
35000+ /* Restore previous allocators only after every object allocated under the
35001+ * test allocators has been freed, so allocator bookkeeping (in builds
35002+ * that wrap the default allocators) is not desynchronised. */
35003+ if (allocators_set)
35004+ (void)wolfSSL_SetAllocators(prev_mc, prev_fc, prev_rc);
35005+ #endif
35006+ return EXPECT_RESULT();
35007+ }
35008+
3482735009static int test_read_write_hs(void)
3482835010{
3482935011
@@ -37660,6 +37842,7 @@ TEST_CASE testCases[] = {
3766037842 TEST_DECL(test_write_dup),
3766137843 TEST_DECL(test_write_dup_want_write),
3766237844 TEST_DECL(test_write_dup_want_write_simul),
37845+ TEST_DECL(test_write_dup_oom),
3766337846 TEST_DECL(test_read_write_hs),
3766437847 TEST_DECL(test_get_signature_nid),
3766537848#ifndef WOLFSSL_TEST_APPLE_NATIVE_CERT_VALIDATION
0 commit comments