From: Igor Ustinov Date: Mon, 15 Dec 2025 14:13:42 +0000 (+0100) Subject: Fix of EOF and retry handling in BIO implementations X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f17230ae6c9f622418b3f45bb68ed9014b4b79c4;p=thirdparty%2Fopenssl.git 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 Reviewed-by: Tomas Mraz MergeDate: Thu Jan 22 17:12:37 2026 (Merged from https://github.com/openssl/openssl/pull/29401) --- diff --git a/crypto/asn1/bio_asn1.c b/crypto/asn1/bio_asn1.c index 610d767d431..6e155c204c2 100644 --- a/crypto/asn1/bio_asn1.c +++ b/crypto/asn1/bio_asn1.c @@ -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; diff --git a/crypto/bio/bf_buff.c b/crypto/bio/bf_buff.c index 3926d1552b9..ea6c72b0e09 100644 --- a/crypto/bio/bf_buff.c +++ b/crypto/bio/bf_buff.c @@ -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: diff --git a/crypto/bio/bf_lbuf.c b/crypto/bio/bf_lbuf.c index 1dfcac8f2ea..6c0e57f8a39 100644 --- a/crypto/bio/bf_lbuf.c +++ b/crypto/bio/bf_lbuf.c @@ -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) diff --git a/crypto/bio/bf_nbio.c b/crypto/bio/bf_nbio.c index 01138729b00..70cfd43b51c 100644 --- a/crypto/bio/bf_nbio.c +++ b/crypto/bio/bf_nbio.c @@ -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; } diff --git a/crypto/bio/bf_null.c b/crypto/bio/bf_null.c index 7add76a4ca5..faaf382dcef 100644 --- a/crypto/bio/bf_null.c +++ b/crypto/bio/bf_null.c @@ -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; } diff --git a/crypto/bio/bss_bio.c b/crypto/bio/bss_bio.c index 5470777f95e..26c26db71f0 100644 --- a/crypto/bio/bss_bio.c +++ b/crypto/bio/bss_bio.c @@ -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 */ diff --git a/crypto/bio/bss_conn.c b/crypto/bio/bss_conn.c index 87a93c4aed5..044ee07a508 100644 --- a/crypto/bio/bss_conn.c +++ b/crypto/bio/bss_conn.c @@ -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)) diff --git a/crypto/bio/bss_dgram.c b/crypto/bio/bss_dgram.c index 1f62ecf844b..8a58237ce0b 100644 --- a/crypto/bio/bss_dgram.c +++ b/crypto/bio/bss_dgram.c @@ -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); diff --git a/crypto/bio/bss_fd.c b/crypto/bio/bss_fd.c index eb0119d63c1..719df1886e4 100644 --- a/crypto/bio/bss_fd.c +++ b/crypto/bio/bss_fd.c @@ -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) { diff --git a/crypto/bio/bss_file.c b/crypto/bio/bss_file.c index 5d9300e74e2..289e8ff2d76 100644 --- a/crypto/bio/bss_file.c +++ b/crypto/bio/bss_file.c @@ -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 diff --git a/crypto/bio/bss_log.c b/crypto/bio/bss_log.c index 8f8e180468e..a0ffc997817 100644 --- a/crypto/bio/bss_log.c +++ b/crypto/bio/bss_log.c @@ -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, diff --git a/crypto/bio/bss_mem.c b/crypto/bio/bss_mem.c index 7d817fecd08..9e06c3b4486 100644 --- a/crypto/bio/bss_mem.c +++ b/crypto/bio/bss_mem.c @@ -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; diff --git a/crypto/bio/bss_sock.c b/crypto/bio/bss_sock.c index 11a8ce200c1..cf54ad28e84 100644 --- a/crypto/bio/bss_sock.c +++ b/crypto/bio/bss_sock.c @@ -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); diff --git a/crypto/evp/bio_b64.c b/crypto/evp/bio_b64.c index 20cf570fe35..0e79ab759db 100644 --- a/crypto/evp/bio_b64.c +++ b/crypto/evp/bio_b64.c @@ -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; diff --git a/crypto/evp/bio_enc.c b/crypto/evp/bio_enc.c index fc319d64255..0daa5bccc35 100644 --- a/crypto/evp/bio_enc.c +++ b/crypto/evp/bio_enc.c @@ -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; diff --git a/crypto/evp/bio_md.c b/crypto/evp/bio_md.c index a2d9afcf0e1..a3ae5923b80 100644 --- a/crypto/evp/bio_md.c +++ b/crypto/evp/bio_md.c @@ -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); diff --git a/doc/man3/BIO_ctrl.pod b/doc/man3/BIO_ctrl.pod index 2f9f3978e8f..357fd039eaa 100644 --- a/doc/man3/BIO_ctrl.pod +++ b/doc/man3/BIO_ctrl.pod @@ -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 close flag to B. B can take the value BIO_CLOSE or BIO_NOCLOSE. Typically BIO_CLOSE is used diff --git a/doc/man3/BIO_s_mem.pod b/doc/man3/BIO_s_mem.pod index f9385672985..dad5f1a2293 100644 --- a/doc/man3/BIO_s_mem.pod +++ b/doc/man3/BIO_s_mem.pod @@ -77,6 +77,11 @@ it will return zero and BIO_should_retry(b) will be false. If B is non zero then it will return B 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 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 to a pointer to the start of the memory BIOs data diff --git a/include/internal/bio.h b/include/internal/bio.h index 1b71b24f519..3602ab5eb1a 100644 --- a/include/internal/bio.h +++ b/include/internal/bio.h @@ -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. diff --git a/ssl/bio_ssl.c b/ssl/bio_ssl.c index 6e7b27546b5..5b7b05abfdc 100644 --- a/ssl/bio_ssl.c +++ b/ssl/bio_ssl.c @@ -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; diff --git a/test/bio_core_test.c b/test/bio_core_test.c index 47705a7fade..95bcaa5c0a4 100644 --- a/test/bio_core_test.c +++ b/test/bio_core_test.c @@ -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)) diff --git a/test/membio_test.c b/test/membio_test.c index eafd4d24fac..642dae48301 100644 --- a/test/membio_test.c +++ b/test/membio_test.c @@ -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 #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 diff --git a/test/sslapitest.c b/test/sslapitest.c index 80ac1f8fffb..1fb0477a8c5 100644 --- a/test/sslapitest.c +++ b/test/sslapitest.c @@ -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);