I think LegacyECChain.UnmarshalCBOR is using the default cbor-gen slice cap, but ECChain has a much smaller valid max length. I am not claiming this bypasses pubsub size limits, zstd limits, or consensus validation. It just looks like the CBOR decoder accepts and allocates a chain length that ECChain.Validate() already treats as invalid.
The generated decoder in gpbft/cbor_gen.go rejects only arrays above 8192:
if extra > 8192 {
return fmt.Errorf("(*t): array too large (%d)", extra)
}
It then allocates make([]TipSet, extra).
But in gpbft/chain.go, ChainMaxLen is 128, and ECChain.Validate() rejects longer chains with chain too long.
I checked this locally with a small test:
- create a
LegacyECChain with ChainMaxLen + 1 tipsets
- marshal it
- decode it into
ECChain
- call
Validate()
Current behavior is that decode succeeds, the decoded chain has length 129, and only then does Validate() reject it.
This matters because decode happens before validation in the paths I checked, including GPBFT pubsub (host.go) and chainexchange pubsub (chainexchange/pubsub.go). Finality certificate decode also goes through ECChain.UnmarshalCBOR before later certificate/chain validation.
Since gpbft/cbor_gen.go is generated from gen/main.go, I am not sure what patch shape you would prefer. But I would expect LegacyECChain decode to reject extra > ChainMaxLen before allocating.
I think
LegacyECChain.UnmarshalCBORis using the default cbor-gen slice cap, butECChainhas a much smaller valid max length. I am not claiming this bypasses pubsub size limits, zstd limits, or consensus validation. It just looks like the CBOR decoder accepts and allocates a chain length thatECChain.Validate()already treats as invalid.The generated decoder in
gpbft/cbor_gen.gorejects only arrays above 8192:It then allocates
make([]TipSet, extra).But in
gpbft/chain.go,ChainMaxLenis 128, andECChain.Validate()rejects longer chains withchain too long.I checked this locally with a small test:
LegacyECChainwithChainMaxLen + 1tipsetsECChainValidate()Current behavior is that decode succeeds, the decoded chain has length 129, and only then does
Validate()reject it.This matters because decode happens before validation in the paths I checked, including GPBFT pubsub (
host.go) and chainexchange pubsub (chainexchange/pubsub.go). Finality certificate decode also goes throughECChain.UnmarshalCBORbefore later certificate/chain validation.Since
gpbft/cbor_gen.gois generated fromgen/main.go, I am not sure what patch shape you would prefer. But I would expectLegacyECChaindecode to rejectextra > ChainMaxLenbefore allocating.