Commit 34f8ce6c49 for openssl.org

commit 34f8ce6c49161f649a18dc83a86c202398451519
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date:   Mon Apr 27 22:50:31 2026 +0900

    Fix base64 BIO write retry handling

    Commit 3a69b19028 changed b64_write from chunked ctx->buf output
    to a dynamic encoded_buf. The new buffer was scratch-only, so
    retryable downstream failures or positive short writes could drop
    encoded output while still reporting the input bytes as consumed.

    Track encoded_buf as pending output with offset and length, include
    it in BIO_wpending, and write any pending output before accepting
    more input. Add tests for retry via -1, retry via 0, positive short
    writes, and short writes followed by retry, with and without
    BIO_FLAGS_BASE64_NO_NL, across both aligned input and final-tail
    input.

    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
    Merge-date: Fri Sep 25 11:02:43 2026
    Merged-from: https://github.com/openssl/openssl/pull/31000

diff --git a/crypto/evp/bio_b64.c b/crypto/evp/bio_b64.c
index 7f32d7a5d0..2a6f06c110 100644
--- a/crypto/evp/bio_b64.c
+++ b/crypto/evp/bio_b64.c
@@ -44,6 +44,8 @@ typedef struct b64_struct {
     unsigned char tmp[B64_BLOCK_SIZE];
     unsigned char *encoded_buf;
     size_t encoded_buf_len;
+    size_t encoded_len;
+    size_t encoded_off;
 } BIO_B64_CTX;

 static const BIO_METHOD methods_b64 = {
@@ -111,6 +113,72 @@ static int b64_free(BIO *a)
     return 1;
 }

+static int b64_write_buffer(BIO *b, BIO *next, unsigned char *buf,
+    size_t *buf_len, size_t *buf_off)
+{
+    size_t n;
+    int i, dlen;
+
+    if (!ossl_assert(*buf_len >= *buf_off)) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+
+    n = *buf_len - *buf_off;
+    if (n > 0 && !ossl_assert(buf != NULL)) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+    while (n > 0) {
+        if (n > INT_MAX)
+            dlen = INT_MAX;
+        else
+            dlen = (int)n;
+        i = BIO_write(next, &(buf[*buf_off]), dlen);
+        if (i <= 0) {
+            BIO_copy_next_retry(b);
+            return i;
+        }
+        if (!ossl_assert((size_t)i <= n)) {
+            ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
+            return -1;
+        }
+        *buf_off += (size_t)i;
+        n -= (size_t)i;
+    }
+    *buf_off = 0;
+    *buf_len = 0;
+
+    return 1;
+}
+
+static int b64_write_buffer_int(BIO *b, BIO *next, unsigned char *buf,
+    int *buf_len, int *buf_off)
+{
+    int ret;
+    size_t len;
+    size_t off;
+
+    if (!ossl_assert(*buf_off >= 0)) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+
+    len = (size_t)*buf_len;
+    off = (size_t)*buf_off;
+    ret = b64_write_buffer(b, next, buf, &len, &off);
+
+    *buf_len = (int)len;
+    *buf_off = (int)off;
+    return ret;
+}
+
+static int b64_write_pending(BIO_B64_CTX *ctx)
+{
+    return ctx->encoded_len != ctx->encoded_off
+        || ctx->buf_len != ctx->buf_off;
+}
+
 /*
  * Unless `BIO_FLAGS_BASE64_NO_NL` is set, this BIO ignores leading lines that
  * aren't exclusively composed of valid Base64 characters (followed by <CRLF>
@@ -141,6 +209,8 @@ static int b64_read(BIO *b, char *out, int outl)
         ctx->buf_len = 0;
         ctx->buf_off = 0;
         ctx->tmp_len = 0;
+        ctx->encoded_len = 0;
+        ctx->encoded_off = 0;
         EVP_DecodeInit(ctx->base64);
     }

@@ -330,7 +400,6 @@ static int b64_read(BIO *b, char *out, int outl)
 static int b64_write(BIO *b, const char *in, int inl)
 {
     int ret = 0;
-    int n;
     int i;
     BIO_B64_CTX *ctx;
     BIO *next;
@@ -350,6 +419,8 @@ static int b64_write(BIO *b, const char *in, int inl)
         ctx->buf_len = 0;
         ctx->buf_off = 0;
         ctx->tmp_len = 0;
+        ctx->encoded_len = 0;
+        ctx->encoded_off = 0;
         EVP_EncodeInit(ctx->base64);
         if (BIO_get_flags(b) & BIO_FLAGS_BASE64_NO_NL)
             evp_encode_ctx_set_flags(ctx->base64, EVP_ENCODE_CTX_NO_NEWLINES);
@@ -366,27 +437,26 @@ static int b64_write(BIO *b, const char *in, int inl)
         ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
         return -1;
     }
-    n = ctx->buf_len - ctx->buf_off;
-    while (n > 0) {
-        i = BIO_write(next, &(ctx->buf[ctx->buf_off]), n);
-        if (i <= 0) {
-            BIO_copy_next_retry(b);
-            return i;
-        }
-        ctx->buf_off += i;
-        if (!ossl_assert(ctx->buf_off <= (int)sizeof(ctx->buf))) {
-            ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
-            return -1;
-        }
-        if (!ossl_assert(ctx->buf_len >= ctx->buf_off)) {
-            ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
-            return -1;
-        }
-        n -= i;
+    if (!ossl_assert(ctx->encoded_len >= ctx->encoded_off)) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+    if (!ossl_assert(ctx->encoded_len <= ctx->encoded_buf_len)) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+
+    i = b64_write_buffer(b, next, ctx->encoded_buf, &ctx->encoded_len,
+        &ctx->encoded_off);
+    if (i <= 0)
+        return i;
+    i = b64_write_buffer_int(b, next, ctx->buf, &ctx->buf_len, &ctx->buf_off);
+    if (i <= 0)
+        return i;
+    if (!ossl_assert(ctx->buf_off <= (int)sizeof(ctx->buf))) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
+        return -1;
     }
-    /* at this point all pending data has been written */
-    ctx->buf_off = 0;
-    ctx->buf_len = 0;

     if (in == NULL || inl <= 0)
         return 0;
@@ -400,6 +470,8 @@ static int b64_write(BIO *b, const char *in, int inl)

     if (ctx->encoded_buf == NULL || encoded_length > ctx->encoded_buf_len) {
         OPENSSL_free(ctx->encoded_buf);
+        ctx->encoded_buf = NULL;
+        ctx->encoded_buf_len = 0;
         ctx->encoded_buf = OPENSSL_malloc(encoded_length);
         if (ctx->encoded_buf == NULL) {
             ERR_raise(ERR_LIB_BIO, ERR_R_MALLOC_FAILURE);
@@ -419,10 +491,19 @@ static int b64_write(BIO *b, const char *in, int inl)
             (unsigned char *)in, inl)) {
         return -1;
     }
+    /*
+     * The encoder state has consumed the input.  Keep any unwritten encoded
+     * output pending so a later retry or flush can complete it.
+     */
     ret += inl;
-    i = BIO_write(next, encoded, n_bytes_enc);
-    if (i <= 0)
-        BIO_copy_next_retry(b);
+    ctx->encoded_len = (size_t)n_bytes_enc;
+    ctx->encoded_off = 0;
+    /*
+     * Try to write it now, but the input is consumed into encoder state, so
+     * report it as written even if downstream cannot accept all encoded output.
+     */
+    (void)b64_write_buffer(b, next, ctx->encoded_buf, &ctx->encoded_len,
+        &ctx->encoded_off);
     return ret;
 }

@@ -447,6 +528,8 @@ static long b64_ctrl(BIO *b, int cmd, long num, void *ptr)
         ctx->cont = 1;
         ctx->start = 1;
         ctx->encode = B64_NONE;
+        ctx->encoded_len = 0;
+        ctx->encoded_off = 0;
         ret = BIO_ctrl(next, cmd, num, ptr);
         break;
     case BIO_CTRL_EOF: /* More to read */
