mirror of
https://github.com/lux-apps/papers.git
synced 2026-10-30 18:15:21 +00:00
Restructured from flat .tex/.pdf files into per-paper subdirectories. Added Antje Worring as co-author on all papers. Synced PDFs to pdfs/ for download site.
138 lines
4.4 KiB
TeX
138 lines
4.4 KiB
TeX
\section{Vulnerability Taxonomy}
|
|
|
|
|
|
\subsection{Reentrancy}
|
|
|
|
\begin{definition}[Reentrancy]
|
|
A reentrancy vulnerability exists when a contract makes an external call before updating
|
|
its state, allowing the callee to call back into the contract and observe or modify
|
|
inconsistent state.
|
|
\end{definition}
|
|
|
|
\begin{lstlisting}[language=Solidity,caption={Reentrancy vulnerability and fix}]
|
|
// VULNERABLE
|
|
function withdraw(uint256 amount) external {
|
|
require(balances[msg.sender] >= amount);
|
|
(bool ok,) = msg.sender.call{value: amount}("");
|
|
require(ok);
|
|
balances[msg.sender] -= amount; // Too late!
|
|
}
|
|
|
|
// FIXED: checks-effects-interactions pattern
|
|
function withdraw(uint256 amount) external {
|
|
require(balances[msg.sender] >= amount);
|
|
balances[msg.sender] -= amount; // State update first
|
|
(bool ok,) = msg.sender.call{value: amount}("");
|
|
require(ok);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Detection: Slither \texttt{reentrancy-eth}, manual review.
|
|
|
|
\subsection{Flash Loan Attacks}
|
|
|
|
Flash loans allow borrowing unlimited capital for the duration of a single transaction.
|
|
This enables attacks on any protocol that relies on spot prices or instantaneous balances.
|
|
|
|
\begin{lstlisting}[language=Solidity,caption={Flash loan attack on a naive oracle}]
|
|
// VULNERABLE: uses spot price
|
|
function getPrice() public view returns (uint256) {
|
|
return reserve0 * 1e18 / reserve1; // Manipulable!
|
|
}
|
|
|
|
// FIXED: uses TWAP (time-weighted average price)
|
|
function getPrice() public view returns (uint256) {
|
|
(uint256 price, uint256 lastUpdate) = oracle.consult(
|
|
address(token), 1e18, 1800 // 30-minute TWAP
|
|
);
|
|
require(block.timestamp - lastUpdate < 3600, "stale");
|
|
return price;
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Detection: manual review (automated tools cannot reason about cross-contract economic interactions).
|
|
|
|
\subsection{Oracle Manipulation}
|
|
|
|
\begin{lstlisting}[language=Solidity,caption={Oracle manipulation defense}]
|
|
// VULNERABLE: single oracle, no staleness check
|
|
function liquidate(address user) external {
|
|
uint256 price = oracle.latestAnswer();
|
|
require(debt(user) * price > collateral(user) * 1e18);
|
|
// liquidate...
|
|
}
|
|
|
|
// FIXED: multiple oracles, staleness check, deviation check
|
|
function liquidate(address user) external {
|
|
(uint256 price1, uint256 ts1) = oracle1.latestRoundData();
|
|
(uint256 price2, uint256 ts2) = oracle2.latestRoundData();
|
|
require(block.timestamp - ts1 < 3600, "oracle1 stale");
|
|
require(block.timestamp - ts2 < 3600, "oracle2 stale");
|
|
require(
|
|
diff(price1, price2) * 100 / price1 < 5,
|
|
"oracle deviation"
|
|
);
|
|
uint256 price = (price1 + price2) / 2;
|
|
require(debt(user) * price > collateral(user) * 1e18);
|
|
// liquidate...
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Access Control}
|
|
|
|
\begin{lstlisting}[language=Solidity,caption={Access control vulnerability and fix}]
|
|
// VULNERABLE: missing access control
|
|
function setFee(uint256 newFee) external {
|
|
fee = newFee; // Anyone can call this!
|
|
}
|
|
|
|
// FIXED: proper access control
|
|
function setFee(uint256 newFee) external onlyOwner {
|
|
require(newFee <= MAX_FEE, "fee too high");
|
|
emit FeeUpdated(fee, newFee);
|
|
fee = newFee;
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Detection: Slither \texttt{missing-zero-check}, Aderyn centralization detector.
|
|
|
|
\subsection{Integer Overflow}
|
|
|
|
Solidity 0.8+ has built-in overflow checks, but \texttt{unchecked} blocks bypass them:
|
|
|
|
\begin{lstlisting}[language=Solidity,caption={Overflow in unchecked block}]
|
|
// VULNERABLE: overflow in unchecked block
|
|
function accumulateFees(uint256 newFees) internal {
|
|
unchecked {
|
|
totalFees += newFees; // Can wrap around!
|
|
}
|
|
}
|
|
|
|
// FIXED: only use unchecked where overflow is proven impossible
|
|
function accumulateFees(uint256 newFees) internal {
|
|
totalFees += newFees; // Solidity 0.8 overflow check
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{Frontrunning}
|
|
|
|
\begin{lstlisting}[language=Solidity,caption={Frontrunning defense with commit-reveal}]
|
|
// VULNERABLE: swap with no slippage protection
|
|
function swap(uint256 amountIn) external returns (uint256) {
|
|
return _swap(amountIn); // Sandwich-able
|
|
}
|
|
|
|
// FIXED: minimum output amount
|
|
function swap(
|
|
uint256 amountIn,
|
|
uint256 minAmountOut,
|
|
uint256 deadline
|
|
) external returns (uint256 amountOut) {
|
|
require(block.timestamp <= deadline, "expired");
|
|
amountOut = _swap(amountIn);
|
|
require(amountOut >= minAmountOut, "slippage");
|
|
}
|
|
\end{lstlisting}
|
|
|
|
%% ============================================================
|