Skip to content

Commit 29dd655

Browse files
authored
doc,lib,src: throw on missing WebAssembly or RX support
Signed-off-by: Paolo Insogna <paolo@cowtech.it> PR-URL: #66533 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent ff31c97 commit 29dd655

20 files changed

Lines changed: 313 additions & 42 deletions

‎BUILDING.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ and libc version. The table below lists the support tier for each supported
9898
combination. A list of [supported compile toolchains](#supported-toolchains) is
9999
also supplied for tier 1 platforms.
100100

101+
Some built-in functionality requires WebAssembly or runtime allocation of
102+
executable memory. When a capability is unavailable, Node.js does not guarantee
103+
an alternative implementation of the affected feature. See the
104+
[`--jitless` documentation](doc/api/cli.md#--jitless) and
105+
[FFI documentation](doc/api/ffi.md) for the corresponding limitations and errors.
106+
101107
**For production applications, run Node.js on supported platforms only (Tier 1 or 2).**
102108

103109
Node.js does not support a platform version if a vendor has expired support

‎doc/api/cli.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2197,6 +2197,16 @@ Disable [runtime allocation of executable memory][jitless]. This may be
21972197
required on some platforms for security reasons. It can also reduce attack
21982198
surface on other platforms, but the performance impact may be severe.
21992199

2200+
When WebAssembly is unavailable in this mode, TypeScript parsing and WebAssembly
2201+
module imports throw [`ERR_WEBASSEMBLY_NOT_SUPPORTED`][]. Node.js does not
2202+
guarantee alternative implementations of features that require WebAssembly or
2203+
runtime allocation of executable memory when those capabilities are unavailable.
2204+
2205+
This flag controls V8's runtime code generation. It does not impose an
2206+
operating-system restriction on executable memory allocated by native addons or
2207+
[`node:ffi`][]. FFI operations that require unavailable executable memory can
2208+
throw [`ERR_RX_MEMORY_NOT_SUPPORTED`][].
2209+
22002210
### `--localstorage-file=file`
22012211

22022212
<!-- YAML
@@ -4916,7 +4926,9 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
49164926
[`Buffer`]: buffer.md#class-buffer
49174927
[`CRYPTO_secure_malloc_init`]: https://www.openssl.org/docs/man3.0/man3/CRYPTO_secure_malloc_init.html
49184928
[`ERR_INVALID_TYPESCRIPT_SYNTAX`]: errors.md#err_invalid_typescript_syntax
4929+
[`ERR_RX_MEMORY_NOT_SUPPORTED`]: errors.md#err_rx_memory_not_supported
49194930
[`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`]: errors.md#err_unsupported_typescript_syntax
4931+
[`ERR_WEBASSEMBLY_NOT_SUPPORTED`]: errors.md#err_webassembly_not_supported
49204932
[`NODE_OPTIONS`]: #node_optionsoptions
49214933
[`NODE_USE_ENV_PROXY=1`]: #node_use_env_proxy1
49224934
[`NODE_V8_COVERAGE=dir`]: #node_v8_coveragedir

‎doc/api/errors.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2964,6 +2964,22 @@ added:
29642964
An attempt was made to `require()` an [ES Module][] while another `import()` call
29652965
was already in progress to load it asynchronously.
29662966

2967+
<a id="ERR_RX_MEMORY_NOT_SUPPORTED"></a>
2968+
2969+
### `ERR_RX_MEMORY_NOT_SUPPORTED`
2970+
2971+
<!-- YAML
2972+
added: REPLACEME
2973+
-->
2974+
2975+
A feature requiring runtime allocation of executable memory was used in an
2976+
environment where that capability is unavailable. This includes creating an
2977+
otherwise eligible FFI Fast API function, or allocating an FFI callback when
2978+
libffi cannot provide a closure and executable memory is unavailable.
2979+
2980+
Node.js does not guarantee a non-generated-code fallback for these operations.
2981+
This error can be caught without terminating the process.
2982+
29672983
<a id="ERR_SCRIPT_EXECUTION_INTERRUPTED"></a>
29682984

29692985
### `ERR_SCRIPT_EXECUTION_INTERRUPTED`
@@ -3653,6 +3669,11 @@ A feature requiring WebAssembly was used, but WebAssembly is not supported or
36533669
has been disabled in the current environment (for example, when running with
36543670
`--jitless`).
36553671

3672+
TypeScript parsing and WebAssembly module imports, including source phase
3673+
imports, report this error when WebAssembly is unavailable. Node.js does not
3674+
provide an alternative implementation for these operations. This error can be
3675+
caught without terminating the process.
3676+
36563677
<a id="ERR_WEBASSEMBLY_RESPONSE"></a>
36573678

36583679
### `ERR_WEBASSEMBLY_RESPONSE`

‎doc/api/esm.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,11 @@ imports is supported.
745745
Both of these integrations are in line with the
746746
[ES Module Integration Proposal for WebAssembly][].
747747
748+
When WebAssembly is unavailable in the current environment, importing a Wasm
749+
module throws [`ERR_WEBASSEMBLY_NOT_SUPPORTED`](errors.md#err_webassembly_not_supported).
750+
This also applies to source phase imports. Node.js does not provide an
751+
alternative implementation when WebAssembly is unavailable.
752+
748753
### Wasm Source Phase Imports
749754
750755
> Stability: 1.2 - Release candidate

‎doc/api/ffi.md‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ The following targets are not supported by bundled libffi:
4545
When using the [Permission Model][], FFI APIs are
4646
restricted unless the [`--allow-ffi`][] flag is provided.
4747

48+
Some FFI operations require runtime allocation of executable memory. If an
49+
otherwise eligible Fast API function cannot be created because executable memory
50+
is unavailable, creation throws
51+
[`ERR_RX_MEMORY_NOT_SUPPORTED`](errors.md#err_rx_memory_not_supported) rather than
52+
selecting another call path. Callback allocation can also report this error.
53+
Loading the module or a library does not itself require generated trampolines.
54+
4855
## Overview
4956

5057
The `node:ffi` module exposes two groups of APIs:
@@ -465,6 +472,11 @@ Returns an object containing all previously resolved symbol addresses.
465472

466473
Creates a native callback pointer backed by a JavaScript function.
467474

475+
If libffi cannot allocate a closure and executable memory is unavailable, this
476+
method throws [`ERR_RX_MEMORY_NOT_SUPPORTED`](errors.md#err_rx_memory_not_supported).
477+
Other closure allocation failures throw `ERR_FFI_CALL_FAILED`. Platform-specific
478+
libffi implementations may provide callbacks without dynamically generated code.
479+
468480
When `signature` is omitted, the callback uses a default `void ()` signature.
469481

470482
The return value is the callback pointer address as a `bigint`. It can be
@@ -640,7 +652,8 @@ met:
640652
PPC64 always use another call path.
641653
* The process can allocate executable memory. Node.js checks once per process
642654
whether it can allocate memory and mark it executable. If that check fails,
643-
this path is disabled for the entire process.
655+
creating an otherwise eligible function throws
656+
[`ERR_RX_MEMORY_NOT_SUPPORTED`](errors.md#err_rx_memory_not_supported).
644657
* Neither the return type nor any argument type is `function`.
645658
* The signature has at most 8 arguments, and every argument fits in the
646659
argument registers available to the trampoline on the current platform.
@@ -650,8 +663,10 @@ The register limits are platform-specific. Integer and pointer-like arguments
650663
share one set of registers, and floating-point arguments share another. The
651664
limits for each architecture are listed in [Type names][].
652665

653-
A signature that fails any of these checks is not an error. The function is
654-
created on the next call path that supports it.
666+
A signature that is unsupported by the Fast API types, argument limits, or
667+
platform is not an error. The function is created on the next call path that
668+
supports it. Missing executable-memory support is checked only after signature
669+
and platform eligibility and does not select another call path.
655670

656671
### Shared buffer call path
657672

‎doc/api/typescript.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ Node.js will replace TypeScript syntax with whitespace,
8282
and no type checking is performed.
8383
To disable this feature, use the flag [`--no-strip-types`][].
8484

85+
The built-in TypeScript parser requires WebAssembly. If WebAssembly is
86+
unavailable, parsing throws
87+
[`ERR_WEBASSEMBLY_NOT_SUPPORTED`](errors.md#err_webassembly_not_supported).
88+
Node.js does not provide a non-WebAssembly parser as a fallback.
89+
8590
Node.js ignores `tsconfig.json` files and therefore
8691
features that depend on settings within `tsconfig.json`,
8792
such as paths or converting newer JavaScript syntax to older standards, are

‎doc/node.1‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,14 @@ Opens the REPL even if stdin does not appear to be a terminal.
11731173
Disable runtime allocation of executable memory. This may be
11741174
required on some platforms for security reasons. It can also reduce attack
11751175
surface on other platforms, but the performance impact may be severe.
1176+
When WebAssembly is unavailable in this mode, TypeScript parsing and WebAssembly
1177+
module imports throw \fBERR_WEBASSEMBLY_NOT_SUPPORTED\fR. Node.js does not
1178+
guarantee alternative implementations of features that require WebAssembly or
1179+
runtime allocation of executable memory when those capabilities are unavailable.
1180+
This flag controls V8's runtime code generation. It does not impose an
1181+
operating-system restriction on executable memory allocated by native addons or
1182+
\fBnode:ffi\fR. FFI operations that require unavailable executable memory can
1183+
throw \fBERR_RX_MEMORY_NOT_SUPPORTED\fR.
11761184
.
11771185
.It Fl -localstorage-file Ns = Ns Ar file
11781186
The file used to store \fBlocalStorage\fR data. If the file does not exist, it is

‎lib/internal/modules/esm/translators.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const { emitExperimentalWarning, kEmptyObject, setOwnProperty, isWindows } = req
5454
const {
5555
ERR_INVALID_RETURN_PROPERTY_VALUE,
5656
ERR_UNKNOWN_BUILTIN_MODULE,
57+
ERR_WEBASSEMBLY_NOT_SUPPORTED,
5758
} = require('internal/errors').codes;
5859
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
5960
const moduleWrap = internalBinding('module_wrap');
@@ -558,6 +559,9 @@ translators.set('wasm', function(url, translateContext) {
558559
const { source } = translateContext;
559560
// WebAssembly global is not available during snapshot building, so we need to get it lazily.
560561
const { WebAssembly } = globalThis;
562+
if (WebAssembly === undefined) {
563+
throw new ERR_WEBASSEMBLY_NOT_SUPPORTED('WebAssembly module imports');
564+
}
561565
assertBufferSource(source, false, 'load');
562566

563567
debug(`Translating WASMModule ${url}`, translateContext);

‎src/ffi/fast.cc‎

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -330,37 +330,29 @@ bool IsFastLibraryGuardSupported() {
330330
#endif
331331
}
332332

333-
std::unique_ptr<FastFFIMetadata> CreateFastFFIMetadata(const FFIFunction& fn,
334-
const bool* closed,
335-
v8::Isolate* isolate) {
336-
// Bail early if executable memory allocation doesn't work on this process
337-
// (missing MAP_JIT entitlement, hardened runtime, SELinux execmem, etc.).
338-
// The self-test runs once and caches the result.
339-
if (!IsJitMemorySupported()) {
340-
return nullptr;
341-
}
342-
333+
v8::Maybe<std::unique_ptr<FastFFIMetadata>> CreateFastFFIMetadata(
334+
const FFIFunction& fn, const bool* closed, v8::Isolate* isolate) {
343335
// Check signature-level eligibility (type checks, register caps, platform
344-
// support). Returning nullptr here lets the caller fall back to SharedBuffer
336+
// support). Null metadata lets the caller fall back to SharedBuffer
345337
// or the generic libffi path.
346338
const char* eligibility_reason;
347339
if (!IsFastCallEligible(fn, &eligibility_reason)) {
348-
return nullptr;
340+
return v8::Just(std::unique_ptr<FastFFIMetadata>());
349341
}
350342

351-
// Reject unsupported result types first. Returning nullptr means the caller
343+
// Reject unsupported result types first. Null metadata means the caller
352344
// can still fall back to SharedBuffer or the generic libffi path.
353345
FastFFIType result;
354346
if (!FastScalarTypeFromName(fn.return_type_name, &result)) {
355-
return nullptr;
347+
return v8::Just(std::unique_ptr<FastFFIMetadata>());
356348
}
357349
if (fn.args.size() != fn.arg_type_names.size()) {
358-
return nullptr;
350+
return v8::Just(std::unique_ptr<FastFFIMetadata>());
359351
}
360352
// Keep the initial Fast API implementation bounded to signatures V8 and the
361353
// platform trampolines can describe without stack argument support.
362354
if (fn.arg_type_names.size() > 8) {
363-
return nullptr;
355+
return v8::Just(std::unique_ptr<FastFFIMetadata>());
364356
}
365357

366358
std::vector<FastFFIType> args;
@@ -373,25 +365,35 @@ std::unique_ptr<FastFFIMetadata> CreateFastFFIMetadata(const FFIFunction& fn,
373365
for (const std::string& name : fn.arg_type_names) {
374366
FastFFIType type;
375367
if (!FastArgTypeFromName(name, &type)) {
376-
return nullptr;
368+
return v8::Just(std::unique_ptr<FastFFIMetadata>());
377369
}
378370
if (type == FastFFIType::kVoid) {
379-
return nullptr;
371+
return v8::Just(std::unique_ptr<FastFFIMetadata>());
380372
}
381373
needs_bigint = needs_bigint || NeedsBigIntRepresentation(type);
382374
needs_callback_options =
383375
needs_callback_options || type == FastFFIType::kBuffer;
384376
args.push_back(type);
385377
}
386378

379+
// Check RX memory only after signature and platform eligibility, so ordinary
380+
// unsupported signatures can still use the non-generated invocation paths.
381+
if (!IsJitMemorySupported()) {
382+
THROW_ERR_RX_MEMORY_NOT_SUPPORTED(
383+
isolate,
384+
"Executable memory is not supported in this environment, "
385+
"but is required for FFI Fast API calls");
386+
return v8::Nothing<std::unique_ptr<FastFFIMetadata>>();
387+
}
388+
387389
auto metadata = std::make_unique<FastFFIMetadata>();
388390
// The platform-specific trampoline is the executable entrypoint V8 calls.
389391
// If the platform rejects the signature, the whole fast metadata object is
390392
// discarded and the caller chooses another invocation path.
391393
FastFFITrampolineConfig config{fn.ptr, closed, isolate};
392394
if (!node_ffi_create_fast_trampoline(
393395
config, args.data(), args.size(), result, &metadata->trampoline)) {
394-
return nullptr;
396+
return v8::Just(std::unique_ptr<FastFFIMetadata>());
395397
}
396398

397399
metadata->arg_info.reserve(args.size() + 1);
@@ -418,7 +420,7 @@ std::unique_ptr<FastFFIMetadata> CreateFastFFIMetadata(const FFIFunction& fn,
418420
metadata->c_function =
419421
v8::CFunction(metadata->trampoline.code, metadata->c_function_info.get());
420422
metadata->guards_library = guards_library;
421-
return metadata;
423+
return v8::Just(std::move(metadata));
422424
}
423425

424426
} // namespace node::ffi

‎src/ffi/fast.h‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ std::shared_ptr<FFIFunction> CloneWithRawPointerArgNames(
7373
const std::shared_ptr<FFIFunction>& fn);
7474
std::shared_ptr<FFIFunction> CloneWithFastBufferArgNames(
7575
const std::shared_ptr<FFIFunction>& fn);
76-
std::unique_ptr<FastFFIMetadata> CreateFastFFIMetadata(const FFIFunction& fn,
77-
const bool* closed,
78-
v8::Isolate* isolate);
76+
// A null metadata value allows signature/platform fallback. Nothing means an
77+
// exception was thrown because an eligible signature requires RX memory.
78+
v8::Maybe<std::unique_ptr<FastFFIMetadata>> CreateFastFFIMetadata(
79+
const FFIFunction& fn, const bool* closed, v8::Isolate* isolate);
7980

8081
} // namespace node::ffi
8182

0 commit comments

Comments
 (0)