Skip to content

store_json_encode: double->int cast evaluated before the magnitude check (UB, same shape as #695) #816

Description

@Nitjsefnie

Describe the bug

store_json_encode's number path in src/ext_store.c narrows a double to int before checking its magnitude:

if (n == (int)n && fabs(n) < 1e15)

&& evaluates left to right, so (int)n runs first. Converting a double whose truncated value is outside int's range is undefined behaviour (C11 6.3.1.4p1), and the magnitude guard that would have prevented it sits on the right-hand side where it cannot help.

This is the same pattern as #695 (value_to_string: double→long long cast evaluated before the range check), which was fixed — so this looks like a second instance of it rather than a new class.

Any script value large enough reaches it: store_put encodes whatever number it is given, and 1e300 is an ordinary EigenScript number.

To reproduce

db is store_open of "/tmp/ub.eigstore"
ignored is store_put of [db, {"n": 1e300}]

Built with -fsanitize=float-cast-overflow, that reports the cast. Note this does not show up under the repo's existing sanitizer job: GCC's -fsanitize=undefined does not include float-cast-overflow, so make asan is silent on it. I verified that separately rather than assuming it:

$ gcc -fsanitize=address,undefined -g -O1 -o probe probe.c && ./probe
(int)1e300 = -2147483648                    <- silent

$ gcc -fsanitize=float-cast-overflow -g -O1 -o probe probe.c && ./probe
probe.c:3:1: runtime error: 1e+300 is outside the range of representable values of type 'int'

Expected behavior

The magnitude is checked before the narrowing cast, so no out-of-range double is ever converted.

Actual behavior

The cast happens first and is undefined. In practice on x86-64 it is harmless — cvttsd2si returns INT_MIN, which then fails the n == (int)n comparison and takes the intended %.15g branch — so the observable output is currently correct. It is correct by hardware accident rather than by the language, and nothing obliges a different target or a more aggressive optimisation level to preserve it.

Environment

  • OS: Debian 13
  • GCC version: 14.2.0
  • EigenScript version: 0.34.0 (main at 14c3a07)

Found while fixing #805; the decoder had the same defect one line from the CodeQL alert that led me there, and that half is fixed in #814. This encoder half is outside that PR's scope, so filing rather than folding it in.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions