Commit 9308b792b8 for openssl.org
commit 9308b792b881c91192f0dda4a24f755900d36451
Author: Bob Beck <beck@openssl.org>
Date: Wed Jun 17 18:35:56 2026 -0600
Deprecate BIO_snprintf() and BIO_vsnprintf().
They were added when C99 was not a thing, and when implementations
differed, to support platforms that did not have them. This
is no longer the case.
They return -1 on truncation rather than the would-have-been length
that snprintf() returns, leading callers who write to the standard
contract to produce bugs. Use snprintf() and vsnprintf() instead.
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Andrew Dinh <andrewd@openssl.org>
MergeDate: Wed Aug 26 16:20:06 2026
(Merged from https://github.com/openssl/openssl/pull/31640)
diff --git a/CHANGES.md b/CHANGES.md
index f4145d7cf6..4c505d7117 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -156,6 +156,13 @@ OpenSSL Releases
*Jakub Zelenka*
+ * Deprecated BIO_snprintf() and BIO_vsnprintf(). C99 snprintf() is now
+ universally available, and the wrappers return -1 on truncation rather
+ than the would-have-been length, so callers reaching for the standard
+ idiom by reflex produce bugs. Use snprintf() and vsnprintf() directly.
+
+ *Bob Beck*
+
* Added -testmode option for `s_time` app.
*Jakub Zelenka*
diff --git a/crypto/bio/bio_print.c b/crypto/bio/bio_print.c
index 3f25356142..3585481ee4 100644
--- a/crypto/bio/bio_print.c
+++ b/crypto/bio/bio_print.c
@@ -81,12 +81,12 @@ done:
return ret;
}
+#ifndef OPENSSL_NO_DEPRECATED_4_1
/*
- * For historical reasons BIO_snprintf and friends return a failure for string
- * truncation (-1) instead of the POSIX requirement of a success with the
- * number of characters that would have been written. Upon seeing -1 on
- * return, the caller must treat output buf as unsafe (as a buf with missing
- * nul terminator).
+ * For historical reasons BIO_snprintf and friends return -1 on truncation
+ * instead of the C99 snprintf semantic of returning the number of characters
+ * that would have been written. Deprecated in 4.1; new code should call
+ * snprintf() / vsnprintf() directly.
*/
int BIO_snprintf(char *buf, size_t n, const char *format, ...)
{
@@ -113,3 +113,4 @@ int BIO_vsnprintf(char *buf, size_t n, const char *format, va_list args)
return ret;
}
+#endif /* OPENSSL_NO_DEPRECATED_4_1 */
diff --git a/doc/man3/BIO_printf.pod b/doc/man3/BIO_printf.pod
index 221881d123..6d85ec8a05 100644
--- a/doc/man3/BIO_printf.pod
+++ b/doc/man3/BIO_printf.pod
@@ -12,6 +12,10 @@ BIO_printf, BIO_vprintf, BIO_snprintf, BIO_vsnprintf
int BIO_printf(BIO *bio, const char *format, ...);
int BIO_vprintf(BIO *bio, const char *format, va_list args);
+Deprecated since OpenSSL 4.1, can be hidden entirely by defining
+B<OPENSSL_API_COMPAT> with a suitable version value, see
+L<openssl_user_macros(7)>:
+
int BIO_snprintf(char *buf, size_t n, const char *format, ...);
int BIO_vsnprintf(char *buf, size_t n, const char *format, va_list args);
@@ -26,28 +30,46 @@ the output is sent to the specified BIO, I<bio>, rather than standard
output. All common format specifiers are supported. The argument
list I<args> is a stdarg argument list.
-BIO_snprintf() is for platforms that do not have the common snprintf()
-function. It is like sprintf() except that the size parameter, I<n>,
-specifies the size of the output buffer.
-
-BIO_vsnprintf() is to BIO_snprintf() as BIO_vprintf() is to BIO_printf().
+BIO_snprintf() and BIO_vsnprintf() were provided when not every supported
+platform shipped a C99 snprintf() implementation. That is no longer the
+case: every supported build target provides C99 snprintf(), and new code
+should use the standard library functions directly. BIO_snprintf() and
+BIO_vsnprintf() are retained for source compatibility but are deprecated
+as of OpenSSL 4.1.
+
+BIO_snprintf() and BIO_vsnprintf() have the same signatures as snprintf()
+and vsnprintf() but they do not have the same return value on truncation.
+When the formatted output would exceed I<n> bytes, snprintf() returns the
+number of bytes that would have been written had the buffer been large
+enough, while BIO_snprintf() and BIO_vsnprintf() return -1. Because the
+functions look identical at the call site, callers reach for the standard
+C99 idiom by reflex, and the divergence becomes a source of bugs. New
+code should call snprintf() and vsnprintf() directly.
=head1 RETURN VALUES
-All functions return the number of bytes written, or -1 on error.
-For BIO_snprintf() and BIO_vsnprintf() this includes when the output
-buffer is too small.
+BIO_printf() and BIO_vprintf() return the number of bytes written, or
+-1 on error.
+
+BIO_snprintf() and BIO_vsnprintf() return the number of bytes written to
+I<buf> (excluding the terminating C<'\0'>) on success, or -1 if the
+formatted output would not fit in I<n> bytes or on other error.
=head1 NOTES
-Except when I<n> is 0, both BIO_snprintf() and BIO_vsnprintf() always
-terminate their output with C<'\0'>. This includes cases where -1 is
-returned, such as when there is insufficient space to output the whole
-string.
+When I<n> is greater than 0, BIO_snprintf() and BIO_vsnprintf() always
+terminate I<buf> with C<'\0'>, including in the truncation case where -1
+is returned.
+
+=head1 HISTORY
+
+BIO_snprintf() and BIO_vsnprintf() were deprecated in OpenSSL 4.1. Callers
+should use the standard C library functions snprintf() and vsnprintf()
+instead.
=head1 COPYRIGHT
-Copyright 2017-2021 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2017-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
diff --git a/include/openssl/bio.h.in b/include/openssl/bio.h.in
index a4d618a894..a2a5972980 100644
--- a/include/openssl/bio.h.in
+++ b/include/openssl/bio.h.in
@@ -942,10 +942,14 @@ int BIO_printf(BIO *bio, const char *format, ...)
ossl_bio__attr__((__format__(__printf__, 2, 3)));
int BIO_vprintf(BIO *bio, const char *format, va_list args)
ossl_bio__attr__((__format__(__printf__, 2, 0)));
+#ifndef OPENSSL_NO_DEPRECATED_4_1
+OSSL_DEPRECATEDIN_4_1_FOR("use snprintf()")
int BIO_snprintf(char *buf, size_t n, const char *format, ...)
ossl_bio__attr__((__format__(__printf__, 3, 4)));
+OSSL_DEPRECATEDIN_4_1_FOR("use vsnprintf()")
int BIO_vsnprintf(char *buf, size_t n, const char *format, va_list args)
ossl_bio__attr__((__format__(__printf__, 3, 0)));
+#endif
#undef ossl_bio__attr__
BIO_METHOD *BIO_meth_new(int type, const char *name);
diff --git a/providers/fips/fipsprov.c b/providers/fips/fipsprov.c
index b7a4f83217..7f50d606ce 100644
--- a/providers/fips/fipsprov.c
+++ b/providers/fips/fipsprov.c
@@ -1190,6 +1190,17 @@ void *CRYPTO_aligned_alloc(size_t num, size_t align, void **freeptr,
return ossl_malloc_align(num, align, freeptr, file, line);
}
+/*
+ * The public BIO_snprintf() prototype is hidden when the public headers
+ * are built with OPENSSL_NO_DEPRECATED_4_1, but the FIPS module still
+ * defines and exports the symbol for callers compiled into the module.
+ * Provide a local prototype in that configuration so the definition is
+ * well-formed.
+ */
+#ifdef OPENSSL_NO_DEPRECATED_4_1
+int BIO_snprintf(char *buf, size_t n, const char *format, ...);
+#endif
+
int BIO_snprintf(char *buf, size_t n, const char *format, ...)
{
va_list args;
diff --git a/test/bio_core_test.c b/test/bio_core_test.c
index 0214040817..9dc2300bc8 100644
--- a/test/bio_core_test.c
+++ b/test/bio_core_test.c
@@ -1,3 +1,4 @@
+
/*
* Copyright 2021-2026 The OpenSSL Project Authors. All Rights Reserved.
*
@@ -66,17 +67,21 @@ static const OSSL_DISPATCH biocbs[] = {
OSSL_DISPATCH_END
};
+#ifndef OPENSSL_NO_DEPRECATED_4_1
static int call_bio_vsnprintf(char *buf, size_t n, const char *format, ...)
{
va_list args;
int ret;
va_start(args, format);
+ OSSL_BEGIN_ALLOW_DEPRECATED
ret = BIO_vsnprintf(buf, n, format, args);
+ OSSL_END_ALLOW_DEPRECATED
va_end(args);
return ret;
}
+#endif
static int test_bio_core(void)
{
@@ -159,7 +164,6 @@ static int test_bio_printf_c99_length_modifiers(void)
static const char expected[] = "zu=12345 zd=-42 zx=3039 td=-7 ju=4294967338 jx=10000002a";
static const char long_tail[] = "12345";
BIO *bio = NULL;
- char buf[128];
char *memdata = NULL;
long memlen;
size_t z = (size_t)12345;
@@ -169,7 +173,10 @@ static int test_bio_printf_c99_length_modifiers(void)
int expected_len = (int)strlen(expected);
size_t long_tail_len = strlen(long_tail);
int testresult = 0;
+#ifndef OPENSSL_NO_DEPRECATED_4_1
+ char buf[128];
+ OSSL_BEGIN_ALLOW_DEPRECATED
if (!TEST_int_eq(BIO_snprintf(buf, sizeof(buf),
"zu=%zu zd=%zd zx=%zx td=%td ju=%ju jx=%jx",
z, zs, z, t, j, j),
@@ -183,7 +190,8 @@ static int test_bio_printf_c99_length_modifiers(void)
expected_len)
|| !TEST_str_eq(buf, expected))
goto err;
-
+ OSSL_END_ALLOW_DEPRECATED
+#endif
if (!TEST_ptr(bio = BIO_new(BIO_s_mem()))
|| !TEST_int_eq(BIO_printf(bio,
"zu=%zu zd=%zd zx=%zx td=%td ju=%ju jx=%jx",
@@ -209,8 +217,12 @@ static int test_bio_printf_c99_length_modifiers(void)
long_tail_len, long_tail, long_tail_len))
goto err;
+#ifndef OPENSSL_NO_DEPRECATED_4_1
+ OSSL_BEGIN_ALLOW_DEPRECATED
if (!TEST_int_eq(BIO_snprintf(buf, 4, "%zu", z), -1))
goto err;
+ OSSL_END_ALLOW_DEPRECATED
+#endif
testresult = 1;
err:
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 3b3143f5a1..66d767ba7f 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -3017,8 +3017,8 @@ BIO_new_bio_dgram_pair 3015 4_0_0 EXIST::FUNCTION:DGRAM
BIO_copy_next_retry 3016 4_0_0 EXIST::FUNCTION:
BIO_printf 3017 4_0_0 EXIST::FUNCTION:
BIO_vprintf 3018 4_0_0 EXIST::FUNCTION:
-BIO_snprintf 3019 4_0_0 EXIST::FUNCTION:
-BIO_vsnprintf 3020 4_0_0 EXIST::FUNCTION:
+BIO_snprintf 3019 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_4_1
+BIO_vsnprintf 3020 4_0_0 EXIST::FUNCTION:DEPRECATEDIN_4_1
BIO_meth_new 3021 4_0_0 EXIST::FUNCTION:
BIO_meth_free 3022 4_0_0 EXIST::FUNCTION:
BIO_meth_set_write 3023 4_0_0 EXIST::FUNCTION: