YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
NetCDF CDF-5 Attribute Array Integer Overflow in Header Parsing
Summary
libsrc/v1hpg.c allocates the global/variable attribute pointer array using untrusted nelems from the file header without an overflow guard:
ncap->value = (NC_attr **) malloc(ncap->nelems * sizeof(NC_attr *));
In CDF-5, nelems is read from the attacker-controlled header as a 64-bit integer. On 32-bit builds, ncap->nelems * sizeof(NC_attr *) can wrap to a small allocation, after which the parser iterates ncap->nelems times and writes past the undersized heap buffer while parsing attributes. This is reachable when opening a malicious .nc file through libnetcdf consumers such as Python netCDF4.Dataset().
Affected Code
File: libsrc/v1hpg.c:894
/* Read a NC_attrarray from the header */
static int
v1h_get_NC_attrarray(v1hs *gsp, NC_attrarray *ncap)
{
...
status = v1h_get_size_t(gsp, &ncap->nelems);
if(status != NC_NOERR)
return status;
if(ncap->nelems == 0)
return NC_NOERR;
/* else */
if(type != NC_ATTRIBUTE)
return EINVAL;
ncap->value = (NC_attr **) malloc(ncap->nelems * sizeof(NC_attr *));
if(ncap->value == NULL)
return NC_ENOMEM;
ncap->nalloc = ncap->nelems;
...
}
Missing Guard vs Existing Hardening
The same file already contains equivalent overflow protection for dimensions and variables, but not for attributes.
Dimension array: guarded
File: libsrc/v1hpg.c:544-546
if (ncap->nelems > SIZE_MAX / sizeof(NC_dim *))
return NC_ERANGE;
ncap->value = (NC_dim **) calloc(1,ncap->nelems * sizeof(NC_dim *));
Variable array: guarded
File: libsrc/v1hpg.c:1198-1200
if (ncap->nelems > SIZE_MAX / sizeof(NC_var *))
return NC_ERANGE;
ncap->value = (NC_var **) calloc(1,ncap->nelems * sizeof(NC_var *));
Attribute array: unguarded
File: libsrc/v1hpg.c:894
ncap->value = (NC_attr **) malloc(ncap->nelems * sizeof(NC_attr *));
This is an inconsistent omission in otherwise similar parser code.
Root Cause
v1h_get_NC_attrarray() trusts the header field nelems and uses it directly in a multiplication for heap allocation. The classic/CDF-5 file format grammar allows the parser to reach the attribute list before any attribute bodies are parsed:
header = magic numrecs dim_list gatt_list var_list
att_list = ABSENT | NC_ATTRIBUTE nelems [attr ...]
As soon as the parser reads NC_ATTRIBUTE and a large nelems, it performs the unchecked multiplication.
Exploitability
32-bit builds
On 32-bit, a crafted value such as nelems = 0xFFFFFFFF causes:
0xFFFFFFFF * 4or0xFFFFFFFF * 8style arithmetic to wrap (depending on pointer size / ABI)malloc()to return a much smaller chunk than required- the subsequent loop to treat the buffer as if it contains
ncap->nelemsentries - heap out-of-bounds writes during attribute parsing
The write primitive is reached in the loop immediately after allocation:
NC_attr **app = ncap->value;
NC_attr *const *const end = &app[ncap->nelems];
for( /*NADA*/; app < end; app++)
{
status = v1h_get_NC_attr(gsp, app);
...
}
64-bit builds
On 64-bit, the same value typically becomes a massive allocation request instead of a wrapped small allocation. For the verified PoC value 0xFFFFFFFF:
malloc(0xFFFFFFFF * 8)requests ~32 GiB- libnetcdf returns
NC_ENOMEM - Python
netCDF4.Dataset()surfaces this asOSError: [Errno -61] NetCDF: Memory allocation (malloc) failurewhen the encoded count exceedsSIZE_MAX / sizeof(NC_attr *)on the running architecture.
This demonstrates reliable attacker-triggered denial of service on common 64-bit targets, while the unchecked arithmetic remains memory-corruption-relevant on 32-bit targets.
Reachability
This bug is reachable through applications that open attacker-supplied classic/CDF-5 .nc files using libnetcdf, including Python bindings that bundle/liblink netcdf-c:
import netCDF4
netCDF4.Dataset('poc.nc')
Verified result with the attached PoC:
OSError: [Errno -61] NetCDF: Memory allocation (malloc) failure
Reproduction
- Generate the malicious file with the attached
poc.py - Open it with Python
netCDF4.Dataset() - Observe libnetcdf fail in header parsing before any normal dataset access occurs
The PoC defaults to a CDF-5 global attribute count of 0x1FFFFFFFFFFFFFFF so current 64-bit builds also hit the allocation failure path. You can pass --attr-count 0xFFFFFFFF to mirror the originally verified 32-bit-relevant wraparound value.
Security Impact
- 32-bit: integer overflow in allocation size -> undersized heap allocation -> heap overflow during attribute parsing
- 64-bit: attacker-triggered excessive allocation / memory exhaustion during file open
- Attack surface: any service, desktop app, pipeline, or notebook that opens untrusted
.ncfiles through libnetcdf/netCDF4
Known CVE Non-Overlap
I checked the 2025 netcdf-c CVEs you referenced:
CVE-2025-14932CVE-2025-14933CVE-2025-14934CVE-2025-14935CVE-2025-14936
None of them cover this bug class. In particular, CVE-2025-14936 concerns an attribute name stack overflow, not an attribute array allocation integer overflow in v1h_get_NC_attrarray().
Suggested Fix
Mirror the existing dimension/variable protections in the attribute path:
if (ncap->nelems > SIZE_MAX / sizeof(NC_attr *))
return NC_ERANGE;
ncap->value = (NC_attr **) calloc(1, ncap->nelems * sizeof(NC_attr *));
At minimum, the SIZE_MAX / sizeof(NC_attr *) bound check is required before allocation.
PoC
See poc.py in the same directory.