Skip to content

Fix undefined double-to-int cast in lexer_construct_number_object (#5304) - #5308

Open
1820893135-pixel wants to merge 1 commit into
jerryscript-project:masterfrom
1820893135-pixel:fix-lexer-number-cast
Open

1820893135-pixel wants to merge 1 commit into
jerryscript-project:masterfrom
1820893135-pixel:fix-lexer-number-cast

Conversation

@1820893135-pixel

Copy link
Copy Markdown

lexer_construct_number_object() tries to fold a numeric literal into a
CBC_PUSH_NUMBER_BYTE_RANGE immediate by casting the parsed double to
int32_t and then checking whether the cast round-trips (js-lexer.c:2539):

int32_t int_num = (int32_t) num;

if (int_num == num && int_num <= CBC_PUSH_NUMBER_BYTE_RANGE_END && ...)

For a literal outside the int32_t range the cast itself is already undefined
behaviour - the comparison afterwards is too late to guard it. Under
-fsanitize=undefined (the OSS-Fuzz configuration) it traps:

js-lexer.c:2539:25: runtime error: 1e+308 is outside the range of representable
values of type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior js-lexer.c:2539:25

The trigger is plain, valid JavaScript - no malformed input involved:

var a = 1e308;

Reachable through jerry_parse / jerry_eval, so any embedder that evaluates
untrusted source deterministically aborts a UBSan build.

Fix

Move the range test in front of the cast:

if (is_expr && num >= (ecma_number_t) INT32_MIN && num <= (ecma_number_t) INT32_MAX)
{
  int32_t int_num = (int32_t) num;
  ...
}

Literals outside the range simply take the float path, which is exactly what
already happened for every value the fold did not apply to.

Testing

Adds tests/jerry/number-literal-range.js covering literals on both sides of
the boundary. On an unpatched -fsanitize=undefined build the new file aborts
with the report above; with the fix it passes.

Fixes #5304.

lexer_construct_number_object() tries to fold a numeric literal into a
CBC_PUSH_NUMBER_BYTE_RANGE immediate by casting the parsed double to
int32_t and then checking whether the cast round-trips:

    int32_t int_num = (int32_t) num;
    if (int_num == num && ...)

For a literal outside the int32_t range the cast itself is already
undefined behaviour; it is not the comparison that guards it.  Under
-fsanitize=undefined (the OSS-Fuzz configuration) this traps:

    js-lexer.c:2539:25: runtime error: 1e+308 is outside the range of
    representable values of type 'int'

The input is plain, valid JavaScript, so any host that evaluates
untrusted source (jerry_parse / jerry_eval) deterministically aborts:
`var a = 1e308;` is enough.

Move the range test in front of the cast:

    if (is_expr && num >= (ecma_number_t) INT32_MIN
                && num <= (ecma_number_t) INT32_MAX)
    {
      int32_t int_num = (int32_t) num;
      ...

Literals outside the range simply take the float path, which is what
already happened for every value the fold did not apply to.

JerryScript-DCO-1.0-Signed-off-by: 1820893135-pixel <1820893135@qq.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]lexer_construct_number_object casts oversized numeric literal to int32 -> UBSan float-cast-overflow (SIGILL) (CWE-681)

1 participant