Commit fd41cfc4a1 for openssl.org

commit fd41cfc4a1403b74c0541f19cf0d9762aa29124c
Author: Anish Aggarwal <ishitaaggarwal8@gmail.com>
Date:   Sat Oct 24 10:05:24 2026 +0100

    crypto/bio/bf_readbuff.c: fix signed integer overflow in readbuffer BIO resize

    The readbuffer filter BIO caches everything read from the underlying
    BIO so that BIO_tell() and BIO_seek() work on non-seekable sources.
    readbuffer_resize() computed the new buffer size in signed int.
    Since ibuf_off grows monotonically as data is cached, on a stream
    of 2 GiB or more the addition and the block rounding overflow, the
    comparison against ibuf_size then fails to trigger a reallocation,
    and readbuffer_read() writes past the end of the buffer.

    Do the size arithmetic in size_t instead, and fail the resize if
    the required size does not fit in the int fields used for the
    buffer size and offsets.

    Fixes: a30823c80f8c "Add new filter BIO BIO_f_readbuffer()"
    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Merge-date: Tue Nov  3 12:07:00 2026
    Merged-from: https://github.com/openssl/openssl/pull/33374

diff --git a/crypto/bio/bf_readbuff.c b/crypto/bio/bf_readbuff.c
index 36971a40c6..66454ce1a0 100644
--- a/crypto/bio/bf_readbuff.c
+++ b/crypto/bio/bf_readbuff.c
@@ -87,18 +87,26 @@ static int readbuffer_free(BIO *a)
 static int readbuffer_resize(BIO_F_BUFFER_CTX *ctx, int sz)
 {
     char *tmp;
+    size_t newsize;

-    /* Figure out how many blocks are required */
-    sz += (ctx->ibuf_off + DEFAULT_BUFFER_SIZE - 1);
-    sz = DEFAULT_BUFFER_SIZE * (sz / DEFAULT_BUFFER_SIZE);
+    /*
+     * Do the arithmetic in size_t and fail if the required size does
+     * not fit in the int size/offset fields: ibuf_off only grows as
+     * data is cached, so the int calculation overflowed near INT_MAX.
+     */
+    newsize = (size_t)ctx->ibuf_off + (size_t)sz;
+    if (newsize > (size_t)(INT_MAX - (DEFAULT_BUFFER_SIZE - 1)))
+        return 0;
+    newsize += DEFAULT_BUFFER_SIZE - 1;
+    newsize = DEFAULT_BUFFER_SIZE * (newsize / DEFAULT_BUFFER_SIZE);

     /* Resize if the buffer is not big enough */
-    if (sz > ctx->ibuf_size) {
-        tmp = OPENSSL_realloc(ctx->ibuf, sz);
+    if (newsize > (size_t)ctx->ibuf_size) {
+        tmp = OPENSSL_realloc(ctx->ibuf, newsize);
         if (tmp == NULL)
             return 0;
         ctx->ibuf = tmp;
-        ctx->ibuf_size = sz;
+        ctx->ibuf_size = (int)newsize;
     }
     return 1;
 }
diff --git a/test/bio_readbuffer_test.c b/test/bio_readbuffer_test.c
index 0dce588893..e568c43e18 100644
--- a/test/bio_readbuffer_test.c
+++ b/test/bio_readbuffer_test.c
@@ -7,6 +7,8 @@
  * https://www.openssl.org/source/license.html
  */

+#include <limits.h>
+#include <string.h>
 #include <openssl/bio.h>
 #include "testutil.h"

@@ -97,6 +99,38 @@ err:
     return ret;
 }

+/*
+ * A read that asks for more than the buffer size/offset fields can
+ * hold must fail, not overflow the size calculation in
+ * readbuffer_resize() and write past the end of the cache buffer.
+ */
+static int test_readbuffer_huge_read(void)
+{
+    int ret = 0;
+    BIO *mem = NULL, *readbuf = NULL, *chain = NULL;
+    char data[8192];
+    char out[sizeof(data)];
+
+    memset(data, 'A', sizeof(data));
+    if (!TEST_ptr(mem = BIO_new_mem_buf(data, (int)sizeof(data)))
+        || !TEST_ptr(readbuf = BIO_new(BIO_f_readbuffer())))
+        goto err;
+
+    chain = BIO_push(readbuf, mem);
+    readbuf = NULL;
+    mem = NULL;
+
+    if (!TEST_int_le(BIO_read(chain, out, INT_MAX), 0))
+        goto err;
+
+    ret = 1;
+err:
+    BIO_free_all(chain);
+    BIO_free_all(mem);
+    BIO_free(readbuf);
+    return ret;
+}
+
 typedef enum OPTION_choice {
     OPT_ERR = -1,
     OPT_EOF = 0,
@@ -128,5 +162,6 @@ int setup_tests(void)
     filename = test_get_argument(0);

     ADD_ALL_TESTS(test_readbuffer_file_bio, 3);
+    ADD_TEST(test_readbuffer_huge_read);
     return 1;
 }