Skip to content

Two defects in vendored nanoarrow 0.9.0 to report upstream #17

Closed

Description

@pedrobtz

Found while building Stages 2 and 3 against the vendored nanoarrow 0.9.0 (src/vendor/nanoarrow/). zuarrow avoids both paths, so neither affects it today; both belong upstream in apache/arrow-nanoarrow.

1. ArrowHalfFloatToFloat() mishandles subnormals. In nanoarrow.h, an exponent of 0 sets the float exponent field to 0 but keeps the shifted fraction, which yields fraction * 2^-136 rather than the binary16 value fraction * 2^-24. For example 0x0001 should widen to 2^-24 (about 5.96e-8). ArrowArrayViewGetDoubleUnsafe() uses it for half_float arrays, so every subnormal half float reads wrong. zuarrow widens half floats itself (zar_half_to_double() in src/zar_to_r.c), as design §6.2 asks, and test-to-r.R checks subnormals.

2. ArrowBasicArrayStreamInit() on its second allocation failure. When ArrowMalloc() for the arrays fails, the function calls ArrowBasicArrayStreamRelease(array_stream) before it has stored private_data on the stream, so the release reads an unset private_data. The schema it has already moved into private_data also leaks. Only an out-of-memory path reaches it.

Next step: open the upstream issues (and a pull request for the first, which is small), then link them here and in src/vendor/PROVENANCE if a patch is ever vendored.

Activity

  1. pedrobtz commented on Oct 21, 2026

    @pedrobtz
    OwnerAuthor

    More nanoarrow 0.9.0 defects to report upstream, all found by fuzzing zuarrow's IPC reader (Stage 5). Each is fixed by a vendored patch listed in src/vendor/PROVENANCE:

    • 0003: a DictionaryBatch's decoder never receives the body's codec or the caller's decompressor, so a compressed dictionary is read as raw bytes.
    • 0004: the IPC reader refuses binary-view, string-view, list-view and run-end-encoded layouts (#833, #824). The patch decodes them, variadic buffers included.
    • 0005: ArrowIpcDecoderPeekFooter() formats %s from one past the end of the unterminated input.
    • 0006: ArrowIpcDecoderReadHeaderPrefix() overflows int32_t adding the prefix to a length near INT32_MAX.
    • 0007, 0008: absent optional tables (DictionaryEncoding.indexType, a message header, a field type, the footer schema) are read through NULL.
    • 0009: a DictionaryBatch's field node and buffer counts are not checked against its schema, so the walk reads past the node vector.
    • 0010: metadata keys and values are read with strlen(). That truncates binary values at a NUL and reads an absent key through NULL.
    • 0011: minimal validation never sizes a list view's sizes buffer.
    • 0012: default validation reads the first offset of a zero-length array from a buffer shorter than one offset.

    Two gaps zuarrow covers itself rather than patching:

    • Views: full validation does not range-check binary or string views.
    • Alignment: the decoder reads values from misaligned buffers in place.
  2. pedrobtz commented on Oct 21, 2026

    @pedrobtz
    OwnerAuthor

    Another nanoarrow 0.9.0 encoder defect, found in Stage 6 (#22): ArrowIpcEncoderFinalizeBuffer() writes the footer's Block structs without zeroing their four bytes of padding. Files are then not byte-for-byte reproducible, and the padding leaks heap bytes. Vendored patch 0013 zeroes the padding along with adding ArrowIpcEncoderEncodeBatch(). Both belong in the upstream pull request.

  3. pedrobtz commented on Oct 21, 2026

    @pedrobtz
    OwnerAuthor

    Found by the fuzz job on #22: nanoarrow 0.9.0's IPC decoder lets two fields share a dictionary id with different value types. The dictionary batch is decoded with one field's type, and ArrowIpcDecoderWalkSetArrayView() then views it with the other's, which reads past the buffers (heap-buffer-overflow in ArrowArrayViewValidateDefault(), large-string offsets). zuarrow's reader now refuses such a schema before setting up the decoder, without a vendored patch. Regression input: tests/testthat/data/crash-shared-dictionary.arrow. Upstream should check this in ArrowIpcDecoderSetSchemaWithDictionaries().

  4. pedrobtz commented on Oct 21, 2026

    @pedrobtz
    OwnerAuthor

    Re-tested every regression input against nanoarrow main at efe4274 (2026-10-21). The inputs went through nanoarrow's own ArrowIpcArrayStreamReader at full validation and ArrowIpcDecoderVerifyFooter()/DecodeFooter(), built with ASan and UBSan.

    Already fixed on main (unreleased):

    Our patch Upstream
    0003 compressed dictionary batches decoded on main
    0007 missing indexType #957
    0008 absent FlatBuffers tables #960
    0009 dictionary batch node counts clean error on main
    0010 metadata strings clean on main
    0011 list-view sizes N/A: main's IPC reader refuses list views
    0013 writing dictionary and compressed batches #926, #935, #968 (the API differs from ours)

    Still present on main, now with upstream pull requests:

    Defect Our fix Upstream PR
    Header size overflows int32_t patch 0006 apache/arrow-nanoarrow#984
    Bad file magic message reads past the input patch 0005 apache/arrow-nanoarrow#985
    Footer Block padding taken from the stack part of patch 0013 apache/arrow-nanoarrow#986
    Short offsets buffer read in default validation patch 0012 apache/arrow-nanoarrow#987
    Buffer-size arithmetic overflows on huge lengths reader's field-node bounds apache/arrow-nanoarrow#988
    Fields sharing a dictionary id with different value types reader check (#22) apache/arrow-nanoarrow#989
    Misaligned reads in default and full validation reader's aligned copies apache/arrow-nanoarrow#990

    Each pull request has a test that fails without its fix, and passes ctest in a plain build and under ASan and UBSan. Upstream CI is waiting for a maintainer to approve workflows for a first-time contributor.

    Not sent: patch 0004 (decoding views, list views and run-end encoding with variadic buffers). It is a feature, not a defect fix, since main's IPC reader refuses those types cleanly.

    Once a release has these, the re-vendor can drop patches 0003 and 0005 to 0012 and most of 0013 (design §15 Q1).

  5. pedrobtz commented on Nov 3, 2026

    @pedrobtz
    OwnerAuthor

    Another nanoarrow 0.9.0 defect for the upstream report, found by the nightly fuzz run (#85): ArrowArrayViewValidateFull() bounds a large list view element as offset + size > child_len, which overflows int64_t for two large non-negative values, so a wrapped sum passes the check. Vendored patch 0014 compares size > child_len - offset. Regression input: tests/testthat/data/crash-list-view-overflow.arrow.

  6. pedrobtz commented on Nov 5, 2026

    @pedrobtz
    OwnerAuthor

    Every defect here is now reported upstream or already fixed on apache/arrow-nanoarrow main (checked at 9bf2d88).

    New upstream PRs. Each has a test that fails without its fix. ctest passes in a plain build and under ASan and UBSan.

    Defect Our fix Upstream
    ArrowBasicArrayStreamInit() leaks the schema and releases an unset stream when its second allocation fails not reached by zuarrow apache/arrow-nanoarrow#1006
    Large list view bound offset + size overflows int64_t patch 0014 apache/arrow-nanoarrow#1005

    Already fixed on main:

    Item Our fix Upstream
    Half-float subnormals zar_half_to_double() apache/arrow-nanoarrow#962
    Encoder writes a dictionary field's children from the index type patch 0016 apache/arrow-nanoarrow#928
    Delta onto a non-empty dictionary refused patch 0015 apache/arrow-nanoarrow#955

    Earlier, still open upstream: apache/arrow-nanoarrow#984 to #990, listed in the comment above. Patch 0004 (decoding views, list views and run-end encoding) was not sent, because it is a feature and main refuses those types cleanly.

    At the next re-vendor (design §15 Q1), drop each patch whose upstream fix has been released: 0003, 0005–0012, 0014–0016 and most of 0013.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions