# Klever: Integer overflow in split-royalty validation enables unbounded minting of KLV (native token)

uint32 sum overflow in Klever KDA split-royalty validation lets two 0x80000000 entries pass, minting 429,496 KLV from a 1 KLV royalty on transfer.

- Payout: Research
- Severity: critical
- Program: Klever
- Found by: nickgs1337
- Disclosed: 2026-08-28
- CVE: CVE-2026-54755
- Write-up quality: 9/10
- Original: https://github.com/advisories/GHSA-cgc5-v3f2-8m2v
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-cgc5-v3f2-8m2v
- Tags: area:web3 domain:web3 impact:funds-loss impact:integrity kind:research platform:ghsa sev:critical tech:go technique:source-review vuln:accounting-error vuln:business-logic vuln:reward-distribution

**Target:** klever-go node: KDA royalty split validation and payout (transfer/market/ITO)
**Root cause:** Split royalty percentages are decoded as raw uint32 with no per-entry bound, and validation sums them in a uint32 accumulator, so two entries of 0x80000000 wrap the sum to 0 and pass CheckValid100Params. Payout then credits pool*pct/10000 per split with no matching debit and silently drops the negative remainder.
**Steps:**
1. Build node/operator/keygenerator from klever-go, configure genesis with a funded wallet, start single node with REST on 127.0.0.1:8080.
2. Create throwaway asset with two split royalties each percentTransferFixed=21474836.48 (stored as 0x80000000) and royaltiesTransferFixed=1: `/tmp/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` — returns resultCode Ok.
3. Trigger with one ordinary transfer: `/tmp/kloperator account send "$R2" 1 --kda KPRT2-<id> -s --await`.
4. Observe two KLV Transfer receipts of 214748364800 (214,748.36 KLV) each to R1 and R2 for a 1 KLV (1000000) fixed royalty; verify R1 balance via `curl -s http://127.0.0.1:8080/address/$R1`.
**How found:** Code review of royalty validation: noticed decodeSplitInfo reads split percentages as raw uint32 with no <= HundredPercent check, unlike the TransferPercentage tier check at create.go:398/trigger.go:780, and that create.go accumulates sums in a uint32. Hypothesized two 0x80000000 entries wrap the sum to 0 and pass CheckValid100Params, then traced payout in accounts.go to confirm over-credit with silent negative-remainder drop.
**Exploitation:** Attacker pays one-time asset creation fee, plants two split royalties of 0x80000000 each (sum wraps to 0, passing validation), then performs a single transfer of the throwaway asset. Sender is debited only the 1 KLV fixed royalty; each split recipient is credited pool*0x80000000/10000 = 214,748.36 KLV via AddToBalance with no matching debit, minting ~429,496.73 KLV per transfer. Repeatable per tx for only tx fees; booked CirculatingSupply/MintedValue unchanged so inflation is invisible to supply dashboards.
**Escalation:** Same root cause reachable via marketplace buy (sale currency and KLV MarketFixed deposit) and ITO buy, and the transfer-percentage path mints arbitrary assets. A smart contract can plant malicious royalties via AssetTrigger/UpdateRoyalties and let a normal account trigger the mint atomically in one tx.
**Insight:** When a validator checks only a sum of per-entry values, look for a narrower accumulator type than the entries themselves: two entries each just over half the type's max wrap the sum to a small passing value. Also check whether payout sites silently discard negative remainders (`if royaltiesToPay <= 0 { return Ok }`) — that turns an over-payment into an unbacked mint, and direct AddToBalance credits bypass booked supply accounting.

## Proof of concept (copied exactly from the original report)

**Asset creation with overflowing split royalties**

```
/tmp/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
```

**Transfer that mints KLV via overflowed royalty**

```
/tmp/kloperator account send "$R2" 1 --kda KPRT2-<id> -s --await
**Expected:** `resultCode: Ok`, with two **KLV** transfer receipts of `214748364800`
(= 214,748.36 KLV) to R1 and R2 — for a `TransferFixed` royalty of `1000000` (1 KLV).

### Verify the mint
# R1 KLV balance went from 0 to 214,748.36 although nobody sent it KLV:
curl -s "http://127.0.0.1:8080/address/$R1" | python3 -c \
 "import sys,json;print(json.load(sys.stdin)['data']['account']['Balance']/1e6,'KLV')"
```

