Files
audits/2026-04-12-oom-audit.tex
zeekayandHanzo Dev 6aedbe7c6a Publish under the org, not a person
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]>
2026-09-01 02:30:09 -07:00

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}