Skip to content

src: add WDAC integration (Windows)

Add calls to Windows Defender Application Control to enforce integrity of .js, .json, .node files.

Motivation

In the past I've spoken to @RafaelGSS and the Node security work group about code integrity. We feel like it's an important defense in depth feature and the removal of the --experimental-policy feature gives us room to use OS-level code integrity features. This is a first pass at a CI implementation that cooperates with the OS.

These additions add an additional layer of defense against malicious code execution on a system that is enforcing code integrity. Code Integrity enforcement mitigates the risk of malicious code modifications after signing-time. For example, they can prevent an arbitrary file-write vulnerability from turning into an RCE. These additional checks are only made if the OS in code integrity enforcement mode and has explicitly set a configuration value in their code integrity policy to have Node enforce CI.

Windows Defender Application Control (WDAC)

Official documentation: https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/wdac-and-applocker-overview

WDAC is the Windows code integrity subsystem. It enforces code integrity using digital signatures. This is straightforward for DLLs and EXEs, since they're loaded using well known entry points in the OS. However, dynamic runtimes (interpreters like Node.js or Python) make it difficult to determine whether a file being read will be executed. In order to assist dynamic runtimes, WDAC provides an interface for runtimes to call with the code they intent to execute, and WDAC will determine if the runtime is allowed to execute the code based off signature information. WDAC (wldp.dll) exposes various methods for runtimes to ask the OS questions about the code integrity policy on the system. These changes leverage three interfaces WldpCanExecuteFile, WldpGetApplicationSettingBoolean, and WldpQuerySecurityPolicy.

WldpCanExecuteFile - given a file handle, determines if the file is allowed to be executed from WDAC policy.

WldpGetApplicationSettingBoolean - Queries WDAC policy for an application-defined setting with a boolean value. This can be thought of as a more secure setting store.

WldpQuerySecurityPolicy - WdlpGetApplicationSettingBoolean is not available on all versions of Windows, so this method provides fallback behavior to query WDAC policy settings.

High-level implementation

At startup, Node will query WDAC policy to see if it should enter integrity enforcement mode. If the policy provider Node.js has set the policy setting EnforceCodeIntegrity to TRUE, then Node will call WldpCanExecuteFile when a .js, .json, or .node file is loaded using require. If this setting is not enabled, the call to WldpCanExecuteFile will not be made and execution will continue as normal.

Additionally, if EnforceCodeIntegrity is set to TRUE, we disable features that allow arbitrary code from being executed by Node (e.g. the "-e" command line option and the REPL) when the system is enforcing code integrity.

Signing files

Signatures are stored in catalog file, .cat. This catalog file can be thought of as a collection of filenames and their associated hashes. A Node application author can generate a catalog using the Powershell command New-FileCatalog documentation.

PS> New-FileCatalog -CatalogFilePath ./MyApplicationCatalog.cat -Path ./MyApplicationRelease/

The catalog then can be signed with a certificate trusted by WDAC policy using Powershell with Set-AuthenticodeSignature documentation or signtool.exe

The signed catalog can then be installed on the system MyApplication is being deployed to.

Other Questions

What about Linux?

At the moment, there is no unified code integrity subsystem that provides similar cooperative interfaces for interpreters on Linux. There are proposals in-flight and we're tracking this work and hope to keep the implementation as similar as possible across OSs.

Merge request reports

Loading