Skip to main content

reentrancy

Auto-loaded by reentrancy-auditor agent during Phase 2. Provides detection patterns for: CEI violations, cross-function/cross-contract reentrancy, read-only reentrancy, token callback exploits (ERC721/777/1155). Core artifact: State Timeline map.

Zur Installation springen

Quellinformationen

Repository
BitterSecurity/Vigilo
Letzte Quellaktivität
2. Februar 2026 um 07:28
Erkannte Sprache von SKILL.md
Englisch
Sterne
66
Forks
17

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
reentrancy
description
Auto-loaded by reentrancy-auditor agent during Phase 2. Provides detection patterns for: CEI violations, cross-function/cross-contract reentrancy, read-only reentrancy, token callback exploits (ERC721/777/1155). Core artifact: State Timeline map.
user-invocable
false
# Reentrancy Vulnerability Analysis **OWASP SC05:2025** - Reentrancy ranks **#5** in OWASP Smart Contract Top 10 (2025). Historical losses exceed **$500M+**, with 2024-2025 attacks including Penpie ($27M), Clober, GemPad, and multiple ERC721/1155 callback exploits. --- ## Why Reentrancy Happens (Root Causes) ### Root Cause 1: State Update After External Call The fundamental CEI (Checks-Effects-Interactions) violation. ```solidity // VULNERABLE: State update AFTER external call function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount); (bool success,) = msg.sender.call{value: amount}(""); // @audit External call require(success); balances[msg.sender] -= amount; // @audit State update AFTER! } ``` **Attacker's view**: "Between the call and the state update, I control execution. I'll call withdraw again before my balance updates." ### Root Cause 2: Execution Flow Transfer Every external call hands control to potentially hostile code. ```solidity // Even "safe" patterns can transfer control: IERC20(token).transfer(recipient, amount); // Could be ERC777 with hooks! NFT.safeTransferFrom(from, to, id); // Triggers onERC721Received! Token1155.safeTransferFrom(...); // Triggers onERC1155Received! ``` **Detection**: Any external call is a potential callback. Check what standards the token implements. ### Root Cause 3: Shared State Dependency Multiple contracts/functions depend on same state variable. ```solidity // Contract has two functions sharing `balances` function withdraw() external { uint256 amount = balances[msg.sender]; msg.sender.call{value: amount}(""); // @audit Callback opportunity balances[msg.sender] = 0; // @audit Updated after } function transfer(address to, uint256 amount) external { require(balances[msg.sender] >= amount); // @audit Same balance! balances[msg.sender] -= amount; balances[to] += amount; } // During withdraw callback, attacker calls transfer() with STALE balance! ``` **Attacker's view**: "ReentrancyGuard on withdraw() doesn't protect transfer(). I'll call transfer() during withdraw callback." ### Root Cause 4: View Function Exposure View functions return stale data during callbacks, affecting external protocols. ```solidity // Contract A (Vault) function withdraw() external { uint256 assets = userAssets[msg.sender]; msg.sender.call{value: assets}(""); // @audit Callback userAssets[msg.sender] = 0; totalAssets -= assets; // @audit Updated after } function getTotalAssets() public view returns (uint256) { return totalAssets; // @audit Returns STALE value during callback! } // Contract B (Lending) reads from Contract A function getCollateralValue(address user) view returns (uint256) { return vaultA.getTotalAssets() * userShares / totalShares; // During withdraw callback, totalAssets is WRONG! } ``` **Attacker's view**: "The view function shows the old value. External protocols that read this will make wrong decisions." --- ## The State Timeline (Core Artifact) Every reentrancy finding MUST include a state timeline: ``` T0: balances[attacker] = 100, contract.balance = 1000 T1: withdraw(100) called T2: call{value: 100}("") → attacker.receive() triggered T3: [CALLBACK] balances[attacker] STILL = 100! ← INCONSISTENT STATE T4: Re-enter withdraw(100) with same balance T5: Another 100 sent T6: ... repeat until drained T7: balances[attacker] -= 100 (executed N times, but all see 100) ``` Document each finding: | Phase | Contract State | Attacker Action | Balance Check | |-------|---------------|-----------------|---------------| | T0 | Initial | - | 100 | | T2 | Sending ETH | receive() callback | 100 (stale) | | T4 | Re-entered | withdraw again | 100 (stale) | | T7 | Unwind | - | Multiple decrements fail | --- ## Detection Patterns ### Pattern 1: Classic CEI Violation **Root Cause**: State Update After External Call ```solidity // VULNERABLE: Textbook reentrancy function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount); // CHECK (bool success,) = msg.sender.call{value: amount}(""); // INTERACTION require(success); balances[msg.sender] -= amount; // EFFECT - Wrong order! } ``` **Attack Flow**: 1. Attacker calls withdraw(100) 2. Contract sends ETH via call{value} 3. Attacker's receive() callback triggers 4. In callback: call withdraw(100) again (balance still 100!) 5. Repeat until contract drained 6. All state updates execute with wrong values **Search Queries**: ``` Grep("\\.call\\{value", glob="**/*.sol") Grep("transfer\\(|send\\(", glob="**/*.sol") ``` **Verification Questions**: - Is state updated BEFORE external call? - Is there a reentrancy guard? - Can callback reach this function again? ### Pattern 2: Cross-Function Reentrancy **Root Cause**: Shared State Dependency ```solidity // Both functions use same `balances` mapping function withdraw() external nonReentrant { // Has guard uint256 amount = balances[msg.sender]; msg.sender.call{value: amount}(""); // @audit Callback balances[msg.sender] = 0; } function transfer(address to, uint256 amt) external { // NO guard! require(balances[msg.sender] >= amt); // @audit Same state! balances[msg.sender] -= amt; balances[to] += amt; } ``` **Attack Flow**: 1. Attacker calls withdraw() with 100 balance 2. During callback, attacker calls transfer(accomplice, 100) 3. transfer() sees balances[attacker] = 100 (not yet updated!) 4. Attacker "transfers" 100 to accomplice 5. withdraw() completes, sets balances[attacker] = 0 6. Accomplice has 100 tokens created from nothing **Search Queries**: ``` Grep("nonReentrant", glob="**/*.sol") Grep("ReentrancyGuard", glob="**/*.sol") ``` **Verification Questions**: - Does guard cover ALL functions sharing this state? - Can other functions be called during callback? - What state do they depend on? ### Pattern 3: Cross-Contract Reentrancy **Root Cause**: ReentrancyGuard Only Protects Single Contract ```solidity // Contract A function withdraw() external nonReentrant { uint256 shares = userShares[msg.sender]; msg.sender.call{value: shares}(""); // @audit Callback userShares[msg.sender] = 0; // @audit Updated after } // Contract B (different contract, NO shared guard!) function borrow() external { uint256 collateral = contractA.userShares(msg.sender); // @audit Stale! require(collateral >= minCollateral); // Borrow against stale collateral value } ``` **Attack Flow**: 1. Attacker has 1000 shares in Contract A 2. Call withdraw() on Contract A 3. During callback, call borrow() on Contract B 4. Contract B reads userShares = 1000 (not yet zeroed!) 5. Attacker borrows against 1000 collateral 6. withdraw() completes, sets shares = 0 7. Attacker has borrowed funds + no collateral **Search Queries**: ``` Grep("external.*view.*returns", glob="**/*.sol") Grep("\\.balanceOf\\(|\\.userShares\\(", glob="**/*.sol") ``` **Verification Questions**: - Do other contracts read this contract's state? - Are those reads during callback windows? - Is there cross-contract reentrancy protection? ### Pattern 4: Read-Only Reentrancy **Root Cause**: View Function Returns Stale Data ```solidity // Vault contract function withdraw() external { uint256 assets = userAssets[msg.sender]; msg.sender.call{value: assets}(""); // @audit Callback totalAssets -= assets; // @audit Updated AFTER } function pricePerShare() public view returns (uint256) { return totalAssets * 1e18 / totalSupply; // @audit Stale totalAssets! } ``` **Attack Flow** (dForce $3.7M exploit): 1. Attacker withdraws from Curve pool 2. During callback, Curve's get_virtual_price() returns stale value 3. dForce protocol reads this wrong price for collateral 4. Attacker borrows more than collateral allows 5. Profit from price discrepancy **Search Queries**: ``` Grep("view.*returns|external.*view", glob="**/*.sol") Grep("getPrice|pricePerShare|totalAssets", glob="**/*.sol") ``` **Verification Questions**: - Do view functions depend on state updated after external calls? - Do external protocols use these view functions? - Is there a "read-only reentrancy" lock? ### Pattern 5: Token Callback Reentrancy **Root Cause**: Hidden Callbacks in Token Standards ```solidity // VULNERABLE: ERC721 safeTransferFrom triggers callback function stake(uint256 tokenId) external { nft.safeTransferFrom(msg.sender, address(this), tokenId); // @audit Callback! userStake[msg.sender] += 1; // @audit After callback } // ERC777 tokensReceived hook function deposit(uint256 amount) external { token.transferFrom(msg.sender, address(this), amount); // @audit ERC777 hook! userDeposit[msg.sender] += amount; // @audit After callback } // ERC1155 onERC1155Received callback function mint(uint256 id, uint256 amount) external { _mint(msg.sender, id, amount, ""); // @audit Triggers onERC1155Received! totalMinted += amount; // @audit After callback } ``` **Attack Flow** (Omni $1.43M exploit): 1. Attacker calls stake() with malicious NFT receiver 2. safeTransferFrom triggers onERC721Received callback 3. In callback, attacker re-enters stake() or other functions 4. State manipulated before original stake() completes **Token Standards with Callbacks**: | Standard | Callback Function | Trigger | |----------|------------------|---------| | ERC721 | onERC721Received | safeTransferFrom, safeMint | | ERC777 | tokensReceived, tokensToSend | transfer, transferFrom | | ERC1155 | onERC1155Received | safeTransferFrom, mint | | ERC1363 | onTransferReceived | transferAndCall | | ERC6909 | None | N/A (no callbacks by design) | **Note on ERC-6909**: ERC-6909 deliberately removed callback hooks to prevent reentrancy vulnerabilities. This makes it a safer alternative to ERC-1155 for protocols prioritizing reentrancy resistance over callback flexibility. **Search Queries**: ``` Grep("safeTransferFrom|safeMint|safeTransfer", glob="**/*.sol") Grep("onERC721Received|onERC1155Received|tokensReceived", glob="**/*.sol") Grep("ERC777|ERC1155|ERC1363|ERC721", glob="**/*.sol") ``` **Verification Questions**: - What token standard is used? - Does the standard have callbacks? - Is state updated before the token transfer? --- ## ReentrancyGuard Analysis ### Check Guard Coverage ``` Grep("nonReentrant|ReentrancyGuard|_locked", glob="**/*.sol") ``` ### Verify Protection Scope | Protection Level | Coverage | Bypass | |-----------------|----------|--------| | Single function | Only that function | Cross-function | | Single contract | All functions in contract | Cross-contract | | Cross-contract | Multiple contracts | Complex dependency | ### Common Guard Mistakes ```solidity // VULNERABLE: Guard on wrong function contract Vault { function deposit() external nonReentrant { ... } // Guarded function withdraw() external { ... } // NOT guarded! ← BUG } // VULNERABLE: Internal function not protected function _transfer() internal { // No guard msg.sender.call{value: amount}(""); } function publicTransfer() external nonReentrant { _transfer(); // Guard bypassed via other entry point } ``` --- ## CEI Pattern Verification Checklist For each function with external calls: 1. **Identify all external calls** - Low-level calls (.call, .delegatecall) - Token transfers (especially safe* variants) - External contract calls 2. **Map state dependencies** - Which state variables are read before call? - Which state variables are modified after call? 3. **Verify ordering** ``` ✓ CORRECT: checks → state update → external call ✗ WRONG: checks → external call → state update ``` 4. **Check cross-function** - Can other functions be called during callback? - Do they share the same state? --- ## Search Query Reference ``` # Find external calls
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen