From b46827e5bad0077ce7b4f2d79145cff995ab3a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afonso=20Janu=C3=A1rio?= Date: Fri, 4 Sep 2026 22:17:25 +0100 Subject: [PATCH] Validate siglen against modulus_len in PSS decode ltc_pkcs_1_pss_decode_mgf1 checked saltlen and modulus_len but never checked that siglen actually matched modulus_len. It then copies modulus_len - 1 bytes out of sig based on modulus_len alone, so a caller passing a shorter buffer (as pkcs_1_pss_decode does, unlike rsa_verify_hash_v2 which happens to validate this indirectly) reads past the end of it. Fixes #802 --- src/pk/pkcs1/pkcs_1_pss_decode.c | 3 ++- tests/pkcs_1_pss_test.c | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/pk/pkcs1/pkcs_1_pss_decode.c b/src/pk/pkcs1/pkcs_1_pss_decode.c index 0247967af..5bec7d903 100644 --- a/src/pk/pkcs1/pkcs_1_pss_decode.c +++ b/src/pk/pkcs1/pkcs_1_pss_decode.c @@ -50,7 +50,8 @@ int ltc_pkcs_1_pss_decode_mgf1(const unsigned char *msghash, unsigned long msgh /* check sizes */ if ((saltlen > modulus_len) || - (modulus_len < hLen + saltlen + 2)) { + (modulus_len < hLen + saltlen + 2) || + (siglen != modulus_len)) { return CRYPT_PK_INVALID_SIZE; } diff --git a/tests/pkcs_1_pss_test.c b/tests/pkcs_1_pss_test.c index 40ed120cd..6911695ce 100644 --- a/tests/pkcs_1_pss_test.c +++ b/tests/pkcs_1_pss_test.c @@ -158,7 +158,7 @@ int pkcs_1_pss_test(void) rsaData_t* s = &t->data[j]; unsigned char buf[20], obuf[256]; unsigned long buflen = sizeof(buf), obuflen = sizeof(obuf); - int stat; + int stat, err; prng_descriptor[rsa_params.wprng].add_entropy(s->o2, s->o2_l, rsa_params.prng); DOX(hash_memory(hash_idx, s->o1, s->o1_l, buf, &buflen), s->name); rsa_params.params.saltlen = s->o2_l; @@ -166,6 +166,28 @@ int pkcs_1_pss_test(void) COMPARE_TESTVECTOR(obuf, obuflen, s->o3, s->o3_l,s->name, j); DOX(rsa_verify_hash_v2(obuf, obuflen, buf, buflen, &rsa_params, &stat, key), s->name); ENSUREX(stat == 1, s->name); + + /* pkcs_1_pss_decode() (unlike rsa_verify_hash_v2()) forwards siglen + * straight to ltc_pkcs_1_pss_decode_mgf1() with no check that it + * matches modulus_len. That function copies modulus_len - 1 bytes + * total out of `sig` based on modulus_len alone, so a caller + * passing a genuinely truncated buffer used to walk past its end + * instead of failing early. + */ + { + unsigned long modulus_bitlen = ltc_mp_count_bits(key->N); + const unsigned char *em = obuf; + unsigned long emlen = obuflen; + if (modulus_bitlen % 8 == 1) { + em++; + emlen--; + } + stat = 1; + err = pkcs_1_pss_decode(buf, buflen, em, emlen - 1, s->o2_l, + hash_idx, modulus_bitlen, &stat); + ENSUREX(err == CRYPT_PK_INVALID_SIZE, s->name); + ENSUREX(stat == 0, s->name); + } } /* for */ ltc_mp_deinit_multi(key->d, key->e, key->N, key->dQ, key->dP, key->qP, key->p, key->q, LTC_NULL);