RFC: Plan for updating to Electron >= 21 without compromising extension host stability #177338
Labels
electron
Issues and items related to Electron
electron-22-update
on-testplan
under-discussion
Issue is under discussion for relevance, priority, approach
upstream
Issue identified as 'upstream' component related (exists outside of VS Code)
Milestone
Copied over from https://github.com/microsoft/vscode-internalbacklog/issues/3548 for visibility.
Tl:dr; For extension authors, to have minimal impact from the V8 sandbox we are making changes to the extension host that will allow your native dependencies to run without having to recompile. No changes needed from the extension side at the moment, but we will document the limitations of the final fix in the release notes.
Background
Electron >= 21 ships with V8 Memory Cage also known as V8 Sandbox. Although, it has the tag sandbox it is completely different from Chromium's renderer sandbox which is more about protecting what system calls or resources a particular process can read/write/execute. From the high level design doc the goal of this sandbox is summarized as
The reason for above attack being possible is that V8 heap can point to ArrayBuffer objects whose memory are usually allocated outside the V8 heap as seen in the diagram in the above design doc. With the sandbox, V8 wants to guarantee that even if an attacker can corrupt any data inside the V8 heap, the attacker cannot gain access to memory regions not associated with V8 and the only way this is possible to ensure with the ArrayBuffers is to pre-allocate a virtual address space (currently 1TB) and check that the buffer memory regions are to reside inside this space, so that you can reference these regions by offsets from the base of the virtual address space rather than having raw pointers, these offset pointers are called
SandboxedPointers
. Memory layout of the V8 sandbox today looks like the following,Problem Statement
The above sandbox is pretty solid on the Web where V8 manages the allocation of ArrayBuffer memory regions and these will now be internally adjusted to fit the sandbox model without having an end user impact. But on the desktop with Node.js, there is another venue of API available to Native module authors for allocating and managing the buffer regions outside of V8 heap. It is achievable by the following set of API,
Consider the following scenario, you have a native addon that is wrapper around a highly performant image compression library. The library that is performing the compression would be allocating its own memory regions for the image data, once compressed to send back this data into V8 so that the addon can expose it to JavaScript without the above API an addon will now have to copy the data from the library's allocated memory region into V8 heap which is going to cost some CPU cycles and additional memory. Instead, with the above API the addon can just get a pointer to the already allocated memory region and pass it to V8, now it is just a pointer operation and V8 is able to read the compressed image data without additional cost.
Keen readers would have already spotted the issue with the above example, given most addons cannot control how memory is allocated from their dependent libraries, it is not possible have the buffer regions to be inside the V8 sandbox leading to FATAL errors when any of the above API eventually calls into V8 at this location
For this reason, Node.js decided to disable the V8 sandbox in order to avoid disrupting the addon community. Additionally, the V8 sandbox will not be offering much security to Node.js since there is already unrestricted access to OS resources from the process. However, for Electron which is aligned with the Chromium security model by default the V8 sandbox was enabled since v21. Now that means, N-API stability is broken with Electron v21 for modules that use any of the above buffer API (**NB:**The ABI stability guarantee of N-API is that if a module is compiled for a particular major version of the binary then it should be fine to run the same module in future major versions of the binary without need for recompilation). Modules are now in requirement to recompile themselves with alternate versions of the buffer API for Electron v21 otherwise they will lead to FATAL error in V8 during execution. The Electron issue linked here is about the stability issue. Tl:dr, from the issue is that
N-API
will now throw an error when trying to create an external buffer when V8 sandbox is enabled and additionallynode-addon-api
have released new version of the package that provides a way for native addons to be adopted in Electron with V8 sandbox enabled, refs https://github.com/nodejs/node-addon-api/blob/main/doc/external_buffer.md.The V8 sandbox feature is controlled by compile time flags, (i-e) you cannot enable/disable the sandbox during execution, you are only allowed to build V8 with either sandbox enabled or disabled. Also, Electron only builds a single version of V8 for both Chromium and Node.js, so sandbox enabled V8 is what gets used by Node.js processes in Electron. This leads to a mixed situation for VSCode, were we have workbench that does not load any Node.js native modules, will have the Chromium sandbox and will also benefit from the V8 sandbox. On the other hand, we have the extension host that is a Node.js process and extensions can load native addons which will violate the V8 sandbox policy unless recompiled. This is what we saw when Electron 22 update was merged and Jupyter extension started crashing the extension host. To share a bit more context here, the Jupyter extension relies on Zeromq@6.0.0-beta.6 native module for message passing, the module uses the following buffer API that leads to the FATAL error. Latest version of the Zeromq module has already adopted the breaking change but the Jupyter extension still needs to bump the module version to avoid the crash.
The bigger problem is, how many other extensions ship with native module that uses the above list of buffer API ? Do we have telemetry on tracking the problematic extensions if they were to crash the extension host due to the sandbox ? Shipping Electron 22 in its current state will lead to destabilized extension host for our users.
Solution
Final answer: Using an allocator shim to reroute heap allocations from Extension host into a partition inside the V8 sandbox
Details of the current solution:
//base/allocator/partition_alloc_support.cc
is a good place to look at this.Gotchas of the current solution:
Previous iterated solutions
Disable V8 sandbox:
NODE_MODULE_VERSION
number embedded in the binary changes for each major release, Node.js will compare this version number with the number from native module during the load stage to ensure module has been recompiled correctly.How to determine impacted extensions
Static analysis:
Telemetry:
dlsym/uv_dlsym
for the buffer API similar to what Node.js does to get the module entry point and emit event with the native module filename back to VSCode.The text was updated successfully, but these errors were encountered: