Files
zeekay abf99d05fc papers: reorganize into per-paper dirs, add Antje Worring as co-author
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.
2026-04-18 21:50:07 -07:00

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}
%% ============================================================