Skip to content

Commit 361b183

Browse files
authored
[Backport] fix data race and use-after-free in napi_threadsafe_function (#199)
1 parent 7eb6a37 commit 361b183

29 files changed

Lines changed: 773 additions & 266 deletions

.github/workflows/main.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ permissions:
3232
contents: write
3333

3434
env:
35-
WASI_VERSION: '27'
36-
WASI_VERSION_FULL: '27.0'
37-
WASI_SDK_PATH: './wasi-sdk-27.0'
38-
EM_VERSION: '4.0.1'
35+
WASI_VERSION: '29'
36+
WASI_VERSION_FULL: '29.0'
37+
WASI_SDK_PATH: './wasi-sdk-29.0'
38+
EM_VERSION: '4.0.11'
3939
EM_CACHE_FOLDER: 'emsdk-cache'
4040
NODE_VERSION: '24.11.1'
4141

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
}
2828
},
2929
"dependencies": {
30-
"@emnapi/wasi-threads": "1.1.0",
30+
"@emnapi/wasi-threads": "1.2.0",
3131
"tslib": "^2.4.0"
3232
},
3333
"scripts": {

packages/emnapi/CMakeLists.txt

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ endif()
143143
if(NODE_WANT_INTERNALS)
144144
add_compile_definitions("NODE_WANT_INTERNALS=${NODE_WANT_INTERNALS}")
145145
endif()
146+
if(EMNAPI_WORKER_POOL_SIZE)
147+
add_compile_definitions("EMNAPI_WORKER_POOL_SIZE=${EMNAPI_WORKER_POOL_SIZE}")
148+
endif()
149+
if(EMNAPI_NEXTTICK_TYPE)
150+
add_compile_definitions("EMNAPI_NEXTTICK_TYPE=${EMNAPI_NEXTTICK_TYPE}")
151+
endif()
152+
if(EMNAPI_USE_PROXYING)
153+
add_compile_definitions("EMNAPI_USE_PROXYING=${EMNAPI_USE_PROXYING}")
154+
endif()
146155

147156
add_library(${EMNAPI_TARGET_NAME} STATIC ${EMNAPI_SRC} ${UV_SRC})
148157
target_include_directories(${EMNAPI_TARGET_NAME} PUBLIC ${EMNAPI_INCLUDE})
@@ -152,11 +161,10 @@ if(IS_EMSCRIPTEN)
152161
target_link_options(${EMNAPI_TARGET_NAME} INTERFACE "--js-library=${EMNAPI_JS_LIB}")
153162
endif()
154163

155-
add_library(${EMNAPI_BASIC_TARGET_NAME} STATIC ${ENAPI_BASIC_SRC})
164+
add_library(${EMNAPI_BASIC_TARGET_NAME} STATIC ${ENAPI_BASIC_SRC} ${UV_SRC})
156165
target_include_directories(${EMNAPI_BASIC_TARGET_NAME} PUBLIC ${EMNAPI_INCLUDE})
157166
target_compile_definitions(${EMNAPI_BASIC_TARGET_NAME}
158167
PUBLIC ${EMNAPI_DEFINES}
159-
PRIVATE "EMNAPI_DISABLE_UV"
160168
)
161169
if(IS_EMSCRIPTEN)
162170
set_target_properties(${EMNAPI_BASIC_TARGET_NAME} PROPERTIES INTERFACE_LINK_DEPENDS ${EMNAPI_JS_LIB})
@@ -184,27 +192,27 @@ if(EMNAPI_BUILD_FOR_NAPI_RS)
184192

185193
add_library(${EMNAPI_BASIC_NAPI_RS_MT_TARGET_NAME} STATIC
186194
${ENAPI_BASIC_SRC}
195+
${UV_SRC}
187196
"${CMAKE_CURRENT_SOURCE_DIR}/src/thread/async_worker_create.c"
188197
"${CMAKE_CURRENT_SOURCE_DIR}/src/thread/async_worker_init.S"
189198
)
190199
target_include_directories(${EMNAPI_BASIC_NAPI_RS_MT_TARGET_NAME} PUBLIC ${EMNAPI_INCLUDE})
191200
target_compile_definitions(${EMNAPI_BASIC_NAPI_RS_MT_TARGET_NAME}
192201
PUBLIC ${EMNAPI_DEFINES} "NAPI_EXTERN=__attribute__((__import_module__(\"env\")))"
193-
PRIVATE "EMNAPI_DISABLE_UV"
194202
)
195203
endif()
196204

197205
if(EMNAPI_BUILD_BASIC_MT)
198206
add_library(${EMNAPI_BASIC_MT_TARGET_NAME} STATIC
199207
${ENAPI_BASIC_SRC}
208+
${UV_SRC}
200209
"${CMAKE_CURRENT_SOURCE_DIR}/src/thread/async_worker_create.c"
201210
"${CMAKE_CURRENT_SOURCE_DIR}/src/thread/async_worker_init.S"
202211
)
203212
target_compile_options(${EMNAPI_BASIC_MT_TARGET_NAME} PUBLIC "-matomics" "-mbulk-memory")
204213
target_include_directories(${EMNAPI_BASIC_MT_TARGET_NAME} PUBLIC ${EMNAPI_INCLUDE})
205214
target_compile_definitions(${EMNAPI_BASIC_MT_TARGET_NAME}
206215
PUBLIC ${EMNAPI_DEFINES}
207-
PRIVATE "EMNAPI_DISABLE_UV"
208216
)
209217

210218
if(IS_EMSCRIPTEN)
@@ -231,15 +239,6 @@ if(EMNAPI_BUILD_MT)
231239
set_target_properties(${EMNAPI_MT_TARGET_NAME} PROPERTIES INTERFACE_LINK_DEPENDS ${EMNAPI_JS_LIB})
232240
target_link_options(${EMNAPI_MT_TARGET_NAME} INTERFACE "--js-library=${EMNAPI_JS_LIB}")
233241
endif()
234-
if(EMNAPI_WORKER_POOL_SIZE)
235-
target_compile_definitions(${EMNAPI_MT_TARGET_NAME} PRIVATE "EMNAPI_WORKER_POOL_SIZE=${EMNAPI_WORKER_POOL_SIZE}")
236-
endif()
237-
if(EMNAPI_NEXTTICK_TYPE)
238-
target_compile_definitions(${EMNAPI_MT_TARGET_NAME} PRIVATE "EMNAPI_NEXTTICK_TYPE=${EMNAPI_NEXTTICK_TYPE}")
239-
endif()
240-
if(EMNAPI_USE_PROXYING)
241-
target_compile_definitions(${EMNAPI_MT_TARGET_NAME} PRIVATE "EMNAPI_USE_PROXYING=${EMNAPI_USE_PROXYING}")
242-
endif()
243242
endif()
244243

245244
if(IS_EMSCRIPTEN)

packages/emnapi/emnapi.gyp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,21 @@
4848
'target_name': 'emnapi_basic',
4949
'type': 'static_library',
5050
'defines': [
51-
'EMNAPI_DISABLE_UV'
5251
],
5352
'sources': [
5453
'src/js_native_api.c',
5554
'src/node_api.c',
5655
'src/async_cleanup_hook.c',
5756
'src/async_context.c',
5857
'src/wasi_wait.c',
58+
59+
'src/uv/uv-common.c',
60+
'src/uv/threadpool.c',
61+
'src/uv/unix/loop.c',
62+
'src/uv/unix/posix-hrtime.c',
63+
'src/uv/unix/thread.c',
64+
'src/uv/unix/async.c',
65+
'src/uv/unix/core.c',
5966
],
6067
'link_settings': {
6168
'target_conditions': [

packages/emnapi/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
},
1515
"scripts": {
1616
"build": "node ./script/build.js",
17-
"version": "node ./script/version.js"
17+
"version": "node ./script/version.js",
18+
"generate-struct-info-wasm32": "node -e \"fs.mkdirSync('script/out',{recursive:true})\" && emcc -Iinclude/node -pthread script/generate_struct_info.c -sNODERAWFS -o script/out/generate_struct_info_wasm32.js && node ./script/out/generate_struct_info_wasm32.js",
19+
"generate-struct-info-wasm64": "node -e \"fs.mkdirSync('script/out',{recursive:true})\" && emcc -Iinclude/node -pthread script/generate_struct_info.c -sNODERAWFS -sMEMORY64 -o script/out/generate_struct_info_wasm64.js && node ./script/out/generate_struct_info_wasm64.js",
20+
"generate-struct-info": "npm run generate-struct-info-wasm32 && npm run generate-struct-info-wasm64"
1821
},
1922
"repository": {
2023
"type": "git",
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include <stdio.h>
2+
#include "../src/emnapi_internal.h"
3+
#include "../src/threadsafe_function.h"
4+
5+
#ifdef __wasm64__
6+
#define WASM_BIT "64"
7+
#else
8+
#define WASM_BIT "32"
9+
#endif
10+
11+
void print_napi_env_info(FILE* f) {
12+
fprintf(f, "declare const enum NapiEnvOffset" WASM_BIT " {\n");
13+
fprintf(f, " __size__ = %zu,\n", sizeof(node_api_base_env__));
14+
fprintf(f, " vptr = %zu,\n", offsetof(node_api_base_env__, vptr));
15+
fprintf(f, " sentinel = %zu,\n", offsetof(node_api_base_env__, sentinel));
16+
fprintf(f, " js_vtable = %zu,\n", offsetof(node_api_base_env__, js_vtable));
17+
fprintf(f, " module_vtable = %zu,\n", offsetof(node_api_base_env__, module_vtable));
18+
fprintf(f, " id = %zu,\n", offsetof(node_api_base_env__, id));
19+
fprintf(f, " last_error = %zu,\n", offsetof(node_api_base_env__, last_error));
20+
fprintf(f, " last_error___size__ = %zu,\n", sizeof(napi_extended_error_info));
21+
fprintf(f, " last_error_error_message = %zu,\n", offsetof(node_api_base_env__, last_error) + offsetof(napi_extended_error_info, error_message));
22+
fprintf(f, " last_error_engine_reserved = %zu,\n", offsetof(node_api_base_env__, last_error) + offsetof(napi_extended_error_info, engine_reserved));
23+
fprintf(f, " last_error_engine_error_code = %zu,\n", offsetof(node_api_base_env__, last_error) + offsetof(napi_extended_error_info, engine_error_code));
24+
fprintf(f, " last_error_error_code = %zu,\n", offsetof(node_api_base_env__, last_error) + offsetof(napi_extended_error_info, error_code));
25+
fprintf(f, "}\n");
26+
}
27+
28+
void print_napi_threadsafe_function_info(FILE* f) {
29+
fprintf(f, "declare const enum NapiTSFNOffset" WASM_BIT " {\n");
30+
fprintf(f, " __size__ = %zu,\n", sizeof(struct napi_threadsafe_function__));
31+
fprintf(f, " async_resource = %zu,\n", offsetof(struct napi_threadsafe_function__, async_resource));
32+
fprintf(f, " async_resource___size__ = %zu,\n", sizeof(optional_async_resource));
33+
fprintf(f, " async_resource_resource = %zu,\n", offsetof(struct napi_threadsafe_function__, async_resource) + offsetof(optional_async_resource, resource_));
34+
fprintf(f, " async_resource_async_context = %zu,\n", offsetof(struct napi_threadsafe_function__, async_resource) + offsetof(optional_async_resource, async_context_));
35+
fprintf(f, " async_resource_async_context___size__ = %zu,\n", sizeof(async_context));
36+
fprintf(f, " async_resource_async_context_async_id = %zu,\n", offsetof(struct napi_threadsafe_function__, async_resource) + offsetof(optional_async_resource, async_context_) + offsetof(async_context, async_id));
37+
fprintf(f, " async_resource_async_context_trigger_async_id = %zu,\n", offsetof(struct napi_threadsafe_function__, async_resource) + offsetof(optional_async_resource, async_context_) + offsetof(async_context, trigger_async_id));
38+
fprintf(f, " async_resource_is_some = %zu,\n", offsetof(struct napi_threadsafe_function__, async_resource) + offsetof(optional_async_resource, is_some));
39+
fprintf(f, " mutex = %zu,\n", offsetof(struct napi_threadsafe_function__, mutex));
40+
fprintf(f, " cond = %zu,\n", offsetof(struct napi_threadsafe_function__, cond));
41+
fprintf(f, " queue_size = %zu,\n", offsetof(struct napi_threadsafe_function__, queue_size));
42+
fprintf(f, " queue = %zu,\n", offsetof(struct napi_threadsafe_function__, queue));
43+
fprintf(f, " async = %zu,\n", offsetof(struct napi_threadsafe_function__, async));
44+
fprintf(f, " thread_count = %zu,\n", offsetof(struct napi_threadsafe_function__, thread_count));
45+
fprintf(f, " state = %zu,\n", offsetof(struct napi_threadsafe_function__, state));
46+
fprintf(f, " dispatch_state = %zu,\n", offsetof(struct napi_threadsafe_function__, dispatch_state));
47+
fprintf(f, " context = %zu,\n", offsetof(struct napi_threadsafe_function__, context));
48+
fprintf(f, " max_queue_size = %zu,\n", offsetof(struct napi_threadsafe_function__, max_queue_size));
49+
fprintf(f, " ref = %zu,\n", offsetof(struct napi_threadsafe_function__, ref));
50+
fprintf(f, " env = %zu,\n", offsetof(struct napi_threadsafe_function__, env));
51+
fprintf(f, " finalize_data = %zu,\n", offsetof(struct napi_threadsafe_function__, finalize_data));
52+
fprintf(f, " finalize_cb = %zu,\n", offsetof(struct napi_threadsafe_function__, finalize_cb));
53+
fprintf(f, " call_js_cb = %zu,\n", offsetof(struct napi_threadsafe_function__, call_js_cb));
54+
fprintf(f, " handles_closing = %zu,\n", offsetof(struct napi_threadsafe_function__, handles_closing));
55+
fprintf(f, " async_ref = %zu,\n", offsetof(struct napi_threadsafe_function__, async_ref));
56+
fprintf(f, "}\n");
57+
}
58+
59+
int main() {
60+
FILE* f = fopen("src/typings/struct-wasm" WASM_BIT ".d.ts", "w");
61+
if (f == NULL) {
62+
fprintf(stderr, "Failed to open file\n");
63+
return 1;
64+
}
65+
66+
print_napi_env_info(f);
67+
print_napi_threadsafe_function_info(f);
68+
69+
fclose(f);
70+
return 0;
71+
}

packages/emnapi/src/async_cleanup_hook.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ _emnapi_ach_handle_create(napi_env env,
8282
return handle;
8383
}
8484

85-
EMNAPI_INTERNAL_EXTERN void _emnapi_set_immediate(void (*callback)(void*), void* data);
86-
8785
static void _emnapi_ach_handle_env_unref(void* arg) {
8886
napi_env env = (napi_env) arg;
8987
_emnapi_env_unref(env);

packages/emnapi/src/core/async-work.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ var emnapiAWMT = {
8989
const worker = onCreateWorker({ type: 'async-work', name: 'emnapi-async-worker' })
9090
const p = PThread.loadWasmModuleToWorker(worker)
9191
emnapiAWMT.addListener(worker)
92+
emnapiTSFN.addListener(worker)
9293
promises.push(p.then(() => {
9394
if (typeof worker.unref === 'function') {
9495
worker.unref()

packages/emnapi/src/core/async.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ function emnapiGetWorkerByPthreadPtr (pthreadPtr: number): any {
2525
return worker
2626
}
2727

28+
/** @__sig vp */
29+
export function _emnapi_worker_ref (pthreadPtr: number): void {
30+
if (ENVIRONMENT_IS_PTHREAD) return
31+
const worker = emnapiGetWorkerByPthreadPtr(pthreadPtr)
32+
if (worker && typeof worker.ref === 'function') {
33+
worker.ref()
34+
}
35+
}
36+
2837
/** @__sig vp */
2938
export function _emnapi_worker_unref (pthreadPtr: number): void {
3039
if (ENVIRONMENT_IS_PTHREAD) return

packages/emnapi/src/emnapi_internal.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#ifndef EMNAPI_INTERNAL_H
2+
#define EMNAPI_INTERNAL_H
3+
14
#include "emnapi.h"
25

36
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
@@ -109,6 +112,13 @@ EMNAPI_INTERNAL_EXTERN int _emnapi_is_main_browser_thread();
109112
EMNAPI_INTERNAL_EXTERN int _emnapi_is_main_runtime_thread();
110113
EMNAPI_INTERNAL_EXTERN double _emnapi_get_now();
111114
EMNAPI_INTERNAL_EXTERN void _emnapi_unwind();
115+
EMNAPI_INTERNAL_EXTERN void _emnapi_set_immediate(void (*callback)(void*), void* data);
116+
EMNAPI_INTERNAL_EXTERN napi_status _emnapi_create_function(napi_env env,
117+
const char* utf8name,
118+
size_t length,
119+
napi_callback cb,
120+
void* data,
121+
napi_value* result);
112122

113123
#if defined(__EMSCRIPTEN_PTHREADS__) || defined(_REENTRANT)
114124
#define EMNAPI_HAVE_THREADS 1
@@ -186,3 +196,5 @@ void _emnapi_env_check_gc_access(napi_env env);
186196
} while (0)
187197

188198
EXTERN_C_END
199+
200+
#endif

0 commit comments

Comments
 (0)