mz-automation / libiec61850

Official repository for libIEC61850, the open-source library for the IEC 61850 protocols
http://libiec61850.com/libiec61850
GNU General Public License v3.0
828 stars 444 forks source link

Out-of-Bound Read in /src/libiec61850/src/goose/goose_receiver.c:665:9 in parseGoosePayload #509

Open gnbon opened 3 weeks ago

gnbon commented 3 weeks ago

Summary:

An Out-of-Bound Read vulnerability was discovered in the parseGooseMessage and parseGoosePayload functions of the goose_receiver.c file in the libiec61850 library through fuzzing. This vulnerability occurs when the size of the input data is 22 bytes.

Detailed Description:

At the beginning of the parseGooseMessagefunction, there is a check to return immediately if numbytes is less than 22. https://github.com/mz-automation/libiec61850/blob/7afa40390b26ad1f4cf93deaa0052fe7e357ef33/src/goose/goose_receiver.c#L908-L914

However, if numbytes is 22 or larger, this check is passed, and the code proceeds to parse the contents of the buffer using bufPos. https://github.com/mz-automation/libiec61850/blob/7afa40390b26ad1f4cf93deaa0052fe7e357ef33/src/goose/goose_receiver.c#L933-L955

For example, if numbytes is exactly 22, bufPos can reach the exact boundary of the buffer. When bufPos becomes 22, buffer + bufPos could refer to an invalid memory location. https://github.com/mz-automation/libiec61850/blob/7afa40390b26ad1f4cf93deaa0052fe7e357ef33/src/goose/goose_receiver.c#L933-L955

At the end of parseGooseMessage, the memory is passed to parseGoosePayload with an incorrect bound. https://github.com/mz-automation/libiec61850/blob/7afa40390b26ad1f4cf93deaa0052fe7e357ef33/src/goose/goose_receiver.c#L1001-L1002

This leads to out-of-bounds read when referencing buffer[bufPos++], causing the program to behave abnormally. https://github.com/mz-automation/libiec61850/blob/7afa40390b26ad1f4cf93deaa0052fe7e357ef33/src/goose/goose_receiver.c#L665

Root Cause:

The vulnerability arises due to insufficient boundary checks when the input data size is exactly 22 bytes. The code fails to properly validate and handle the case when bufPos reaches the end of the buffer, leading to out-of-bounds memory access.

Impact:

An attacker can use this vulnerability to leak data by sending deliberately crafted input data to trigger abnormal behavior in the program. This can potentially lead to information disclosure or other unintended consequences.

Recommendation:

To mitigate this vulnerability, it is crucial to perform thorough boundary checks regardless of the size of the input data. The code should be modified to ensure that bufPos never exceeds the valid range of the buffer. Additionally, proper input validation and error handling should be implemented to gracefully handle cases where the input data size is unexpected or malformed.

Proof of Concept (PoC):

A proof-of-concept exploit has been provided in the attached poc.zip file. This PoC demonstrates how the vulnerability can be triggered by sending specially crafted input data to the affected functions.

AddressSanitizer

The AddressSanitizer (ASan) log shows:

./fuzz_goose_parse poc-oobr-001.goose                                  
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 1941454991
INFO: Loaded 1 modules   (1694 inline 8-bit counters): 1694 [0x556316da7760, 0x556316da7dfe), 
INFO: Loaded 1 PC tables (1694 PCs): 1694 [0x556316da7e00,0x556316dae7e0), 
./fuzz_goose_parse: Running 1 inputs 1 time(s) each.
Running: poc-oobr-001
=================================================================
==56280==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x503000000176 at pc 0x556316d94425 bp 0x7ffca8eec5b0 sp 0x7ffca8eec5a8
READ of size 1 at 0x503000000176 thread T0
    #0 0x556316d94424 in parseGoosePayload /src/libiec61850/src/goose/goose_receiver.c:665:9
    #1 0x556316d94424 in parseGooseMessage /src/libiec61850/src/goose/goose_receiver.c:1002:9
    #2 0x556316d73e84 in LLVMFuzzerTestOneInput /src/libiec61850/build/../fuzz/fuzz_goose_parse.c:14:5
    #3 0x556316c24cd0 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:614:13
    #4 0x556316c0e814 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:327:6
    #5 0x556316c142aa in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:862:9
    #6 0x556316c412f2 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
    #7 0x7f0fed7d8d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 962015aa9d133c6cbcfb31ec300596d7f44d3348)
    #8 0x7f0fed7d8e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 962015aa9d133c6cbcfb31ec300596d7f44d3348)
    #9 0x556316c052ed in _start (/home/user/oss-fuzz/build/out/libiec61850/fuzz_goose_parse+0xa92ed)

0x503000000176 is located 0 bytes after 22-byte region [0x503000000160,0x503000000176)
allocated by thread T0 here:
    #0 0x556316d3345e in malloc /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:69:3
    #1 0x556316c42c96 in operator new(unsigned long) cxa_noexception.cpp
    #2 0x556316c0e814 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:327:6
    #3 0x556316c142aa in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:862:9
    #4 0x556316c412f2 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
    #5 0x7f0fed7d8d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 962015aa9d133c6cbcfb31ec300596d7f44d3348)

SUMMARY: AddressSanitizer: heap-buffer-overflow /src/libiec61850/src/goose/goose_receiver.c:665:9 in parseGoosePayload
Shadow bytes around the buggy address:
  0x502ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x503000000000: fa fa 00 00 00 fa fa fa 00 00 00 fa fa fa 00 00
  0x503000000080: 00 fa fa fa 00 00 00 fa fa fa 00 00 00 fa fa fa
=>0x503000000100: 00 00 00 00 fa fa 00 00 06 fa fa fa 00 00[06]fa
  0x503000000180: fa fa 00 00 05 fa fa fa fa fa fa fa fa fa fa fa
  0x503000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x503000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x503000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x503000000380: 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
==56280==ABORTING

CVE Assignment Request:

I kindly request the assignment of a Common Vulnerabilities and Exposures (CVE) identifier for the Out-of-Bound Read vulnerability