Skip to content

fix: fix launch crash when null device is disabled on Windows #44882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

yangllu
Copy link
Contributor

@yangllu yangllu commented Nov 28, 2024

add node flag node::ProcessInitializationFlags::kNoStdioInitialization

Fixes #44881

Description of Change

Checklist

Release Notes

Notes:

Copy link

welcome bot commented Nov 28, 2024

💖 Thanks for opening this pull request! 💖

Semantic PR titles

We use semantic commit messages to streamline the release process. Before your pull request can be merged, you should update your pull request title to start with a semantic prefix.

Examples of commit messages with semantic prefixes:

  • fix: don't overwrite prevent_default if default wasn't prevented
  • feat: add app.isPackaged() method
  • docs: app.isDefaultProtocolClient is now available on Linux

Commit signing

This repo enforces commit signatures for all incoming PRs.
To sign your commits, see GitHub's documentation on Telling Git about your signing key.

PR tips

Things that will help get your PR across the finish line:

  • Follow the JavaScript, C++, and Python coding style.
  • Run npm run lint locally to catch formatting errors earlier.
  • Document any user-facing changes you've made following the documentation styleguide.
  • Include tests when adding/changing behavior.
  • Include screenshots and animated GIFs whenever possible.

We get a lot of pull requests on this repo, so please be patient and we will get back to you as soon as we can.

@electron-cation electron-cation bot added the new-pr 🌱 PR opened recently label Nov 28, 2024
@yangllu yangllu force-pushed the fix/node-platforminit-crash-with-nul-device branch from 1619402 to ecf4d8a Compare November 28, 2024 08:09
@yangllu yangllu changed the title fix: fix launch crash when null device is disabled on Windows (#44881) fix: fix launch crash when null device is disabled on Windows Nov 28, 2024
Copy link
Member

@ckerr ckerr left a comment

Choose a reason for hiding this comment

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

The change looks like it does what the summary says, but I'm not familiar with the null device crash. @yangllu could you add a explanation to the description about why adding kNoStdioInitialization fixes this?

Also, would setting it on all three platforms, instead of just on Windows, cause any potential problems?

@yangliu2018
Copy link

yangliu2018 commented Dec 2, 2024

@ckerr As explained in this issue #44881 , null device can be disabled on windows. Node doesn't handle this situation and invokes crash when trying to initialize stdio using nul device, which makes all electron apps crash definitely when nul device is disabled on user's machine.

As far as I know, nul device is part of the posix standand and will casue the os to run abnormally if disabled on macos and linux. Also, not like windows gui apps, where stdio is not associated with an underlying stream, on macos and linux, stdio usually has a valid fd and the code to initialize stdio in node will usually not be called. So I think it's both ok to use or not to use kNoStdioInitialization on macos and linux, no other potential problems.

Copy link
Member

@deepak1556 deepak1556 left a comment

Choose a reason for hiding this comment

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

I remember this PR being raised before but it is no longer available when I search, rewriting some comments I had from there.

  1. What are the side effects of using kNoStdioInitialization specifically related to handle inheritance for child process when we attach to console via https://source.chromium.org/chromium/chromium/src/+/main:base/process/launch_win.cc;l=167-234, with the flag we will now miss this call https://github.com/nodejs/node/blob/5ef498517574e1534041cdd25c920f8d0b2cf845/src/node.cc#L819-L823

  2. We also use nul device to support the ignore option on child_process and utilityProcess.fork api. When the device is not available the child process may not be created and the applications functionality will be broken, should we still continue execution in these cases ?

@electron-cation electron-cation bot removed the new-pr 🌱 PR opened recently label Dec 5, 2024
@yangllu
Copy link
Contributor Author

yangllu commented Dec 5, 2024

I remember this PR being raised before but it is no longer available when I search, rewriting some comments I had from there.

  1. What are the side effects of using kNoStdioInitialization specifically related to handle inheritance for child process when we attach to console via https://source.chromium.org/chromium/chromium/src/+/main:base/process/launch_win.cc;l=167-234, with the flag we will now miss this call https://github.com/nodejs/node/blob/5ef498517574e1534041cdd25c920f8d0b2cf845/src/node.cc#L819-L823
  2. We also use nul device to support the ignore option on child_process and utilityProcess.fork api. When the device is not available the child process may not be created and the applications functionality will be broken, should we still continue execution in these cases ?
  1. Without kNoStdioInitialization, stdio inheritance is disabled, then the child process will call AttachConsole(ATTACH_PARENT_PROCESS) to attach its stdio to the parent process's stdio. With kNoStdioInitialization, the child process will inherit the stdio of parent process. I think inheriting stdio works the same as attaching parent's console.

  2. I also find that utility process is not constructed completely and the app crash later once using utility process without nul device. When failing to open nul device in utilityProcess.fork, I try to create a pipe, close it's read end, and use it's write end as a substitute for nul device. The app works normally with this method.
    But such kind of pipe (WriteFile fails) is not the same with nul device (WriteFile succeeds), so I have another idea: Once nul device service of the operating system is disabled, we could create a nul device service in our application. We could create a pipe, then read and drop strings in our nul device service (maybe as a thread in our process), and the write end of the pipe will work the same as nul device. I'm stilling considering if it's necessary to do such a heavy thing.

@brjsp
Copy link
Contributor

brjsp commented Dec 5, 2024

What's your motive for disabling the null device? Honestly you should expect disabling basic drivers would break the system in unexpected ways.

@yangllu
Copy link
Contributor Author

yangllu commented Dec 5, 2024

@brjsp In my personal view, I really don’t want the null device to be disabled. However, according to our statistics, around 0.1% windows machines have disabled null device. Without this issue fixed we will lose 0.1% users when upgrading electron to the new version. This is indeed a serious number.

I have tested adequately that the null device service can be safely disabled and the windows system will not break. I am also really confused about why Microsoft has implemented such a feature.

@brjsp
Copy link
Contributor

brjsp commented Dec 5, 2024

why Microsoft has implemented such a feature.

“If i delete ntoskrnl.exe, the system won't boot. I'm confused about why Microsoft has implemented such feature!”

sorry but this PR seems to be a bad-faith effort to disable security features in an effort to make the code “compatible” with intentionally broken installations.

@zuohuiyang
Copy link
Contributor

zuohuiyang commented Dec 5, 2024

I think disabling null devices is different from deleting core system files. As mentioned in the Issue, #44881 we can disable null devices through a registry setting - this is really a Windows feature. After disabling null devices, most software can still run normally. However, if system core files are deleted, the system cannot run.
“To reproduce this issue, we can just set Start = 4 (which is 1 normally) in registry key HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Null
image

Since this condition is so simple, malicious attackers could potentially crash all Electron applications with just one registry command - this is practically a vulnerability. If there is a security application built with Electron, an attacker will be able to crash this security app by setting this registry key, then the attacker can do everything he wants.
reg add "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Null" /v start /t REG_DWORD /d 4 /f

Additionally, as mentioned above, some users have indeed configured this registry setting. Although we don't know why they chose to do so, but the user set this register is so many. Just like what was mentioned above, about 0.1% of the Windows machines among our users are affected. Although this data was only collected during one of our upgrades and it might be a bit high.

I think it's better to add some compatibility to allow more users to enjoy Electron applications.

@brjsp
Copy link
Contributor

brjsp commented Dec 5, 2024

we can disable null devices through a registry setting - this is really a Windows feature.

If you want to limit yourself to disabling services, then disable pci and your system won't boot. There are really no measures in place to prevent the administrator from shooting themself in the head.

Since this condition is so simple, malicious attackers could potentially crash all Electron applications with just one registry command - this is practically a vulnerability.

Even if it's a compatibility/usability issue, what it's definitely not is a vulnerability. Malicious attackers cannot write anything under HKLM\System. If you can modify services configuration, you could just add a new service that runs the code you want to run.

@yangllu
Copy link
Contributor Author

yangllu commented Dec 9, 2024

I don't think it is appropriate compare the nul device to core components that can cause the windows system to fail to boot.

I have disabled nul deivce on my windows computer and used it for servel days. The windows system has been functioning normally, and I haven't found any other application that crashes except for electron.

I think the nul device service is only an optional sercive that can be disabled safely on windows, although we have not fully figured out in which specific scenarios the nul device service will be disabled (I haven't found much official material describing windows nul device, and I hope anyone can find and share some authoritative information).

@yangllu
Copy link
Contributor Author

yangllu commented Dec 9, 2024

I think that what we should executly discuss here is what is the best way to solve this issue.

To be honest, I do not agree that my code is elegant to fix it by adding kNoStdioInitialization. I have a new idea, would it be suitable to just ignore the error of opening a nul device and continue executing? Does anyone have any opinions on this?

I have found this relevant code segment in the chromium project. When failing to open a nul device, maybe it would be ok to just ignore the error to avoid crashes ? FuzzerUtilWindows.cpp DiscardOutput

void DiscardOutput(int Fd) {
  FILE* Temp = fopen("nul", "w");
  if (!Temp)
    return;
  _dup2(_fileno(Temp), Fd);
  fclose(Temp);
}

@deepak1556
Copy link
Member

Sorry for the delay, based on your testing given the application does break when either utilityprocess or childprocess attempts to use the nul device, ignoring the early exit from Node.js in these cases doesn't help much. But again not all use cases of the child process api require the nul device, so it cannot made into a startup check as well.

In my mind, this situation runs similar to how the application will fail to start when sandbox cannot be used on linux. But only in this case, we don't have enough feedback from the application to let users know why the application failed to launch and maybe provide an opt-out mechanism. Can this PR try the following,

  1. Add a clear error message when nul device cannot be used
  2. Add support for a feature flag --no-stdio-init which will
    a) enable kNoStdioInitialization for Node.js environment
  3. Utility process should emit error event when nul device cannot be used.

@yangllu
Copy link
Contributor Author

yangllu commented Dec 10, 2024

@deepak1556 Great! I will revise my PR based on your suggestions.

add node flag node::ProcessInitializationFlags::kNoStdioInitialization
@yangllu yangllu force-pushed the fix/node-platforminit-crash-with-nul-device branch from ecf4d8a to 4e280d9 Compare December 24, 2024 13:27
@yangllu
Copy link
Contributor Author

yangllu commented Dec 24, 2024

@deepak1556 I have added the feature flag --no-stdio-init
When nul device disabled, launch electron without this flag will print a log to remind the user.
image
Then electron launches successfully with this flag on.
image

Also, I added Emit("error", "Failed to create null handle for ignoring stdio"); in UtilityProcessWrapper::UtilityProcessWrapper, but I encountered two problems.

  1. I failed to find a way to handle this error event. I’m not sure if it suitable to emit error event here.
    image
  2. Still the user has no option but to use ignore in utilityProcess.fork
    This demo trying not to open a nul device will throw an exception stdin value other than ignore is not supported.
const { app, utilityProcess } = require('electron');
const path = require('node:path');

const child = utilityProcess.fork(path.join(__dirname, 'test.js'), {
    stdio: ['inherit', 'inherit', 'inherit']
});

image
It seems to compel the use of ignore here.
image

@deepak1556
Copy link
Member

Sorry for the delay here, I missed the notification on this PR. I will review it by Monday.

@yangllu
Copy link
Contributor Author

yangllu commented Feb 7, 2025

@deepak1556 Hi, do you have any new opinions about my code?

@codebytere codebytere requested a review from deepak1556 February 7, 2025 10:21
if (command_line->HasSwitch(switches::kNoStdioInit)) {
process_flags |= node::ProcessInitializationFlags::kNoStdioInitialization;
// remove the option to avoid node error "bad option: --no-stdio-init"
std::string option = std::string("--") + switches::kNoStdioInit;
Copy link

@t13m t13m Jul 1, 2025

Choose a reason for hiding this comment

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

Hi @yangllu , thanks for your work. I've tried this patch once, here is what I found:

it seems that in a windows machine without NUL device, the main process works normally, but the nodejs integrated with renderer processes still don't work, though --no-stdio-init is provided from command line. maybe the reason is that the command line flag is erased here.

Copy link
Member

Choose a reason for hiding this comment

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

Good catch, very likely need to pass the flag in

if (process_type == ::switches::kRendererProcess) {

Copy link
Member

@deepak1556 deepak1556 left a comment

Choose a reason for hiding this comment

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

Sorry for the delay in reviewing this change, couple more comments.

if (fd < 0) {
ret = false;
} else {
ret = true;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ret = true;

Comment on lines +201 to +203
LOG(FATAL) << "Fail to open nul device and node initialization may "
"crash! Try using --"
<< switches::kNoStdioInit;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
LOG(FATAL) << "Fail to open nul device and node initialization may "
"crash! Try using --"
<< switches::kNoStdioInit;
LOG(FATAL) << "Unable to open nul device needed for initialization, aborting startup. As a workaround, try starting with --"
<< switches::kNoStdioInit;

Comment on lines +579 to +581
LOG(FATAL) << "Fail to open nul device and node initialization may "
"crash! Try using --"
<< switches::kNoStdioInit;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
LOG(FATAL) << "Fail to open nul device and node initialization may "
"crash! Try using --"
<< switches::kNoStdioInit;
LOG(FATAL) << "Unable to open nul device needed for initialization, aborting startup. As a workaround, try starting with --"
<< switches::kNoStdioInit;

if (command_line->HasSwitch(switches::kNoStdioInit)) {
process_flags |= node::ProcessInitializationFlags::kNoStdioInitialization;
// remove the option to avoid node error "bad option: --no-stdio-init"
std::string option = std::string("--") + switches::kNoStdioInit;
Copy link
Member

Choose a reason for hiding this comment

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

Good catch, very likely need to pass the flag in

if (process_type == ::switches::kRendererProcess) {

@yangllu
Copy link
Contributor Author

yangllu commented Jul 24, 2025

Hi @deepak1556
I'm handing over this PR to @zuohuiyang .
All review feedback has been addressed in the new PR: fix: fix launch crash when null device is disabled on Windows by zuohuiyang · Pull Request #47870 · electron/electron
Please continue in the new PR.
This PR can be closed.
Thanks!

@deepak1556
Copy link
Member

Closing in favor of #47870

@deepak1556 deepak1556 closed this Aug 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Electron fails to launch on Windows when null device is disabled
8 participants