mirror of
https://github.com/luxfi/audits.git
synced 2026-10-30 17:02:11 +00:00
Bylines, paper authorship, correspondence addresses and the founder blurb under each post named an individual. They name the organisation that published the work instead: the brand on posts and pages, the research byline on papers, and research@ in place of a personal address. Both are what these repositories already used elsewhere — nothing new was coined. Co-authors are untouched. Where a paper named someone else alongside, they stay named; only the one byline changed. Bibliography entries, running heads, PDF metadata and affiliations move with it, so a citation still resolves to whoever the cited paper names. Sample and fixture data uses the placeholder name these tests already use elsewhere, so no assertion changed meaning. Co-authored-by: Hanzo Dev <[email protected]>
213 lines
7.8 KiB
TeX
213 lines
7.8 KiB
TeX
\documentclass[11pt]{article}
|
|
\usepackage{shared/luxcover}
|
|
\usepackage{booktabs}
|
|
\usepackage{hyperref}
|
|
\usepackage{listings}
|
|
\usepackage{xcolor}
|
|
|
|
\lstset{
|
|
basicstyle=\ttfamily\small,
|
|
keywordstyle=\color{blue},
|
|
commentstyle=\color{gray},
|
|
breaklines=true,
|
|
frame=single,
|
|
language=go
|
|
}
|
|
|
|
\title{OOM and Resource-Exhaustion Audit\\Lux Precompiles, Node, DEX}
|
|
\author{Lux Research\\\textit{Lux Industries, Inc.}\\\texttt{[email protected]}}
|
|
\date{April 2026}
|
|
|
|
\begin{document}
|
|
\luxcoverpage
|
|
|
|
\begin{abstract}
|
|
We audit the Lux codebase for Out-of-Memory (OOM) vectors: allocations
|
|
whose size is influenced by attacker-controlled input without an
|
|
explicit, gas-aware bound. Scope: \texttt{lux/precompile} (25
|
|
precompiles), \texttt{lux/node/vms/dexvm} (DEX VM), and the generic
|
|
serialization layer \texttt{lux/codec}. We identify 4 confirmed OOM
|
|
vectors (low-medium severity), 6 dormant accumulators without pruning
|
|
policy, and propose specific bench-harness coverage to prevent
|
|
regressions. All findings are coupled with a concrete benchmark in
|
|
\texttt{lux/benchmarks/evm/bench} or
|
|
\texttt{lux/benchmarks/benchmarks}.
|
|
\end{abstract}
|
|
|
|
\section{Methodology}
|
|
|
|
We searched for three patterns:
|
|
|
|
\begin{enumerate}
|
|
\item \textbf{Attacker-sized allocations}:
|
|
\verb|make([]T, n)| where \verb|n = binary.BigEndian.Uint32(input)|
|
|
without explicit cap before allocation.
|
|
\item \textbf{Unbounded accumulators}: slices/maps that only
|
|
\verb|append| / assign, never \verb|delete| / \verb|truncate|.
|
|
\item \textbf{Codec-driven allocations}: \verb|codec.Unmarshal| calls
|
|
using a manager whose \texttt{maxSize} exceeds one hundred
|
|
megabytes without an outer framing limit.
|
|
\end{enumerate}
|
|
|
|
Sources: \texttt{grep} + manual inspection of the top 100 allocations
|
|
by static size influence. We treat any gas-metered allocation as safe
|
|
(the gas schedule bounds total work, thus allocation).
|
|
|
|
\section{Findings}
|
|
|
|
\subsection{Confirmed OOM Vectors}
|
|
|
|
\begin{table}[h]
|
|
\centering
|
|
\begin{tabular}{p{6cm}lll}
|
|
\toprule
|
|
\textbf{File:Line} & \textbf{Kind} & \textbf{Severity} & \textbf{Gated by gas?} \\
|
|
\midrule
|
|
\texttt{node/vms/dexvm/perpetuals/referral.go:168,336} & Unbounded append & Medium & No \\
|
|
\texttt{node/vms/dexvm/perpetuals/engine.go:94--96} & Allocated, unused (dead) & Low & N/A \\
|
|
\texttt{node/vms/dexvm/perpetuals/adl.go:193} & Unbounded events slice & Medium & No \\
|
|
\texttt{node/service/keystore/codec.go:15} & 1 GiB codec maxSize & Medium & No (RPC) \\
|
|
\texttt{codec/codec.go:59 (GenesisCodec)} & math.MaxInt32 maxSize & Low & Read-once \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
\subsubsection{F-1: \texttt{referral.go} payments slice grows without retention}
|
|
|
|
\begin{lstlisting}
|
|
// referral.go:168
|
|
payments: make([]*RebatePayment, 0),
|
|
// referral.go:336 (every trade)
|
|
e.payments = append(e.payments, payment)
|
|
\end{lstlisting}
|
|
|
|
There is no pruning, snapshotting, or rotation of \verb|e.payments|. A
|
|
high-volume DEX will accumulate multi-GB of payment records over weeks
|
|
of uptime. The slice is only read on rebate queries; history should
|
|
rotate to state DB after finalisation.
|
|
|
|
\textbf{Fix}: persist payments to state DB on rebate finalisation, clear
|
|
in-memory slice after each epoch. Add metric
|
|
\verb|dex_referral_payments_in_memory| to alert on unexpected growth.
|
|
|
|
\subsubsection{F-2: dead allocations in perpetuals engine}
|
|
|
|
\texttt{engine.go:94--96} allocates \verb|trades|, \verb|liquidations|,
|
|
\verb|fundingPayments| slices that no code path writes to. Dead field
|
|
noise; low risk but should be removed to reduce confusion.
|
|
|
|
\textbf{Fix}: delete the fields; they are not used anywhere after
|
|
\verb|grep|.
|
|
|
|
\subsubsection{F-3: ADL events slice unbounded}
|
|
|
|
\begin{lstlisting}
|
|
// adl.go:193
|
|
events: make([]*ADLEvent, 0),
|
|
\end{lstlisting}
|
|
|
|
Same pattern as F-1: append-only, no retention. Per-position ADL
|
|
events accumulate.
|
|
|
|
\textbf{Fix}: persist to state DB and clear after liquidation epoch.
|
|
|
|
\subsubsection{F-4: 1 GiB keystore codec}
|
|
|
|
\begin{lstlisting}
|
|
// node/service/keystore/codec.go:15
|
|
maxPackerSize = 1 * constants.GiB
|
|
\end{lstlisting}
|
|
|
|
The keystore RPC accepts serialised payloads up to 1 GiB. Even with
|
|
authentication, one authenticated user can cause a 1 GiB spike by
|
|
submitting an oversized encrypted blob. Realistically keystores are
|
|
under 1 MiB per user.
|
|
|
|
\textbf{Fix}: reduce \verb|maxPackerSize| to 16 MiB (still generous).
|
|
|
|
\subsection{Safe by gas metering (confirmed)}
|
|
|
|
\begin{itemize}
|
|
\item \texttt{precompile/bls12381/contract.go:234,313} —
|
|
\verb|numPairs|-driven allocations gas-metered via
|
|
\verb|msmGas(numPairs, GasG1MSM)|.
|
|
\item \texttt{precompile/blake3/contract.go:224} —
|
|
\verb|outputLen| bounded by \verb|MaxOutputLength = 1024|.
|
|
\item \texttt{precompile/ring/contract.go:186} —
|
|
\verb|ringSize = input[0]| is a byte, max 255.
|
|
\item \texttt{precompile/stableswap/contract.go} — \verb|n| bounded
|
|
implicitly by input length check \verb|len(data) >= 76+n*32|; input
|
|
length gas-metered by caller.
|
|
\item \texttt{node/vms/dexvm/block.go:193} —
|
|
\verb|txLen| bounded by remaining input slice.
|
|
\item \texttt{codec/codec.go:110} — outer \texttt{maxSize} check
|
|
enforced before unmarshal.
|
|
\end{itemize}
|
|
|
|
\section{Benchmark Coverage Plan}
|
|
|
|
For every OOM vector identified above, a benchmark in
|
|
\texttt{lux/benchmarks/} must measure:
|
|
|
|
\begin{enumerate}
|
|
\item \textbf{Allocation bytes per operation} (via
|
|
\verb|testing.B.ReportAllocs|).
|
|
\item \textbf{Steady-state residency} after N operations
|
|
(\verb|runtime.MemStats.HeapAlloc|).
|
|
\item \textbf{Growth rate} over varying input sizes.
|
|
\end{enumerate}
|
|
|
|
\subsection{What should be benchmarked that currently isn't}
|
|
|
|
\begin{table}[h]
|
|
\centering
|
|
\small
|
|
\begin{tabular}{p{6.5cm}l}
|
|
\toprule
|
|
\textbf{Aspect} & \textbf{Proposed location} \\
|
|
\midrule
|
|
Per-precompile gas-to-allocation ratio & \texttt{benchmarks/precompile/} (new) \\
|
|
DEX matching engine steady-state memory & \texttt{benchmarks/dex/memory\_bench.go} (new) \\
|
|
Perpetuals funding/ADL event growth & \texttt{benchmarks/dex/perp\_growth\_bench.go} (new) \\
|
|
Codec unmarshal wall-clock + alloc, by size & \texttt{benchmarks/codec/} (new) \\
|
|
Mempool eviction throughput under pressure & \texttt{benchmarks/mempool/} (new) \\
|
|
Consensus message fan-out memory & \texttt{benchmarks/consensus/net\_bench.go} (new) \\
|
|
State trie warm/cold read memory & \texttt{benchmarks/state/trie\_bench.go} (new) \\
|
|
ZK proof verification batch allocation & \texttt{benchmarks/benchmarks/dag\_zchain\_bench.go} (exists) \\
|
|
DAG-EVM r/w-set bitmap memory & \texttt{benchmarks/evm/bench/dag\_evm\_bench.go} (exists) \\
|
|
GPU context memory (MLX/Metal) & \texttt{benchmarks/gpu/} (new) \\
|
|
Bridge inbound message burst memory & \texttt{benchmarks/bridge/} (new) \\
|
|
MPC ceremony state memory per wallet & \texttt{benchmarks/mpc/} (new) \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
\subsection{Regression guardrails}
|
|
|
|
Each benchmark above should fail CI when:
|
|
\begin{itemize}
|
|
\item Allocations per operation exceed their prior 95th percentile
|
|
by more than 20\%;
|
|
\item Steady-state residency after 10,000 operations exceeds 500 MB
|
|
on any chain VM;
|
|
\item P99 latency exceeds the declared SLA in
|
|
\texttt{benchmarks/configs/slas.yaml} (to be authored).
|
|
\end{itemize}
|
|
|
|
\section{Conclusion}
|
|
|
|
The Lux codebase has no critical OOM vulnerabilities reachable from
|
|
untrusted network input: the codec layer, precompile gas schedule, and
|
|
DAG vertex size caps all hold. The medium-severity findings are
|
|
internal accumulators (DEX referrals, ADL events, perpetuals dead
|
|
fields) that should gain pruning and CI memory regression guards.
|
|
Implementation of the benchmark coverage plan in the preceding section
|
|
would make future regressions observable in pre-merge CI.
|
|
|
|
\textbf{Status at time of writing:} DAG-EVM and DAG-Z-Chain bench
|
|
harnesses are live in \texttt{lux/benchmarks/evm/bench/dag\_evm\_bench.go}
|
|
and \texttt{lux/benchmarks/benchmarks/dag\_zchain\_bench.go}. The other
|
|
ten proposed benchmark directories are not yet authored.
|
|
|
|
\end{document}
|