emscripten-core / emscripten-core/emscripten

Heap corruption after fetch when using WASM exceptions

Open
#23,168 8 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
27.6k
Forks
3.6k
Avg merge
1d 1h
Merged PRs (30d)
105

Description

This is a weird one!

After calling `emscripten_fetch`, heap-allocated memory held by members of objects allocated on the stack (such as the `test_vector` in the example below) becomes corrupted with garbage values. This **only** happens when exception mode is WASM exceptions.

I isolated a minimal example of this with `emscripten_fetch`, but I believe it's not limited to the fetch functionality, as I get similar symptoms elsewhere when enabling WASM exceptions in more complex programs.

Below is a minimal example to trigger this behaviour, with a CMakeLists to aid in setting the right flags:

main.cpp
```cpp
#include
#include
#include

struct test_struct {
unsigned int test_int{123};
std::vector test_vector{123, 456, 789};

test_struct() {
std::cout << "At construction: Test int " << test_int << ", vector " << test_vector[0] << ", " << test_vector[1] << ", " << test_vector[2] << std::endl;
}
};

void on_success(emscripten_fetch_t *fetch) {
/// Success callback
auto &test{*static_cast(fetch->userData)};
std::cout << "In success callback: Test int " << test.test_int << ", vector " << test.test_vector[0] << ", " << test.test_vector[1] << ", " << test.test_vector[2] << std::endl;
emscripten_fetch_close(fetch);
};

auto main()->int {
test_struct test;

emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
std::strcpy(attr.requestMethod, "GET");
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE;
attr.userData = &test;
attr.onsuccess = on_success;
emscripten_fetch(&attr, "test.txt");

std::cout << "Immediately after fetch: Test int " << test.test_int << ", vector " << test.test_vector[0] << ", " << test.test_vector[1] << ", " << test.test_vector[2] << std::endl;

emscripten_set_main_loop_arg([](void *data){
auto &test{*reinterpret_cast(data)};
std::cout << "In main loop: Test int " << test.test_int << ", vector " << test.test_vector[0] << ", " << test.test_vector[1] << ", " << test.test_vector[2] << std::endl;
}, &test, 0, true);

std::unreachable();
}
```

CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.13)

project(client)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(EXCEPTION_HANDLING wasm CACHE STRING "Exception handling mode: none, js or wasm")
if(EXCEPTION_HANDLING STREQUAL "none")
message(STATUS "Exception handling disabled")
set(exception_compile_definitions
DISABLE_EXCEPTION_THROWING
DISABLE_EXCEPTION_CATCHING
)
set(exception_compile_options
-fignore-exceptions
-fno-exceptions
)
set(exception_link_options
-fno-except
-fignore-exceptions
)
elseif(EXCEPTION_HANDLING STREQUAL "js")
message(STATUS "Exception handling using JS (slow)")
set(exception_compile_definitions
NO_DISABLE_EXCEPTION_CATCHING
)
set(exception_compile_options
-fexceptions
)
set(exception_link_options
-fexceptions
)
elseif(EXCEPTION_HANDLING STREQUAL "wasm")
message(STATUS "Exception handling using WASM)")
set(exception_compile_definitions
NO_DISABLE_EXCEPTION_CATCHING
)
set(exception_compile_options
-fwasm-exceptions
)
set(exception_link_options
-fwasm-exceptions
)
else()
message(FATAL_ERROR "Invalid exception handling mode \"${EXCEPTION_HANDLING}\"")
endif()

add_executable(client
main.cpp
)

target_compile_options(client PRIVATE
${exception_compile_options}
)

target_link_options(client PRIVATE
-sFETCH
${exception_link_options}
-sENVIRONMENT=web
)

set(CMAKE_EXECUTABLE_SUFFIX ".html")
```

Build and run:
```sh
emcmake cmake -B build
emmake cmake --build build -j$(nproc)
emrun build/client.html
```

Example console output in Chromium 131.0.6778.108:
![image](https://github.com/user-attachments/assets/89970cad-3aac-4beb-836f-2ba134599e24)

Example output in Firefox Nightly:
![image](https://github.com/user-attachments/assets/b8060a63-e3c3-474c-8278-df66fa29ae57)

Note that we expect the `test_vector` contents to be `123`, `456`, `789` but these values get replaced after the `emscripten_fetch` call.

Switch `EXCEPTION_HANDLING` in `CMakeLists.txt` from `wasm` to `js` or `none` to observe that this issue only happens when using WASM exceptions.

Comment out `emscripten_fetch` to observe the heap corruption goes away, even in WASM exception mode.

You can create a `test.txt` file to trigger the on_success callback, but the issue occurs even if the download doesn't succeed.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.