Skip to content

Commit 8b74614

Browse files
committed
src: add WDAC integration (Windows)
Add calls to Windows Defender Application Control to enforce integrity of .js, .json, .node files. add typings. fix version number add integrity checks to esm loader fix esm code integrity tests only load code integrity module on Windows address typings feedback fix readability and formatting fix naming conventions fix error in merge fix formatting properly load isWindows fix formatting remove code_integrity from builtins on non windows fix spacing before comment only load code integrity module on Windows clarify docs fix comment fix ordering of links
1 parent ef2e084 commit 8b74614

23 files changed

+781
-3
lines changed

doc/api/code_integrity.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Code Integrity
2+
3+
<!--introduced_in=REPLACEME-->
4+
5+
<!-- type=misc -->
6+
7+
> Stability: 1.1 - Active development
8+
9+
This feature is only available on Windows platforms.
10+
11+
Code integrity refers to the assurance that software code has not been
12+
altered or tampered with in any unauthorized way. It ensures that
13+
the code running on a system is exactly what was intended by the developers.
14+
15+
Code integrity in Node.js integrates with platform features for code integrity
16+
policy enforcement. See platform speficic sections below for more information.
17+
18+
The Node.js threat model considers the code that the runtime executes to be
19+
trusted. As such, this feature is an additional safety belt, not a strict
20+
security boundary.
21+
22+
If you find a potential security vulnerability, please refer to our
23+
[Security Policy][].
24+
25+
## Code Integrity on Windows
26+
27+
Code integrity is an opt-in feature that leverages Window Defender Application Control
28+
to verify the code executing conforms to system policy and has not been modified since
29+
signing time.
30+
31+
There are three audiences that are involved when using Node.js in an
32+
environment enforcing code integrity: the application developers,
33+
those administrating the system enforcing code integrity, and
34+
the end user. The following sections describe how each audience
35+
can interact with code integrity enforcement.
36+
37+
### Windows Code Integrity and Application Developers
38+
39+
Windows Defender Application Control uses digital signatures to verify
40+
a file's integrity. Application developers are responsible for generating and
41+
distributing the signature information for their Node.js application.
42+
Application developers are also expected to design their application
43+
in robust ways to avoid unintended code execution. This includes
44+
avoiding the use of `eval` and avoiding loading modules outside
45+
of standard methods.
46+
47+
Signature information for files which Node.js is intended to execute
48+
can be stored in a catalog file. Application developers can generate
49+
a Windows catalog file to store the hash of all files Node.js
50+
is expected to execute.
51+
52+
A catalog can be generated using the `New-FileCatalog` Powershell
53+
cmdlet. For example
54+
55+
```powershell
56+
New-FileCatalog -Version 2 -CatalogFilePath MyApplicationCatalog.cat -Path \my\application\path\
57+
```
58+
59+
The `Path` argument should point to the root folder containing your application's code. If
60+
your application's code is fully contained in one file, `Path` can point to that single file.
61+
62+
Be sure that the catalog is generated using the final version of the files that you intend to ship
63+
(i.e. after minifying).
64+
65+
The application developer should then sign the generated catalog with their Code Signing certificate
66+
to ensure the catalog is not tampered with between distribution and execution.
67+
68+
This can be done with the [Set-AuthenticodeSignature commandlet][].
69+
70+
### Windows Code Integrity and System Administrators
71+
72+
This section is intended for system administrators who want to enable Node.js
73+
code integrity features in their environments.
74+
75+
This section assumes familiarity with managing WDAC polcies.
76+
[Official documentation for WDAC][].
77+
78+
Code integrity enforcement on Windows has two toggleable settings:
79+
`EnforceCodeIntegrity` and `DisableInteractiveMode`. These settings are configured
80+
by WDAC policy.
81+
82+
`EnforceCodeIntegrity` causes Node.js to call WldpCanExecuteFile whenever a module is loaded using `require`.
83+
WldpCanExecuteFile verifies that the file's integrity has not been tampered with from signing time.
84+
The system administrator should sign and install the application's file catalog where the application
85+
is running, per WDAC guidance.
86+
87+
`DisableInteractiveMode` prevents Node.js from being run in interactive mode, and also disables the `-e` and `--eval`
88+
command line options.
89+
90+
#### Enabling Code Integrity Enforcement
91+
92+
On newer Windows versions (22H2+), the preferred method of configuring application settings is done using
93+
`AppSettings` in your WDAC Policy.
94+
95+
```text
96+
<AppSettings>
97+
<App Manifest="wdac-manifest.xml">
98+
<Setting Name="EnforceCodeIntegrity" >
99+
<Value>True</Value>
100+
</Setting>
101+
<Setting Name="DisableInteractiveMode" >
102+
<Value>True</Value>
103+
</Setting>
104+
</App>
105+
</AppSettings>
106+
```
107+
108+
On older Windows versions, use the `Settings` section of your WDAC Policy.
109+
110+
```text
111+
<Settings>
112+
<Setting Provider="Node.js" Key="Settings" ValueName="EnforceCodeIntegrity">
113+
<Value>
114+
<Boolean>true</Boolean>
115+
</Value>
116+
</Setting>
117+
<Setting Provider="Node.js" Key="Settings" ValueName="DisableInteractiveMode">
118+
<Value>
119+
<Boolean>true</Boolean>
120+
</Value>
121+
</Setting>
122+
</Settings>
123+
```
124+
125+
## Code Integrity on Linux
126+
127+
Code integrity on Linux is not yet implemented. Plans for implementation will
128+
be made once the necessary APIs on Linux have been upstreamed. More information
129+
can be found here: <https://github.com/nodejs/security-wg/issues/1388>
130+
131+
## Code Integrity on MacOS
132+
133+
Code integrity on MacOS is not yet implemented. Currently, there is no
134+
timeline for implementation.
135+
136+
[Official documentation for WDAC]: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/
137+
[Security Policy]: https://github.com/nodejs/node/blob/main/SECURITY.md
138+
[Set-AuthenticodeSignature commandlet]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-authenticodesignature

doc/api/errors.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,22 @@ changes:
794794
There was an attempt to use a `MessagePort` instance in a closed
795795
state, usually after `.close()` has been called.
796796

797+
<a id="ERR_CODE_INTEGRITY_BLOCKED"></a>
798+
799+
### `ERR_CODE_INTEGRITY_BLOCKED`
800+
801+
> Stability: 1.1 - Active development
802+
803+
Feature has been disabled due to OS Code Integrity policy.
804+
805+
<a id="ERR_CODE_INTEGRITY_VIOLATION"></a>
806+
807+
### `ERR_CODE_INTEGRITY_VIOLATION`
808+
809+
> Stability: 1.1 - Active development
810+
811+
JavaScript code intended to be executed was rejected by system code integrity policy.
812+
797813
<a id="ERR_CONSOLE_WRITABLE_STREAM"></a>
798814

799815
### `ERR_CONSOLE_WRITABLE_STREAM`

doc/api/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* [C++ embedder API](embedding.md)
2020
* [Child processes](child_process.md)
2121
* [Cluster](cluster.md)
22+
* [Code integrity](code_integrity.md)
2223
* [Command-line options](cli.md)
2324
* [Console](console.md)
2425
* [Crypto](crypto.md)

doc/api/wdac-manifest.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!-- Manifest for WDAC integration on Windows. See docs/api/code_integrity.md for
2+
more information regarding WDAC and code integrity -->
3+
<?xml version="1.0" encoding="utf-8"?>
4+
<AppManifest Id="Node.js" xmlns="urn:schemas-microsoft-com:windows-defender-application-control">
5+
<SettingDefinition Name="EnforceCodeIntegrity" Type="Bool" IgnoreAuditPolicies="false"/>
6+
<SettingDefinition Name="DisableInteractiveMode" Type="Bool" IgnoreAuditPolicies="false"/>
7+
</AppManifest>

