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: