-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
Frozen intrinsics experimental flag #25685
Conversation
6f8f222
to
496cf4d
Compare
https://github.com/nodejs/node/blob/master/doc/node.1 may also need to be updated) |
I'd be more interested in seeing a solution where userland can still polyfill and such. There have been other proposals where node uses the original methods without needing to freeze everything. also fwiw: IteratorPrototype = TypedArray = ThrowTypeError = |
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.
- What’s the idea behind an
--experimental-...
flag? Does this mean the goal is to eventually enable this by default? That seems … maybe not feasible? - Should this be applied in a per-Context way, i.e. also affect code inside
vm.*
environments? I think that would require makinglib/internal/per_context.js
more powerful, but it might be worth it? (/cc @joyeecheung)
The goal would be to make it a non-experimental flag, but there are still a few things to ensure before such a stage:
So the --experimental aspect allows as to move forward despite 1 - 3 above. Although I'm certainly open to discussion about this approach. @devsnek thanks for filling in the remaining anonymous intrinsics there, that's a huge help. |
Do we have numbers regarding a potential perf impact here? Also, if I'm not mistaken this would prevent adding methods to global classes but since Global is not frozen, one could still replace the value of say Also, could it be possible to make this list expendable or to expose the deep freeze method for users who might want to use it? |
In that case I’d prefer to give the flag its desired non-experimental name and document it as experimental. |
978762c
to
dc2b969
Compare
@addaleax sounds good, I've renamed to
In most benchmarks these days for v8, frozen objects behave identically to non-frozen ones. The time to freeze on my machine is about 20ms which is no insignificant (against a 80ms startup). But we should be able to integrate the frozen objects into the snapshot in future to avoid this.
Yes exactly, I've explained this clearer in the notes. We could use a getter / setter approach to fix this, or we could look at some context approaches too.
Exposing the freeze API is something for userland, this internal API may even be optimized out in future so this is a non-goal. |
dc2b969
to
9c5d7c7
Compare
I noticed
|
lib/internal/bootstrap/node.js
Outdated
@@ -340,6 +340,10 @@ function startup() { | |||
NativeModule.require('internal/process/report').setup(); | |||
} | |||
|
|||
if (getOptionValue('--frozen-intrinsics')) { |
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'd say prepareUserCodeExecution
is a better place for this, as this does not make a lot of sense in use cases like node --help
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.
Thanks. This is now in pre_execution.js
. Let me know if that seems right.
@addaleax How would this option get passed to
I recall there are still some perf issues regarding frozen objects in V8, cc @caitp who's been working on that |
FWIW, this looks more like a v8 flag than a Node flag to me, or this looks less overreaching if V8 provides an API for us to do this. |
It's not a v8 flag, and v8 does not currently provide an API which does this.
As discussed offline, there are some well known issues regarding indexed property access to frozen objects --- however, these would generally not affect the things on the white list, currently. OTOH, the main cost would be some slight increased startup cost, since these frozen objects aren't frozen in any snapshot. I'll be working on speeding up other cases, so please ping me on perf bugs regarding frozen object access. |
Yes, I was talking hypothetically
@guybedford The freezing depends on a CLI flag, so we can't include it in a snapshot for the default startup, the best workaround I can think of is to build two snapshots and include them both by default, but I am not sure how much the size overhead would be if we do this for different combinations of CLI flags. |
i agree that this would make sense as a feature for v8, rather than node. even if they don't come up with a magical optimization for the snapshot, it would be a lot faster than this, since they can just directly flip some flags. (and we wouldn't have to worry about stuff like new globals exposed by v8 flags, which this misses (for example, |
Notable Changes * bootstrap: * Add experimental `--frozen-intrinsics` flag (Guy Bedford) nodejs#25685 * build: * Enable v8's siphash for hash seed creation (Rod Vagg) nodejs#26367 * deps: * Upgrade openssl to 1.1.1b (Sam Roberts) nodejs#26327 * process: * Make `process[Symbol.toStringTag]` writable again (Ruben Bridgewater) nodejs#26488 * repl: * Add `util.inspect.replDefaults` to customize the writer (Ruben Bridgewater) nodejs#26375 * report: * Rename `triggerReport()` to `writeReport()` (Colin Ihrig) nodejs#26527
PR-URL: #25685 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
Notable Changes * bootstrap: * Add experimental `--frozen-intrinsics` flag (Guy Bedford) #25685 * build: * Enable v8's siphash for hash seed creation (Rod Vagg) #26367 * deps: * Upgrade openssl to 1.1.1b (Sam Roberts) #26327 * process: * Make `process[Symbol.toStringTag]` writable again (Ruben Bridgewater) #26488 * repl: * Add `util.inspect.replDefaults` to customize the writer (Ruben Bridgewater) #26375 * report: * Rename `triggerReport()` to `writeReport()` (Colin Ihrig) #26527
Notable Changes * bootstrap: * Add experimental `--frozen-intrinsics` flag (Guy Bedford) #25685 * build: * Enable v8's siphash for hash seed creation (Rod Vagg) #26367 * deps: * Upgrade openssl to 1.1.1b (Sam Roberts) #26327 * process: * Make `process[Symbol.toStringTag]` writable again (Ruben Bridgewater) #26488 * repl: * Add `util.inspect.replDefaults` to customize the writer (Ruben Bridgewater) #26375 * report: * Rename `triggerReport()` to `writeReport()` (Colin Ihrig) #26527
Notable Changes * bootstrap: * Add experimental `--frozen-intrinsics` flag (Guy Bedford) nodejs#25685 * build: * Enable v8's siphash for hash seed creation (Rod Vagg) nodejs#26367 * deps: * Upgrade openssl to 1.1.1b (Sam Roberts) nodejs#26327 * process: * Make `process[Symbol.toStringTag]` writable again (Ruben Bridgewater) nodejs#26488 * repl: * Add `util.inspect.replDefaults` to customize the writer (Ruben Bridgewater) nodejs#26375 * report: * Rename `triggerReport()` to `writeReport()` (Colin Ihrig) nodejs#26527
I've been trying to figure out how to move this forward and make it usable. I can't find a response to #25685 (comment), and the issue still exists in 12.1.0, any idea what is happening there? Is there a plan to get this out of experimental somewhere I've missed? I expected monkey patching of JS primordials to be relatively uncommon in node.js code, at least in well-behaved node code, so was hoping this flag would actually be usable, but it turns out it isn't. I ran on a couple pretty vanilla projects I had around, and they failed for reasons like these:
Those both look to me like reasonable bits of code mutating user-created objects, not attempts to monkey-patch intrinsics. I don't think we can get this out of experimental without addressing these issues, but they are part of the definition of Assuming no change in the language spec, how can we move this forward? Some possibilities:
Are there any other options? @nodejs/v8 @ofrobots, do you have any opinions? Or any suggestions - is it possible for Node.js to use existing V8 APIs to achieve a kind of "freeze" that is more useful, i.e., does not break prototypical inheritance? If we have no options to freeze intrinsics in a useful way, disappointing as it is, maybe we should consider removing the experimental feature. |
It is extremely unlikely that a language change will occur, initial tests show that ~ 15% of all websites hit usage counters showing we would alter their behavior. |
The getOriginals proposal sounds like an alternative to this? |
@hashseed to an extent, but it is even harder to use ergonomically. also if |
AFAICT, it allows writing code that defends against mutation of "originals", but frozen internals was intended to protect pre-existing/all code from that mutation ocurring in the first place. |
... which would be a fairly severe departure from the ECMA spec. I'm not sure splitting off into a "safe" JS dialect is desirable. |
@hashseed can you explain that, this topic of freezing globals is brought up in quite a large % of TC39 meetings and is not thought of as splitting the language usually in committee discussion. |
A number of people have expressed the desire the ability to make it impossible, at least after app initialization, to prevent mutation of internals. I don't think anybody is suggesting it be the default (which would make it a dialect), or that everyone would desire this feature. @hashseed Am I to take it that you/V8 don't think adding such a run mode is desireable? Or are you saying that if it isn't a TC39 feature, V8 is unlikely to implement it? @bmeck was there discussion ever in TC39 that you recall of a "make-the-world-immutable" API, that could be called by user-code, perhaps after some initial setup, to indicate no futher mutation of intrinsics would be allowed? Note how I'm careful to avoid the word "freeze", since freezing has other side-effects that are not so useful. |
This is more the domain of @ajklein, so I'd like to defer to him. |
@sam-github there have been several ideas on what to call such an API, historically this has been called the "deep freeze" operation but more recent talks in the Realms WG calls seem to lead towards the term "purify". There has certainly been investigations into polyfills and mutations but people from @Agoric or Bloomberg could state more than myself in terms of actual deployments of those, however these often are done via "endowments". However, allowing such an operation is a bit tricky since a trusted Realm generally needs to exist to prevent unwelcome mutations / to whitelist the mutations (this exists for polyfill mutations in Realms for properties as well as if import maps replace the "originals" module specifiers). |
This thread is new to me; I think I've mostly understood what's under discussion, but forgive me if I'm covering something that's already addressed above. The thing I want to make sure is clear (in reference to #25685 (comment)) is that it's not the definition of |
@ajklein not sure what you mean, but tc39/ecma262#1320 is problematic for anything trying to use things with non-writable data props on their prototypes. There are alternative workarounds as discussed in TC39 around getters/setters (which do not solve this in userland), proposing a new extension to the object model (I find that unlikely), or somehow finding a workaround for tc39/ecma262#1320. In reality I find most of those unlikely, but do find the results of freezing appealing, and quite a bit of code does work, in particular things like classes and object literals which use non-delegating operations via define work fine. Ideally we would have a way to freeze things such that frozen objects remain usable for the cases like above, but right now JS does not provide such a path. Freezing is quite unergonomic/requires non-intuitive code to maintain its usefulness without such workarounds, but does work with some workarounds quite well as discussed via the deployments at Bloomberg and the like. |
@bmeck My comment was in response to @sam-github's suggestion that V8 could somehow provide an API that would avoid the kind of breakage listed in this thread (and that you agree is a problem). |
@sam-github thanks for trying this out and sharing your thoughts. It is great to see discussion on these cases stirred up from its use - this was the hope from the start! One goal of making this flag public was to see if these breaks can be fixed and PR'd to common libraries as they happen. That Apart from any potential Node.js core bugs like that, there are basically two main issues that come up for these workflows - one for objects and one for classes. For the object case - And for the case of classes: class X {
constructor () {
this.toString = 'asdf';
}
} which can be resolved by declaring the overridden builtin properties as class instance properties at the time of definition of the class: class X {
toString = undefined;
constructor () {
this.toString = 'asdf';
}
} Since these are such straightforward fixes for both of these problems, they should also be easily acceptable PRs for any library. We're very much seeing how far we get here by testing this out. If it turns out there is far too much ecosystem friction then yes we can revert and come up with new primitives for frozen intrinsics. But if it is just a couple of PRs to libraries with the above fixes then we can already get those benefits today. I am still hoping for a better primitive though I must admit, although I have limited time myself to champion anything there. By making these issues explicit in Node.js hopefully we can continue this conversation. |
@ajklein If I understand you, you are saying that since Object.freeze works by setting If so, I don't think the distinction is meaningful. If Object.freeze was defined in terms os a primitive ( I don't know the purpose/history of Changing the definition of Object.feeze sounds like its web-breaking, so are there other options? I can see V8 not being keen to create non-standard language facilities. I'm curious to know whether exposing a "make all of these .... objects immutable" C++ API would be anathema to V8 as a non-standard js dialect, or perhaps could be considered a useful V8 embedder specific API. |
@guybedford You showed how to fix one of the two issues I ran into before stopping, what about the other? Function is not a class, and the object literal syntax can't be used to define a function, so I don't see how to adopt the approach to the express router example:
|
@sam-github it would help to understand what fixes might be possible if you could point to the exact piece of code in Express that is doing this? |
@guybedford I believe this would be reproducible with any express app, but I used github.com/strongloop/express-example-app, because I had it lying around, and it has no deps other than express. |
This PR implements a
node --frozen-intrinsics
flag which applies the deep freeze method from SES (https://github.com/Agoric/SES/blob/15ecf1520c8314f7ec29165548eccff1118e294d/src/bundle/deepFreeze.js) to all known intrinsics.There is a more detailed write up by those involved in these security initiatives at https://docs.google.com/document/d/1h__FmXsEWRuNrzAV_l3Iw9i_z8fCXSokGfBiW8-nDNg/edit?ts=5c1adaed#heading=h.q1x6on1y6aju, I just put this together as it seems an easy low-hanging fruit to improve security in Node.js.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes