Fix of EOF and retry handling in BIO implementations

Added handling for negative length in read functions.

Fixes openssl/project#1739

Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
MergeDate: Thu Jan 22 17:12:37 2026
(Merged from https://github.com/openssl/openssl/pull/29401)
This commit is contained in:
Igor Ustinov 2025-12-15 15:13:42 +01:00 committed by Tomas Mraz
parent 045ca33cef
commit f17230ae6c
23 changed files with 279 additions and 35 deletions

View file

@ -252,11 +252,16 @@ static int asn1_bio_flush_ex(BIO *b, BIO_ASN1_BUF_CTX *ctx,
asn1_ps_func *cleanup, asn1_bio_state_t next)
{
int ret;
BIO *next_bio = BIO_next(b);
if (ctx->ex_len <= 0)
return 1;
if (next_bio == NULL)
return 0;
for (;;) {
ret = BIO_write(BIO_next(b), ctx->ex_buf + ctx->ex_pos, ctx->ex_len);
ret = BIO_write(next_bio, ctx->ex_buf + ctx->ex_pos, ctx->ex_len);
BIO_clear_retry_flags(b);
BIO_copy_next_retry(b);
if (ret <= 0)
break;
ctx->ex_len -= ret;
@ -291,10 +296,14 @@ static int asn1_bio_setup_ex(BIO *b, BIO_ASN1_BUF_CTX *ctx,
static int asn1_bio_read(BIO *b, char *in, int inl)
{
int ret = 0;
BIO *next = BIO_next(b);
if (next == NULL)
return 0;
return BIO_read(next, in, inl);
ret = BIO_read(next, in, inl);
BIO_clear_retry_flags(b);
BIO_copy_next_retry(b);
return ret;
}
static int asn1_bio_puts(BIO *b, const char *str)
@ -309,10 +318,14 @@ static int asn1_bio_puts(BIO *b, const char *str)
static int asn1_bio_gets(BIO *b, char *str, int size)
{
int ret = 0;
BIO *next = BIO_next(b);
if (next == NULL)
return 0;
return BIO_gets(next, str, size);
ret = BIO_gets(next, str, size);
BIO_clear_retry_flags(b);
BIO_copy_next_retry(b);
return ret;
}
static long asn1_bio_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp)
@ -368,6 +381,14 @@ static long asn1_bio_ctrl(BIO *b, int cmd, long arg1, void *arg2)
*(void **)arg2 = ctx->ex_arg;
break;
case BIO_C_DO_STATE_MACHINE:
if (next == NULL)
return 0;
BIO_clear_retry_flags(b);
ret = BIO_ctrl(next, cmd, arg1, arg2);
BIO_copy_next_retry(b);
break;
case BIO_CTRL_FLUSH:
if (next == NULL)
return 0;
@ -386,13 +407,24 @@ static long asn1_bio_ctrl(BIO *b, int cmd, long arg1, void *arg2)
return ret;
}
if (ctx->state == ASN1_STATE_DONE)
return BIO_ctrl(next, cmd, arg1, arg2);
else {
BIO_clear_retry_flags(b);
BIO_clear_retry_flags(b);
if (ctx->state == ASN1_STATE_DONE) {
ret = BIO_ctrl(next, cmd, arg1, arg2);
BIO_copy_next_retry(b);
return ret;
} else {
return 0;
}
case BIO_CTRL_EOF:
/*
* If there is no next BIO, BIO_read() returns 0, which means EOF.
* BIO_eof() should return 1 in this case.
*/
if (next == NULL)
return 1;
return BIO_ctrl(next, cmd, arg1, arg2);
default:
if (next == NULL)
return 0;

View file

@ -256,6 +256,12 @@ static long buffer_ctrl(BIO *b, int cmd, long num, void *ptr)
case BIO_CTRL_EOF:
if (ctx->ibuf_len > 0)
return 0;
/*
* If there is no ctx or no next BIO, BIO_read() returns 0, which means
* EOF, BIO_eof() should return 1 in this case.
*/
if (ctx == NULL || b->next_bio == NULL)
return 1;
ret = BIO_ctrl(b->next_bio, cmd, num, ptr);
break;
case BIO_CTRL_INFO:

View file

@ -287,6 +287,15 @@ static long linebuffer_ctrl(BIO *b, int cmd, long num, void *ptr)
if (BIO_set_write_buffer_size(dbio, ctx->obuf_size) <= 0)
ret = 0;
break;
case BIO_CTRL_EOF:
/*
* If there is no next BIO, BIO_read() returns 0, which means EOF.
* BIO_eof() should return 1 in this case.
*/
if (b->next_bio == NULL)
return 1;
ret = BIO_ctrl(b->next_bio, cmd, num, ptr);
break;
default:
if (b->next_bio == NULL)
return 0;
@ -305,9 +314,14 @@ static long linebuffer_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp)
static int linebuffer_gets(BIO *b, char *buf, int size)
{
int ret = 0;
if (b->next_bio == NULL)
return 0;
return BIO_gets(b->next_bio, buf, size);
ret = BIO_gets(b->next_bio, buf, size);
BIO_clear_retry_flags(b);
BIO_copy_next_retry(b);
return ret;
}
static int linebuffer_puts(BIO *b, const char *str)

View file

@ -149,9 +149,14 @@ static long nbiof_ctrl(BIO *b, int cmd, long num, void *ptr)
{
long ret;
/*
* If there is no next BIO, BIO_read() returns 0, which means EOF.
* BIO_eof() should return 1 in this case.
*/
if (b->next_bio == NULL)
return 0;
return cmd == BIO_CTRL_EOF;
switch (cmd) {
case BIO_CTRL_FLUSH:
case BIO_C_DO_STATE_MACHINE:
BIO_clear_retry_flags(b);
ret = BIO_ctrl(b->next_bio, cmd, num, ptr);
@ -176,14 +181,22 @@ static long nbiof_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp)
static int nbiof_gets(BIO *bp, char *buf, int size)
{
int ret = 0;
if (bp->next_bio == NULL)
return 0;
return BIO_gets(bp->next_bio, buf, size);
ret = BIO_gets(bp->next_bio, buf, size);
BIO_clear_retry_flags(bp);
BIO_copy_next_retry(bp);
return ret;
}
static int nbiof_puts(BIO *bp, const char *str)
{
int ret = 0;
if (bp->next_bio == NULL)
return 0;
return BIO_puts(bp->next_bio, str);
ret = BIO_puts(bp->next_bio, str);
BIO_clear_retry_flags(bp);
BIO_copy_next_retry(bp);
return ret;
}

View file

@ -74,9 +74,14 @@ static long nullf_ctrl(BIO *b, int cmd, long num, void *ptr)
{
long ret;
/*
* If there is no next BIO, BIO_read() returns 0, which means EOF.
* BIO_eof() should return 1 in this case.
*/
if (b->next_bio == NULL)
return 0;
return cmd == BIO_CTRL_EOF;
switch (cmd) {
case BIO_CTRL_FLUSH:
case BIO_C_DO_STATE_MACHINE:
BIO_clear_retry_flags(b);
ret = BIO_ctrl(b->next_bio, cmd, num, ptr);
@ -100,14 +105,24 @@ static long nullf_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp)
static int nullf_gets(BIO *bp, char *buf, int size)
{
int ret = 0;
if (bp->next_bio == NULL)
return 0;
return BIO_gets(bp->next_bio, buf, size);
ret = BIO_gets(bp->next_bio, buf, size);
BIO_clear_retry_flags(bp);
BIO_copy_next_retry(bp);
return ret;
}
static int nullf_puts(BIO *bp, const char *str)
{
int ret = 0;
if (bp->next_bio == NULL)
return 0;
return BIO_puts(bp->next_bio, str);
ret = BIO_puts(bp->next_bio, str);
BIO_clear_retry_flags(bp);
BIO_copy_next_retry(bp);
return ret;
}

View file

@ -112,6 +112,9 @@ static int bio_read(BIO *bio, char *buf, int size_)
size_t rest;
struct bio_bio_st *b, *peer_b;
if (buf == NULL || size_ <= 0)
return 0;
BIO_clear_retry_flags(bio);
if (!bio->init)
@ -126,9 +129,6 @@ static int bio_read(BIO *bio, char *buf, int size_)
peer_b->request = 0; /* will be set in "retry_read" situation */
if (buf == NULL || size == 0)
return 0;
if (peer_b->len == 0) {
if (peer_b->closed)
return 0; /* writer has closed, and no data is left */

View file

@ -373,8 +373,9 @@ static int conn_read(BIO *b, char *out, int outl)
return ret;
}
if (out != NULL) {
if (out != NULL && outl > 0) {
clear_socket_error();
b->flags &= ~BIO_FLAGS_IN_EOF;
#ifndef OPENSSL_NO_KTLS
if (BIO_get_ktls_recv(b))
ret = ktls_read_record(b->num, out, outl);
@ -777,6 +778,7 @@ int conn_gets(BIO *bio, char *buf, int size)
}
clear_socket_error();
bio->flags &= ~BIO_FLAGS_IN_EOF;
while (size-- > 1) {
#ifndef OPENSSL_NO_KTLS
if (BIO_get_ktls_recv(bio))

View file

@ -427,7 +427,7 @@ static int dgram_read(BIO *b, char *out, int outl)
BIO_ADDR peer;
socklen_t len = sizeof(peer);
if (out != NULL) {
if (out != NULL && outl > 0) {
clear_socket_error();
BIO_ADDR_clear(&peer);
dgram_adjust_rcv_timeout(b);

View file

@ -114,8 +114,9 @@ static int fd_read(BIO *b, char *out, int outl)
{
int ret = 0;
if (out != NULL) {
if (out != NULL && outl > 0) {
clear_sys_error();
b->flags &= ~BIO_FLAGS_IN_EOF;
ret = (int)UP_read(b->num, out, outl);
BIO_clear_retry_flags(b);
if (ret <= 0) {

View file

@ -137,7 +137,7 @@ static int file_read(BIO *b, char *out, int outl)
{
int ret = 0;
if (b->init && (out != NULL)) {
if (b->init != 0 && out != NULL && outl > 0) {
if (b->flags & BIO_FLAGS_UPLINK_INTERNAL)
ret = (int)UP_fread(out, 1, outl, b->ptr);
else

View file

@ -91,10 +91,10 @@ static const BIO_METHOD methods_slg = {
"syslog",
bwrite_conv,
slg_write,
NULL, /* slg_write_old, */
NULL, /* slg_read, */
NULL, /* slg_read */
NULL, /* slg_read_old */
slg_puts,
NULL,
NULL, /* slg_gets */
slg_ctrl,
slg_new,
slg_free,

View file

@ -125,6 +125,7 @@ static int mem_init(BIO *bi, unsigned long flags)
bi->shutdown = 1;
bi->init = 1;
bi->num = -1;
bi->flags |= BIO_FLAGS_MEM_LEGACY_EOF;
bi->ptr = (char *)bb;
return 1;
}
@ -288,10 +289,14 @@ static long mem_ctrl(BIO *b, int cmd, long num, void *ptr)
ret = -1;
break;
case BIO_CTRL_EOF:
ret = (long)(bm->length == 0);
if (b->num == 0 || (b->flags & BIO_FLAGS_MEM_LEGACY_EOF) != 0)
ret = (long)(bm->length == 0);
else
ret = 0;
break;
case BIO_C_SET_BUF_MEM_EOF_RETURN:
b->num = (int)num;
b->flags &= ~BIO_FLAGS_MEM_LEGACY_EOF;
break;
case BIO_CTRL_INFO:
ret = (long)bm->length;

View file

@ -106,8 +106,9 @@ static int sock_read(BIO *b, char *out, int outl)
{
int ret = 0;
if (out != NULL) {
if (out != NULL && outl > 0) {
clear_socket_error();
b->flags &= ~BIO_FLAGS_IN_EOF;
#ifndef OPENSSL_NO_KTLS
if (BIO_get_ktls_recv(b))
ret = ktls_read_record(b->num, out, outl);

View file

@ -323,7 +323,6 @@ static int b64_read(BIO *b, char *out, int outl)
outl -= i;
out += i;
}
/* BIO_clear_retry_flags(b); */
BIO_copy_next_retry(b);
return ret == 0 ? ret_code : ret;
}
@ -431,8 +430,12 @@ static long b64_ctrl(BIO *b, int cmd, long num, void *ptr)
ctx = (BIO_B64_CTX *)BIO_get_data(b);
next = BIO_next(b);
/*
* If there is no ctx or no next BIO, BIO_read() returns 0, which means EOF.
* BIO_eof() should return 1 in this case.
*/
if (ctx == NULL || next == NULL)
return 0;
return cmd == BIO_CTRL_EOF;
switch (cmd) {
case BIO_CTRL_RESET:
@ -485,6 +488,7 @@ static long b64_ctrl(BIO *b, int cmd, long num, void *ptr)
}
}
/* Finally flush the underlying BIO */
BIO_clear_retry_flags(b);
ret = BIO_ctrl(next, cmd, num, ptr);
BIO_copy_next_retry(b);
break;

View file

@ -306,8 +306,12 @@ static long enc_ctrl(BIO *b, int cmd, long num, void *ptr)
ctx = BIO_get_data(b);
next = BIO_next(b);
/*
* If there is no ctx, BIO_read() returns 0, which means EOF.
* BIO_eof() should return 1 in this case.
*/
if (ctx == NULL)
return 0;
return cmd == BIO_CTRL_EOF;
switch (cmd) {
case BIO_CTRL_RESET:
@ -322,7 +326,11 @@ static long enc_ctrl(BIO *b, int cmd, long num, void *ptr)
if (ctx->cont <= 0)
ret = 1;
else
ret = BIO_ctrl(next, cmd, num, ptr);
/*
* If there is no next BIO, BIO_read() returns 0, which means EOF.
* BIO_eof() should return 1 in this case.
*/
ret = (next == NULL) ? 1 : BIO_ctrl(next, cmd, num, ptr);
break;
case BIO_CTRL_WPENDING:
ret = ctx->buf_len - ctx->buf_off;

View file

@ -167,12 +167,22 @@ static long md_ctrl(BIO *b, int cmd, long num, void *ptr)
else
ret = 0;
break;
case BIO_CTRL_EOF:
/*
* If there is no ctx or no next BIO, BIO_read() returns 0, which means
* EOF, BIO_eof() should return 1 in this case.
*/
if (ctx == NULL || next == NULL)
ret = 1;
else
ret = BIO_ctrl(next, cmd, num, ptr);
break;
case BIO_CTRL_FLUSH:
case BIO_C_DO_STATE_MACHINE:
BIO_clear_retry_flags(b);
ret = BIO_ctrl(next, cmd, num, ptr);
BIO_copy_next_retry(b);
break;
case BIO_C_SET_MD:
md = ptr;
ret = EVP_DigestInit_ex(ctx, md, NULL);

View file

@ -65,8 +65,11 @@ BIO_tell() returns the current file position of a file related BIO.
BIO_flush() normally writes out any internally buffered data, in some
cases it is used to signal EOF and that no more data will be written.
BIO_eof() returns 1 if the BIO has read EOF, the precise meaning of
"EOF" varies according to the BIO type.
BIO_eof() returns 1 if the BIO has reached end-of-file as a result of
the most recent read operation. The precise meaning of "EOF" varies
according to the BIO type. The function reports the result of the
previous read attempt and does not update this state based on subsequent
operations.
BIO_set_close() sets the BIO B<b> close flag to B<flag>. B<flag> can
take the value BIO_CLOSE or BIO_NOCLOSE. Typically BIO_CLOSE is used

View file

@ -77,6 +77,11 @@ it will return zero and BIO_should_retry(b) will be false. If B<v> is non
zero then it will return B<v> when it is empty and it will set the read retry
flag (that is BIO_read_retry(b) is true). To avoid ambiguity with a normal
positive return value B<v> should be set to a negative value, typically -1.
The default behaviour for read-only BIOs is as if BIO_set_mem_eof_return(0)
were called. The default behaviour for read-write BIOs is special: BIO_eof()
returns EOF for an empty buffer, while the BIO_read() behaviour remains
identical to the case BIO_set_mem_eof_return(-1). This default behaviour
is maintained for backward compatibility.
Calling this macro will fail for datagram mem BIOs.
BIO_get_mem_data() sets *B<pp> to a pointer to the start of the memory BIOs data

View file

@ -44,6 +44,12 @@ int bread_conv(BIO *bio, char *data, size_t datal, size_t *read);
#define BIO_CTRL_CLEAR_KTLS_TX_CTRL_MSG 75
#define BIO_CTRL_SET_KTLS_TX_ZEROCOPY_SENDFILE 90
/*
* This is used with memory BIOs:
* BIO_FLAGS_MEM_LEGACY_EOF means legacy behaviour of BIO_eof()
*/
#define BIO_FLAGS_MEM_LEGACY_EOF 0x1000
/*
* This is used with socket BIOs:
* BIO_FLAGS_KTLS_TX means we are using ktls with this BIO for sending.

View file

@ -106,6 +106,7 @@ static int ssl_read(BIO *b, char *buf, size_t size, size_t *readbytes)
ssl = sb->ssl;
BIO_clear_retry_flags(b);
BIO_clear_flags(b, BIO_FLAGS_IN_EOF);
ret = ssl_read_internal(ssl, buf, size, readbytes);
@ -150,9 +151,11 @@ static int ssl_read(BIO *b, char *buf, size_t size, size_t *readbytes)
BIO_set_retry_special(b);
retry_reason = BIO_RR_CONNECT;
break;
case SSL_ERROR_ZERO_RETURN:
BIO_set_flags(b, BIO_FLAGS_IN_EOF);
break;
case SSL_ERROR_SYSCALL:
case SSL_ERROR_SSL:
case SSL_ERROR_ZERO_RETURN:
default:
break;
}
@ -410,6 +413,11 @@ static long ssl_ctrl(BIO *b, int cmd, long num, void *ptr)
if (!SSL_get_wpoll_descriptor(ssl, (BIO_POLL_DESCRIPTOR *)ptr))
ret = 0;
break;
case BIO_CTRL_EOF:
ret = BIO_test_flags(b, BIO_FLAGS_IN_EOF)
? 1
: BIO_ctrl(SSL_get_rbio(ssl), cmd, num, ptr);
break;
default:
ret = BIO_ctrl(SSL_get_rbio(ssl), cmd, num, ptr);
break;

View file

@ -84,6 +84,7 @@ static int test_bio_core(void)
|| !TEST_ptr((cbio = BIO_new_from_core_bio(libctx, &corebio))))
goto err;
BIO_set_mem_eof_return(cbio, 0);
if (!TEST_int_gt(BIO_puts(corebio.bio, msg), 0)
/* Test a ctrl via BIO_eof */
|| !TEST_false(BIO_eof(cbio))

View file

@ -1,5 +1,5 @@
/*
* Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
* Copyright 2022-2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
@ -10,6 +10,41 @@
#include <openssl/bio.h>
#include "testutil.h"
static int test_eof(void)
{
BIO *bio = BIO_new(BIO_s_mem());
char buf[1];
int testresult = 0;
if (!TEST_ptr(bio))
goto err;
/* legacy default behaviour */
if (!TEST_int_eq(BIO_read(bio, buf, 1), -1)
|| !TEST_true(BIO_eof(bio))
|| !TEST_true(BIO_should_retry(bio)))
goto err;
/* manually set eof behaviour */
BIO_set_mem_eof_return(bio, 0);
if (!TEST_int_eq(BIO_read(bio, buf, 1), 0)
|| !TEST_true(BIO_eof(bio))
|| !TEST_false(BIO_should_retry(bio)))
goto err;
/* manually set retry behaviour */
BIO_set_mem_eof_return(bio, -1);
if (!TEST_int_eq(BIO_read(bio, buf, 1), -1)
|| !TEST_false(BIO_eof(bio))
|| !TEST_true(BIO_should_retry(bio)))
goto err;
testresult = 1;
err:
BIO_free(bio);
return testresult;
}
#ifndef OPENSSL_NO_DGRAM
static int test_dgram(void)
{
@ -118,6 +153,7 @@ int setup_tests(void)
return 0;
}
ADD_TEST(test_eof);
#ifndef OPENSSL_NO_DGRAM
ADD_TEST(test_dgram);
#endif

View file

@ -9460,6 +9460,79 @@ end:
return testresult;
}
/*
* Test that SSL BIO reports EOF correctly
* Test 0: EOF after peer close_notify has been received
* Test 1: EOF after the underlying BIO reports EOF
*/
static int test_ssl_bio_eof(int tst)
{
SSL_CTX *cctx = NULL, *sctx = NULL;
SSL *clientssl = NULL, *serverssl = NULL;
int testresult = 0;
BIO *clientbio = BIO_new(BIO_f_ssl());
char buf[1];
size_t nbytes = 0;
if (!TEST_ptr(clientbio))
goto end;
if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
TLS_client_method(),
0, 0,
&sctx, &cctx, cert, privkey)))
goto end;
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, NULL,
NULL)))
goto end;
if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE)))
goto end;
if (!TEST_int_eq(BIO_set_ssl(clientbio, clientssl, BIO_NOCLOSE), 1))
goto end;
/* Check we don't receive EOF in normal flow */
if (!TEST_true(SSL_write_ex(serverssl, "1", 1, &nbytes)))
goto end;
if (!TEST_true(BIO_read_ex(clientbio, buf, 1, &nbytes))
|| !TEST_false(BIO_eof(clientbio)))
goto end;
/* Absence of data doesn't cause the EOF state */
if (!TEST_false(BIO_read_ex(clientbio, buf, 1, &nbytes))
|| !TEST_false(BIO_eof(clientbio)))
goto end;
/* In test 0 send close_notify from the server */
if (tst == 0)
SSL_shutdown(serverssl);
/* In test 1 force the underlying BIO_s_mem to report EOF at end of data */
if (tst == 1) {
BIO *rbio = SSL_get_rbio(clientssl);
if (!TEST_ptr(rbio)
|| !TEST_int_eq(BIO_method_type(rbio), BIO_TYPE_MEM))
goto end;
if (!TEST_true(BIO_set_mem_eof_return(rbio, 0)))
goto end;
}
/* Now client should observe EOF on the SSL BIO */
if (!TEST_int_eq(BIO_read_ex(clientbio, buf, 1, &nbytes), 0)
|| !TEST_true(BIO_eof(clientbio)))
goto end;
testresult = 1;
end:
BIO_free_all(clientbio);
SSL_free(serverssl);
SSL_free(clientssl);
SSL_CTX_free(sctx);
SSL_CTX_free(cctx);
return testresult;
}
#if !defined(OPENSSL_NO_TLS1_2) || !defined(OSSL_NO_USABLE_TLS1_3)
static int cert_cb_cnt;
@ -14046,6 +14119,7 @@ int setup_tests(void)
ADD_ALL_TESTS(test_ticket_callbacks, 20);
ADD_ALL_TESTS(test_shutdown, 7);
ADD_TEST(test_async_shutdown);
ADD_ALL_TESTS(test_ssl_bio_eof, 2);
ADD_ALL_TESTS(test_incorrect_shutdown, 2);
ADD_ALL_TESTS(test_cert_cb, 6);
ADD_ALL_TESTS(test_client_cert_cb, 2);