From a8915366b6afa0e4b987be7149611f1fcefa2fb4 Mon Sep 17 00:00:00 2001 From: nullun Date: Wed, 10 Jun 2026 13:23:34 +0100 Subject: [PATCH 1/3] Length handling for compressed det1024 signatures Add a couple of small length checks when working with compressed det1024 signatures, and tidy up the ordering of the bounds check in falcon_det1024_verify_compressed. Mirrors the existing checks on both the C and Go sides. falcon_det1024_convert_compressed_to_ct only checked that comp_decode succeeded, not that it consumed the entire signature, so a valid compressed signature with arbitrary trailing bytes would still convert to a valid CT signature. Enforce exact consumption, matching the check falcon_verify applies to compressed signatures. Add tests covering the rejection paths for signatures too short to contain a header and salt version, and for signatures with trailing bytes, in both Verify and ConvertToCT. --- deterministic.c | 15 ++++++++++++--- falcon.go | 4 ++++ falcon_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/deterministic.c b/deterministic.c index f912683..9afc89f 100644 --- a/deterministic.c +++ b/deterministic.c @@ -85,6 +85,10 @@ int falcon_det1024_convert_compressed_to_ct(void *sig_ct, int16_t coeffs[1 << FALCON_DET1024_LOGN]; size_t v; + if (sig_compressed_len < 2) { + return FALCON_ERR_BADSIG; + } + if (((uint8_t*)sig_compressed)[0] != FALCON_DET1024_SIG_COMPRESSED_HEADER) { return FALCON_ERR_BADSIG; } @@ -95,6 +99,12 @@ int falcon_det1024_convert_compressed_to_ct(void *sig_ct, return FALCON_ERR_SIZE; } + // Reject trailing bytes, matching the exact-consumption check + // that falcon_verify applies to compressed signatures. + if (v != sig_compressed_len-2) { + return FALCON_ERR_BADSIG; + } + uint8_t *sig = sig_ct; sig[0] = FALCON_DET1024_SIG_CT_HEADER; sig[1] = ((uint8_t*)sig_compressed)[1]; // Copy the salt_version byte. @@ -133,12 +143,11 @@ int falcon_det1024_verify_compressed(const void *sig, size_t sig_len, } // Add back the salt; drop the version byte. - size_t salted_sig_len = sig_len + 40 - 1; - - if (salted_sig_len > FALCON_DET1024_SALTED_SIG_COMPRESSED_MAXSIZE){ + if (sig_len - 1 > FALCON_DET1024_SALTED_SIG_COMPRESSED_MAXSIZE - 40) { return FALCON_ERR_BADSIG; } + size_t salted_sig_len = sig_len + 40 - 1; falcon_det1024_resalt(salted_sig, sig, sig_len); diff --git a/falcon.go b/falcon.go index 055ab5c..912ad8b 100644 --- a/falcon.go +++ b/falcon.go @@ -119,6 +119,10 @@ func (sk *PrivateKey) SignCompressed(msg []byte) (CompressedSignature, error) { func (sig *CompressedSignature) ConvertToCT() (CTSignature, error) { sigCT := CTSignature{} + if len(*sig) < 2 { + return CTSignature{}, fmt.Errorf("signature too short: %w", ErrConvertFail) + } + r := C.falcon_det1024_convert_compressed_to_ct(unsafe.Pointer(&sigCT[0]), unsafe.Pointer(&(*sig)[0]), C.size_t(len(*sig))) if r != 0 { return CTSignature{}, fmt.Errorf("error code %d: %w", int(r), ErrConvertFail) diff --git a/falcon_test.go b/falcon_test.go index 581a19f..7931588 100644 --- a/falcon_test.go +++ b/falcon_test.go @@ -422,3 +422,47 @@ func BenchmarkFalconVerify(b *testing.B) { pk.Verify(sigs[i], strs[i][:]) } } + +func TestFalconMalformedSignatures(t *testing.T) { + seed := make([]byte, 64) + rand.Read(seed) + + pub, priv, err := GenerateKey(seed) + if err != nil { + t.Fatalf("failed to generate keys. err message: %s", err) + } + + msg := make([]byte, 64) + rand.Read(msg) + + sig, err := priv.SignCompressed(msg) + if err != nil { + t.Fatalf("failed to sign message. err message: %s", err) + } + + // A signature shorter than the 2-byte header and salt-version prefix must be rejected. + for _, short := range []CompressedSignature{nil, {}, sig[:0], sig[:1]} { + err = pub.Verify(short, msg) + if err == nil { + t.Fatalf("expected verify to fail on %d-byte signature", len(short)) + } + + _, err = short.ConvertToCT() + if err == nil { + t.Fatalf("expected ConvertToCT to fail on %d-byte signature", len(short)) + } + } + + // A valid signature with trailing bytes appended must be rejected. + trailing := append(append(CompressedSignature{}, sig...), 0) + + err = pub.Verify(trailing, msg) + if err == nil { + t.Fatalf("expected verify to fail on signature with trailing bytes") + } + + _, err = trailing.ConvertToCT() + if err == nil { + t.Fatalf("expected ConvertToCT to fail on signature with trailing bytes") + } +} From 061acc790ecc0f681dad4f76b4d9b6e6a5451187 Mon Sep 17 00:00:00 2001 From: nullun Date: Wed, 10 Jun 2026 14:13:22 +0100 Subject: [PATCH 2/3] Make sigs_ct static to silence macOS linker alignment warning The ~769 KiB sigs_ct array was a non-static tentative definition, so clang emitted it as a common symbol and ld64 requested 32 KiB alignment for it, exceeding the 16 KiB segment maximum on arm64 macOS: ld: warning: reducing alignment of section __DATA,__common from 0x8000 to 0x4000 because it exceeds segment maximum alignment The array is only used in this file, so make it static, which places it in __bss with ordinary alignment and avoids the warning. --- tests/test_deterministic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_deterministic.c b/tests/test_deterministic.c index bbf6b3e..35be483 100644 --- a/tests/test_deterministic.c +++ b/tests/test_deterministic.c @@ -62,7 +62,7 @@ hextobin(uint8_t *buf, size_t max_len, const char *src) } } -uint8_t sigs_ct[NUM_KATS][FALCON_DET1024_SIG_CT_SIZE]; +static uint8_t sigs_ct[NUM_KATS][FALCON_DET1024_SIG_CT_SIZE]; void test_inner(size_t data_len) { uint8_t pubkey[FALCON_DET1024_PUBKEY_SIZE]; From b58119d42f53f3fe119ec7a4847989fab937e3e4 Mon Sep 17 00:00:00 2001 From: nullun Date: Wed, 17 Jun 2026 17:22:59 +0100 Subject: [PATCH 3/3] Make falcon_det1024_salt_rest static and const --- deterministic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deterministic.c b/deterministic.c index 9afc89f..0526720 100644 --- a/deterministic.c +++ b/deterministic.c @@ -22,7 +22,7 @@ int falcon_det1024_keygen(shake256_context *rng, void *privkey, void *pubkey) { } // Domain separator used to construct the fixed versioned salt string. -uint8_t falcon_det1024_salt_rest[38] = {"FALCON_DET"}; +static const uint8_t falcon_det1024_salt_rest[38] = {"FALCON_DET"}; // Construct the fixed salt for a given version. void falcon_det1024_write_salt(uint8_t dst[40], uint8_t salt_version) {