# Wrong handling of call data check indices, forcing it sometimes to revert

Kleidi Timelock computes calldata-check slice length as end-start instead of end-start+1, so whitelisted calls with full-byte params revert.

- Payout: Audit contest
- Severity: medium
- Program: Kleidi
- Credited: 0xAlix2
- Disclosed: 2024-11-20
- Write-up quality: 6/10
- Original: https://code4rena.com/reports/2024-10-kleidi#m-02
- Library page: https://bugbountylibrary.com/r/c4%3A2024-10-kleidi%3AM-02
- Tags: domain:web3 impact:dos kind:contest platform:code4rena sev:medium tech:solidity technique:regression technique:source-review vuln:accounting-error

**Target:** Timelock.sol / BytesHelper.sol calldata check index handling
**Root cause:** Slice length is computed as endIndex - startIndex instead of endIndex - startIndex + 1, and the same off-by-one is enforced in _addCalldataCheck's data length require, so indices must be shifted to compensate.
**Steps:**
1. Whitelist an ERC20.approve call with amount = type(uint256).max via addCalldataCheck on spender (16..36) and amount (37..69).
2. Trigger executeWhitelisted with abi.encodeWithSelector(IERC20.approve.selector, owner, type(uint256).max).
3. Observe revert 'End index is greater than the length of the byte string' because amount bytes length is 68 but end index is 69.
4. With mitigation (+1), correct indices 16..35 and 36..67 succeed and allowance is set.
**How found:** Code reading of sliceBytes and _addCalldataCheck noticed length = end - start lacks +1; hypothesized failure when a checked parameter fills all its bytes (uint256 max), confirmed with a Foundry PoC.
**Insight:** When auditing index-based calldata slicing/whitelisting, test boundary values that fill the entire field (type(uint256).max, all-0xff bytes) since off-by-one length math only manifests at full-width parameters.
