Describe the bug
A critical Double-Spending / Infinite Mint vulnerability exists in the x/nft-transfer module (ICS-721) of the Cronos POS Chain. The IBC packet processing is non-atomic due to a missing sdk.CacheContext in the packet handler. During a multi-NFT batch transfer, partial failures (e.g., deterministic gas exhaustion) cause some NFTs to be permanently minted on the destination chain while the source chain refunds all original assets, effectively cloning NFTs.
Severity: Extreme (Mainnet protocol-level impact)
Affected Asset: Cronos POS Chain (Mainnet Core)
Repository Reference: Cronos chain-main GitHub
To Reproduce
Steps to reproduce the behavior:
- Select a high-value NFT collection supported by the Cronos IBC bridge.
- Construct an IBC NonFungibleTokenPacketData containing a batch of 50 Token IDs (replace with actual IDs if available).
- Profile the deterministic gas cost for a single MintNFT (~10k–15k gas per iteration).
- Send an IBC transaction with a Gas Limit set to fail mid-loop (e.g., at the 25th NFT).
- Observe:
Destination chain: first NFTs are permanently minted
Source chain: refunds all original NFTs
Result: Duplicate NFTs exist → Double-Spending / Infinite Min
Expected behavior
The IBC transaction should be atomic: either all NFTs are minted on the destination chain, or none. Partial commits must never occur.
Screenshots / PoC
.Attach evidence showing:
.artial NFT minting
.Source chain refund
.Duplicate NFTs on Cronos
.Optional: attach exploit_poc.go or TX Hashes demonstrating the issue
Desktop (please complete the following information):
OS: Kali Linux / Ubuntu
Browser: Chrome / Firefox
Version: latest
Smartphone (optional):
Device / OS / Browser: N/A (if applicable)
Additional context
Additional context Vulnerable Code:
// x/nft-transfer/ibc_module.go:169
if err := im.keeper.OnRecvPacket(ctx, channelVersion, packet, data); err != nil {
ack = types.NewErrorAcknowledgement(err)
// ERROR CAUGHT BUT NO ROLLBACK PERFORMED
}
// x/nft-transfer/keeper/packet.go:180-184
for i, tokenID := range data.TokenIds {
// Each successful call here is PERMANENTLY COMMITTED to the KVStore
if err := k.nftKeeper.MintNFT(ctx, voucherClassID, tokenID, ..., receiver); err != nil {
return err // Partial failure aborts loop, but previous changes persist
}
}
Proof of Concept:
.Shows state inconsistency: NFTs minted on Cronos while refunded on source chain
.TX Hashes / Token IDs can be added for verification
.Screenshots / demonstrate duplication
Recommended Fix:
cacheCtx, writeCache := ctx.CacheContext()
err := im.keeper.OnRecvPacket(cacheCtx, packet, data)
if err == nil {
writeCache() // Only commit if the entire batch succeeds
}
Next Steps for Devs:
Next Steps for Devs:
- Validate PoC on Testnet / Localnet
- Apply atomic execution fix with CacheContext
- Ensure IBC packets either fully succeed or fully rollback

Describe the bug
A critical Double-Spending / Infinite Mint vulnerability exists in the x/nft-transfer module (ICS-721) of the Cronos POS Chain. The IBC packet processing is non-atomic due to a missing sdk.CacheContext in the packet handler. During a multi-NFT batch transfer, partial failures (e.g., deterministic gas exhaustion) cause some NFTs to be permanently minted on the destination chain while the source chain refunds all original assets, effectively cloning NFTs.
Severity: Extreme (Mainnet protocol-level impact)
Affected Asset: Cronos POS Chain (Mainnet Core)
Repository Reference: Cronos chain-main GitHub
To Reproduce
Steps to reproduce the behavior:
Destination chain: first NFTs are permanently minted
Source chain: refunds all original NFTs
Result: Duplicate NFTs exist → Double-Spending / Infinite Min
Expected behavior
The IBC transaction should be atomic: either all NFTs are minted on the destination chain, or none. Partial commits must never occur.
Screenshots / PoC
.Attach evidence showing:
.artial NFT minting
.Source chain refund
.Duplicate NFTs on Cronos
.Optional: attach exploit_poc.go or TX Hashes demonstrating the issue
Desktop (please complete the following information):
OS: Kali Linux / Ubuntu
Browser: Chrome / Firefox
Version: latest
Smartphone (optional):
Device / OS / Browser: N/A (if applicable)
Additional context
Additional context Vulnerable Code:
// x/nft-transfer/ibc_module.go:169
if err := im.keeper.OnRecvPacket(ctx, channelVersion, packet, data); err != nil {
ack = types.NewErrorAcknowledgement(err)
// ERROR CAUGHT BUT NO ROLLBACK PERFORMED
}
// x/nft-transfer/keeper/packet.go:180-184
for i, tokenID := range data.TokenIds {
// Each successful call here is PERMANENTLY COMMITTED to the KVStore
if err := k.nftKeeper.MintNFT(ctx, voucherClassID, tokenID, ..., receiver); err != nil {
return err // Partial failure aborts loop, but previous changes persist
}
}
Proof of Concept:
.Shows state inconsistency: NFTs minted on Cronos while refunded on source chain
.TX Hashes / Token IDs can be added for verification
.Screenshots / demonstrate duplication
Recommended Fix:
cacheCtx, writeCache := ctx.CacheContext()
err := im.keeper.OnRecvPacket(cacheCtx, packet, data)
if err == nil {
writeCache() // Only commit if the entire batch succeeds
}
Next Steps for Devs:
Next Steps for Devs: