Commit 2da90c2f05 for openssl.org
commit 2da90c2f053dd97821a518b62a3553888af861e2
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date: Thu Jul 23 22:35:19 2026 +0900
Fix C99 printf length modifiers with VS2013 CRT
The Visual Studio 2013 CRT does not support the C99 z, t and j length modifiers. Passing such formats to _vsnprintf_s invokes the invalid-parameter path and normally terminates the process.
For _MSC_VER < 1900, translate z and t to the pointer-sized I modifier and j to I64 before calling the CRT. Allocate a translated format only when needed, leaving newer MSVC versions and unaffected formats unchanged. Delegate BIO_snprintf to BIO_vsnprintf so all BIO formatting entry points use the same compatibility path.
Add focused coverage for BIO_snprintf, BIO_vsnprintf and BIO_printf, including the large-output allocation path and truncation behavior.
Fixes #31645
Assisted-by: OpenCode:GLM-5.2
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Milan Broz <mbroz@openssl.org>
MergeDate: Fri Jul 31 13:13:06 2026
(Merged from https://github.com/openssl/openssl/pull/31988)
diff --git a/crypto/bio/bio_print.c b/crypto/bio/bio_print.c
index 5366587a2a..382a64047a 100644
--- a/crypto/bio/bio_print.c
+++ b/crypto/bio/bio_print.c
@@ -47,20 +47,107 @@ int BIO_printf(BIO *bio, const char *format, ...)
* https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010
*
*/
+static int msvc_translate_printf_format(const char *format, const char **out,
+ char **tmp)
+{
+ /* Valid printf conversion specifiers, grouped by category: signed
+ * integers (d i), unsigned (o u x X), floating-point (f F e E g G a A),
+ * misc (c s p n) and MSVC-specific (S Z C). */
+ static const char conv[] = "diouxXfFeEgGaAcspnSZC";
+ const char *p = format;
+ char *dst = NULL, *q = NULL;
+
+ /*
+ * The VS 2013 CRT does not understand the C99 z, t and j length
+ * modifiers. Translate z and t to I (both are pointer-sized on Windows)
+ * and j to I64 (intmax_t is 64 bits). Every input character expands to
+ * at most three output characters (j -> I64), so 3 * length is a safe
+ * bound for the buffer.
+ *
+ * This is done in a single pass: nothing is allocated until the first
+ * modifier is seen, so formats that need no translation return the
+ * original string untouched. EMIT_CHAR() appends a character to the
+ * output once the buffer exists; before that it is a no-op.
+ */
+#define EMIT_CHAR(c) \
+ do { \
+ if (dst != NULL) \
+ *q++ = (c); \
+ } while (0)
+
+ *out = format;
+ *tmp = NULL;
+
+ while (*p != '\0') {
+ if (*p != '%') { /* literal character */
+ EMIT_CHAR(*p);
+ p++;
+ continue;
+ }
+ p++; /* consume '%' */
+ if (*p == '%') { /* literal "%%" */
+ EMIT_CHAR('%');
+ EMIT_CHAR('%');
+ p++;
+ continue;
+ }
+ EMIT_CHAR('%');
+ while (*p != '\0' && strchr(conv, *p) == NULL) {
+ char c = *p++;
+ if (c != 'z' && c != 't' && c != 'j') { /* verbatim */
+ EMIT_CHAR(c);
+ continue;
+ }
+ if (dst == NULL) { /* first modifier: allocate + flush prefix */
+ size_t len = strlen(format);
+ if (len > (SIZE_MAX - 1) / 3) /* make static analysis happy */
+ return 0;
+ dst = (char *)OPENSSL_malloc(3 * len + 1);
+ if (dst == NULL)
+ return 0;
+ q = dst;
+ memcpy(q, format, (size_t)(p - 1 - format));
+ q += p - 1 - format;
+ }
+ EMIT_CHAR('I');
+ if (c == 'j') {
+ EMIT_CHAR('6');
+ EMIT_CHAR('4');
+ }
+ }
+ if (*p != '\0') { /* copy the conversion specifier */
+ EMIT_CHAR(*p);
+ p++;
+ }
+ }
+#undef EMIT_CHAR
+
+ if (dst != NULL) {
+ *q = '\0';
+ *out = dst;
+ *tmp = dst;
+ }
+ return 1;
+}
+
static int msvc_bio_vprintf(BIO *bio, const char *format, va_list args)
{
char buf[512];
- char *abuf;
+ char *abuf, *fmt_alloc;
+ const char *fmt;
int ret, sz;
- sz = _vsnprintf_s(buf, sizeof(buf), _TRUNCATE, format, args);
+ if (!msvc_translate_printf_format(format, &fmt, &fmt_alloc))
+ return -1;
+
+ sz = _vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args);
if (sz == -1) {
- sz = _vscprintf(format, args) + 1;
+ sz = _vscprintf(fmt, args) + 1;
abuf = (char *)OPENSSL_malloc(sz);
if (abuf == NULL) {
ret = -1;
} else {
- sz = _vsnprintf(abuf, sz, format, args);
+ sz = _vsnprintf(abuf, sz, fmt, args);
ret = BIO_write(bio, abuf, sz);
OPENSSL_free(abuf);
}
@@ -68,6 +155,7 @@ static int msvc_bio_vprintf(BIO *bio, const char *format, va_list args)
ret = BIO_write(bio, buf, sz);
}
+ OPENSSL_free(fmt_alloc);
return ret;
}
#endif
@@ -82,7 +170,21 @@ int ossl_BIO_snprintf_msvc(char *buf, size_t n, const char *format, ...)
int ret;
va_start(args, format);
+#if defined(_MSC_VER) && _MSC_VER < 1900
+ {
+ char *fmt_alloc;
+ const char *fmt;
+
+ if (!msvc_translate_printf_format(format, &fmt, &fmt_alloc)) {
+ ret = -1;
+ } else {
+ ret = _vsnprintf_s(buf, n, _TRUNCATE, fmt, args);
+ OPENSSL_free(fmt_alloc);
+ }
+ }
+#else
ret = _vsnprintf_s(buf, n, _TRUNCATE, format, args);
+#endif
va_end(args);
return ret;
@@ -144,14 +246,7 @@ int BIO_snprintf(char *buf, size_t n, const char *format, ...)
int ret;
va_start(args, format);
-
-#if defined(_MSC_VER) && _MSC_VER < 1900
- ret = _vsnprintf_s(buf, n, _TRUNCATE, format, args);
-#else
- ret = vsnprintf(buf, n, format, args);
- if ((size_t)ret >= n)
- ret = -1;
-#endif
+ ret = BIO_vsnprintf(buf, n, format, args);
va_end(args);
return ret;
@@ -159,10 +254,17 @@ 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)
{
+#if defined(_MSC_VER) && _MSC_VER < 1900
+ char *fmt_alloc;
+ const char *fmt;
+#endif
int ret;
#if defined(_MSC_VER) && _MSC_VER < 1900
- ret = _vsnprintf_s(buf, n, _TRUNCATE, format, args);
+ if (!msvc_translate_printf_format(format, &fmt, &fmt_alloc))
+ return -1;
+ ret = _vsnprintf_s(buf, n, _TRUNCATE, fmt, args);
+ OPENSSL_free(fmt_alloc);
#else
ret = vsnprintf(buf, n, format, args);
if ((size_t)ret >= n)
diff --git a/test/bio_core_test.c b/test/bio_core_test.c
index 26ff787eec..0214040817 100644
--- a/test/bio_core_test.c
+++ b/test/bio_core_test.c
@@ -7,6 +7,8 @@
* https://www.openssl.org/source/license.html
*/
+#include <stdarg.h>
+#include <stddef.h>
#include <string.h>
#include <openssl/bio.h>
#include "testutil.h"
@@ -64,6 +66,18 @@ static const OSSL_DISPATCH biocbs[] = {
OSSL_DISPATCH_END
};
+static int call_bio_vsnprintf(char *buf, size_t n, const char *format, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start(args, format);
+ ret = BIO_vsnprintf(buf, n, format, args);
+ va_end(args);
+
+ return ret;
+}
+
static int test_bio_core(void)
{
BIO *cbio = NULL, *cbiobad = NULL;
@@ -140,6 +154,70 @@ err:
return testresult;
}
+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;
+ ossl_ssize_t zs = (ossl_ssize_t)-42;
+ ptrdiff_t t = (ptrdiff_t)-7;
+ ossl_uintmax_t j = (((ossl_uintmax_t)1) << 32) + 42;
+ int expected_len = (int)strlen(expected);
+ size_t long_tail_len = strlen(long_tail);
+ int testresult = 0;
+
+ 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),
+ expected_len)
+ || !TEST_str_eq(buf, expected))
+ goto err;
+
+ if (!TEST_int_eq(call_bio_vsnprintf(buf, sizeof(buf),
+ "zu=%zu zd=%zd zx=%zx td=%td ju=%ju jx=%jx",
+ z, zs, z, t, j, j),
+ expected_len)
+ || !TEST_str_eq(buf, expected))
+ goto err;
+
+ 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",
+ z, zs, z, t, j, j),
+ expected_len))
+ goto err;
+
+ memlen = BIO_get_mem_data(bio, &memdata);
+ if (!TEST_long_eq(memlen, (long)strlen(expected))
+ || !TEST_mem_eq(memdata, (size_t)memlen, expected, strlen(expected)))
+ goto err;
+
+ BIO_free(bio);
+ bio = NULL;
+ memdata = NULL;
+ if (!TEST_ptr(bio = BIO_new(BIO_s_mem()))
+ || !TEST_int_eq(BIO_printf(bio, "%600zu", z), 600))
+ goto err;
+
+ memlen = BIO_get_mem_data(bio, &memdata);
+ if (!TEST_long_eq(memlen, 600)
+ || !TEST_mem_eq(memdata + (size_t)memlen - long_tail_len,
+ long_tail_len, long_tail, long_tail_len))
+ goto err;
+
+ if (!TEST_int_eq(BIO_snprintf(buf, 4, "%zu", z), -1))
+ goto err;
+
+ testresult = 1;
+err:
+ BIO_free(bio);
+ return testresult;
+}
+
int setup_tests(void)
{
if (!test_skip_common_options()) {
@@ -149,5 +227,6 @@ int setup_tests(void)
ADD_TEST(test_bio_core);
ADD_TEST(test_bio_vprintf_boundary);
+ ADD_TEST(test_bio_printf_c99_length_modifiers);
return 1;
}