From: Kirill A. Korinsky Subject: libcrypto: BIO_read_ex and BIO_write_ex To: OpenBSD tech Date: Wed, 18 Mar 2026 01:48:25 +0100 tech@, Here a bit naive implementation of BIO_read_ex and BIO_write_ex for libcrypto. Why naive? Because I've implemented only wrappers and haven't touched callbacks which limits the length as INT_MAX. I need it for ffmpeg-8.1 upgrade, and it was briefly tested with it. Thoughts? OKs? Index: Symbols.list =================================================================== RCS file: /home/cvs/src/lib/libcrypto/Symbols.list,v diff -u -p -r1.224 Symbols.list --- Symbols.list 24 Oct 2025 11:33:38 -0000 1.224 +++ Symbols.list 18 Mar 2026 00:21:07 -0000 @@ -305,6 +305,7 @@ BIO_ptr_ctrl BIO_push BIO_puts BIO_read +BIO_read_ex BIO_s_accept BIO_s_bio BIO_s_connect @@ -337,6 +338,7 @@ BIO_test_flags BIO_up_ref BIO_vfree BIO_write +BIO_write_ex BN_CTX_end BN_CTX_free BN_CTX_get Index: bio/bio.h =================================================================== RCS file: /home/cvs/src/lib/libcrypto/bio/bio.h,v diff -u -p -r1.65 bio.h --- bio/bio.h 16 Jul 2025 18:12:54 -0000 1.65 +++ bio/bio.h 18 Mar 2026 00:21:07 -0000 @@ -541,9 +541,13 @@ void BIO_set_shutdown(BIO *a, int shut); void BIO_vfree(BIO *a); int BIO_read(BIO *b, void *data, int len) __attribute__((__bounded__(__buffer__,2,3))); +int BIO_read_ex(BIO *b, void *data, size_t len, size_t *readbytes) + __attribute__((__bounded__(__buffer__,2,3))); int BIO_gets(BIO *bp, char *buf, int size) __attribute__((__bounded__ (__string__,2,3))); int BIO_write(BIO *b, const void *data, int len) + __attribute__((__bounded__(__buffer__,2,3))); +int BIO_write_ex(BIO *b, const void *data, size_t len, size_t *written) __attribute__((__bounded__(__buffer__,2,3))); int BIO_puts(BIO *bp, const char *buf); int BIO_indent(BIO *b, int indent, int max); Index: bio/bio_lib.c =================================================================== RCS file: /home/cvs/src/lib/libcrypto/bio/bio_lib.c,v diff -u -p -r1.55 bio_lib.c --- bio/bio_lib.c 10 May 2025 05:54:38 -0000 1.55 +++ bio/bio_lib.c 18 Mar 2026 00:21:07 -0000 @@ -380,6 +380,69 @@ BIO_read(BIO *b, void *out, int outl) LCRYPTO_ALIAS(BIO_read); int +BIO_read_ex(BIO *b, void *out, size_t outl, size_t *readbytes) +{ + size_t callback_len, processed = 0; + int len, ret; + + if (readbytes == NULL) { + BIOerror(ERR_R_PASSED_NULL_PARAMETER); + return (0); + } + *readbytes = 0; + + if (b == NULL) { + BIOerror(ERR_R_PASSED_NULL_PARAMETER); + return (0); + } + + if (outl == 0) + return (0); + + if (out == NULL) { + BIOerror(ERR_R_PASSED_NULL_PARAMETER); + return (0); + } + + if (b->method == NULL || b->method->bread == NULL) { + BIOerror(BIO_R_UNSUPPORTED_METHOD); + return (0); + } + + callback_len = outl; + if (b->callback_ex == NULL && callback_len > INT_MAX) + callback_len = INT_MAX; + + if (b->callback != NULL || b->callback_ex != NULL) { + if ((ret = (int)bio_call_callback(b, BIO_CB_READ, out, + callback_len, 0, 0L, 1L, NULL)) <= 0) + return (0); + } + + if (!b->init) { + BIOerror(BIO_R_UNINITIALIZED); + return (0); + } + + len = (outl > INT_MAX) ? INT_MAX : (int)outl; + + if ((ret = b->method->bread(b, out, len)) > 0) + *readbytes = (size_t)ret; + + b->num_read += *readbytes; + + if (b->callback != NULL || b->callback_ex != NULL) { + processed = *readbytes; + ret = (int)bio_call_callback(b, BIO_CB_READ | BIO_CB_RETURN, + out, callback_len, 0, 0L, (ret > 0) ? 1 : ret, &processed); + *readbytes = processed; + } + + return (ret > 0); +} +LCRYPTO_ALIAS(BIO_read_ex); + +int BIO_write(BIO *b, const void *in, int inl) { size_t writebytes = 0; @@ -435,6 +498,64 @@ BIO_write(BIO *b, const void *in, int in return (ret); } LCRYPTO_ALIAS(BIO_write); + +int +BIO_write_ex(BIO *b, const void *in, size_t inl, size_t *written) +{ + size_t callback_len, processed = 0, *writebytes; + int len, ret; + + writebytes = (written == NULL) ? &processed : written; + *writebytes = 0; + + if (inl == 0) + return (1); + + if (b == NULL) + return (0); + + if (in == NULL) { + BIOerror(ERR_R_PASSED_NULL_PARAMETER); + return (0); + } + + if (b->method == NULL || b->method->bwrite == NULL) { + BIOerror(BIO_R_UNSUPPORTED_METHOD); + return (0); + } + + callback_len = inl; + if (b->callback_ex == NULL && callback_len > INT_MAX) + callback_len = INT_MAX; + + if (b->callback != NULL || b->callback_ex != NULL) { + if ((ret = (int)bio_call_callback(b, BIO_CB_WRITE, in, + callback_len, 0, 0L, 1L, NULL)) <= 0) + return (0); + } + + if (!b->init) { + BIOerror(BIO_R_UNINITIALIZED); + return (0); + } + + len = (inl > INT_MAX) ? INT_MAX : (int)inl; + + if ((ret = b->method->bwrite(b, in, len)) > 0) + *writebytes = (size_t)ret; + + b->num_write += *writebytes; + + if (b->callback != NULL || b->callback_ex != NULL) { + processed = *writebytes; + ret = (int)bio_call_callback(b, BIO_CB_WRITE | BIO_CB_RETURN, + in, callback_len, 0, 0L, (ret > 0) ? 1 : ret, &processed); + *writebytes = processed; + } + + return (ret > 0); +} +LCRYPTO_ALIAS(BIO_write_ex); int BIO_puts(BIO *b, const char *in) Index: hidden/openssl/bio.h =================================================================== RCS file: /home/cvs/src/lib/libcrypto/hidden/openssl/bio.h,v diff -u -p -r1.9 bio.h --- hidden/openssl/bio.h 16 Jul 2025 15:59:26 -0000 1.9 +++ hidden/openssl/bio.h 18 Mar 2026 00:26:48 -0000 @@ -78,8 +78,10 @@ LCRYPTO_USED(BIO_get_shutdown); LCRYPTO_USED(BIO_set_shutdown); LCRYPTO_USED(BIO_vfree); LCRYPTO_USED(BIO_read); +LCRYPTO_USED(BIO_read_ex); LCRYPTO_USED(BIO_gets); LCRYPTO_USED(BIO_write); +LCRYPTO_USED(BIO_write_ex); LCRYPTO_USED(BIO_puts); LCRYPTO_USED(BIO_indent); LCRYPTO_USED(BIO_ctrl); Index: man/BIO_read.3 =================================================================== RCS file: /home/cvs/src/lib/libcrypto/man/BIO_read.3,v diff -u -p -r1.12 BIO_read.3 --- man/BIO_read.3 8 Jun 2025 22:40:29 -0000 1.12 +++ man/BIO_read.3 18 Mar 2026 00:22:31 -0000 @@ -69,6 +69,8 @@ .Dt BIO_READ 3 .Os .Sh NAME +.Nm BIO_read_ex , +.Nm BIO_write_ex , .Nm BIO_read , .Nm BIO_number_read , .Nm BIO_gets , @@ -81,6 +83,20 @@ .Lb libcrypto .In openssl/bio.h .Ft int +.Fo BIO_read_ex +.Fa "BIO *b" +.Fa "void *buf" +.Fa "size_t len" +.Fa "size_t *readbytes" +.Fc +.Ft int +.Fo BIO_write_ex +.Fa "BIO *b" +.Fa "const void *buf" +.Fa "size_t len" +.Fa "size_t *written" +.Fc +.Ft int .Fo BIO_read .Fa "BIO *b" .Fa "void *buf" @@ -118,6 +134,45 @@ .Fa "BIO *b" .Fc .Sh DESCRIPTION +.Fn BIO_read_ex +attempts to read up to +.Fa len +bytes from +.Fa b +and places the data in +.Fa buf . +If successful, +the number of bytes read is stored in +.Fa *readbytes . +.Pp +.Fn BIO_write_ex +attempts to write up to +.Fa len +bytes from +.Fa buf +to +.Fa b . +If successful and +.Fa written +is not +.Dv NULL , +the number of bytes written is stored in +.Fa *written . +.Pp +Because the underlying BIO method interfaces take +.Vt int +length arguments, +a single call to +.Fn BIO_read_ex +or +.Fn BIO_write_ex +processes at most +.Dv INT_MAX +bytes, +even if +.Fa len +is larger. +.Pp .Fn BIO_read attempts to read .Fa len @@ -131,6 +186,8 @@ returns the grand total of bytes read fr .Fa b using .Fn BIO_read +and +.Fn BIO_read_ex so far. Bytes read with .Fn BIO_gets @@ -188,6 +245,7 @@ returns the grand total of bytes written .Fa b using .Fn BIO_write , +.Fn BIO_write_ex , .Fn BIO_puts , and .Fn BIO_indent @@ -238,6 +296,13 @@ to the chain. returns 1 if successful, even if nothing was written, or 0 if writing fails. .Pp +.Fn BIO_read_ex +returns 1 if data was successfully read and 0 otherwise. +.Pp +.Fn BIO_write_ex +returns 1 if no error was encountered writing data and 0 otherwise. +Requesting to write 0 bytes is not considered an error. +.Pp .Fn BIO_number_read and .Fn BIO_number_written @@ -264,6 +329,12 @@ the application should retry the operati .Xr BIO_new 3 , .Xr BIO_should_retry 3 .Sh HISTORY +.Fn BIO_read_ex +and +.Fn BIO_write_ex +first appeared in +.Ox 7.9 . +.Pp .Fn BIO_read , .Fn BIO_gets , .Fn BIO_write , -- wbr, Kirill