Skip to content

undefined behavior in array index handling for infinite #3533

@fuyu0425

Description

@fuyu0425

Summary (Describe the bug)

On jq commit 5f2a14dd1b03a8b43015058ed006dd4ab24fb58f, the ordinary CLI
array-index surface still triggers undefined behavior when infinite is used
as an array index for has(...) or del(...).

On a sanitizer build, these witness commands both hit an inf to int
conversion error:

  • jq -c 'has(infinite)'
  • jq -c 'del(.[infinite])'

Nearby controls on the same build behave sensibly:

  • has(1) returns true
  • has(nan) returns false
  • del(.[1]) returns [0]
  • del(.[nan]) returns [0,1]
  • getpath([infinite]) returns null

This report is intentionally limited to the reproduced current-head UBSan
witness on jq's normal array-index surface. I am not claiming memory
corruption or exploitability beyond the observed undefined behavior.

Reproduction (To Reproduce)

One working build recipe:

git clone https://github.com/jqlang/jq.git
cd jq
git checkout 5f2a14dd1b03a8b43015058ed006dd4ab24fb58f
autoreconf -fi
CC=clang \
CFLAGS='-O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer' \
LDFLAGS='-fsanitize=address,undefined' \
./configure --with-oniguruma=builtin --disable-docs
make -j"$(nproc)"

Witness commands:

echo '[0,1]' | ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./jq -c 'has(infinite)'
echo '[0,1]' | ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./jq -c 'del(.[infinite])'

Useful controls:

echo '[0,1]' | ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./jq -c 'has(1)'
echo '[0,1]' | ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./jq -c 'has(nan)'
echo '[0,1]' | ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./jq -c 'del(.[1])'
echo '[0,1]' | ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./jq -c 'del(.[nan])'
echo '[0,1]' | ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./jq -c 'getpath([infinite])'

Observed Result / Expected Behavior

Fresh witness/control summary on commit
5f2a14dd1b03a8b43015058ed006dd4ab24fb58f:

has(1)               -> exit 0, stdout: true
has(nan)             -> exit 0, stdout: false
has(infinite)        -> exit 1, UBSan runtime error
del(.[1])            -> exit 0, stdout: [0]
del(.[nan])          -> exit 0, stdout: [0,1]
del(.[infinite])     -> exit 1, UBSan runtime error
getpath([infinite])  -> exit 0, stdout: null

Observed behavior: has(infinite) and del(.[infinite]) trigger UBSan on
inf to int conversion.

Expected behavior: jq should reject or normalize infinite array indices without
undefined behavior, consistent with nearby special-number handling that already
fails closed or returns a stable non-crashing result.

Sanitized UBSan excerpts:

src/jv_aux.c:249:33: runtime error: inf is outside the range of representable values of type 'int'
    #0 ... in jv_has src/jv_aux.c:249:33
    #1 ... in jq_next src/execute.c:916:21
    #2 ... in process src/main.c:179:31
    #3 ... in main src/main.c:682:15
src/jv_aux.c:322:22: runtime error: inf is outside the range of representable values of type 'int'
    #0 ... in jv_dels src/jv_aux.c:322:22
    #1 ... in delpaths_sorted src/jv_aux.c:498:14
    #2 ... in jv_delpaths src/jv_aux.c:539:10
    #3 ... in jq_next src/execute.c:916:21

Environment

  • Source revision / version: jq-1.8.2rc1 at commit
    5f2a14dd1b03a8b43015058ed006dd4ab24fb58f
  • Host OS: Arch Linux
  • Architecture: x86_64
  • Toolchain: clang 22.1.3
  • Build mode: AddressSanitizer + UndefinedBehaviorSanitizer with
    -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer
  • Configure flags: --with-oniguruma=builtin --disable-docs
  • Runtime knobs: ASAN_OPTIONS=detect_leaks=0 and
    UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1

Additional Context

The failing surface is ordinary jq CLI behavior, not a harness-only helper.
The sibling controls show that jq already distinguishes special numeric array
indices in nearby paths:

  • has(nan) already fails closed
  • del(.[nan]) already fails closed
  • getpath([infinite]) already stays clean on the same input family

The retained witness is consistent with jv_has() and jv_dels() still doing
raw double -> int conversions for infinite indices while nearby logic already
normalizes or bounds the value first.

Affected Commit

  • latest reproduced head: 5f2a14dd1b03a8b43015058ed006dd4ab24fb58f

Proposed Patch Or Fix Sketch

I am not attaching a full patch yet because jq's current callers do not all
want the same fallback behavior:

  • has(nan) returns false
  • del(.[nan]) leaves the input unchanged
  • getpath([infinite]) returns null

The narrow fix direction seems to be:

  • add one shared numeric-array-index normalization helper before any
    double -> int cast
  • reject nan, +infinity, and -infinity before the cast
  • handle out-of-range finite values in the same helper
  • let each caller map the normalized result into its existing caller-specific
    behavior

At minimum, regression coverage should include:

  • has(1)
  • has(nan)
  • has(infinite)
  • del(.[1])
  • del(.[nan])
  • del(.[infinite])
  • getpath([infinite])

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions