Skip to content

Commit 1ebd6d3

Browse files
ShogunPandarichardlau
authored andcommitted
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 1722ced commit 1ebd6d3

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
@@ -2128,6 +2128,16 @@ Disable [runtime allocation of executable memory][jitless]. This may be
21282128
required on some platforms for security reasons. It can also reduce attack
21292129
surface on other platforms, but the performance impact may be severe.
21302130

2131+
When WebAssembly is unavailable in this mode, TypeScript parsing and WebAssembly
2132+
module imports throw [`ERR_WEBASSEMBLY_NOT_SUPPORTED`][]. Node.js does not
2133+
guarantee alternative implementations of features that require WebAssembly or
2134+
runtime allocation of executable memory when those capabilities are unavailable.
2135+
2136+
This flag controls V8's runtime code generation. It does not impose an
2137+
operating-system restriction on executable memory allocated by native addons or
2138+
[`node:ffi`][]. FFI operations that require unavailable executable memory can
2139+
throw [`ERR_RX_MEMORY_NOT_SUPPORTED`][].
2140+
21312141
### `--localstorage-file=file`
21322142

21332143
<!-- YAML
@@ -4861,7 +4871,9 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
48614871
[`Buffer`]: buffer.md#class-buffer
48624872
[`CRYPTO_secure_malloc_init`]: https://www.openssl.org/docs/man3.0/man3/CRYPTO_secure_malloc_init.html
48634873
[`ERR_INVALID_TYPESCRIPT_SYNTAX`]: errors.md#err_invalid_typescript_syntax
4874+
[`ERR_RX_MEMORY_NOT_SUPPORTED`]: errors.md#err_rx_memory_not_supported
48644875
[`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`]: errors.md#err_unsupported_typescript_syntax
4876+
[`ERR_WEBASSEMBLY_NOT_SUPPORTED`]: errors.md#err_webassembly_not_supported
48654877
[`NODE_OPTIONS`]: #node_optionsoptions
48664878
[`NODE_USE_ENV_PROXY=1`]: #node_use_env_proxy1
48674879
[`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
@@ -2943,6 +2943,22 @@ added: v26.1.0
29432943
An attempt was made to `require()` an [ES Module][] while another `import()` call
29442944
was already in progress to load it asynchronously.
29452945

2946+
<a id="ERR_RX_MEMORY_NOT_SUPPORTED"></a>
2947+
2948+
### `ERR_RX_MEMORY_NOT_SUPPORTED`
2949+
2950+
<!-- YAML
2951+
added: REPLACEME
2952+
-->
2953+
2954+
A feature requiring runtime allocation of executable memory was used in an
2955+
environment where that capability is unavailable. This includes creating an
2956+
otherwise eligible FFI Fast API function, or allocating an FFI callback when
2957+
libffi cannot provide a closure and executable memory is unavailable.
2958+
2959+
Node.js does not guarantee a non-generated-code fallback for these operations.
2960+
This error can be caught without terminating the process.
2961+
29462962
<a id="ERR_SCRIPT_EXECUTION_INTERRUPTED"></a>
29472963

29482964
### `ERR_SCRIPT_EXECUTION_INTERRUPTED`
@@ -3632,6 +3648,11 @@ A feature requiring WebAssembly was used, but WebAssembly is not supported or
36323648
has been disabled in the current environment (for example, when running with
36333649
`--jitless`).
36343650

3651+
TypeScript parsing and WebAssembly module imports, including source phase
3652+
imports, report this error when WebAssembly is unavailable. Node.js does not
3653+
provide an alternative implementation for these operations. This error can be
3654+
caught without terminating the process.
3655+
36353656
<a id="ERR_WEBASSEMBLY_RESPONSE"></a>
36363657

36373658
### `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
@@ -1133,6 +1133,14 @@ Opens the REPL even if stdin does not appear to be a terminal.
11331133
Disable runtime allocation of executable memory. This may be
11341134
required on some platforms for security reasons. It can also reduce attack
11351135
surface on other platforms, but the performance impact may be severe.
1136+
When WebAssembly is unavailable in this mode, TypeScript parsing and WebAssembly
1137+
module imports throw \fBERR_WEBASSEMBLY_NOT_SUPPORTED\fR. Node.js does not
1138+
guarantee alternative implementations of features that require WebAssembly or
1139+
runtime allocation of executable memory when those capabilities are unavailable.
1140+
This flag controls V8's runtime code generation. It does not impose an
1141+
operating-system restriction on executable memory allocated by native addons or
1142+
\fBnode:ffi\fR. FFI operations that require unavailable executable memory can
1143+
throw \fBERR_RX_MEMORY_NOT_SUPPORTED\fR.
11361144
.
11371145
.It Fl -localstorage-file Ns = Ns Ar file
11381146
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)