From 035c8f417e6a66d919630ebf8a131af3b991f760 Mon Sep 17 00:00:00 2001 From: Ramya Eliger Date: Wed, 23 Sep 2026 13:00:24 +0530 Subject: [PATCH 1/2] Fix out-of-bounds read in slh_dsa verify on short signature Signed-off-by: Ramya Eliger --- src/sig/slh_dsa/patches/verify_bounds.patch | 32 +++++++++++++++++++++ src/sig/slh_dsa/slh_dsa_c/slh_dsa.c | 14 +++++++++ tests/test_sig.c | 18 ++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 src/sig/slh_dsa/patches/verify_bounds.patch diff --git a/src/sig/slh_dsa/patches/verify_bounds.patch b/src/sig/slh_dsa/patches/verify_bounds.patch new file mode 100644 index 0000000000..8dc54b90e9 --- /dev/null +++ b/src/sig/slh_dsa/patches/verify_bounds.patch @@ -0,0 +1,32 @@ +diff --git a/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c b/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c +index 07052e7..7d5244a 100644 +--- a/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c ++++ b/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c +@@ -688,6 +688,13 @@ OQS_API int slh_verify_internal(const uint8_t *m, size_t m_sz, const uint8_t *si + slh_var_t var; + uint8_t digest[SLH_MAX_M]; + ++ /* Reject a wrong-length signature before Hmsg reads the n-byte randomizer R ++ from sig; a shorter sig would otherwise be read out of bounds. */ ++ if (sig_sz != slh_sig_sz(prm)) ++ { ++ return 0; /* false */ ++ } ++ + /* use Hmsg directly */ + prm->mk_var(&var, pk, NULL, prm); + prm->h_msg(&var, digest, sig, m, m_sz, NULL, SLH_CTX_SZ_NO_CONTEXT); +@@ -710,6 +717,13 @@ int slh_verify(const uint8_t *m, size_t m_sz, const uint8_t *sig, size_t sig_sz, + return 0; /* false */ + } + ++ /* Reject a wrong-length signature before Hmsg reads the n-byte randomizer R ++ from sig; a shorter sig would otherwise be read out of bounds. */ ++ if (sig_sz != slh_sig_sz(prm)) ++ { ++ return 0; /* false */ ++ } ++ + /* create the "pure" hash (with context) */ + prm->mk_var(&var, pk, NULL, prm); + prm->h_msg(&var, digest, sig, m, m_sz, ctx, ctx_sz); diff --git a/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c b/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c index 07052e729a..7d5244ab4b 100644 --- a/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c +++ b/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c @@ -688,6 +688,13 @@ OQS_API int slh_verify_internal(const uint8_t *m, size_t m_sz, const uint8_t *si slh_var_t var; uint8_t digest[SLH_MAX_M]; + /* Reject a wrong-length signature before Hmsg reads the n-byte randomizer R + from sig; a shorter sig would otherwise be read out of bounds. */ + if (sig_sz != slh_sig_sz(prm)) + { + return 0; /* false */ + } + /* use Hmsg directly */ prm->mk_var(&var, pk, NULL, prm); prm->h_msg(&var, digest, sig, m, m_sz, NULL, SLH_CTX_SZ_NO_CONTEXT); @@ -710,6 +717,13 @@ int slh_verify(const uint8_t *m, size_t m_sz, const uint8_t *sig, size_t sig_sz, return 0; /* false */ } + /* Reject a wrong-length signature before Hmsg reads the n-byte randomizer R + from sig; a shorter sig would otherwise be read out of bounds. */ + if (sig_sz != slh_sig_sz(prm)) + { + return 0; /* false */ + } + /* create the "pure" hash (with context) */ prm->mk_var(&var, pk, NULL, prm); prm->h_msg(&var, digest, sig, m, m_sz, ctx, ctx_sz); diff --git a/tests/test_sig.c b/tests/test_sig.c index 67c9f098fc..06971f628d 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -123,6 +123,24 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al goto err; } + /* A signature shorter than the SLH-DSA parameter set's n-byte randomizer must + be rejected before Hmsg reads it; verifying against a 1-byte buffer used to + read out of bounds in the slh_dsa verify path. */ + if (!strncmp(sig->method_name, "SLH_DSA", 7)) { + uint8_t *short_sig = OQS_MEM_malloc(1); + if (short_sig == NULL) { + fprintf(stderr, "ERROR: OQS_MEM_malloc failed\n"); + goto err; + } + short_sig[0] = 0; + rc = OQS_SIG_verify(sig, message, message_len, short_sig, 1, public_key); + OQS_MEM_insecure_free(short_sig); + if (rc != OQS_ERROR) { + fprintf(stderr, "ERROR: OQS_SIG_verify accepted a truncated signature\n"); + goto err; + } + } + if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, false, NULL, 0); OQS_TEST_CT_DECLASSIFY(&rc, sizeof(rc)); From 32626703e85a193adfe1f9062cc963271fbc82d3 Mon Sep 17 00:00:00 2001 From: Ramya Eliger Date: Sun, 11 Oct 2026 22:46:17 +0530 Subject: [PATCH 2/2] Bump slhdsa-c pin to d25ccc6 instead of carrying verify_bounds.patch Per review, take the upstream fix for the out-of-bounds read (pq-code-package/slhdsa-c#48, merged as d25ccc6) by bumping the pinned commit in copy_from_slh_dsa_c.py and dropping verify_bounds.patch. The regenerated slh_dsa_c tree also picks up upstream CBMC contract annotations and a sha3_init cleanup; no algorithm behaviour changes. The short-signature regression test in tests/test_sig.c is kept and passes under ASan. Signed-off-by: Ramya Eliger --- ALGORITHMS.md | 2 +- README.md | 2 +- docs/algorithms/sig/slh_dsa.md | 4 +-- docs/algorithms/sig/slh_dsa.yml | 2 +- .../copy_from_upstream/copy_from_slh_dsa_c.py | 2 +- src/sig/slh_dsa/patches/verify_bounds.patch | 32 ------------------- src/sig/slh_dsa/slh_dsa_c/cbmc.h | 4 +++ src/sig/slh_dsa/slh_dsa_c/sha2_256.c | 7 +++- src/sig/slh_dsa/slh_dsa_c/sha3_api.c | 10 +++--- src/sig/slh_dsa/slh_dsa_c/sha3_api.h | 9 +++++- src/sig/slh_dsa/slh_dsa_c/slh_adrs.h | 4 +++ src/sig/slh_dsa/slh_dsa_c/slh_dsa.c | 5 --- 12 files changed, 32 insertions(+), 51 deletions(-) delete mode 100644 src/sig/slh_dsa/patches/verify_bounds.patch diff --git a/ALGORITHMS.md b/ALGORITHMS.md index 61669e524c..9efbda7c04 100644 --- a/ALGORITHMS.md +++ b/ALGORITHMS.md @@ -46,7 +46,7 @@ The **OQS tier** column records OQS's own support commitment for each algorithm. | MAYO | Under [NIST](https://csrc.nist.gov/Projects/pqc-dig-sig/round-3-additional-signatures) consideration | [`PQCMayo/MAYO-C@74de637`](https://github.com/PQCMayo/MAYO-C/commit/74de637a7c4a7f9fafa888183bc05f5cf920cd2d) | [Actively maintained](https://github.com/PQCMayo/MAYO-C) | Tier 3 (Community) | 1, 3, 5 | ✓ | — | portable, ARM64_V8, x86_64 (avx2) | | ML-DSA | Standardized by [NIST](https://csrc.nist.gov/pubs/fips/204/final) | [`pq-code-package/mldsa-native@834a90d`](https://github.com/pq-code-package/mldsa-native/commit/834a90d5e846ffa1e1611bd24e160bb2e9b86d35) | [Actively maintained](https://github.com/pq-code-package/mldsa-native) | Tier 2 (Supported) | 2, 3, 5 | ✓ | — | portable, ARM64_V8, x86_64 (avx2+bmi2+popcnt) | | MQOM | Under [NIST](https://csrc.nist.gov/projects/pqc-dig-sig) consideration | [`mqom/mqom-v3@df26239`](https://github.com/mqom/mqom-v3/commit/df26239dd22f194371003f403ec1a9920772b769) | [Actively maintained](https://github.com/mqom/mqom-v3/blob/main/integration/liboqs/SUPPORT.md) | Tier 3 (Community) | 1, 3, 5 | ✓ | — | portable, ARM64_V8 (sha3), x86_64 (aes+avx2) | -| SLH-DSA | Standardized by [NIST](https://csrc.nist.gov/pubs/fips/205/final) | [`pq-code-package/slhdsa-c@a0fc1ff`](https://github.com/pq-code-package/slhdsa-c/commit/a0fc1ff253930060d0246aebca06c2538eb92b88) | Best effort | Tier 3 (Community) | 1, 3, 5 | — | — | portable | +| SLH-DSA | Standardized by [NIST](https://csrc.nist.gov/pubs/fips/205/final) | [`pq-code-package/slhdsa-c@d25ccc6`](https://github.com/pq-code-package/slhdsa-c/commit/d25ccc6a2c6512dd44cfaf81e44181a3010757b1) | Best effort | Tier 3 (Community) | 1, 3, 5 | — | — | portable | | SNOVA | Under [NIST](https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-3/spec-files/snova-spec-round3-web.pdf) consideration | [`PQCLAB-SNOVA/S@9b29c1e`](https://github.com/PQCLAB-SNOVA/S/commit/9b29c1e332cbd85757c8d6362a57b9a8c8298a38) | Best effort | Tier 3 (Community) | 1, 3, 5 | ✓ | — | portable, ARM64_V8, x86_64 (avx2) | | UOV | Under [NIST](https://csrc.nist.gov/Projects/pqc-dig-sig/round-3-additional-signatures) consideration | [`pqov/pqov@fac2ec1`](https://github.com/pqov/pqov/commit/fac2ec1fe6d8ea508eab4b98eb5341e1eed930a4) | Best effort | Tier 3 (Community) | 1, 3, 5 | ✓ | — | portable, ARM64_V8, x86_64 (avx2) | diff --git a/README.md b/README.md index 6fbb527344..4b716fc645 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Names of algorithms standardized by NIST — [`ML-KEM`](https://csrc.nist.gov/pu | MAYO | Under [NIST](https://csrc.nist.gov/Projects/pqc-dig-sig/round-3-additional-signatures) consideration | [`PQCMayo/MAYO-C@74de637`](https://github.com/PQCMayo/MAYO-C/commit/74de637a7c4a7f9fafa888183bc05f5cf920cd2d) | [Actively maintained](https://github.com/PQCMayo/MAYO-C) | Tier 3 (Community) | | ML-DSA | Standardized by [NIST](https://csrc.nist.gov/pubs/fips/204/final) | [`pq-code-package/mldsa-native@834a90d`](https://github.com/pq-code-package/mldsa-native/commit/834a90d5e846ffa1e1611bd24e160bb2e9b86d35) | [Actively maintained](https://github.com/pq-code-package/mldsa-native) | Tier 2 (Supported) | | MQOM | Under [NIST](https://csrc.nist.gov/projects/pqc-dig-sig) consideration | [`mqom/mqom-v3@df26239`](https://github.com/mqom/mqom-v3/commit/df26239dd22f194371003f403ec1a9920772b769) | [Actively maintained](https://github.com/mqom/mqom-v3/blob/main/integration/liboqs/SUPPORT.md) | Tier 3 (Community) | -| SLH-DSA | Standardized by [NIST](https://csrc.nist.gov/pubs/fips/205/final) | [`pq-code-package/slhdsa-c@a0fc1ff`](https://github.com/pq-code-package/slhdsa-c/commit/a0fc1ff253930060d0246aebca06c2538eb92b88) | Best effort | Tier 3 (Community) | +| SLH-DSA | Standardized by [NIST](https://csrc.nist.gov/pubs/fips/205/final) | [`pq-code-package/slhdsa-c@d25ccc6`](https://github.com/pq-code-package/slhdsa-c/commit/d25ccc6a2c6512dd44cfaf81e44181a3010757b1) | Best effort | Tier 3 (Community) | | SNOVA | Under [NIST](https://csrc.nist.gov/csrc/media/Projects/pqc-dig-sig/documents/round-3/spec-files/snova-spec-round3-web.pdf) consideration | [`PQCLAB-SNOVA/S@9b29c1e`](https://github.com/PQCLAB-SNOVA/S/commit/9b29c1e332cbd85757c8d6362a57b9a8c8298a38) | Best effort | Tier 3 (Community) | | UOV | Under [NIST](https://csrc.nist.gov/Projects/pqc-dig-sig/round-3-additional-signatures) consideration | [`pqov/pqov@fac2ec1`](https://github.com/pqov/pqov/commit/fac2ec1fe6d8ea508eab4b98eb5341e1eed930a4) | Best effort | Tier 3 (Community) | diff --git a/docs/algorithms/sig/slh_dsa.md b/docs/algorithms/sig/slh_dsa.md index 2b323f281d..ae99fa39b1 100644 --- a/docs/algorithms/sig/slh_dsa.md +++ b/docs/algorithms/sig/slh_dsa.md @@ -7,11 +7,11 @@ - **Authors' website**: https://csrc.nist.gov/pubs/fips/205/final - **Specification version**: SLH-DSA. - **Primary Source**: - - **Source**: https://github.com/pq-code-package/slhdsa-c/commit/a0fc1ff253930060d0246aebca06c2538eb92b88 + - **Source**: https://github.com/pq-code-package/slhdsa-c/commit/d25ccc6a2c6512dd44cfaf81e44181a3010757b1 - **Implementation license (SPDX-Identifier)**: MIT or ISC or Apache 2.0 - **Optimized Implementation sources**: - **slhdsa-c**: - - **Source**: https://github.com/pq-code-package/slhdsa-c/commit/a0fc1ff253930060d0246aebca06c2538eb92b88 + - **Source**: https://github.com/pq-code-package/slhdsa-c/commit/d25ccc6a2c6512dd44cfaf81e44181a3010757b1 - **Implementation license (SPDX-Identifier)**: MIT or ISC or Apache 2.0 diff --git a/docs/algorithms/sig/slh_dsa.yml b/docs/algorithms/sig/slh_dsa.yml index 904121c073..58b1df78bd 100644 --- a/docs/algorithms/sig/slh_dsa.yml +++ b/docs/algorithms/sig/slh_dsa.yml @@ -32,7 +32,7 @@ oqs-support-tier: 3 spec-version: SLH-DSA spdx-license-identifier: MIT or ISC or Apache 2.0 primary-upstream: - source: https://github.com/pq-code-package/slhdsa-c/commit/a0fc1ff253930060d0246aebca06c2538eb92b88 + source: https://github.com/pq-code-package/slhdsa-c/commit/d25ccc6a2c6512dd44cfaf81e44181a3010757b1 spdx-license-identifier: MIT or ISC or Apache 2.0 upstream-ancestors: - https://github.com/pq-code-package/slhdsa-c diff --git a/scripts/copy_from_upstream/copy_from_slh_dsa_c.py b/scripts/copy_from_upstream/copy_from_slh_dsa_c.py index d332802747..2eaa83c6ef 100644 --- a/scripts/copy_from_upstream/copy_from_slh_dsa_c.py +++ b/scripts/copy_from_upstream/copy_from_slh_dsa_c.py @@ -250,7 +250,7 @@ def main(): alg_support_path, oqsconfig_path # This commit hash will need to be updated - commit_hash = "a0fc1ff253930060d0246aebca06c2538eb92b88" + commit_hash = "d25ccc6a2c6512dd44cfaf81e44181a3010757b1" # internal paths slh_dir = os.path.join(os.environ['LIBOQS_DIR'], 'src/sig/slh_dsa') diff --git a/src/sig/slh_dsa/patches/verify_bounds.patch b/src/sig/slh_dsa/patches/verify_bounds.patch deleted file mode 100644 index 8dc54b90e9..0000000000 --- a/src/sig/slh_dsa/patches/verify_bounds.patch +++ /dev/null @@ -1,32 +0,0 @@ -diff --git a/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c b/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c -index 07052e7..7d5244a 100644 ---- a/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c -+++ b/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c -@@ -688,6 +688,13 @@ OQS_API int slh_verify_internal(const uint8_t *m, size_t m_sz, const uint8_t *si - slh_var_t var; - uint8_t digest[SLH_MAX_M]; - -+ /* Reject a wrong-length signature before Hmsg reads the n-byte randomizer R -+ from sig; a shorter sig would otherwise be read out of bounds. */ -+ if (sig_sz != slh_sig_sz(prm)) -+ { -+ return 0; /* false */ -+ } -+ - /* use Hmsg directly */ - prm->mk_var(&var, pk, NULL, prm); - prm->h_msg(&var, digest, sig, m, m_sz, NULL, SLH_CTX_SZ_NO_CONTEXT); -@@ -710,6 +717,13 @@ int slh_verify(const uint8_t *m, size_t m_sz, const uint8_t *sig, size_t sig_sz, - return 0; /* false */ - } - -+ /* Reject a wrong-length signature before Hmsg reads the n-byte randomizer R -+ from sig; a shorter sig would otherwise be read out of bounds. */ -+ if (sig_sz != slh_sig_sz(prm)) -+ { -+ return 0; /* false */ -+ } -+ - /* create the "pure" hash (with context) */ - prm->mk_var(&var, pk, NULL, prm); - prm->h_msg(&var, digest, sig, m, m_sz, ctx, ctx_sz); diff --git a/src/sig/slh_dsa/slh_dsa_c/cbmc.h b/src/sig/slh_dsa/slh_dsa_c/cbmc.h index 452390f6ce..83046fca70 100644 --- a/src/sig/slh_dsa/slh_dsa_c/cbmc.h +++ b/src/sig/slh_dsa/slh_dsa_c/cbmc.h @@ -138,6 +138,10 @@ #define array_abs_bound(arr, lb, ub, k) \ array_bound((arr), (lb), (ub), -((int)(k)) + 1, (k)) +#define VALID_SLH_VAR_T(var) \ + (memory_no_alias(var, sizeof(slh_var_t)) && memory_no_alias(var->prm, sizeof(slh_param_t)) && \ + memory_no_alias(var->adrs, sizeof(adrs_t))) + #endif /* CBMC */ #endif /* !SLH_CBMC_H */ diff --git a/src/sig/slh_dsa/slh_dsa_c/sha2_256.c b/src/sig/slh_dsa/slh_dsa_c/sha2_256.c index 1102f0e472..c4bc36b7a0 100644 --- a/src/sig/slh_dsa/slh_dsa_c/sha2_256.c +++ b/src/sig/slh_dsa/slh_dsa_c/sha2_256.c @@ -142,7 +142,12 @@ void sha2_256_compress(void *v) /* initialize */ -static void sha2_256_init_h0(sha2_256_t *sha, const uint8_t *h0) +static void sha2_256_init_h0(sha2_256_t *sha, const uint8_t h0[32]) +__contract__( + requires(memory_no_alias(sha, sizeof(sha2_256_t))) + requires(memory_no_alias(h0, 32)) + assigns(object_whole(sha)) +) { memcpy(sha->s, h0, 32); sha->i = 0; diff --git a/src/sig/slh_dsa/slh_dsa_c/sha3_api.c b/src/sig/slh_dsa/slh_dsa_c/sha3_api.c index e756fb3460..22d7d9e62a 100644 --- a/src/sig/slh_dsa/slh_dsa_c/sha3_api.c +++ b/src/sig/slh_dsa/slh_dsa_c/sha3_api.c @@ -5,19 +5,17 @@ /* === FIPS 202: SHA-3 hash and SHAKE eXtensible Output Functions (XOF) */ +#include #include "sha3_api.h" +#include "cbmc.h" /* These functions have not been optimized for performance. */ /* initialize the context for SHA3 */ void sha3_init(sha3_var_t *c, size_t md_sz) { - size_t i; - - for (i = 0; i < 25; i++) - { - c->st.d[i] = 0; - } + memset(c->st.d, 0, 25*sizeof(uint64_t)); + c->md_sz = md_sz; /* in SHAKE; if 0, padding done */ c->r_sz = 200 - 2 * md_sz; c->pt = 0; diff --git a/src/sig/slh_dsa/slh_dsa_c/sha3_api.h b/src/sig/slh_dsa/slh_dsa_c/sha3_api.h index a47baa8179..6c1a4ec084 100644 --- a/src/sig/slh_dsa/slh_dsa_c/sha3_api.h +++ b/src/sig/slh_dsa/slh_dsa_c/sha3_api.h @@ -15,6 +15,7 @@ extern "C" #include #include +#include "cbmc.h" typedef struct { /* state context */ @@ -31,7 +32,13 @@ extern "C" /* incremental interfece */ void sha3_init(sha3_var_t *c, - size_t md_sz); /* md_sz = hash output in bytes */ + size_t md_sz) /* md_sz = hash output in bytes */ + __contract__( + requires(memory_no_alias(c, sizeof(sha3_var_t))) + requires(md_sz <= 64) + assigns(object_whole(c)) + ); + void sha3_update(sha3_var_t *c, const void *data, size_t data_sz); void sha3_final(sha3_var_t *c, uint8_t *md); /* digest goes to md */ diff --git a/src/sig/slh_dsa/slh_dsa_c/slh_adrs.h b/src/sig/slh_dsa/slh_dsa_c/slh_adrs.h index 8bd0ac86cb..2799934d09 100644 --- a/src/sig/slh_dsa/slh_dsa_c/slh_adrs.h +++ b/src/sig/slh_dsa/slh_dsa_c/slh_adrs.h @@ -126,6 +126,10 @@ static SLH_INLINE uint32_t adrs_get_tree_index(const slh_var_t *var) /* === Set WOTS+ hash address. */ static SLH_INLINE void adrs_set_hash_address(slh_var_t *var, uint32_t x) +__contract__( + requires(VALID_SLH_VAR_T(var)) + assigns(var->adrs->u32[7]) +) { var->adrs->u32[7] = rev8_be32(x); } diff --git a/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c b/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c index 7d5244ab4b..afc446239e 100644 --- a/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c +++ b/src/sig/slh_dsa/slh_dsa_c/slh_dsa.c @@ -688,13 +688,10 @@ OQS_API int slh_verify_internal(const uint8_t *m, size_t m_sz, const uint8_t *si slh_var_t var; uint8_t digest[SLH_MAX_M]; - /* Reject a wrong-length signature before Hmsg reads the n-byte randomizer R - from sig; a shorter sig would otherwise be read out of bounds. */ if (sig_sz != slh_sig_sz(prm)) { return 0; /* false */ } - /* use Hmsg directly */ prm->mk_var(&var, pk, NULL, prm); prm->h_msg(&var, digest, sig, m, m_sz, NULL, SLH_CTX_SZ_NO_CONTEXT); @@ -717,8 +714,6 @@ int slh_verify(const uint8_t *m, size_t m_sz, const uint8_t *sig, size_t sig_sz, return 0; /* false */ } - /* Reject a wrong-length signature before Hmsg reads the n-byte randomizer R - from sig; a shorter sig would otherwise be read out of bounds. */ if (sig_sz != slh_sig_sz(prm)) { return 0; /* false */