Inadequate Input Validation and Bounds Checking in JSON parser
- Langage dominant
- Jupyter Notebook
- Étoiles
- 15.1k
- Forks
- 1.8k
- Métriques de merge des PR
- Aucune PR mergée en 30 j
Description
Hi,
I was taking a look at the robustness of the used JSON handler, and I found that the JSON parser in `json.h` is susceptible to several security vulnerabilities that can be triggered by malformed or malicious JSON input. These issues all stem from a single root cause: a lack of robust input validation and bounds checking during the parsing process. This can lead to out-of-bounds reads, resulting in crashes and undefined behavior.
## Description:
The JSON parser in json.h is susceptible to several security vulnerabilities that can be triggered by malformed or malicious JSON input. These issues all stem from a single root cause: a lack of robust input validation and bounds checking during the parsing process. This can lead to out-of-bounds reads, resulting in crashes, undefined behavior, or potential information disclosure.
## Root Cause:
The parser does not consistently verify that it is operating within the bounds of the input string, nor does it sufficiently handle unexpected or non-compliant data structures. This permissive parsing can be exploited by an attacker to cause denial of service or other security-related issues.
## Affected Functions and Vulnerabilities:
The provided patch addresses critical vulnerabilities in the following parsing functions:
- parse_object:
- Out-of-Bounds Read: A malformed object, such as one that is abruptly terminated (e.g., `{"key":`), could cause the parser to read past the end of the input string while searching for a colon, value, or closing brace.
- **Incorrect Key Type**: The original implementation did not validate that the key in a key-value pair is a string, as required by the JSON standard. This could lead to undefined behavior if a non-string key is encountered.
- parse_array:
- Out-of-Bounds Read: Similar to parse_object, a truncated or improperly formatted array (e.g., `[1, 2,`) could cause the parser to read beyond the input buffer when looking for a comma or closing bracket.
- parse_string:
- Out-of-Bounds Read: An unterminated string literal (e.g., `"abc`) or a malformed escape sequence at the end of the input (e.g., `"abc\\`) could lead to an out-of-bounds read as the parser continues to search for a closing quote or the character following a backslash.
- parse_number:
- Out-of-Bounds Read: A number followed by an unexpected character could cause the parser to read past the end of the valid number string.
- **Unhandled Exceptions**: The original code did not handle exceptions that can be thrown by `std::stol` and `std::stod` for invalid arguments, potentially leading to a crash if the parsed number string is malformed (e.g., containing non-numeric characters).
All of the above bugs were successfully triggered by my fuzzer.
## Example log:
```
0x7bc956de0073 is located 0 bytes after 35-byte region [0x7bc956de0050,0x7bc956de0073)
allocated by thread T0 here:
# 0 0x55a4ae1ab179(/ home / foo / vosk - api / fuzz / harness + 0x25b179)( \
BuildId : 44f859b11e459f2f02a6984fe9aefbe87662a9dd)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/foo/vosk-api/fuzz/harness+0x844774) (BuildId: 44f859b11e459f2f02a6984fe9aefbe87662a9dd)
Shadow bytes around the buggy address:
0x7bc956ddfd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7bc956ddfe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7bc956ddfe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7bc956ddff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7bc956ddff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7bc956de0000: fa fa fd fd fd fd fd fd fa fa 00 00 00 00[03]fa
0x7bc956de0080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7bc956de0100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7bc956de0180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7bc956de0200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7bc956de0280: 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
==1222728==ABORTING
[2025-07-29T10:28:56Z ERROR libafl::executors::hooks::unix::unix_signal_handler] Crashed with SIGABRT
[2025-07-29T10:28:56Z ERROR libafl::executors::hooks::unix::unix_signal_handler] Child crashed!
[2025-07-29T10:28:56Z ERROR libafl::executors::hooks::unix::unix_signal_handler] input: "d0cd3f16254b9e1e"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ CRASH ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Received signal SIGABRT at 0x007f8957aa8f2b, fault address: 0x00000000000000
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ REGISTERS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
r8 : 0x00000000000016, r9 : 0x007f89581068c0, r10: 0x00000000000003, r11: 0x00000000000246,
r12: 0x00000000000000, r13: 0x00000000000001, r14: 0x007bc956de0073, r15: 0x00000000000001,
rdi: 0x0000000012a848, rsi: 0x0000000012a848, rbp: 0x0000000012a848, rbx: 0x00000000000006,
rdx: 0x00000000000006, rax: 0x00000000000000, rcx: 0x007f8957aa8f2b, rsp: 0x007ffcd82aacb0,
rip: 0x007f8957aa8f2b, efl: 0x00000000000246,
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
0: libafl_bolts::minibsod::generate_minibsod
at /home/foo/git/publ/LibAFL/crates/libafl_bolts/src/minibsod.rs:1122:30
1: libafl::executors::hooks::unix::unix_signal_handler::inproc_crash_handler
at /home/foo/git/publ/LibAFL/crates/libafl/src/executors/hooks/unix.rs:243:36
2: libafl::executors::hooks::unix::unix_signal_handler::::handle
3: libafl_bolts::os::unix_signals::handle_signal
at /home/foo/git/publ/LibAFL/crates/libafl_bolts/src/os/unix_signals.rs:446:17
4:
5: pthread_kill
6: gsignal
7: abort
8: _ZN11__sanitizer5AbortEv.cold
9: _ZN11__sanitizer3DieEv
10: _ZN6__asan19ScopedInErrorReportD2Ev
11: _ZN6__asan18ReportGenericErrorEmmmmbmjb.part.0
12: __asan_report_load1
13: _ZN4json12_GLOBAL__N_112parse_stringERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERm
at /home/foo/vosk-api/fuzz/./../src/json.h:522:0
_ZN4json12_GLOBAL__N_110parse_nextERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERm
at /home/foo/vosk-api/fuzz/./../src/json.h:638:31
14: _ZN4json4JSON4LoadERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
at /home/foo/vosk-api/fuzz/./../src/json.h:652:12
LLVMFuzzerTestOneInput
at /home/foo/vosk-api/fuzz/harness.cc:18:3
```
### Crashing input
```
00000000: 2229 6178 5c72 745c 7276 5c72 5c72 605c ")ax\rt\rv\r\r`\
00000010: 7274 5c72 765c 725c 7274 7479 645c b874 rt\rv\r\rttyd\.t
00000020: 5c72
```
## Additional notes
So far the JSON implementation has only been tested in an isolated context. I will take a closer look at the overall usage, e.g. in `recgonizer.cc` next.
__NOTE__: The JSON parser is mainly used for validating the `grammar` object. Regardless, the current JSON implementation is easiliy crashable which can also be be mitigated with some sanitiy/bounds-check which in turn would make the overall application more robust.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Évaluation
Cette issue n'a pas encore été évaluée.