end-of-file state properly.
Related to openssl/project#1745
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
MergeDate: Thu Feb 12 08:34:31 2026
(Merged from https://github.com/openssl/openssl/pull/29290)
}
/*
- * This is essentially the same as BIO_read_ex() except that it allows
- * 0 or a negative value to indicate failure (retryable or not) in the return.
- * This is for compatibility with the old style BIO_read(), where existing code
- * may make assumptions about the return value that it might get.
+ * Internal BIO read function. Attempts to read dlen bytes from BIO b and
+ * places them in data. If any bytes were successfully read, then the number
+ * of bytes read is stored in readbytes.
+ * For compatibility with the old-style BIO_read() API, the function uses a
+ * return-value convention where a positive value indicates success,
+ * 0 indicates end-of-file, and a negative value indicates an error
+ * (including retryable errors).
+ * It also returns 0 if dlen==0.
*/
static int bio_read_intern(BIO *b, void *data, size_t dlen, size_t *readbytes)
{
if (ret > 0)
b->num_read += (uint64_t)*readbytes;
+ /*
+ * If method->bread() returned 0 when dlen>0, it can be either EOF or
+ * an error, and we should distinguish them
+ */
+ if (ret == 0 && dlen > 0 && BIO_eof(b) != 1)
+ ret = -1;
+
if (HAS_CALLBACK(b))
ret = (int)bio_call_callback(b, BIO_CB_READ | BIO_CB_RETURN, data,
dlen, 0, 0L, ret, readbytes);
size_t readbytes;
int ret;
- if (dlen < 0)
- return 0;
+ if (dlen < 0) {
+ ERR_raise(ERR_LIB_BIO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return -1;
+ }
ret = bio_read_intern(b, data, (size_t)dlen, &readbytes);
return ret;
}
+int BIO_eof(BIO *b)
+{
+ if ((b->flags & BIO_FLAGS_AUTO_EOF) != 0)
+ return 1;
+ return (int)BIO_ctrl(b, BIO_CTRL_EOF, 0, NULL);
+}
+
long BIO_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp)
{
long ret;
{
int ret;
+ if (datal == 0) {
+ *readbytes = 0;
+ return 1;
+ }
+
if (datal > INT_MAX)
datal = INT_MAX;
ret = bio->method->bread_old(bio, data, (int)datal);
- if (ret <= 0) {
+ bio->flags &= ~BIO_FLAGS_AUTO_EOF;
+ if (ret == 0) {
+ if (BIO_ctrl(bio, BIO_CTRL_EOF, 0, NULL) == 0)
+ bio->flags |= BIO_FLAGS_AUTO_EOF;
+ *readbytes = 0;
+ return 0;
+ }
+
+ if (ret < 0) {
*readbytes = 0;
return ret;
}
used for writing arbitrary length data to the BIO respectively. This function
will be called in response to the application calling BIO_write_ex() or
BIO_write(). The parameters for the function have the same meaning as for
-BIO_write_ex(). Older code may call BIO_meth_get_write() and
-BIO_meth_set_write() instead. Applications should not call both
-BIO_meth_set_write_ex() and BIO_meth_set_write() or call BIO_meth_get_write()
+BIO_write_ex() and it must return values as described for BIO_write_ex().
+
+Older code may call BIO_meth_get_write() and BIO_meth_set_write() instead
+to set an old-style write function. The parameters for the function have the
+same meaning as for BIO_write() and it must return values as described for
+BIO_write().
+
+Functions set by BIO_meth_set_write_ex() and BIO_meth_set_write() must call
+BIO_set_flags() to set the BIO_FLAGS_SHOULD_RETRY flag in relevant situations.
+
+Applications should not call both BIO_meth_set_write_ex() and
+BIO_meth_set_write() or call BIO_meth_get_write()
when the function was set with BIO_meth_set_write_ex().
BIO_meth_get_read_ex() and BIO_meth_set_read_ex() get and set the function used
for reading arbitrary length data from the BIO respectively. This function will
be called in response to the application calling BIO_read_ex() or BIO_read().
-The parameters for the function have the same meaning as for BIO_read_ex().
-Older code may call BIO_meth_get_read() and BIO_meth_set_read() instead.
+The parameters for the function have the same meaning as for BIO_read_ex()
+and it must return values as described for BIO_read_ex().
+The function must handle the end-of-file condition (if applicable) and return 0
+in this case.
+
+Older code may call BIO_meth_get_read() and BIO_meth_set_read() instead to
+set an old-style read function. The parameters for the function have the same
+meaning as for BIO_read() and it must return values as described for BIO_read().
+
+Functions set by BIO_meth_set_read_ex() and BIO_meth_set_read() must call
+BIO_set_flags() to set the BIO_FLAGS_SHOULD_RETRY flag in relevant situations.
+
Applications should not call both BIO_meth_set_read_ex() and BIO_meth_set_read()
or call BIO_meth_get_read() when the function was set with
BIO_meth_set_read_ex().
more information. This function will be called in response to the application
calling BIO_ctrl(). The parameters for the function have the same meaning as for
BIO_ctrl().
+If the concept of end-of-file is meaningful for a BIO and the read method is
+set using BIO_meth_set_read_ex(), the ctrl function must handle the BIO_CTRL_EOF
+command and return an appropriate value (1 if EOF has been reached, 0 if not,
+or a negative value on failure), at least immediately after a read operation.
+If the read method is set using BIO_meth_set_read(), handling of the
+BIO_CTRL_EOF command is not mandatory; however, if such handling is implemented,
+it must return 1 if the read function returned 0 when attempting to read a
+nonzero number of bytes.
BIO_meth_get_create() and BIO_meth_set_create() get and set the function used
for creating a new instance of the BIO respectively. This function will be
BIO_write_ex() returns 1 if no error was encountered writing data, 0 otherwise.
Requesting to write 0 bytes is not considered an error.
+BIO_read() returns the number of bytes read on success.
+A return value of 0 indicates that end-of-file was reached, or that a read
+of zero bytes was requested.
+A negative return value indicates an error condition.
+
BIO_write() returns -2 if the "write" operation is not implemented by the BIO
or -1 on other errors.
Otherwise it returns the number of bytes written.
If this bit is not set then the condition is treated as an error.
This flag is normally set by the B<BIO> implementation.
+=back
+
+The following flag can be used only with the B<base64 encoding BIO>.
+The result of using this flag with other BIOs is unpredictable.
+
+=over 4
+
=item B<BIO_FLAGS_BASE64_NO_NL>
When set on a base64 filter B<BIO> this flag disables the generation of
in the input. See also L<BIO_f_base64(3)>.
The flag has no effect on any other built-in B<BIO> types.
+=back
+
+The following flags can be used only with the B<memory buffer BIO> and
+B<secure memory buffer BIO>. The result of using these flags with other BIOs
+is unpredictable.
+
+=over 4
+
=item B<BIO_FLAGS_MEM_RDONLY>
When set on a memory B<BIO> this flag indicates that the underlying buffer is
=item B<BIO_FLAGS_IN_EOF>
-This flag may be used by a B<BIO> implementation to indicate that the end
-of the input stream has been reached. However, B<BIO> types are not
-required to use this flag to signal end-of-file conditions; they may rely
-on other mechanisms such as system calls or by querying the next B<BIO> in a
-chain. Applications must therefore not test this flag directly to
-determine whether EOF has been reached, and must use BIO_eof() instead.
+This flag is for internal use only. It should not be used outside BIO
+implementations.
=back
#define BIO_CTRL_CLEAR_KTLS_TX_CTRL_MSG 75
#define BIO_CTRL_SET_KTLS_TX_ZEROCOPY_SENDFILE 90
+/* Internal BIO flags */
+
+#define BIO_FLAGS_AUTO_EOF 0x80
+
/*
* This is used with memory BIOs:
* BIO_FLAGS_MEM_LEGACY_EOF means legacy behaviour of BIO_eof()
#define BIO_FLAGS_UPLINK 0
#endif
+/* the BIO FLAGS values 0x10 to 0x80 are reserved for internal use */
+
+/*
+ * BIO FLAGS in the range 0x0100..0x8000 are BIO-type specific.
+ * Their meaning is defined by the particular BIO implementation and
+ * is not shared across different BIO types. The same bit value may
+ * have a different meaning or no meaning at all in other BIOs.
+ * Such flags may be part of the public API or internal to the BIO.
+ */
+
+/* This is used with base64 BIO */
#define BIO_FLAGS_BASE64_NO_NL 0x100
/*
#define BIO_FLAGS_NONCLEAR_RST 0x400
#define BIO_FLAGS_IN_EOF 0x800
-/* the BIO FLAGS values 0x1000 to 0x8000 are reserved for internal KTLS flags */
-
typedef union bio_addr_st BIO_ADDR;
typedef struct bio_addrinfo_st BIO_ADDRINFO;
#define BIO_dup_state(b, ret) BIO_ctrl(b, BIO_CTRL_DUP, 0, (char *)(ret))
#define BIO_reset(b) (int)BIO_ctrl(b, BIO_CTRL_RESET, 0, NULL)
-#define BIO_eof(b) (int)BIO_ctrl(b, BIO_CTRL_EOF, 0, NULL)
#define BIO_set_close(b, c) (int)BIO_ctrl(b, BIO_CTRL_SET_CLOSE, (c), NULL)
#define BIO_get_close(b) (int)BIO_ctrl(b, BIO_CTRL_GET_CLOSE, 0, NULL)
#define BIO_pending(b) (int)BIO_ctrl(b, BIO_CTRL_PENDING, 0, NULL)
int BIO_puts(BIO *bp, const char *buf);
int BIO_indent(BIO *b, int indent, int max);
long BIO_ctrl(BIO *bp, int cmd, long larg, void *parg);
+int BIO_eof(BIO *b);
long BIO_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp);
void *BIO_ptr_ctrl(BIO *bp, int cmd, long larg);
long BIO_int_ctrl(BIO *bp, int cmd, long larg, int iarg);
#include "testutil.h"
-#define MAXCOUNT 5
+#define MAXCOUNT 7
static int my_param_count;
static BIO *my_param_b[MAXCOUNT];
static int my_param_oper[MAXCOUNT];
my_param_count = 0;
i = BIO_read(bio, buf, sizeof(buf));
if (!TEST_int_eq(i, 0)
- || !TEST_int_eq(my_param_count, 2)
+ || !TEST_int_eq(my_param_count, 6)
|| !TEST_ptr_eq(my_param_b[0], bio)
|| !TEST_int_eq(my_param_oper[0], BIO_CB_READ)
|| !TEST_ptr_eq(my_param_argp[0], buf)
|| !TEST_size_t_eq(my_param_len[0], sizeof(buf))
|| !TEST_long_eq(my_param_argl[0], 0L)
|| !TEST_int_eq((int)my_param_ret[0], 1)
- || !TEST_ptr_eq(my_param_b[1], bio)
- || !TEST_int_eq(my_param_oper[1], BIO_CB_READ | BIO_CB_RETURN)
- || !TEST_ptr_eq(my_param_argp[1], buf)
- || !TEST_size_t_eq(my_param_len[1], sizeof(buf))
- || !TEST_long_eq(my_param_argl[1], 0L)
- || !TEST_size_t_eq(my_param_processed[1], 0)
- || !TEST_int_eq((int)my_param_ret[1], 0))
+ || !TEST_ptr_eq(my_param_b[5], bio)
+ || !TEST_int_eq(my_param_oper[5], BIO_CB_READ | BIO_CB_RETURN)
+ || !TEST_ptr_eq(my_param_argp[5], buf)
+ || !TEST_size_t_eq(my_param_len[5], sizeof(buf))
+ || !TEST_long_eq(my_param_argl[5], 0L)
+ || !TEST_size_t_eq(my_param_processed[5], 0)
+ || !TEST_int_eq((int)my_param_ret[5], 0))
goto err;
my_param_count = 0;
my_param_count = 0;
i = BIO_read(bio, buf, sizeof(buf));
if (!TEST_int_eq(i, 0)
- || !TEST_int_eq(my_param_count, 2)
+ || !TEST_int_eq(my_param_count, 6)
|| !TEST_ptr_eq(my_param_b[0], bio)
|| !TEST_int_eq(my_param_oper[0], BIO_CB_READ)
|| !TEST_ptr_eq(my_param_argp[0], buf)
|| !TEST_int_eq(my_param_argi[0], sizeof(buf))
|| !TEST_long_eq(my_param_argl[0], 0L)
|| !TEST_long_eq(my_param_ret[0], 1L)
- || !TEST_ptr_eq(my_param_b[1], bio)
- || !TEST_int_eq(my_param_oper[1], BIO_CB_READ | BIO_CB_RETURN)
- || !TEST_ptr_eq(my_param_argp[1], buf)
- || !TEST_int_eq(my_param_argi[1], sizeof(buf))
- || !TEST_long_eq(my_param_argl[1], 0L)
- || !TEST_long_eq(my_param_ret[1], 0L))
+ || !TEST_ptr_eq(my_param_b[5], bio)
+ || !TEST_int_eq(my_param_oper[5], BIO_CB_READ | BIO_CB_RETURN)
+ || !TEST_ptr_eq(my_param_argp[5], buf)
+ || !TEST_int_eq(my_param_argi[5], sizeof(buf))
+ || !TEST_long_eq(my_param_argl[5], 0L)
+ || !TEST_long_eq(my_param_ret[5], 0L))
goto err;
my_param_count = 0;
--- /dev/null
+/*
+ * Copyright 2025 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
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/bio.h>
+#include "testutil.h"
+
+#define TEST_FLAG_EOF_BEHAVIOUR 0x1000
+
+static int bio_create(BIO *bio)
+{
+ BIO_set_init(bio, 1);
+ return 1;
+}
+
+static int bio_destroy(BIO *bio)
+{
+ BIO_set_init(bio, 0);
+ return 1;
+}
+
+/*
+ * Test1 & Test2 read callback (old style):
+ * returns 0 if TEST_FLAG_EOF_BEHAVIOUR is set, else -1.
+ */
+static int old_read_returns_0_or_minus1(BIO *bio, char *buf, int len)
+{
+ (void)buf;
+ (void)len;
+ return BIO_test_flags(bio, TEST_FLAG_EOF_BEHAVIOUR) ? 0 : -1;
+}
+
+/*
+ * Test3 read_ex callback (new style):
+ * does nothing, always returns 0, sets *readbytes to 0.
+ */
+static int new_read_ex_always_0(BIO *bio, char *buf, size_t len, size_t *readbytes)
+{
+ (void)bio;
+ (void)buf;
+ (void)len;
+ if (readbytes != NULL)
+ *readbytes = 0;
+ return 0;
+}
+
+/* Test1 ctrl: does nothing */
+static long ctrl_noop(BIO *bio, int cmd, long num, void *ptr)
+{
+ (void)bio;
+ (void)cmd;
+ (void)num;
+ (void)ptr;
+ return 0;
+}
+
+/* Test2 ctrl: BIO_CTRL_EOF always returns 1 */
+static long ctrl_eof_always_1(BIO *bio, int cmd, long num, void *ptr)
+{
+ (void)bio;
+ (void)num;
+ (void)ptr;
+ if (cmd == BIO_CTRL_EOF)
+ return 1;
+ return 0;
+}
+
+/* Test3 ctrl: BIO_CTRL_EOF returns 1 if TEST_FLAG_EOF_BEHAVIOUR is set */
+static long ctrl_eof_depends_on_flag(BIO *bio, int cmd, long num, void *ptr)
+{
+ (void)num;
+ (void)ptr;
+ if (cmd == BIO_CTRL_EOF)
+ return BIO_test_flags(bio, TEST_FLAG_EOF_BEHAVIOUR) ? 1 : 0;
+ return 0;
+}
+
+static BIO_METHOD *make_meth_oldread(long (*ctrl)(BIO *, int, long, void *),
+ const char *name)
+{
+ BIO_METHOD *meth = NULL;
+
+ if (!TEST_ptr(meth = BIO_meth_new(BIO_TYPE_SOURCE_SINK, name)))
+ goto err;
+ if (!TEST_int_eq(BIO_meth_set_read(meth, old_read_returns_0_or_minus1), 1))
+ goto err;
+ if (!TEST_int_eq(BIO_meth_set_ctrl(meth, ctrl), 1))
+ goto err;
+ if (!TEST_int_eq(BIO_meth_set_create(meth, bio_create), 1))
+ goto err;
+ if (!TEST_int_eq(BIO_meth_set_destroy(meth, bio_destroy), 1))
+ goto err;
+ return meth;
+
+err:
+ BIO_meth_free(meth);
+ return NULL;
+}
+
+static BIO_METHOD *make_meth_newreadex(long (*ctrl)(BIO *, int, long, void *),
+ const char *name)
+{
+ BIO_METHOD *meth = NULL;
+
+ if (!TEST_ptr(meth = BIO_meth_new(BIO_TYPE_SOURCE_SINK, name)))
+ goto err;
+ if (!TEST_int_eq(BIO_meth_set_read_ex(meth, new_read_ex_always_0), 1))
+ goto err;
+ if (!TEST_int_eq(BIO_meth_set_ctrl(meth, ctrl), 1))
+ goto err;
+ if (!TEST_int_eq(BIO_meth_set_create(meth, bio_create), 1))
+ goto err;
+ if (!TEST_int_eq(BIO_meth_set_destroy(meth, bio_destroy), 1))
+ goto err;
+ return meth;
+
+err:
+ BIO_meth_free(meth);
+ return NULL;
+}
+
+static int run_subtest(const char *label, BIO_METHOD *meth,
+ int set_flag, int use_read_ex,
+ int exp_read_ret, int exp_eof_ret)
+{
+ BIO *bio = NULL;
+ char b = 0;
+ int r, eofr;
+ size_t n;
+
+ if (!TEST_ptr(bio = BIO_new(meth)))
+ goto err;
+
+ if (set_flag)
+ BIO_set_flags(bio, TEST_FLAG_EOF_BEHAVIOUR);
+ else
+ BIO_clear_flags(bio, TEST_FLAG_EOF_BEHAVIOUR);
+
+ if (use_read_ex) {
+ r = BIO_read_ex(bio, &b, 1, &n);
+ if (!TEST_int_eq(r, exp_read_ret)) {
+ TEST_info("%s: BIO_read_ex ret=%d expected=%d", label, r, exp_read_ret);
+ goto err;
+ }
+ } else {
+ r = BIO_read(bio, &b, 1);
+ if (!TEST_int_eq(r, exp_read_ret)) {
+ TEST_info("%s: BIO_read ret=%d expected=%d", label, r, exp_read_ret);
+ goto err;
+ }
+ }
+
+ eofr = BIO_eof(bio);
+ if (!TEST_int_eq(eofr, exp_eof_ret)) {
+ TEST_info("%s: BIO_eof ret=%d expected=%d", label, eofr, exp_eof_ret);
+ goto err;
+ }
+
+ BIO_free(bio);
+ return 1;
+
+err:
+ BIO_free(bio);
+ return 0;
+}
+
+static int old_style_read_without_eof_ctrl(void)
+{
+ int ok = 1;
+ BIO_METHOD *meth = NULL;
+
+ if (!TEST_ptr(meth = make_meth_oldread(ctrl_noop,
+ "Old-style read without eof ctrl")))
+ return 0;
+
+ ok &= run_subtest("BIO_read, eof", meth, 1, 0, 0, 1);
+ ok &= run_subtest("BIO_read_ex, eof", meth, 1, 1, 0, 1);
+ ok &= run_subtest("BIO_read, error", meth, 0, 0, -1, 0);
+ ok &= run_subtest("BIO_read_ex, error", meth, 0, 1, 0, 0);
+
+ BIO_meth_free(meth);
+ return ok;
+}
+
+static int old_style_read_with_eof_ctrl(void)
+{
+ int ok = 1;
+ BIO_METHOD *meth = NULL;
+
+ if (!TEST_ptr(meth = make_meth_oldread(ctrl_eof_always_1,
+ "Old-stype read with eof ctrl")))
+ return 0;
+
+ ok &= run_subtest("BIO_read, eof", meth, 1, 0, 0, 1);
+ ok &= run_subtest("BIO_read_ex, eof", meth, 1, 1, 0, 1);
+ ok &= run_subtest("BIO_read, error", meth, 0, 0, -1, 1);
+ ok &= run_subtest("BIO_read_ex, error", meth, 0, 1, 0, 1);
+
+ BIO_meth_free(meth);
+ return ok;
+}
+
+static int new_style_read_ex(void)
+{
+ int ok = 1;
+ BIO_METHOD *meth = NULL;
+
+ if (!TEST_ptr(meth = make_meth_newreadex(ctrl_eof_depends_on_flag,
+ "New-style read_ex")))
+ return 0;
+
+ ok &= run_subtest("BIO_read, eof", meth, 1, 0, 0, 1);
+ ok &= run_subtest("BIO_read_ex, eof", meth, 1, 1, 0, 1);
+ ok &= run_subtest("BIO_read, error", meth, 0, 0, -1, 0);
+ ok &= run_subtest("BIO_read_ex, error", meth, 0, 1, 0, 0);
+
+ BIO_meth_free(meth);
+ return ok;
+}
+
+int setup_tests(void)
+{
+ ADD_TEST(old_style_read_without_eof_ctrl);
+ ADD_TEST(old_style_read_with_eof_ctrl);
+ ADD_TEST(new_style_read_ex);
+ return 1;
+}
fips_version_test x509_test hpke_test pairwise_fail_test \
nodefltctxtest evp_xof_test x509_load_cert_file_test bio_meth_test \
x509_acert_test x509_req_test strtoultest bio_pw_callback_test \
- engine_stubs_test base64_simdutf_test
+ engine_stubs_test base64_simdutf_test bio_eof_test
IF[{- !$disabled{'rpk'} -}]
PROGRAMS{noinst}=rpktest
INCLUDE[bio_meth_test]=../include ../apps/include
DEPEND[bio_meth_test]=../libcrypto libtestutil.a
+ SOURCE[bio_eof_test]=bio_eof_test.c
+ INCLUDE[bio_eof_test]=../include ../apps/include
+ DEPEND[bio_eof_test]=../libcrypto libtestutil.a
+
SOURCE[bioprinttest]=bioprinttest.c
INCLUDE[bioprinttest]=../include ../apps/include
IF[{- $config{target} =~ /^VC/ -}]
--- /dev/null
+#! /usr/bin/env perl
+# Copyright 2025 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
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use OpenSSL::Test::Simple;
+
+simple_test("test_bio_eof", "bio_eof_test");
+
BIO_puts ? 4_0_0 EXIST::FUNCTION:
BIO_indent ? 4_0_0 EXIST::FUNCTION:
BIO_ctrl ? 4_0_0 EXIST::FUNCTION:
+BIO_eof ? 4_0_0 EXIST::FUNCTION:
BIO_callback_ctrl ? 4_0_0 EXIST::FUNCTION:
BIO_ptr_ctrl ? 4_0_0 EXIST::FUNCTION:
BIO_int_ctrl ? 4_0_0 EXIST::FUNCTION: