GHSA-9wh6-9hq7-9688: Input Validation
Location: core/kapp/validators/validators.go:201 (Register), (genesis/checking/nodesSetupChecker.go:73). core/consensus/slot/bls/subslotStartSlot.go:165 core/consensus/.../headerSignatureVerify.go:123 (Create(...)).
Description
Klever uses a BDN (Boneh-Drijvers-Neven) BLS multi-signature over BLS12-381 to finalize blocks (crypto/signing/mcl/multisig/bls.go, herumi/bls-go-binary). The library is initialized with only bls.Init(bls.BLS12381) and no order-verification flags, so Deserialize does not enforce prime-order-subgroup membership subgroup safety relies on explicit IsValidOrder() calls in the wrappers.
Runtime validator registration (validators.Register) stores the submitted 96-byte BLSPublicKey via SetBLSPublicKey with no curve check, no subgroup check, and no proof-of-possession (CreateValidatorContract carries the key but no signature proving key ownership; grep confirms no proofOfPossession/VerifyProof/BLSSignature verification anywhere in core/kapp/validators/). CheckPublicKeyValid — which does IsValid && IsValidOrder && !IsZero — runs only at genesis, not on runtime registration/update.
POC
Attack: (1) stake the minimum to register a validator, submitting a 96-byte BLSPublicKey that is not a valid G2 point (arbitrary bytes). Registration succeeds. (2) Once the validator is eligible and selected into a consensus group, every in-group node including the honest leader (group[0]) calls MultiSigner().Reset(groupPubKeys, selfIndex) at slot start, which deserializes all group keys via PublicKeyFromByteArray → herumi Deserialize deterministically fails on the malformed key → SetSlotCanceled(true). The verify side (Create(consensusPubKeys)) fails identically. No block is produced for that round.
Impact
Every consensus round whose group contains the malformed-key validator is a missed slot. One eligible bad-key validator poisons roughly a groupSize / eligibleSet fraction of rounds → sustained liveness degradation. Where the consensus group equals the eligible set (small or early-stage networks), this is a full chain halt. Cost is the minimum validator stake, permissionless, and repeatable; no fork flag gates the missing validation.
Rated High (Byzantine liveness; Critical on small validator sets). The severity scales down to a fractional missed-slot / throughput-degradation attack on a large validator set where the bad validator is only occasionally in the active group.
Recommendation
Enforce CheckPublicKeyValid (curve + prime-order subgroup + non-zero) on the submitted BLS key at runtime validator registration and config-update, and ideally require a proof-of-possession (a BLS signature over the validator's own key/identity) at registration to prove key ownership and well-formedness. Gate the stricter validation behind an epoch fork flag for reprocessing consistency.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/klever-io/klever-goto a version that resolves this vulnerability.Fixed in 1.7.20 - Configuration
Gate the stricter BLS public-key validation behind an epoch fork flag so the validation change is applied consistently during reprocessing.
epoch fork flag = enabled - Compensating control
Enforce CheckPublicKeyValid on submitted BLS public keys during runtime validator registration and configuration updates; require curve validity, prime-order subgroup membership via IsValidOrder, and a non-zero key.
- Compensating control
Require proof-of-possession at validator registration using a BLS signature over the validator's own public key or identity, and verify the proof before accepting the validator.
Event History
Frequently Asked Questions
Are validators defined at genesis affected in the same way as validators registered later?
Genesis validation calls CheckPublicKeyValid, which checks that the key is valid, in the correct subgroup, and nonzero. Those checks are not performed for runtime validator registration or update.
What does an attacker need to submit through the runtime registration path?
The runtime registration path accepts a submitted 96-byte BLS public key and stores it through SetBLSPublicKey. It does not perform a curve check, subgroup check, or proof-of-possession verification.
Does the BLS library configuration itself enforce subgroup membership during deserialization?
No. The library is initialized only with bls.Init(bls.BLS12_381) and without order-verification flags, so Deserialize does not enforce prime-order-subgroup membership.
Is proof of ownership for a validator BLS key checked when creating a validator?
No. CreateValidatorContract carries the public key without a signature proving ownership, and the provided data identifies no proof-of-possession or BLS signature verification in the validator code.