@@ -456,11 +539,16 @@ static long b64_ctrl(BIO *b, int cmd, long num, void *ptr)
             ret = BIO_ctrl(next, cmd, num, ptr);
         break;
     case BIO_CTRL_WPENDING: /* More to write in buffer */
+        if (!ossl_assert(ctx->encoded_len >= ctx->encoded_off)) {
+            ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
+            return -1;
+        }
         if (!ossl_assert(ctx->buf_len >= ctx->buf_off)) {
             ERR_raise(ERR_LIB_BIO, ERR_R_INTERNAL_ERROR);
             return -1;
         }
-        ret = ctx->buf_len - ctx->buf_off;
+        ret = (long)(ctx->encoded_len - ctx->encoded_off)
+            + (ctx->buf_len - ctx->buf_off);
         if (ret == 0 && ctx->encode != B64_NONE
             && EVP_ENCODE_CTX_num(ctx->base64) != 0)
             ret = 1;
@@ -480,9 +568,9 @@ static long b64_ctrl(BIO *b, int cmd, long num, void *ptr)
         if (ctx->encode == B64_ENCODE) {
             /* do a final write */
         again:
-            while (ctx->buf_len != ctx->buf_off) {
+            while (b64_write_pending(ctx)) {
                 i = b64_write(b, NULL, 0);
-                if (i < 0)
+                if (i < 0 || (i == 0 && b64_write_pending(ctx)))
                     return i;
             }
             if (EVP_ENCODE_CTX_num(ctx->base64) != 0) {
diff --git a/test/bio_base64_test.c b/test/bio_base64_test.c
index 9d9ef4dfa0..2ef639e45f 100644
--- a/test/bio_base64_test.c
+++ b/test/bio_base64_test.c
@@ -50,6 +50,159 @@ static unsigned linelengths[] = {
 };
 static unsigned wscnts[] = { 0, 1, 2, 4, 8, 16, 0xFFFF };

+#define B64_WRITE_INJECT_RETRY_NEG 0
+#define B64_WRITE_INJECT_RETRY_ZERO 1
+#define B64_WRITE_INJECT_SHORT 2
+#define B64_WRITE_INJECT_SHORT_THEN_RETRY 3
+#define B64_WRITE_INJECT_NONE (-1)
+#define B64_WRITE_INJECT_AFTER_SHORT_RETRY (-2)
+#define B64_WRITE_INJECT_COUNT 4
+#define B64_WRITE_TEST_LINE_INPUT_LEN 48
+#define B64_WRITE_TEST_INPUT_LEN_ALIGNED 96
+#define B64_WRITE_TEST_INPUT_LEN_TAIL 97
+#define B64_WRITE_TEST_INPUT_COUNT 2
+#define B64_WRITE_TEST_SECOND_INPUT_LEN 49
+#define B64_WRITE_TEST_SHORT_LEN 5
+#define B64_WRITE_SCENARIO_FLUSH_ONLY 0
+#define B64_WRITE_SCENARIO_SECOND_WRITE_DRAINS 1
+#define B64_WRITE_SCENARIO_SECOND_WRITE_INJECTS 2
+#define B64_WRITE_SCENARIO_COUNT 3
+
+typedef struct {
+    int inject;
+} b64_write_test_data;
+
+static BIO_METHOD *b64_write_test_method = NULL;
+static const int b64_write_test_input_lens[B64_WRITE_TEST_INPUT_COUNT] = {
+    B64_WRITE_TEST_INPUT_LEN_ALIGNED,
+    B64_WRITE_TEST_INPUT_LEN_TAIL
+};
+
+static int b64_write_test_update_len(int inl, int no_nl)
+{
+    int update_inl = inl - inl % B64_WRITE_TEST_LINE_INPUT_LEN;
+    int ret = update_inl / 3 * 4;
+
+    if (!no_nl)
+        ret += update_inl / B64_WRITE_TEST_LINE_INPUT_LEN;
+    return ret;
+}
+
+static int b64_write_test_update_len_since(int previous_inl, int inl,
+    int no_nl)
+{
+    return b64_write_test_update_len(previous_inl + inl, no_nl)
+        - b64_write_test_update_len(previous_inl, no_nl);
+}
+
+static int b64_write_test_final_pending(int inl)
+{
+    return inl % B64_WRITE_TEST_LINE_INPUT_LEN != 0;
+}
+
+static int b64_write_test_new(BIO *bio)
+{
+    b64_write_test_data *data = OPENSSL_zalloc(sizeof(*data));
+
+    if (data == NULL)
+        return 0;
+
+    BIO_set_data(bio, data);
+    BIO_set_init(bio, 1);
+    return 1;
+}
+
+static int b64_write_test_free(BIO *bio)
+{
+    b64_write_test_data *data = BIO_get_data(bio);
+
+    OPENSSL_free(data);
+    BIO_set_data(bio, NULL);
+    BIO_set_init(bio, 0);
+    return 1;
+}
+
+static int b64_write_test_write(BIO *bio, const char *in, int inl)
+{
+    b64_write_test_data *data = BIO_get_data(bio);
+    BIO *next = BIO_next(bio);
+    int ret;
+
+    BIO_clear_retry_flags(bio);
+    if (data == NULL || next == NULL)
+        return 0;
+
+    switch (data->inject) {
+    case B64_WRITE_INJECT_RETRY_NEG:
+    case B64_WRITE_INJECT_AFTER_SHORT_RETRY:
+        data->inject = B64_WRITE_INJECT_NONE;
+        BIO_set_retry_write(bio);
+        return -1;
+
+    case B64_WRITE_INJECT_RETRY_ZERO:
+        data->inject = B64_WRITE_INJECT_NONE;
+        BIO_set_retry_write(bio);
+        return 0;
+
+    case B64_WRITE_INJECT_SHORT:
+        data->inject = B64_WRITE_INJECT_NONE;
+        if (inl > B64_WRITE_TEST_SHORT_LEN)
+            inl = B64_WRITE_TEST_SHORT_LEN;
+        break;
+
+    case B64_WRITE_INJECT_SHORT_THEN_RETRY:
+        data->inject = B64_WRITE_INJECT_AFTER_SHORT_RETRY;
+        if (inl > B64_WRITE_TEST_SHORT_LEN)
+            inl = B64_WRITE_TEST_SHORT_LEN;
+        break;
+    }
+
+    ret = BIO_write(next, in, inl);
+    BIO_copy_next_retry(bio);
+    return ret;
+}
+
+static long b64_write_test_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+    BIO *next = BIO_next(bio);
+    long ret;
+
+    if (next == NULL)
+        return 0;
+
+    BIO_clear_retry_flags(bio);
+    ret = BIO_ctrl(next, cmd, num, ptr);
+    BIO_copy_next_retry(bio);
+    return ret;
+}
+
+static const BIO_METHOD *bio_f_b64_write_test(void)
+{
+    int type;
+
+    if (b64_write_test_method == NULL) {
+        type = BIO_get_new_index();
+        if (type == -1)
+            return NULL;
+
+        if ((b64_write_test_method = BIO_meth_new(type | BIO_TYPE_FILTER, "b64 write test")) == NULL
+            || !BIO_meth_set_write(b64_write_test_method,
+                b64_write_test_write)
+            || !BIO_meth_set_ctrl(b64_write_test_method,
+                b64_write_test_ctrl)
+            || !BIO_meth_set_create(b64_write_test_method,
+                b64_write_test_new)
+            || !BIO_meth_set_destroy(b64_write_test_method,
+                b64_write_test_free)) {
+            BIO_meth_free(b64_write_test_method);
+            b64_write_test_method = NULL;
+            return NULL;
+        }
+    }
+
+    return b64_write_test_method;
+}
+
 /* Generate `len` random octets */
 static unsigned char *genbytes(unsigned len)
 {
@@ -428,6 +581,195 @@ static int test_bio_base64_corner_case_bug(int idx)
     return generic_case(&t, 0);
 }

+static int base64_encode_reference(const char *in, int inl, int no_nl,
+    unsigned char **out, size_t *out_len)
+{
+    BIO *b64 = NULL;
+    BIO *mem = NULL;
+    BUF_MEM *bptr = NULL;
+    int ok = 0;
+
+    *out = NULL;
+    *out_len = 0;
+
+    if (!TEST_ptr(b64 = BIO_new(BIO_f_base64()))
+        || !TEST_ptr(mem = BIO_new(BIO_s_mem())))
+        goto done;
+
+    if (no_nl)
+        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+
+    if (!TEST_ptr_eq(BIO_push(b64, mem), b64)
+        || !TEST_int_eq(BIO_write(b64, in, inl), inl)
+        || !TEST_true(BIO_flush(b64)))
+        goto done;
+
+    BIO_get_mem_ptr(mem, &bptr);
+    if (!TEST_ptr(bptr))
+        goto done;
+
+    if (bptr->length > 0) {
+        if (!TEST_ptr(*out = OPENSSL_memdup(bptr->data, bptr->length)))
+            goto done;
+        *out_len = bptr->length;
+    }
+    ok = 1;
+
+done:
+    BIO_free_all(b64);
+    return ok;
+}
+
+static int b64_write_test_check_injected_state(BIO *b64, int mode,
+    int update_len,
+    int final_pending)
+{
+    switch (mode) {
+    case B64_WRITE_INJECT_RETRY_NEG:
+    case B64_WRITE_INJECT_RETRY_ZERO:
+        return TEST_true(BIO_should_retry(b64))
+            && TEST_int_eq(BIO_wpending(b64), update_len);
+
+    case B64_WRITE_INJECT_SHORT_THEN_RETRY:
+        return TEST_true(BIO_should_retry(b64))
+            && TEST_int_eq(BIO_wpending(b64),
+                update_len - B64_WRITE_TEST_SHORT_LEN);
+
+    case B64_WRITE_INJECT_SHORT:
+        return TEST_false(BIO_should_retry(b64))
+            && TEST_int_eq(BIO_wpending(b64), final_pending);
+
+    default:
+        TEST_error("Invalid base64 write injection mode: %d", mode);
+        return 0;
+    }
+}
+
+static int test_bio_base64_write_retry(int idx)
+{
+    char msg[B64_WRITE_TEST_INPUT_LEN_TAIL + B64_WRITE_TEST_SECOND_INPUT_LEN];
+    BIO *b64 = NULL;
+    BIO *inject = NULL;
+    BIO *mem = NULL;
+    BIO *membio = NULL;
+    BUF_MEM *bptr = NULL;
+    b64_write_test_data *data;
+    unsigned char *expected = NULL;
+    size_t expected_len = 0;
+    int mode;
+    int no_nl;
+    int scenario;
+    int input_idx;
+    int in_len;
+    int total_len;
+    int update_len;
+    int first_final_pending;
+    int q = idx;
+    int ret;
+    int ok = 0;
+
+    mode = quotrem(q, B64_WRITE_INJECT_COUNT, &q);
+    no_nl = quotrem(q, 2, &q);
+    scenario = quotrem(q, B64_WRITE_SCENARIO_COUNT, &q);
+    input_idx = quotrem(q, B64_WRITE_TEST_INPUT_COUNT, &q);
+    if (q != 0) {
+        fprintf(stderr, "Test index out of range: %d", idx);
+        return 0;
+    }
+    in_len = b64_write_test_input_lens[input_idx];
+    total_len = in_len;
+    if (scenario != B64_WRITE_SCENARIO_FLUSH_ONLY)
+        total_len += B64_WRITE_TEST_SECOND_INPUT_LEN;
+    update_len = b64_write_test_update_len_since(0, in_len, no_nl);
+    first_final_pending = b64_write_test_final_pending(in_len);
+    memset(msg, 'A', in_len);
+    memset(msg + in_len, 'B', B64_WRITE_TEST_SECOND_INPUT_LEN);
+
+    if (!TEST_true(base64_encode_reference(msg, total_len, no_nl,
+            &expected, &expected_len))
+        || !TEST_ptr(b64 = BIO_new(BIO_f_base64()))
+        || !TEST_ptr(inject = BIO_new(bio_f_b64_write_test()))
+        || !TEST_ptr(mem = BIO_new(BIO_s_mem())))
+        goto done;
+
+    data = BIO_get_data(inject);
+    if (!TEST_ptr(data))
+        goto done;
+    data->inject = scenario == B64_WRITE_SCENARIO_SECOND_WRITE_INJECTS
+        ? B64_WRITE_INJECT_NONE
+        : mode;
+
+    if (no_nl)
+        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+
+    membio = mem;
+    if (!TEST_ptr_eq(BIO_push(inject, mem), inject))
+        goto done;
+    mem = NULL;
+
+    if (!TEST_ptr_eq(BIO_push(b64, inject), b64))
+        goto done;
+    inject = NULL;
+
+    ret = BIO_write(b64, msg, in_len);
+    if (!TEST_int_eq(ret, in_len))
+        goto done;
+
+    if (scenario == B64_WRITE_SCENARIO_SECOND_WRITE_INJECTS) {
+        if (!TEST_false(BIO_should_retry(b64))
+            || !TEST_int_eq(BIO_wpending(b64), first_final_pending))
+            goto done;
+    } else if (!b64_write_test_check_injected_state(b64, mode, update_len,
+                   first_final_pending)) {
+        goto done;
+    }
+
+    /*
+     * Verify both second-write paths: pending encoded output is flushed before
+     * newly supplied input is accepted, and an injected second write with no
+     * prior encoded-output pending retains its own unwritten output.
+     */
+    if (scenario != B64_WRITE_SCENARIO_FLUSH_ONLY) {
+        if (scenario == B64_WRITE_SCENARIO_SECOND_WRITE_INJECTS)
+            data->inject = mode;
+
+        ret = BIO_write(b64, msg + in_len, B64_WRITE_TEST_SECOND_INPUT_LEN);
+        if (!TEST_int_eq(ret, B64_WRITE_TEST_SECOND_INPUT_LEN))
+            goto done;
+
+        if (scenario == B64_WRITE_SCENARIO_SECOND_WRITE_INJECTS) {
+            update_len = b64_write_test_update_len_since(in_len,
+                B64_WRITE_TEST_SECOND_INPUT_LEN,
+                no_nl);
+            if (!b64_write_test_check_injected_state(
+                    b64, mode, update_len,
+                    b64_write_test_final_pending(total_len)))
+                goto done;
+        } else if (!TEST_false(BIO_should_retry(b64))
+            || !TEST_int_eq(BIO_wpending(b64),
+                b64_write_test_final_pending(total_len))) {
+            goto done;
+        }
+    }
+
+    if (!TEST_true(BIO_flush(b64)))
+        goto done;
+
+    BIO_get_mem_ptr(membio, &bptr);
+    if (!TEST_ptr(bptr)
+        || !TEST_mem_eq(expected, expected_len, bptr->data, bptr->length))
+        goto done;
+
+    ok = 1;
+
+done:
+    BIO_free_all(b64);
+    BIO_free_all(inject);
+    BIO_free(mem);
+    OPENSSL_free(expected);
+    return ok;
+}
+
 #define MEM_CHK "QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB" \
                 "QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB" \
                 "QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB"
@@ -522,6 +864,15 @@ int setup_tests(void)
     numidx = 2 * 2;
     ADD_ALL_TESTS(test_bio_base64_corner_case_bug, numidx);

+    ADD_ALL_TESTS(test_bio_base64_write_retry,
+        B64_WRITE_INJECT_COUNT * 2 * B64_WRITE_SCENARIO_COUNT
+            * B64_WRITE_TEST_INPUT_COUNT);
     ADD_TEST(test_bio_base64_no_nl);
     return 1;
 }
+
+void cleanup_tests(void)
+{
+    BIO_meth_free(b64_write_test_method);
+    b64_write_test_method = NULL;
+}