Skip to content

Commit 7b53303

Browse files
authored
Merge pull request #10051 from anhu/mp_int_bounds
Add bounds checks for MP integer size in SizeASN_Items
2 parents 5151a69 + 92bccf1 commit 7b53303

3 files changed

Lines changed: 127 additions & 1 deletion

File tree

tests/api/test_rsa.c

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,3 +1152,117 @@ int test_wc_RsaDecrypt_BoundsCheck(void)
11521152
return EXPECT_RESULT();
11531153
} /* END test_wc_RsaDecryptBoundsCheck */
11541154

1155+
/*
1156+
* Test wc_RsaKeyToDer with an mp_int large enough to wrap size calculations.
1157+
*/
1158+
int test_wc_RsaKeyToDer_SizeOverflow(void)
1159+
{
1160+
EXPECT_DECLS;
1161+
#if !defined(NO_RSA) && defined(USE_INTEGER_HEAP_MATH) && \
1162+
!defined(USE_FAST_MATH) && \
1163+
defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_PUBLIC_MP) && \
1164+
(defined(WOLFSSL_KEY_GEN) || defined(WOLFSSL_KEY_TO_DER))
1165+
RsaKey key;
1166+
int i;
1167+
int derRet;
1168+
int crafted_used;
1169+
int top_bits;
1170+
mp_digit top_digit;
1171+
mp_digit storage = 0; /* the only digit mp_count_bits ever reads */
1172+
mp_digit* fake_dp = NULL;
1173+
1174+
int orig_used = 0;
1175+
int orig_alloc = 0;
1176+
int orig_sign = 0;
1177+
mp_digit* orig_dp = NULL;
1178+
1179+
mp_int* fields[8];
1180+
1181+
XMEMSET(&key, 0, sizeof(key));
1182+
1183+
/* Skip on 32-bit: biasing dp by ~half the address space is unsafe. */
1184+
if (sizeof(void*) < 8) {
1185+
return TEST_SKIPPED;
1186+
}
1187+
1188+
/* Find 'used' count that makes (used-1)*DIGIT_BIT + top_bits = -48
1189+
* as signed int, causing mp_unsigned_bin_size to return -6. */
1190+
{
1191+
unsigned int target = 0xFFFFFFD0u; /* -48 as unsigned 32-bit */
1192+
int found = 0;
1193+
1194+
crafted_used = 0;
1195+
top_bits = 0;
1196+
top_digit = 0;
1197+
1198+
for (top_bits = 1; top_bits < DIGIT_BIT; top_bits++) {
1199+
unsigned int base = target - (unsigned int)top_bits;
1200+
if (base % (unsigned int)DIGIT_BIT == 0) {
1201+
crafted_used = (int)(base / (unsigned int)DIGIT_BIT) + 1;
1202+
top_digit = (mp_digit)1 << (top_bits - 1);
1203+
found = 1;
1204+
break;
1205+
}
1206+
}
1207+
if (!found) {
1208+
return TEST_SKIPPED;
1209+
}
1210+
}
1211+
1212+
ExpectIntEQ(wc_InitRsaKey(&key, HEAP_HINT), 0);
1213+
1214+
/* Set up dummy RSA private key fields. */
1215+
key.type = RSA_PRIVATE;
1216+
fields[0] = &key.n;
1217+
fields[1] = &key.e;
1218+
fields[2] = &key.d;
1219+
fields[3] = &key.p;
1220+
fields[4] = &key.q;
1221+
fields[5] = &key.dP;
1222+
fields[6] = &key.dQ;
1223+
fields[7] = &key.u;
1224+
1225+
for (i = 0; i < 8; i++) {
1226+
if (EXPECT_SUCCESS()) {
1227+
ExpectIntEQ(mp_init(fields[i]), 0);
1228+
mp_set(fields[i], 0x42);
1229+
}
1230+
}
1231+
1232+
if (EXPECT_SUCCESS()) {
1233+
orig_used = key.p.used;
1234+
orig_alloc = key.p.alloc;
1235+
orig_sign = key.p.sign;
1236+
orig_dp = key.p.dp;
1237+
}
1238+
1239+
/* The vulnerable path (mp_unsigned_bin_size -> mp_count_bits, and
1240+
* mp_leading_bit) only reads dp[used-1]. Bias dp so that index
1241+
* (used-1) lands on our single real digit -- no giant allocation
1242+
* (and no mmap/VirtualAlloc) needed. */
1243+
if (EXPECT_SUCCESS()) {
1244+
storage = top_digit;
1245+
fake_dp = (mp_digit*)((wc_ptr_t)&storage
1246+
- (wc_ptr_t)(crafted_used - 1) * sizeof(mp_digit));
1247+
1248+
key.p.dp = fake_dp;
1249+
key.p.used = crafted_used;
1250+
key.p.alloc = crafted_used;
1251+
key.p.sign = 0; /* MP_ZPOS */
1252+
}
1253+
1254+
/* Should return an error, not a bogus small size. */
1255+
derRet = wc_RsaKeyToDer(&key, NULL, 0);
1256+
ExpectIntLT(derRet, 0);
1257+
1258+
/* Restore key.p before cleanup. */
1259+
key.p.dp = orig_dp;
1260+
key.p.used = orig_used;
1261+
key.p.alloc = orig_alloc;
1262+
key.p.sign = orig_sign;
1263+
1264+
DoExpectIntEQ(wc_FreeRsaKey(&key), 0);
1265+
#endif
1266+
return EXPECT_RESULT();
1267+
} /* END test_wc_RsaKeyToDer_SizeOverflow */
1268+

tests/api/test_rsa.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ int test_wc_RsaEncryptSize(void);
4242
int test_wc_RsaSSL_SignVerify(void);
4343
int test_wc_RsaFlattenPublicKey(void);
4444
int test_wc_RsaDecrypt_BoundsCheck(void);
45+
int test_wc_RsaKeyToDer_SizeOverflow(void);
4546

4647
#define TEST_RSA_DECLS \
4748
TEST_DECL_GROUP("rsa", test_wc_InitRsaKey), \
@@ -61,6 +62,7 @@ int test_wc_RsaDecrypt_BoundsCheck(void);
6162
TEST_DECL_GROUP("rsa", test_wc_RsaEncryptSize), \
6263
TEST_DECL_GROUP("rsa", test_wc_RsaSSL_SignVerify), \
6364
TEST_DECL_GROUP("rsa", test_wc_RsaFlattenPublicKey), \
64-
TEST_DECL_GROUP("rsa", test_wc_RsaDecrypt_BoundsCheck)
65+
TEST_DECL_GROUP("rsa", test_wc_RsaDecrypt_BoundsCheck), \
66+
TEST_DECL_GROUP("rsa", test_wc_RsaKeyToDer_SizeOverflow)
6567

6668
#endif /* WOLFCRYPT_TEST_RSA_H */

wolfcrypt/src/asn.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,8 +864,18 @@ int SizeASN_Items(const ASNItem* asn, ASNSetData *data, int count,
864864
case ASN_DATA_TYPE_MP:
865865
/* Calculate the size of the MP integer data. */
866866
length = mp_unsigned_bin_size(data[i].data.mp);
867+
if (length < 0) {
868+
return ASN_PARSE_E;
869+
}
867870
length += mp_leading_bit(data[i].data.mp) ? 1 : 0;
871+
if (length < 0) {
872+
return ASN_PARSE_E;
873+
}
868874
len = (word32)SizeASNHeader((word32)length) + (word32)length;
875+
/* Check for overflow: header + length must not wrap word32. */
876+
if (len < (word32)length) {
877+
return ASN_PARSE_E;
878+
}
869879
break;
870880

871881
case ASN_DATA_TYPE_REPLACE_BUFFER:

0 commit comments

Comments
 (0)