From 64a715bd469b65112205567b71fa8603f38533fb Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Fri, 9 Oct 2026 21:48:47 -0700 Subject: [PATCH] wasi: accept wasm i32 pointers with high bit set Wasm i32 arguments with the high bit set are passed to JS as negative Int32 numbers, so WASI functions taking uint32_t pointers rejected them with EINVAL. Accept Int32 in CheckType and reinterpret as uint32_t on conversion, fixing all WASI functions at once. Fixes: https://github.com/nodejs/node/issues/62671 Refs: https://github.com/nodejs/node/pull/62822 Assisted-by: OpenCode Signed-off-by: Guy Bedford --- src/node_wasi.cc | 7 ++++++- test/fixtures/wasi-high-bit-ptr.wasm | Bin 0 -> 311 bytes test/fixtures/wasi-high-bit-ptr.wat | 22 ++++++++++++++++++++++ test/wasi/test-wasi-high-bit-ptr.js | 24 ++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/wasi-high-bit-ptr.wasm create mode 100644 test/fixtures/wasi-high-bit-ptr.wat create mode 100644 test/wasi/test-wasi-high-bit-ptr.js diff --git a/src/node_wasi.cc b/src/node_wasi.cc index 6c2adc826571..844221b88983 100644 --- a/src/node_wasi.cc +++ b/src/node_wasi.cc @@ -36,6 +36,7 @@ using v8::FastApiCallbackOptions; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; +using v8::Int32; using v8::Integer; using v8::Isolate; using v8::Local; @@ -296,13 +297,17 @@ static bool CheckType(Local v); template static VT ConvertType(Local V); +// Wasm i32 values with the high bit set arrive as negative Int32 numbers. template <> bool CheckType(Local value) { - return value->IsUint32(); + return value->IsUint32() || value->IsInt32(); } template <> uint32_t ConvertType(Local value) { + if (value->IsInt32()) { + return static_cast(value.As()->Value()); + } return value.As()->Value(); } diff --git a/test/fixtures/wasi-high-bit-ptr.wasm b/test/fixtures/wasi-high-bit-ptr.wasm new file mode 100644 index 0000000000000000000000000000000000000000..78f123ffbde2691e35054364ed00057192e93813 GIT binary patch literal 311 zcmZ`!OA5j;5Pg%RDprac>)M^*$|KYpc!D%YXoJR-LzM&Qql04;($m2|jGmPASDw9F^6yVEB2DvVOt?C$(KN literal 0 HcmV?d00001 diff --git a/test/fixtures/wasi-high-bit-ptr.wat b/test/fixtures/wasi-high-bit-ptr.wat new file mode 100644 index 000000000000..5336be289364 --- /dev/null +++ b/test/fixtures/wasi-high-bit-ptr.wat @@ -0,0 +1,22 @@ +;; Build: wasm-tools parse wasi-high-bit-ptr.wat -o wasi-high-bit-ptr.wasm +(module + (import "wasi_snapshot_preview1" "clock_time_get" + (func $clock_time_get (param i32 i64 i32) (result i32))) + (import "wasi_snapshot_preview1" "args_sizes_get" + (func $args_sizes_get (param i32 i32) (result i32))) + (memory (export "memory") 1) + (func (export "_start")) + (func (export "clock_time_get_high_ptr") (result i32) + i32.const 0 + i64.const 1 + i32.const 0x80000000 + call $clock_time_get) + (func (export "clock_time_get_low_ptr") (result i32) + i32.const 0 + i64.const 1 + i32.const 8 + call $clock_time_get) + (func (export "args_sizes_get_high_ptr") (result i32) + i32.const 0x80000000 + i32.const 0xfffffff0 + call $args_sizes_get)) diff --git a/test/wasi/test-wasi-high-bit-ptr.js b/test/wasi/test-wasi-high-bit-ptr.js new file mode 100644 index 000000000000..e2ea0bdbc6c9 --- /dev/null +++ b/test/wasi/test-wasi-high-bit-ptr.js @@ -0,0 +1,24 @@ +'use strict'; +// Wasm i32 arguments with the high bit set arrive in JS as negative Int32 +// numbers. WASI functions must reinterpret them as uint32 pointers rather +// than rejecting them with EINVAL. +const common = require('../common'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const { WASI } = require('wasi'); + +const UVWASI_EOVERFLOW = 61; + +(async () => { + const wasi = new WASI({ version: 'preview1', returnOnExit: true }); + const importObject = { wasi_snapshot_preview1: wasi.wasiImport }; + const { instance } = await WebAssembly.instantiate( + fixtures.readSync('wasi-high-bit-ptr.wasm'), importObject); + wasi.start(instance); + + assert.strictEqual(instance.exports.clock_time_get_low_ptr(), 0); + assert.strictEqual(instance.exports.clock_time_get_high_ptr(), + UVWASI_EOVERFLOW); + assert.strictEqual(instance.exports.args_sizes_get_high_ptr(), + UVWASI_EOVERFLOW); +})().then(common.mustCall());