diff --git a/doc/api/code_integrity.md b/doc/api/code_integrity.md new file mode 100644 index 00000000000000..dd2e4e30d1a202 --- /dev/null +++ b/doc/api/code_integrity.md @@ -0,0 +1,131 @@ +# Code Integrity + + + +> Stability: 1.1 - Active development + +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 +use of `eval` and 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](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-authenticodesignature). + +### 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 can be found [here](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/). + +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 + + + + True + + + True + + + +``` + +On older Windows versions, use the `Settings` section of your WDAC Policy. + +```text + + + + true + + + + + true + + + +``` + +## 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: + +## Code Integrity on MacOS + +Code integrity on MacOS is not yet implemented. Currently, there is no +timeline for implementation. + +[Security Policy]: https://github.com/nodejs/node/blob/main/SECURITY.md diff --git a/doc/api/errors.md b/doc/api/errors.md index f721ed221eab21..081cadcba4ea18 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -789,6 +789,22 @@ changes: There was an attempt to use a `MessagePort` instance in a closed state, usually after `.close()` has been called. + + +### `ERR_CODE_INTEGRITY_BLOCKED` + +> Stability: 1.1 - Active development + +Feature has been disabled due to OS Code Integrity policy. + + + +### `ERR_CODE_INTEGRITY_VIOLATION` + +> Stability: 1.1 - Active development + +JavaScript code intended to be executed was rejected by system code integrity policy. + ### `ERR_CONSOLE_WRITABLE_STREAM` diff --git a/doc/api/wdac-manifest.xml b/doc/api/wdac-manifest.xml new file mode 100644 index 00000000000000..064aed76720d09 --- /dev/null +++ b/doc/api/wdac-manifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/lib/internal/code_integrity.js b/lib/internal/code_integrity.js new file mode 100644 index 00000000000000..ec14b534e4251b --- /dev/null +++ b/lib/internal/code_integrity.js @@ -0,0 +1,69 @@ +// 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 { isWindows } = require('internal/util'); + +let isCodeIntegrityEnforced; +let alreadyQueriedSystemCodeEnforcmentMode = false; + +// Binding stub for non-Windows platforms +let binding = { + isFileTrustedBySystemCodeIntegrityPolicy: () => true, + isInteractiveModeDisabledInternal: () => false, + isSystemEnforcingCodeIntegrity: () => false, +}; +// Load the actual binding if on Windows +if (isWindows) { + binding = internalBinding('code_integrity'); +} + +const { + isFileTrustedBySystemCodeIntegrityPolicy, + isInteractiveModeDisabledInternal, + isSystemEnforcingCodeIntegrity, +} = binding; + +function isAllowedToExecuteFile(filepath) { + // At the moment code integrity is only implemented on Windows + if (!isWindows) { + return true; + } + + 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); +} + +function isInteractiveModeDisabled() { + if (!isWindows) { + return false; + } + return isInteractiveModeDisabledInternal(); +} + +module.exports = { + isAllowedToExecuteFile, + isFileTrustedBySystemCodeIntegrityPolicy, + isInteractiveModeDisabled, + isSystemEnforcingCodeIntegrity, +}; diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 47d4fbf677e5eb..8bb008a8115f49 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1144,6 +1144,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_CONTEXT_NOT_INITIALIZED', 'context used is not initialized', Error); diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js index ee402f50fbdd2b..cd8f6ba65b8b28 100644 --- a/lib/internal/main/eval_string.js +++ b/lib/internal/main/eval_string.js @@ -23,6 +23,17 @@ const { const { addBuiltinLibsToObject } = require('internal/modules/helpers'); const { getOptionValue } = require('internal/options'); +const { + codes: { + ERR_CODE_INTEGRITY_BLOCKED, + }, +} = require('internal/errors'); + +const ci = require('internal/code_integrity'); +if (ci.isInteractiveModeDisabled()) { + throw new ERR_CODE_INTEGRITY_BLOCKED('"eval"'); +} + prepareMainThreadExecution(); addBuiltinLibsToObject(globalThis, ''); markBootstrapComplete(); diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 39eea53e429681..61ee67b1fc8336 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -181,6 +181,7 @@ const { const { codes: { + ERR_CODE_INTEGRITY_VIOLATION, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_INVALID_MODULE_SPECIFIER, @@ -216,6 +217,8 @@ const onRequire = getLazy(() => tracingChannel('module.require')); const relativeResolveCache = { __proto__: null }; +const ci = require('internal/code_integrity'); + let requireDepth = 0; let isPreloading = false; let statCache = null; @@ -1217,6 +1220,11 @@ Module._load = function(request, parent, isMain) { // For backwards compatibility, if the request itself starts with node:, load it before checking // Module._cache. Otherwise, load it after the check. if (StringPrototypeStartsWith(request, 'node:')) { + + const isAllowedToExecute = ci.isAllowedToExecuteFile(filename); + if (!isAllowedToExecute) { + throw new ERR_CODE_INTEGRITY_VIOLATION(filename); + } const result = loadBuiltinWithHooks(filename, url, format); if (result) { return result; @@ -1247,6 +1255,11 @@ Module._load = function(request, parent, isMain) { cachedModule[kModuleCircularVisited] = true; } + const isAllowedToExecute = ci.isAllowedToExecuteFile(filename); + if (!isAllowedToExecute) { + throw new ERR_CODE_INTEGRITY_VIOLATION(filename); + } + if (BuiltinModule.canBeRequiredWithoutScheme(filename)) { const result = loadBuiltinWithHooks(filename, url, format); if (result) { @@ -1878,6 +1891,7 @@ function getRequireESMError(mod, pkg, content, filename) { * @param {string} filename The file path of the module */ Module._extensions['.js'] = function(module, filename) { + let format, pkg; if (StringPrototypeEndsWith(filename, '.cjs')) { format = 'commonjs'; @@ -1897,6 +1911,7 @@ Module._extensions['.js'] = function(module, filename) { throw err; } module._compile(source, filename, loadedFormat); + }; /** @@ -1905,6 +1920,7 @@ Module._extensions['.js'] = function(module, filename) { * @param {string} filename The file path of the module */ Module._extensions['.json'] = function(module, filename) { + const { source: content } = loadSource(module, filename, 'json'); try { @@ -1921,6 +1937,7 @@ Module._extensions['.json'] = function(module, filename) { * @param {string} filename The file path of the module */ Module._extensions['.node'] = function(module, filename) { + // Be aware this doesn't use `content` return process.dlopen(module, path.toNamespacedPath(filename)); }; diff --git a/node.gyp b/node.gyp index ba1f50d7541cca..93f18df2158d75 100644 --- a/node.gyp +++ b/node.gyp @@ -447,6 +447,14 @@ }, { 'use_openssl_def%': 0, }], + # Only compile node_code_integrity on Windows + [ 'OS=="win"', { + 'node_sources': [ + '<(node_sources)', + 'src/node_code_integrity.cc', + 'src/node_code_integrity.h', + ], + }], ], }, diff --git a/src/node_binding.cc b/src/node_binding.cc index e93f3312cc6425..da796f77058772 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -99,13 +99,21 @@ V(worker) \ V(zlib) +#define NODE_BUILTIN_OS_SPECIFIC_BINDINGS(V) + +#ifdef _WIN32 +#define NODE_BUILTIN_OS_SPECIFIC_BINDINGS(V) \ + V(code_integrity) +#endif + #define NODE_BUILTIN_BINDINGS(V) \ NODE_BUILTIN_STANDARD_BINDINGS(V) \ NODE_BUILTIN_OPENSSL_BINDINGS(V) \ NODE_BUILTIN_ICU_BINDINGS(V) \ NODE_BUILTIN_PROFILER_BINDINGS(V) \ NODE_BUILTIN_DEBUG_BINDINGS(V) \ - NODE_BUILTIN_QUIC_BINDINGS(V) + NODE_BUILTIN_QUIC_BINDINGS(V) \ + NODE_BUILTIN_OS_SPECIFIC_BINDINGS(V) // This is used to load built-in bindings. Instead of using // __attribute__((constructor)), we call the _register_ diff --git a/src/node_code_integrity.cc b/src/node_code_integrity.cc new file mode 100644 index 00000000000000..d1215ae917be00 --- /dev/null +++ b/src/node_code_integrity.cc @@ -0,0 +1,263 @@ +#include "node_code_integrity.h" +#include "v8.h" +#include "node.h" +#include "env-inl.h" +#include "node_external_reference.h" + +namespace node { + +using v8::Boolean; +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::Local; +using v8::Object; +using v8::Value; + +namespace per_process { + bool isWldpInitialized = false; + pfnWldpCanExecuteFile WldpCanExecuteFile; + pfnWldpGetApplicationSettingBoolean WldpGetApplicationSettingBoolean; + pfnWldpQuerySecurityPolicy WldpQuerySecurityPolicy; +} + +namespace code_integrity { + +static PCWSTR NODEJS = L"Node.js"; +static PCWSTR ENFORCE_CODE_INTEGRITY_SETTING_NAME = L"EnforceCodeIntegrity"; +static PCWSTR DISABLE_INTERPRETIVE_MODE_SETTING_NAME = + L"DisableInteractiveMode"; + +void InitWldp(Environment* env) { + if (per_process::isWldpInitialized) { + return; + } + + HMODULE wldp_module = LoadLibraryExA( + "wldp.dll", + nullptr, + LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (wldp_module == nullptr) { + return env->ThrowError("Unable to load wldp.dll"); + } + + per_process::WldpCanExecuteFile = + (pfnWldpCanExecuteFile)GetProcAddress( + wldp_module, + "WldpCanExecuteFile"); + + per_process::WldpGetApplicationSettingBoolean = + (pfnWldpGetApplicationSettingBoolean)GetProcAddress( + wldp_module, + "WldpGetApplicationSettingBoolean"); + + per_process::WldpQuerySecurityPolicy = + (pfnWldpQuerySecurityPolicy)GetProcAddress( + wldp_module, + "WldpQuerySecurityPolicy"); + + per_process::isWldpInitialized = true; +} + +static void IsFileTrustedBySystemCodeIntegrityPolicy( + const FunctionCallbackInfo& args) { + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsString()); + + Environment* env = Environment::GetCurrent(args); + if (!per_process::isWldpInitialized) { + InitWldp(env); + } + + BufferValue path(env->isolate(), args[0]); + if (*path == nullptr) { + return env->ThrowError("path cannot be empty"); + } + + HANDLE hFile = CreateFileA( + *path, + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (hFile == INVALID_HANDLE_VALUE || hFile == nullptr) { + return env->ThrowError("Unable to open file"); + } + + const GUID wldp_host_other = WLDP_HOST_OTHER; + WLDP_EXECUTION_POLICY result; + HRESULT hr = per_process::WldpCanExecuteFile( + wldp_host_other, + WLDP_EXECUTION_EVALUATION_OPTION_NONE, + hFile, + NODEJS, + &result); + CloseHandle(hFile); + + if (FAILED(hr)) { + return env->ThrowError("WldpCanExecuteFile failed"); + } + + bool isFileTrusted = (result == WLDP_EXECUTION_POLICY_ALLOWED); + args.GetReturnValue().Set(isFileTrusted); +} + +static void IsInteractiveModeDisabledInternal( + const FunctionCallbackInfo& args) { + CHECK_EQ(args.Length(), 0); + + Environment* env = Environment::GetCurrent(args); + + if (!per_process::isWldpInitialized) { + InitWldp(env); + } + + if (per_process::WldpGetApplicationSettingBoolean != nullptr) { + BOOL ret; + HRESULT hr = per_process::WldpGetApplicationSettingBoolean( + NODEJS, + DISABLE_INTERPRETIVE_MODE_SETTING_NAME, + &ret); + + if (SUCCEEDED(hr)) { + args.GetReturnValue().Set( + Boolean::New(env->isolate(), ret)); + return; + } else if (hr != E_NOTFOUND) { + // If the setting is not found, continue through to attempt + // WldpQuerySecurityPolicy, as the setting may be defined + // in the old settings format + args.GetReturnValue().Set(Boolean::New(env->isolate(), false)); + return; + } + } + + // WldpGetApplicationSettingBoolean is the preferred way for applications to + // query security policy values. However, this method only exists on Windows + // versions going back to circa Win10 2023H2. In order to support systems + // older than that (down to Win10RS2), we can use the deprecated + // WldpQuerySecurityPolicy + if (per_process::WldpQuerySecurityPolicy != nullptr) { + DECLARE_CONST_UNICODE_STRING(providerName, L"Node.js"); + DECLARE_CONST_UNICODE_STRING(keyName, L"Settings"); + DECLARE_CONST_UNICODE_STRING(valueName, L"DisableInteractiveMode"); + WLDP_SECURE_SETTING_VALUE_TYPE valueType = + WLDP_SECURE_SETTING_VALUE_TYPE_BOOLEAN; + ULONG valueSize = sizeof(int); + int ret = 0; + HRESULT hr = per_process::WldpQuerySecurityPolicy( + &providerName, + &keyName, + &valueName, + &valueType, + &ret, + &valueSize); + if (FAILED(hr)) { + args.GetReturnValue().Set(Boolean::New(env->isolate(), false)); + return; + } + + args.GetReturnValue().Set( + Boolean::New(env->isolate(), static_cast(ret))); + } +} + +static void IsSystemEnforcingCodeIntegrity( + const FunctionCallbackInfo& args) { + CHECK_EQ(args.Length(), 0); + + Environment* env = Environment::GetCurrent(args); + + if (!per_process::isWldpInitialized) { + InitWldp(env); + } + + if (per_process::WldpGetApplicationSettingBoolean != nullptr) { + BOOL ret; + HRESULT hr = per_process::WldpGetApplicationSettingBoolean( + NODEJS, + ENFORCE_CODE_INTEGRITY_SETTING_NAME, + &ret); + + if (SUCCEEDED(hr)) { + args.GetReturnValue().Set( + Boolean::New(env->isolate(), ret)); + return; + } else if (hr != E_NOTFOUND) { + // If the setting is not found, continue through to attempt + // WldpQuerySecurityPolicy, as the setting may be defined + // in the old settings format + args.GetReturnValue().Set(Boolean::New(env->isolate(), false)); + return; + } + } + + // WldpGetApplicationSettingBoolean is the preferred way for applications to + // query security policy values. However, this method only exists on Windows + // versions going back to circa Win10 2023H2. In order to support systems + // older than that (down to Win10RS2), we can use the deprecated + // WldpQuerySecurityPolicy + if (per_process::WldpQuerySecurityPolicy != nullptr) { + DECLARE_CONST_UNICODE_STRING(providerName, L"Node.js"); + DECLARE_CONST_UNICODE_STRING(keyName, L"Settings"); + DECLARE_CONST_UNICODE_STRING(valueName, L"EnforceCodeIntegrity"); + WLDP_SECURE_SETTING_VALUE_TYPE valueType = + WLDP_SECURE_SETTING_VALUE_TYPE_BOOLEAN; + ULONG valueSize = sizeof(int); + int ret = 0; + HRESULT hr = per_process::WldpQuerySecurityPolicy( + &providerName, + &keyName, + &valueName, + &valueType, + &ret, + &valueSize); + if (FAILED(hr)) { + args.GetReturnValue().Set(Boolean::New(env->isolate(), false)); + return; + } + + args.GetReturnValue().Set( + Boolean::New(env->isolate(), static_cast(ret))); + } +} + +void Initialize(Local target, + Local unused, + Local context, + void* priv) { + SetMethod( + context, + target, + "isFileTrustedBySystemCodeIntegrityPolicy", + IsFileTrustedBySystemCodeIntegrityPolicy); + + SetMethod( + context, + target, + "isInteractiveModeDisabledInternal", + IsInteractiveModeDisabledInternal); + + SetMethod( + context, + target, + "isSystemEnforcingCodeIntegrity", + IsSystemEnforcingCodeIntegrity); +} + +void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + registry->Register(IsFileTrustedBySystemCodeIntegrityPolicy); + registry->Register(IsInteractiveModeDisabledInternal); + registry->Register(IsSystemEnforcingCodeIntegrity); +} + +} // namespace code_integrity +} // namespace node + +NODE_BINDING_CONTEXT_AWARE_INTERNAL(code_integrity, + node::code_integrity::Initialize) +NODE_BINDING_EXTERNAL_REFERENCE(code_integrity, + node::code_integrity::RegisterExternalReferences) diff --git a/src/node_code_integrity.h b/src/node_code_integrity.h new file mode 100644 index 00000000000000..8c586e2e821658 --- /dev/null +++ b/src/node_code_integrity.h @@ -0,0 +1,86 @@ +// Windows API documentation for WLDP can be found at +// https://learn.microsoft.com/en-us/windows/win32/api/wldp/ + +#ifndef SRC_NODE_CODE_INTEGRITY_H_ +#define SRC_NODE_CODE_INTEGRITY_H_ + +#include + +#define WLDP_HOST_OTHER \ + {0x626cbec3, 0xe1fa, 0x4227, \ + {0x98, 0x0, 0xed, 0x21, 0x2, 0x74, 0xcf, 0x7c}}; + +// +// Enumeration types for WldpCanExecuteFile +// +typedef enum WLDP_EXECUTION_POLICY { + WLDP_EXECUTION_POLICY_BLOCKED, + WLDP_EXECUTION_POLICY_ALLOWED, + WLDP_EXECUTION_POLICY_REQUIRE_SANDBOX, +} WLDP_EXECUTION_POLICY; + +typedef enum WLDP_EXECUTION_EVALUATION_OPTIONS { + WLDP_EXECUTION_EVALUATION_OPTION_NONE = 0x0, + WLDP_EXECUTION_EVALUATION_OPTION_EXECUTE_IN_INTERACTIVE_SESSION = 0x1, +} WLDP_EXECUTION_EVALUATION_OPTIONS; + +typedef HRESULT(WINAPI* pfnWldpCanExecuteFile)( + _In_ REFGUID host, + _In_ WLDP_EXECUTION_EVALUATION_OPTIONS options, + _In_ HANDLE contentFileHandle, + _In_opt_ PCWSTR auditInfo, + _Out_ WLDP_EXECUTION_POLICY* result); + +typedef HRESULT(WINAPI* pfnWldpCanExecuteBuffer)( + _In_ REFGUID host, + _In_ WLDP_EXECUTION_EVALUATION_OPTIONS options, + _In_reads_(bufferSize) const BYTE* buffer, + _In_ ULONG bufferSize, + _In_opt_ PCWSTR auditInfo, + _Out_ WLDP_EXECUTION_POLICY* result); + +typedef HRESULT(WINAPI* pfnWldpGetApplicationSettingBoolean)( + _In_ PCWSTR id, + _In_ PCWSTR setting, + _Out_ BOOL* result); + +typedef enum WLDP_SECURE_SETTING_VALUE_TYPE { + WLDP_SECURE_SETTING_VALUE_TYPE_BOOLEAN = 0, + WLDP_SECURE_SETTING_VALUE_TYPE_ULONG, + WLDP_SECURE_SETTING_VALUE_TYPE_BINARY, + WLDP_SECURE_SETTING_VALUE_TYPE_STRING +} WLDP_SECURE_SETTING_VALUE_TYPE, *PWLDP_SECURE_SETTING_VALUE_TYPE; + +/* from winternl.h */ +#if !defined(__UNICODE_STRING_DEFINED) && defined(__MINGW32__) +#define __UNICODE_STRING_DEFINED +#endif +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +typedef const UNICODE_STRING* PCUNICODE_STRING; + +typedef HRESULT(WINAPI* pfnWldpQuerySecurityPolicy)( + _In_ const UNICODE_STRING * providerName, + _In_ const UNICODE_STRING * keyName, + _In_ const UNICODE_STRING * valueName, + _Out_ PWLDP_SECURE_SETTING_VALUE_TYPE valueType, + _Out_writes_bytes_opt_(*valueSize) PVOID valueAddress, + _Inout_ PULONG valueSize); + + +#ifndef DECLARE_CONST_UNICODE_STRING +#define DECLARE_CONST_UNICODE_STRING(_var, _string) \ +const WCHAR _var ## _buffer[] = _string; \ +const UNICODE_STRING _var = \ +{ sizeof(_string) - sizeof(WCHAR), sizeof(_string), (PWCH) _var ## _buffer } +#endif + +#ifndef E_NOTFOUND +#define E_NOTFOUND 0x80070490 +#endif + +#endif // SRC_NODE_CODE_INTEGRITY_H_ diff --git a/src/node_external_reference.h b/src/node_external_reference.h index 3976a0e9fb23d4..caecd197883cd9 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -224,12 +224,20 @@ class ExternalReferenceRegistry { #define EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V) #endif +#define EXTERNAL_REFERENCE_BINDING_LIST_OS_SPECIFIC(V) + +#ifdef _WIN32 +#define EXTERNAL_REFERENCE_BINDING_LIST_OS_SPECIFIC(V) \ + V(code_integrity) +#endif + #define EXTERNAL_REFERENCE_BINDING_LIST(V) \ EXTERNAL_REFERENCE_BINDING_LIST_BASE(V) \ EXTERNAL_REFERENCE_BINDING_LIST_INSPECTOR(V) \ EXTERNAL_REFERENCE_BINDING_LIST_I18N(V) \ EXTERNAL_REFERENCE_BINDING_LIST_CRYPTO(V) \ - EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V) + EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V) \ + EXTERNAL_REFERENCE_BINDING_LIST_OS_SPECIFIC(V) } // namespace node diff --git a/test/fixtures/code_integrity_test.js b/test/fixtures/code_integrity_test.js new file mode 100644 index 00000000000000..ec630ce6953fbd --- /dev/null +++ b/test/fixtures/code_integrity_test.js @@ -0,0 +1 @@ +1 + 1; \ No newline at end of file diff --git a/test/fixtures/code_integrity_test.json b/test/fixtures/code_integrity_test.json new file mode 100644 index 00000000000000..9e26dfeeb6e641 --- /dev/null +++ b/test/fixtures/code_integrity_test.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/code_integrity_test.node b/test/fixtures/code_integrity_test.node new file mode 100644 index 00000000000000..af84f6510f0d90 --- /dev/null +++ b/test/fixtures/code_integrity_test.node @@ -0,0 +1 @@ +exports.file1 = 'file1.node'; diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index d340f98b83a3d6..6ada1b27591ef0 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -105,7 +105,11 @@ expected.beforePreExec = new Set([ 'Internal Binding wasm_web_api', 'NativeModule internal/events/abort_listener', 'NativeModule internal/modules/typescript', + 'NativeModule internal/code_integrity', ]); +if (common.isWindows) { + expected.beforePreExec.add('Internal Binding code_integrity'); +} expected.atRunTime = new Set([ 'Internal Binding worker', diff --git a/test/parallel/test-code-integrity.js b/test/parallel/test-code-integrity.js new file mode 100644 index 00000000000000..a88a6e2fe84e62 --- /dev/null +++ b/test/parallel/test-code-integrity.js @@ -0,0 +1,71 @@ +// Flags: --expose-internals + +'use strict'; + +require('../common'); +const assert = require('node:assert'); +const { describe, it } = require('node:test'); +const ci = require('internal/code_integrity'); + +describe('cjs loader code integrity integration tests', () => { + it('should throw an error if a .js file does not pass code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return false; }); + + assert.throws( + () => { + require('../fixtures/code_integrity_test.js'); + }, + { + code: 'ERR_CODE_INTEGRITY_VIOLATION', + }, + ); + } + ); + it('should NOT throw an error if a .js file passes code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return true; }); + + assert.ok( + require('../fixtures/code_integrity_test.js') + ); + } + ); + it('should throw an error if a .json file does not pass code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return false; }); + + assert.throws( + () => { + require('../fixtures/code_integrity_test.json'); + }, + { + code: 'ERR_CODE_INTEGRITY_VIOLATION', + }, + ); + } + ); + it('should NOT throw an error if a .json file passes code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return true; }); + + assert.ok( + require('../fixtures/code_integrity_test.json') + ); + } + ); + it('should throw an error if a .node file does not pass code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return false; }); + + assert.throws( + () => { + require('../fixtures/code_integrity_test.node'); + }, + { + code: 'ERR_CODE_INTEGRITY_VIOLATION', + }, + ); + } + ); +}); diff --git a/tsconfig.json b/tsconfig.json index 3f5a3067ced063..9d280df245cf60 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,7 @@ "buffer": ["./lib/buffer.js"], "child_process": ["./lib/child_process.js"], "cluster": ["./lib/cluster.js"], + "codeintegrity": ["./lib/codeintegrity.js"], "console": ["./lib/console.js"], "constants": ["./lib/constants.js"], "crypto": ["./lib/crypto.js"],