BIO: reject negative length in memory BIO read

Reject a negative read length in the memory BIO read path so it cannot bypass
the signed bounds checks and reach a wild copy. Adds a regression test.
This commit is contained in:
Tobias Frauenschläger
2026-06-16 18:38:13 +02:00
parent 8f55480a1d
commit eaa563419e
3 changed files with 51 additions and 0 deletions
+10
View File
@@ -120,6 +120,16 @@ static int wolfSSL_BIO_MEMORY_read(WOLFSSL_BIO* bio, void* buf, int len)
WOLFSSL_ENTER("wolfSSL_BIO_MEMORY_read");
}
/* Reject a negative length up front. Callers are expected to validate, but
* guarding here too prevents a negative len from defeating the signed
* bounds checks below (sz/memSz comparisons) and reaching XMEMCPY with a
* length of (size_t)-1. Return the same error the public wolfSSL_BIO_read()
* does for a negative length rather than silently reporting 0 bytes read. A
* zero length is handled correctly by the logic below (copies nothing). */
if (len < 0) {
return WOLFSSL_BIO_ERROR;
}
sz = wolfSSL_BIO_pending(bio);
if (sz > 0) {
int memSz;
+39
View File
@@ -980,6 +980,45 @@ int test_wolfSSL_BIO_write(void)
return EXPECT_RESULT();
}
/* A negative length must never defeat the memory-BIO read bounds check and
* reach XMEMCPY with (size_t)-1. This exercises the public BIO_read() boundary
* (which rejects a negative length before dispatch); the matching guard in the
* static wolfSSL_BIO_MEMORY_read() sink is defense-in-depth and not separately
* reachable through the public API. Verify a negative length is rejected with
* an error without copying, a zero length reads nothing, and the pending data
* is left intact and still readable. */
int test_wolfSSL_BIO_read_negative_len(void)
{
EXPECT_DECLS;
#if defined(OPENSSL_EXTRA)
BIO* bio = NULL;
char msg[] = "negative length test";
int msgLen = (int)XSTRLEN(msg);
char out[64];
ExpectNotNull(bio = BIO_new(BIO_s_mem()));
ExpectIntEQ(BIO_write(bio, msg, msgLen), msgLen);
/* Negative length: must be rejected with an error, not a wild copy and not
* a silent 0-byte read. */
XMEMSET(out, 0, sizeof(out));
ExpectIntLT(BIO_read(bio, out, -1), 0);
/* Data must be untouched - still all pending. */
ExpectIntEQ(BIO_pending(bio), msgLen);
/* Zero length: nothing read, data still pending. */
ExpectIntEQ(BIO_read(bio, out, 0), 0);
ExpectIntEQ(BIO_pending(bio), msgLen);
/* A normal read still returns the intact message. */
ExpectIntEQ(BIO_read(bio, out, (int)sizeof(out)), msgLen);
ExpectIntEQ(XMEMCMP(out, msg, msgLen), 0);
BIO_free(bio);
#endif
return EXPECT_RESULT();
}
int test_wolfSSL_BIO_printf(void)
{
+2
View File
@@ -35,6 +35,7 @@ int test_wolfSSL_BIO_datagram(void);
int test_wolfSSL_BIO_s_null(void);
int test_wolfSSL_BIO_accept(void);
int test_wolfSSL_BIO_write(void);
int test_wolfSSL_BIO_read_negative_len(void);
int test_wolfSSL_BIO_printf(void);
int test_wolfSSL_BIO_f_md(void);
int test_wolfSSL_BIO_up_ref(void);
@@ -55,6 +56,7 @@ int test_wolfSSL_BIO_get_init(void);
TEST_DECL_GROUP("ossl_bio", test_wolfSSL_BIO_should_retry), \
TEST_DECL_GROUP("ossl_bio", test_wolfSSL_BIO_s_null), \
TEST_DECL_GROUP("ossl_bio", test_wolfSSL_BIO_write), \
TEST_DECL_GROUP("ossl_bio", test_wolfSSL_BIO_read_negative_len), \
TEST_DECL_GROUP("ossl_bio", test_wolfSSL_BIO_printf), \
TEST_DECL_GROUP("ossl_bio", test_wolfSSL_BIO_f_md), \
TEST_DECL_GROUP("ossl_bio", test_wolfSSL_BIO_up_ref), \