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;
}