lib/internal/code_integrity.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Code integrity is a security feature which prevents unsigned
2+
// code from executing. More information can be found in the docs
3+
// doc/api/code_integrity.md
4+
5+
'use strict';
6+
7+
const { emitWarning } = require('internal/process/warning');
8+
const {
9+
isFileTrustedBySystemCodeIntegrityPolicy,
10+
isInteractiveModeDisabled,
11+
isSystemEnforcingCodeIntegrity,
12+
} = internalBinding('code_integrity');
13+
14+
let isCodeIntegrityEnforced;
15+
let alreadyQueriedSystemCodeEnforcmentMode = false;
16+
17+
function isAllowedToExecuteFile(filepath) {
18+
if (!alreadyQueriedSystemCodeEnforcmentMode) {
19+
isCodeIntegrityEnforced = isSystemEnforcingCodeIntegrity();
20+
21+
if (isCodeIntegrityEnforced) {
22+
emitWarning(
23+
'Code integrity is being enforced by system policy.' +
24+
'\nCode integrity is an experimental feature.' +
25+
' See docs for more info.',
26+
'ExperimentalWarning');
27+
}
28+
29+
alreadyQueriedSystemCodeEnforcmentMode = true;
30+
}
31+
32+
if (!isCodeIntegrityEnforced) {
33+
return true;
34+
}
35+
36+
return isFileTrustedBySystemCodeIntegrityPolicy(filepath);
37+
}
38+
39+
module.exports = {
40+
isAllowedToExecuteFile,
41+
isFileTrustedBySystemCodeIntegrityPolicy,
42+
isInteractiveModeDisabled,
43+
isSystemEnforcingCodeIntegrity,
44+
};

lib/internal/errors.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,10 @@ E('ERR_CHILD_PROCESS_IPC_REQUIRED',
11571157
Error);
11581158
E('ERR_CHILD_PROCESS_STDIO_MAXBUFFER', '%s maxBuffer length exceeded',
11591159
RangeError);
1160+
E('ERR_CODE_INTEGRITY_BLOCKED',
1161+
'The feature "%s" is blocked by OS Code Integrity policy', Error);
1162+
E('ERR_CODE_INTEGRITY_VIOLATION',
1163+
'The file %s did not pass OS Code Integrity validation', Error);
11601164
E('ERR_CONSOLE_WRITABLE_STREAM',
11611165
'Console expects a writable stream instance for %s', TypeError);
11621166
E('ERR_CONTEXT_NOT_INITIALIZED', 'context used is not initialized', Error);

