Missing Input Validation
Detects functions that accept user-supplied parameters and use them in sensitive operations without sufficient bounds checking, type validation, or access control enforcement.
Missing Input Validation
Overview
The input-validation detector identifies smart contract functions that accept parameters from transaction calldata and use them directly in privileged operations — address routing, amount calculations, storage writes, or external calls — without first validating that the parameters conform to expected bounds, types, or authorization constraints.
Input validation failures are the leading cause of smart contract losses in 2024-2025, accounting for over $2.5 billion in documented exploits. Unlike more specific vulnerability classes (reentrancy, overflow), missing input validation is a broad class that encompasses dozens of attack patterns: passing zero addresses, arbitrary large amounts, out-of-range indices, attacker-controlled recipient addresses, and unchecked array lengths.
Sigvex performs taint analysis on function parameters: values originating from CALLDATALOAD and CALLDATASIZE are marked as tainted, and the detector tracks these values through assignments, arithmetic, and memory operations to identify cases where tainted values flow into sensitive sinks (external calls, storage writes, value transfers) without an intervening validation check. The detector categorizes findings by the type of missing validation: address validation, amount bounds, index bounds, and access control.
Why This Is an Issue
Input validation failures are exploitable in numerous ways depending on what the unvalidated input controls:
Zero address inputs: Sending funds or granting ownership to address(0) permanently locks tokens. Many protocols have lost millions by failing to check recipient addresses before token transfers.
Unbounded amounts: Without a maximum amount check, users can specify extremely large transfer amounts that overflow accounting or drain liquidity pools in a single transaction. The Radiant Capital exploit ($50M, 2024) involved improperly validated flash loan parameters.
Array index out of bounds: Solidity arrays accessed with unchecked user-supplied indices can access unintended storage slots. While modern EVM reverts on out-of-bounds dynamic array access, static arrays and manual storage slot calculations do not.
Recipient address control: Functions that accept a user-supplied recipient address and route funds to it without validation enable arbitrary fund routing. The Poly Network exploit ($611M, 2021) exploited a user-controlled _toContract parameter passed to crossChain().
Arbitrary large loop iterations: User-controlled loop bounds can exhaust gas and create denial-of-service conditions.
How to Resolve
Validate all user-supplied inputs at function entry, before any state changes or external calls:
// Before: Vulnerable — no input validation
contract VulnerableVault {
function transferTo(address recipient, uint256 amount) external {
// VULNERABLE: recipient could be address(0), amount could be 0 or enormous
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
balances[recipient] += amount;
}
function batchTransfer(address[] calldata recipients, uint256[] calldata amounts) external {
// VULNERABLE: no check that lengths match, no zero-address check, no amount validation
for (uint256 i = 0; i < recipients.length; i++) {
balances[recipients[i]] += amounts[i];
}
balances[msg.sender] -= totalAmount; // totalAmount not validated
}
}
// After: Fixed — full input validation
contract SafeVault {
uint256 public constant MAX_TRANSFER = 1_000_000 ether;
function transferTo(address recipient, uint256 amount) external {
// Validate all inputs first
require(recipient != address(0), "Zero address recipient");
require(amount > 0, "Zero amount");
require(amount <= MAX_TRANSFER, "Amount exceeds maximum");
require(balances[msg.sender] >= amount, "Insufficient balance");
// Then perform state changes
balances[msg.sender] -= amount;
balances[recipient] += amount;
}
function batchTransfer(
address[] calldata recipients,
uint256[] calldata amounts
) external {
// Validate array consistency
require(recipients.length == amounts.length, "Array length mismatch");
require(recipients.length <= 100, "Batch too large");
uint256 totalAmount;
for (uint256 i = 0; i < recipients.length; i++) {
require(recipients[i] != address(0), "Zero address recipient");
require(amounts[i] > 0, "Zero amount in batch");
totalAmount += amounts[i]; // Safe in Solidity 0.8+
}
require(balances[msg.sender] >= totalAmount, "Insufficient balance");
balances[msg.sender] -= totalAmount;
for (uint256 i = 0; i < recipients.length; i++) {
balances[recipients[i]] += amounts[i];
}
}
}
For address validation in proxy or routing contexts:
// Validate that a target address is a contract with expected interface
function callTarget(address target, bytes calldata data) external onlyAdmin {
require(target != address(0), "Zero address target");
require(target.code.length > 0, "Target is not a contract");
// Optional: verify ERC-165 interface if applicable
// bool supportsInterface = IERC165(target).supportsInterface(EXPECTED_INTERFACE_ID);
// require(supportsInterface, "Target does not support required interface");
(bool success, ) = target.call(data);
require(success, "Call failed");
}
Examples
Vulnerable Code
// Cross-chain bridge with user-controlled call target
contract VulnerableBridge {
function crossChain(
address toContract, // User-controlled — not validated!
bytes calldata data
) external payable {
// Attacker passes their own malicious contract as toContract
(bool success, ) = toContract.call{value: msg.value}(data);
require(success, "Cross-chain call failed");
}
}
// Lending protocol with unbounded liquidation
contract VulnerableLending {
function liquidate(address borrower, uint256 repayAmount) external {
// No check that repayAmount <= borrower's outstanding debt
// Attacker can liquidate up to the entire pool in one call
_repayAndSeize(borrower, repayAmount);
}
}
Fixed Code
// Cross-chain bridge with allowlisted destinations
contract SafeBridge {
mapping(address => bool) public approvedContracts;
function crossChain(
address toContract,
bytes calldata data
) external payable {
require(approvedContracts[toContract], "Destination not approved");
require(msg.value > 0, "No value provided");
require(data.length > 0, "Empty call data");
(bool success, ) = toContract.call{value: msg.value}(data);
require(success, "Cross-chain call failed");
}
}
// Lending protocol with bounded liquidation
contract SafeLending {
function liquidate(address borrower, uint256 repayAmount) external {
uint256 debt = outstandingDebt[borrower];
require(debt > 0, "Borrower has no debt");
require(repayAmount > 0, "Zero repay amount");
require(repayAmount <= debt, "Repay amount exceeds debt");
// Validate borrower is actually undercollateralized
require(isUndercollateralized(borrower), "Not eligible for liquidation");
_repayAndSeize(borrower, repayAmount);
}
}
Sample Sigvex Output
{
"detector_id": "input-validation",
"severity": "critical",
"confidence": 0.74,
"description": "Function crossChain() routes msg.value to a user-supplied address toContract without validating it against an allowlist or checking that it is a contract. An attacker can pass an arbitrary address to redirect funds.",
"location": { "function": "crossChain(address,bytes)", "offset": 38 }
}
Detection Methodology
Sigvex performs multi-phase taint analysis on function calldata:
- Taint source identification: Marks all values loaded from
CALLDATALOADas tainted, including ABI-decoded function parameters. - Validation gate detection: Identifies
REVERT-preceded comparison instructions (LT,GT,EQ,ISZERO) that operate on tainted values and clear the taint for that value on the non-reverting execution path. - Sink classification: Categorizes potential sinks: external call targets (
CALL,DELEGATECALL), Ether value transfers, storage writes to owner/authority slots, and array index computations. - Flow analysis: For each tainted value reaching a sink, determines whether at least one validation gate existed on every path from the function entry to the sink.
- Confidence adjustment: Reduces confidence for common false positive patterns (e.g.,
amount > 0checks that are present but incomplete), and increases confidence for high-value sinks (Ether transfers, ownership writes).
Limitations
False positives:
- Functions in testing or simulation contexts that intentionally accept arbitrary inputs may be flagged.
- Internal functions called only from validated contexts may produce findings since the detector analyzes functions in isolation.
- Some validation patterns use external libraries or modifiers that are inlined differently and may not be recognized as validation.
False negatives:
- Validation in modifier functions that pre-validate parameters before the function body runs may not be tracked across the modifier boundary.
- Multi-step validation where a parameter is validated in a prior transaction and a flag is set may not be recognized as protection.
Remediation
- Remediation Guide: Input Validation Remediation
Related Detectors
- Access Control — detects missing caller authorization checks
- Unchecked Call — detects failure to check external call return values
- Business Logic Error — detects violations of protocol invariants that input validation should protect