Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deps/uv/include/uv/win.h
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ typedef struct {
#define UV_FS_O_TEMPORARY _O_TEMPORARY
#define UV_FS_O_TRUNC _O_TRUNC
#define UV_FS_O_WRONLY _O_WRONLY
#define UV_FS_O_READLOCK 0x40000000 /* READ ONLY SHARING MODE*/

/* fs open() flags supported on other platforms (or mapped on this platform): */
#define UV_FS_O_DIRECT 0x02000000 /* FILE_FLAG_NO_BUFFERING */
Expand Down
2 changes: 2 additions & 0 deletions deps/uv/src/win/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ void fs__open(uv_fs_t* req) {
*/
if (flags & UV_FS_O_EXLOCK) {
share = 0;
} else if (flags & UV_FS_O_READLOCK) {
share = FILE_SHARE_READ;;
} else {
share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
}
Expand Down
138 changes: 138 additions & 0 deletions doc/api/code_integrity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Code Integrity

<!--introduced_in=REPLACEME-->

<!-- type=misc -->

> Stability: 1.1 - Active development

This feature is only available on Windows platforms.

Code integrity refers to the assurance that software code has not been
altered or tampered with in any unauthorized way. It ensures that
the code running on a system is exactly what was intended by the developers.

Code integrity in Node.js integrates with platform features for code integrity
policy enforcement. See platform speficic sections below for more information.

The Node.js threat model considers the code that the runtime executes to be
trusted. As such, this feature is an additional safety belt, not a strict
security boundary.

If you find a potential security vulnerability, please refer to our
[Security Policy][].

## Code Integrity on Windows

Code integrity is an opt-in feature that leverages Window Defender Application Control
to verify the code executing conforms to system policy and has not been modified since
signing time.

There are three audiences that are involved when using Node.js in an
environment enforcing code integrity: the application developers,
those administrating the system enforcing code integrity, and
the end user. The following sections describe how each audience
can interact with code integrity enforcement.

### Windows Code Integrity and Application Developers

Windows Defender Application Control uses digital signatures to verify
a file's integrity. Application developers are responsible for generating and
distributing the signature information for their Node.js application.
Application developers are also expected to design their application
in robust ways to avoid unintended code execution. This includes
avoiding the use of `eval` and avoiding loading modules outside
of standard methods.

Signature information for files which Node.js is intended to execute
can be stored in a catalog file. Application developers can generate
a Windows catalog file to store the hash of all files Node.js
is expected to execute.

A catalog can be generated using the `New-FileCatalog` Powershell
cmdlet. For example

```powershell
New-FileCatalog -Version 2 -CatalogFilePath MyApplicationCatalog.cat -Path \my\application\path\
```

The `Path` argument should point to the root folder containing your application's code. If
your application's code is fully contained in one file, `Path` can point to that single file.

Be sure that the catalog is generated using the final version of the files that you intend to ship
(i.e. after minifying).

The application developer should then sign the generated catalog with their Code Signing certificate
to ensure the catalog is not tampered with between distribution and execution.

This can be done with the [Set-AuthenticodeSignature commandlet][].

### Windows Code Integrity and System Administrators

This section is intended for system administrators who want to enable Node.js
code integrity features in their environments.

This section assumes familiarity with managing WDAC polcies.
[Official documentation for WDAC][].

Code integrity enforcement on Windows has two toggleable settings:
`EnforceCodeIntegrity` and `DisableInteractiveMode`. These settings are configured
by WDAC policy.

`EnforceCodeIntegrity` causes Node.js to call WldpCanExecuteFile whenever a module is loaded using `require`.
WldpCanExecuteFile verifies that the file's integrity has not been tampered with from signing time.
The system administrator should sign and install the application's file catalog where the application
is running, per WDAC guidance.

`DisableInteractiveMode` prevents Node.js from being run in interactive mode, and also disables the `-e` and `--eval`
command line options.

#### Enabling Code Integrity Enforcement

On newer Windows versions (22H2+), the preferred method of configuring application settings is done using
`AppSettings` in your WDAC Policy.

```text
<AppSettings>
<App Manifest="wdac-manifest.xml">
<Setting Name="EnforceCodeIntegrity" >
<Value>True</Value>
</Setting>
<Setting Name="DisableInteractiveMode" >
<Value>True</Value>
</Setting>
</App>
</AppSettings>
```

On older Windows versions, use the `Settings` section of your WDAC Policy.

```text
<Settings>
<Setting Provider="Node.js" Key="Settings" ValueName="EnforceCodeIntegrity">
<Value>
<Boolean>true</Boolean>
</Value>
</Setting>
<Setting Provider="Node.js" Key="Settings" ValueName="DisableInteractiveMode">
<Value>
<Boolean>true</Boolean>
</Value>
</Setting>
</Settings>
```

## Code Integrity on Linux

Code integrity on Linux is not yet implemented. Plans for implementation will
be made once the necessary APIs on Linux have been upstreamed. More information
can be found here: <https://github.com/nodejs/security-wg/issues/1388>

## Code Integrity on MacOS

Code integrity on MacOS is not yet implemented. Currently, there is no
timeline for implementation.

[Official documentation for WDAC]: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/
[Security Policy]: https://github.com/nodejs/node/blob/main/SECURITY.md
[Set-AuthenticodeSignature commandlet]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-authenticodesignature
16 changes: 16 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,22 @@ changes:
There was an attempt to use a `MessagePort` instance in a closed
state, usually after `.close()` has been called.

<a id="ERR_CODE_INTEGRITY_BLOCKED"></a>

### `ERR_CODE_INTEGRITY_BLOCKED`

> Stability: 1.1 - Active development

Feature has been disabled due to OS Code Integrity policy.

<a id="ERR_CODE_INTEGRITY_VIOLATION"></a>

### `ERR_CODE_INTEGRITY_VIOLATION`

> Stability: 1.1 - Active development

JavaScript code intended to be executed was rejected by system code integrity policy.

<a id="ERR_CONSOLE_WRITABLE_STREAM"></a>

### `ERR_CONSOLE_WRITABLE_STREAM`
Expand Down
1 change: 1 addition & 0 deletions doc/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* [C++ embedder API](embedding.md)
* [Child processes](child_process.md)
* [Cluster](cluster.md)
* [Code integrity](code_integrity.md)
* [Command-line options](cli.md)
* [Console](console.md)
* [Crypto](crypto.md)
Expand Down
7 changes: 7 additions & 0 deletions doc/api/wdac-manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!-- Manifest for WDAC integration on Windows. See docs/api/code_integrity.md for
more information regarding WDAC and code integrity -->
<?xml version="1.0" encoding="utf-8"?>
<AppManifest Id="Node.js" xmlns="urn:schemas-microsoft-com:windows-defender-application-control">
<SettingDefinition Name="EnforceCodeIntegrity" Type="Bool" IgnoreAuditPolicies="false"/>
<SettingDefinition Name="DisableInteractiveMode" Type="Bool" IgnoreAuditPolicies="false"/>
</AppManifest>
44 changes: 44 additions & 0 deletions lib/internal/code_integrity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Code integrity is a security feature which prevents unsigned
// code from executing. More information can be found in the docs
// doc/api/code_integrity.md

'use strict';

const { emitWarning } = require('internal/process/warning');
const {
isFileTrustedBySystemCodeIntegrityPolicy,
isInteractiveModeDisabled,
isSystemEnforcingCodeIntegrity,
} = internalBinding('code_integrity');

let isCodeIntegrityEnforced;
let alreadyQueriedSystemCodeEnforcmentMode = false;

function isAllowedToExecuteFile(filepath) {
if (!alreadyQueriedSystemCodeEnforcmentMode) {
isCodeIntegrityEnforced = isSystemEnforcingCodeIntegrity();

if (isCodeIntegrityEnforced) {
emitWarning(
'Code integrity is being enforced by system policy.' +
'\nCode integrity is an experimental feature.' +
' See docs for more info.',
'ExperimentalWarning');
}

alreadyQueriedSystemCodeEnforcmentMode = true;
}

if (!isCodeIntegrityEnforced) {
return true;
}

return isFileTrustedBySystemCodeIntegrityPolicy(filepath);
}

module.exports = {
isAllowedToExecuteFile,
isFileTrustedBySystemCodeIntegrityPolicy,
isInteractiveModeDisabled,
isSystemEnforcingCodeIntegrity,
};
4 changes: 4 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1161,6 +1161,10 @@ E('ERR_CHILD_PROCESS_IPC_REQUIRED',
Error);
E('ERR_CHILD_PROCESS_STDIO_MAXBUFFER', '%s maxBuffer length exceeded',
RangeError);
E('ERR_CODE_INTEGRITY_BLOCKED',
'The feature "%s" is blocked by OS Code Integrity policy', Error);
E('ERR_CODE_INTEGRITY_VIOLATION',
'The file %s did not pass OS Code Integrity validation', Error);
E('ERR_CONSOLE_WRITABLE_STREAM',
'Console expects a writable stream instance for %s', TypeError);
E('ERR_CONSTRUCT_CALL_REQUIRED', 'Class constructor %s cannot be invoked without `new`', TypeError);
Expand Down
14 changes: 14 additions & 0 deletions lib/internal/main/eval_string.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,24 @@ const {
const { addBuiltinLibsToObject } = require('internal/modules/helpers');
const { getOptionValue } = require('internal/options');

const {
codes: {
ERR_CODE_INTEGRITY_BLOCKED,
},
} = require('internal/errors');

prepareMainThreadExecution();
addBuiltinLibsToObject(globalThis, '<eval>');
markBootstrapComplete();

const { isWindows } = require('internal/util');
if (isWindows) {
const ci = require('internal/code_integrity');
if (ci.isInteractiveModeDisabled()) {
throw new ERR_CODE_INTEGRITY_BLOCKED('"eval"');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I understanding correctly that this blocks use of eval(...)? If so, what about new Function('...') and new ShadowRealm('...')?

}
}

const code = getOptionValue('--eval');

const print = getOptionValue('--print');
Expand Down
34 changes: 33 additions & 1 deletion lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ const {

const {
codes: {
ERR_CODE_INTEGRITY_VIOLATION,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_MODULE_SPECIFIER,
Expand Down Expand Up @@ -225,6 +226,11 @@ const onRequire = getLazy(() => tracingChannel('module.require'));

const relativeResolveCache = { __proto__: null };

let ci;
if (isWindows) {
ci = require('internal/code_integrity');
}

let requireDepth = 0;
let isPreloading = false;
let statCache = null;
Expand Down Expand Up @@ -1164,7 +1170,19 @@ function defaultLoadImpl(filename, format) {
case 'module-typescript':
case 'commonjs-typescript':
case 'typescript': {
return fs.readFileSync(filename, 'utf8');
let fd;
if (isWindows) {
fd = fs.openSync(filename, 0x40000000);
const isAllowedToExecute = ci.isAllowedToExecuteFile(fd);
if (!isAllowedToExecute) {
throw new ERR_CODE_INTEGRITY_VIOLATION(filename);
}
let source = fs.readFileSync(fd, 'utf8');
//fs.closeSync(fd);
return source;
} else {
return fs.readFileSync(filename, 'utf8');
}
}
case 'builtin':
return null;
Expand Down Expand Up @@ -1268,6 +1286,13 @@ Module._load = function(request, parent, isMain, internalResolveOptions = kEmpty
// TODO(joyeecheung): a more sensible handling is probably, if there are hooks, always go through the hooks
// first before checking the cache. Otherwise, check the cache first, then proceed to default loading.
if (request === url && StringPrototypeStartsWith(request, 'node:')) {
if (isWindows) {
const isAllowedToExecute = ci.isAllowedToExecuteFile(filename);
if (!isAllowedToExecute) {
throw new ERR_CODE_INTEGRITY_VIOLATION(filename);
}
}

const normalized = BuiltinModule.normalizeRequirableId(request);
if (normalized) { // It's a builtin module.
const { resultFromHook, builtinExports } = loadBuiltinWithHooks(normalized, url, format);
Expand Down Expand Up @@ -1309,6 +1334,13 @@ Module._load = function(request, parent, isMain, internalResolveOptions = kEmpty
}

if (resultFromLoadHook === undefined && BuiltinModule.canBeRequiredWithoutScheme(filename)) {
if (isWindows) {
const isAllowedToExecute = ci.isAllowedToExecuteFile(filename);
if (!isAllowedToExecute) {
throw new ERR_CODE_INTEGRITY_VIOLATION(filename);
}
}

const { resultFromHook, builtinExports } = loadBuiltinWithHooks(filename, url, format);
if (builtinExports) {
return builtinExports;
Expand Down
Loading