lib/internal/main/eval_string.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,24 @@ const {
2323
const { addBuiltinLibsToObject } = require('internal/modules/helpers');
2424
const { getOptionValue } = require('internal/options');
2525

26+
const {
27+
codes: {
28+
ERR_CODE_INTEGRITY_BLOCKED,
29+
},
30+
} = require('internal/errors');
31+
2632
prepareMainThreadExecution();
2733
addBuiltinLibsToObject(globalThis, '<eval>');
2834
markBootstrapComplete();
2935

36+
const { isWindows } = require('internal/util');
37+
if (isWindows) {
38+
const ci = require('internal/code_integrity');
39+
if (ci.isInteractiveModeDisabled()) {
40+
throw new ERR_CODE_INTEGRITY_BLOCKED('"eval"');
41+
}
42+
}
43+
3044
const code = getOptionValue('--eval');
3145

3246
const print = getOptionValue('--print');

lib/internal/modules/cjs/loader.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ const {
181181

182182
const {
183183
codes: {
184+
ERR_CODE_INTEGRITY_VIOLATION,
184185
ERR_INVALID_ARG_TYPE,
185186
ERR_INVALID_ARG_VALUE,
186187
ERR_INVALID_MODULE_SPECIFIER,
@@ -216,6 +217,11 @@ const onRequire = getLazy(() => tracingChannel('module.require'));
216217

217218
const relativeResolveCache = { __proto__: null };
218219

220+
let ci;
221+
if (isWindows) {
222+
ci = require('internal/code_integrity');
223+
}
224+
219225
let requireDepth = 0;
220226
let isPreloading = false;
221227
let statCache = null;
@@ -1180,6 +1186,13 @@ Module._load = function(request, parent, isMain) {
11801186
// For backwards compatibility, if the request itself starts with node:, load it before checking
11811187
// Module._cache. Otherwise, load it after the check.
11821188
if (StringPrototypeStartsWith(request, 'node:')) {
1189+
1190+
if (isWindows) {
1191+
const isAllowedToExecute = ci.isAllowedToExecuteFile(filename);
1192+
if (!isAllowedToExecute) {
1193+
throw new ERR_CODE_INTEGRITY_VIOLATION(filename);
1194+
}
1195+
}
11831196
const result = loadBuiltinWithHooks(filename, url, format);
11841197
if (result) {
11851198
return result;
@@ -1210,6 +1223,13 @@ Module._load = function(request, parent, isMain) {
12101223
cachedModule[kModuleCircularVisited] = true;
12111224
}
12121225

1226+
if (isWindows) {
1227+
const isAllowedToExecute = ci.isAllowedToExecuteFile(filename);
1228+
if (!isAllowedToExecute) {
1229+
throw new ERR_CODE_INTEGRITY_VIOLATION(filename);
1230+
}
1231+
}
1232+
12131233
if (BuiltinModule.canBeRequiredWithoutScheme(filename)) {
12141234
const result = loadBuiltinWithHooks(filename, url, format);
12151235
if (result) {

lib/internal/modules/esm/load.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const {
44
RegExpPrototypeExec,
55
} = primordials;
66
const {
7+
isWindows,
78
kEmptyObject,
89
} = require('internal/util');
910

@@ -13,8 +14,9 @@ const { readFileSync } = require('fs');
1314

1415
const { Buffer: { from: BufferFrom } } = require('buffer');
1516

16-
const { URL } = require('internal/url');
17+
const { URL, fileURLToPath } = require('internal/url');
1718
const {
19+
ERR_CODE_INTEGRITY_VIOLATION,
1820
ERR_INVALID_URL,
1921
ERR_UNKNOWN_MODULE_FORMAT,
2022
ERR_UNSUPPORTED_ESM_URL_SCHEME,
@@ -24,6 +26,11 @@ const {
2426
dataURLProcessor,
2527
} = require('internal/data_url');
2628

29+
let ci;
30+
if (isWindows) {
31+
ci = require('internal/code_integrity');
32+
}
33+
2734
/**
2835
* @param {URL} url URL to the module
2936
* @param {LoadContext} context used to decorate error messages
@@ -34,6 +41,12 @@ function getSourceSync(url, context) {
3441
const responseURL = href;
3542
let source;
3643
if (protocol === 'file:') {
44+
if (isWindows) {
45+
const isAllowedToExecute = ci.isAllowedToExecuteFile(fileURLToPath(url));
46+
if (!isAllowedToExecute) {
47+
throw new ERR_CODE_INTEGRITY_VIOLATION(url);
48+
}
49+
}
3750
source = readFileSync(url);
3851
} else if (protocol === 'data:') {
3952
const result = dataURLProcessor(url);

node.gyp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@
232232
'src/node_blob.h',
233233
'src/node_buffer.h',
234234
'src/node_builtins.h',
235+
'src/node_code_integrity.h',
235236
'src/node_config_file.h',
236237
'src/node_constants.h',
237238
'src/node_context_data.h',
@@ -455,6 +456,14 @@
455456
}, {
456457
'use_openssl_def%': 0,
457458
}],
459+
# Only compile node_code_integrity on Windows
460+
[ 'OS=="win"', {
461+
'node_sources': [
462+
'<(node_sources)',
463+
'src/node_code_integrity.cc',
464+
'src/node_code_integrity.h',
465+
],
466+
}],
458467
],
459468
},
460469

src/node_binding.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,21 @@
9797
V(worker) \
9898
V(zlib)
9999

100+
#define NODE_BUILTIN_OS_SPECIFIC_BINDINGS(V)
101+
102+
#ifdef _WIN32
103+
#define NODE_BUILTIN_OS_SPECIFIC_BINDINGS(V) V(code_integrity)
104+
#endif
105+
100106
#define NODE_BUILTIN_BINDINGS(V) \
101107
NODE_BUILTIN_STANDARD_BINDINGS(V) \
102108
NODE_BUILTIN_OPENSSL_BINDINGS(V) \
103109
NODE_BUILTIN_ICU_BINDINGS(V) \
104110
NODE_BUILTIN_PROFILER_BINDINGS(V) \
105111
NODE_BUILTIN_DEBUG_BINDINGS(V) \
106112
NODE_BUILTIN_QUIC_BINDINGS(V) \
107-
NODE_BUILTIN_SQLITE_BINDINGS(V)
113+
NODE_BUILTIN_SQLITE_BINDINGS(V) \
114+
NODE_BUILTIN_OS_SPECIFIC_BINDINGS(V)
108115

109116
// This is used to load built-in bindings. Instead of using
110117
// __attribute__((constructor)), we call the _register_<modname>

0 commit comments

Comments
 (0)