Skip to content
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: make Error objects instantiation less vulnerable to prototype pollution #46065

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

aduh95
Copy link
Contributor

@aduh95 aduh95 commented Jan 2, 2023

Having a more robust Error instantiation makes sure the user would get the actual error rather than an unrelated one if the prototype was altered somewhere else.

Before this change:

$ node -e 'Object.defineProperty(Object.prototype, "code", {set(){throw new Error}});fs.readFile()'              
[eval]:1
Object.defineProperty(Object.prototype, "code", {set(){throw new Error}});fs.readFile()
                                                       ^

Error
    at TypeError.set ([eval]:1:62)
    at new NodeError (node:internal/errors:401:16)
    at __node_internal_ (node:internal/validators:421:11)
    at maybeCallback (node:fs:169:3)
    at Object.readFile (node:fs:372:14)
    at [eval]:1:78
    at Script.runInThisContext (node:vm:129:12)
    at Object.runInThisContext (node:vm:307:38)
    at node:internal/process/execution:83:21
    at [eval]-wrapper:6:24

Node.js v19.3.0

After this change:

$ node -e 'Object.defineProperty(Object.prototype, "code", {set(){throw new Error}});fs.readFile()'
node:internal/validators:421
    throw new ERR_INVALID_ARG_TYPE(name, 'Function', value);
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "cb" argument must be of type function. Received undefined
    at maybeCallback (node:fs:169:3)
    at Object.readFile (node:fs:372:14)
    at [eval]:1:78
    at Script.runInThisContext (node:vm:128:12)
    at Object.runInThisContext (node:vm:306:38)
    at node:internal/process/execution:83:21
    at [eval]-wrapper:6:24
    at runScript (node:internal/process/execution:82:62)
    at evalScript (node:internal/process/execution:104:10)
    at node:internal/main/eval_string:50:3 {
  code: 'ERR_INVALID_ARG_TYPE'
}

Node.js v20.0.0-pre

@aduh95 aduh95 added errors Issues and PRs related to JavaScript errors originated in Node.js core. needs-benchmark-ci PR that need a benchmark CI run. labels Jan 2, 2023
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/http2
  • @nodejs/loaders
  • @nodejs/modules
  • @nodejs/net
  • @nodejs/test_runner

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Jan 2, 2023
const kIsNodeError = Symbol('kIsNodeError');

const isWindows = process.platform === 'win32';

const messages = new SafeMap();
const codes = {};
const codes = ObjectCreate(null);
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
const codes = ObjectCreate(null);
const codes = { __proto__: null };

this is equally robust and avoids a function call.

Copy link
Contributor

Choose a reason for hiding this comment

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

I do prefer this, but I know Antoine prefers the other, and the diff is pretty "6 of one".

Copy link
Member

@ljharb ljharb Jan 3, 2023

Choose a reason for hiding this comment

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

Why the preference?

Even setting aside that Object.create is a regretted addition to the JS language, it's still got the potential overhead of a function call, as compared to the very straightforward syntax that node already uses with objects initialized with more than one property. Why is "no properties" a special case that requires a function call?

Copy link
Contributor

Choose a reason for hiding this comment

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

it's still got the potential overhead of a function call

I don't think that should be a concern here because lib/internal/errors.js is already a part of the snapshot, so this code won't get executed at startup, rather the context would get deserialized from the snapshot.

Copy link
Member

Choose a reason for hiding this comment

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

ok, fair - it’s still indirection, and it’s using an API call for something that’s available with syntax.

Copy link
Member

Choose a reason for hiding this comment

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

This is a style question that’s out of scope for this PR. If someone feels strongly about this, they should open a new PR that updates our eslint to enforce a particular style.

Copy link
Member

Choose a reason for hiding this comment

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

yes thanks, i'm sure that won't get bogged down in blocks, i'll get right on that

Copy link
Member

Choose a reason for hiding this comment

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

opened here: #46083

Copy link
Member

Choose a reason for hiding this comment

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

(that was landed, so this can be resolved)

* @returns {T}
*/
function assignOwnProperties(obj, ...sources) {
const descriptors = ObjectCreate(null);
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
const descriptors = ObjectCreate(null);
const descriptors = { __proto__: null };

lib/internal/util/safe-property-assignment.js Show resolved Hide resolved
@aduh95
Copy link
Contributor Author

aduh95 commented Jan 3, 2023

Benchmak CI: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1287/

Results
                               confidence improvement accuracy (*)   (**)  (***)
error/error.js n=10000000                     -2.16 %       ±2.30% ±3.06% ±3.99%
error/node-error.js n=10000000        ***     -8.85 %       ±2.53% ±3.37% ±4.39%

Copy link
Member

@BridgeAR BridgeAR left a comment

Choose a reason for hiding this comment

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

I do not think we should do this. It increases the implementation complexity signficantly and reduces the performance of a critical part of the application.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

The slowdown is not acceptable in this specific case.
Errors are already a slow path for us given all the modifications we do to them, and we should not slow them down further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
errors Issues and PRs related to JavaScript errors originated in Node.js core. lib / src Issues and PRs related to general changes in the lib or src directory. needs-benchmark-ci PR that need a benchmark CI run. needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants