-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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
lib: be robust when process global is clobbered #10135
Conversation
Nice. I like it. |
/* eslint-disable no-global-assign */ | ||
/* eslint-disable required-modules */ | ||
'use strict'; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
require('../common');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the required-modules eslint directive so I didn't have to include test/common.js, because while core is now robust to tampering, the code in test/common.js need not be.
This probably won't work in ES Modules, also this changes |
Why would that be an issue for built-in modules?
Seems unlikely. Is there a plausible scenario where it would make a difference? It can't be monkey-patching the module loader because core modules use (the inaccessible to the outside world) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
lib/internal/bootstrap_node.js
Outdated
@@ -504,7 +504,7 @@ | |||
}; | |||
|
|||
NativeModule.wrapper = [ | |||
'(function (exports, require, module, __filename, __dirname) { ', | |||
'(function (exports, require, module, __filename, __dirname, process) { ', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you might as well lose the __dirname
argument here?
I'm +1 on this. Can process be made read-only, so it can't be assigned null? Are people deleting the process global on purpose, or accident? |
Program bugs sometimes happen to overwrite the `process` global object, leading to unpredictable runtime errors. The bug is often hard to track down because the crash usually looks unrelated and happens far away from the bug point. Make core mostly immune to such corruption by ensuring that all core modules close over a direct reference to the process object instead of a dynamic reference.
d3a744b
to
54aec1e
Compare
@addaleax Removed @sam-github I've added a commit that makes CI: https://ci.nodejs.org/job/node-test-pull-request/5355/ EDIT: To answer your question: accidental. |
Still LGTM but tbh I’d feel a bit better considering the second commit to be semver-major? |
No strong feelings. semver-major is fine by me. |
@@ -504,7 +513,7 @@ | |||
}; | |||
|
|||
NativeModule.wrapper = [ | |||
'(function (exports, require, module, __filename, __dirname) { ', | |||
'(function (exports, require, module, __filename, process) { ', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its confusing that there are two wrappers now, and they are different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I reading this correctly, internal modules no longer have __dirname
? I guess it would be too big a change to have process
be injected as a module argument for all modules, internal and external, rather than be in globals
? Ben, was there discussion about whether node's "globals" should be true-global or module locals? module/exports/__filename/__dirname have meaning only in local context, but why did require()
become a local?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its confusing that there are two wrappers now, and they are different.
That's the price of progress.
Am I reading this correctly, internal modules no longer have __dirname?
They never really did, it was always undefined because built-in modules don't live on disk.
I guess it would be too big a change to have
process
be injected as a module argument for all modules, internal and external, rather than be inglobals
?
I'm not against that per se but that's a bigger change and probably needs more discussion.
why did
require()
become a local?
I believe it always was. It sort of has to be because it works relative to the calling script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assumed it knew its current module using some other mechanism, but that makes sense. Also about __dirname.
@sam-github every time I've seen it happen has been someone naming a function |
@nodejs/ctc ... any thoughts on this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
+0, I can see this causing some hurt but it's probably a good idea |
Actually, a thought: could this mess up people who use spies or mocks in place of |
I know at one point N|Solid's agent was heavily guarding against this style of attack by creating local |
@@ -196,7 +200,12 @@ | |||
enumerable: false, | |||
configurable: true | |||
}); | |||
global.process = process; | |||
Object.defineProperty(global, 'process', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is needed / warranted. If someone wants to delete global.process
it should still work. Natives having proper refs to process
is already achieved via the wrapper hange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was requested by @sam-github in #10135 (comment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I as well as @ljharb disagree with this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically, if we set this precedent I would be much more comfortable locking down all things that core could depend on, either by adding them to a closure ala https://github.com/bmeck/node/tree/no-globals or by freezing them on the global. There are many of these globals that break npm
modules in the wild if they are overwritten. I do not see process
as exceptionally high use vs Error
for example. I am against freezing them on the global for polyfill reasons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there many? https://nodejs.org/api/globals.html documents only a bit more than a dozen "globals", and half of them aren't global, they are module-scoped variables. Perhaps all the "globals" should be module-scoped variables? So that its impossible to mess with them globally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this does not include prototype hijacking protection, thats raw global refs.
require('../common'); | ||
const assert = require('assert'); | ||
|
||
assert.throws(() => process = null, /Cannot assign to read only property/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should throw. Natives should be robust w/o relying on globals once startup finishes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The built-in libraries are, this checks that process
is not assignable per the second commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that is appropriate to lock down.
|
Btw, Electron ships with a different module wrapper, maybe it’s worth considering a change like the one proposed here? |
That issue makes a good point about breaking |
While I am guilty of suggesting this, I'm also OK to give node users enough guns to shoot themselves in the foot. Its possible to creatively or accidently corrupt your runtime in many languages, node is no exception, so if we want to leave that as a possibility, I'm OK with it. |
The language runtime, which includes process, should indeed behave that way. Core code, however, should not be so fragile as to be easily broken by users doing inadvisable things. |
@ljharb unfortunately some popular libraries mutate internal prototypes so to some extent there is a need to preserve some inadvisable behavior. |
Unless we freeze all the prototypes of built-in objects, breaking most node monitoring products as a pretty nasty side-effect, the runtime can be broken by users. Its just a question of how hard we want to make them work to do it. |
Again, that's fine in the runtime, but core code should not be broken by those changes. |
@ljharb I actually don't know what you mean by attempting to distinguish "runtime" and "core node". Those words mean the same thing to me, but apparently not to you, can you define them? |
@ljharb some libraries like express change how internals work, in ways that could potentially break core assumptions. Node just was not designed to defend against monkey patching. |
@sam-github I'm saying that the behavior of non-spec (ie, non-language) code that the user did not write or install should not be changeable by user code in unintended ways. @bmeck and I think that "changing how internals work" is something that shouldn't be wantonly permitted (ie, outside of documented/established/endorsed mechanisms to do so), and that if that warrants breaking changes to make node core more robust against third-party code I run making my own code behave in ways I can't defend against (I can defend against language builtins being modified; I can't defend against core being modified), then that's worth it. |
I suggest adding a new CTC discussion around the definition of |
Note that a significant amount of packages seem to override Examples:
Some others do |
I expect making |
why don't we just add |
@bnoordhuis ... how do you want to proceed with this one? |
I'm going to close and revisit when I have time. |
Program bugs sometimes happen to overwrite the
process
global object,leading to unpredictable runtime errors. The bug is often hard to track
down because the crash usually looks unrelated and happens far away from
the bug point.
Make core mostly immune to such corruption by ensuring that all core
modules close over a direct reference to the process object instead
of a dynamic reference.
CI: https://ci.nodejs.org/job/node-test-pull-request/5234/